From fbae55b9f957b1ac379fa7cd06ff552673f2c6ce Mon Sep 17 00:00:00 2001 From: FlightControl Date: Thu, 9 Mar 2017 13:37:13 +0100 Subject: [PATCH] Progress on tasking --- Moose Development/Moose/Core/Database.lua | 5 + Moose Development/Moose/Core/Zone.lua | 8 +- .../Moose/Functional/Detection.lua | 7 +- Moose Development/Moose/Moose.lua | 2 +- .../Moose/Tasking/DetectionManager.lua | 274 - Moose Development/Moose/Tasking/Task_A2G.lua | 361 +- .../Moose/Tasking/Task_A2G_Dispatcher.lua | 302 + Moose Development/Moose/Tasking/Task_SEAD.lua | 258 - Moose Development/Moose/Wrapper/Unit.lua | 8 +- .../l10n/DEFAULT/Moose.lua | 6117 +++++++++++------ Moose Mission Setup/Moose.lua | 6117 +++++++++++------ Moose Mission Setup/Moose_Create.bat | 2 +- ...DET-255 - Detection AEAS with Destroys.miz | Bin 26289 -> 26289 bytes ...DET-900 - Detection Test with RED FACA.lua | 25 + ...DET-900 - Detection Test with RED FACA.miz | Bin 0 -> 259004 bytes ...- A2G Task Dispatching DETECTION_AREAS.lua | 6 +- ...- A2G Task Dispatching DETECTION_AREAS.miz | Bin 42328 -> 267961 bytes 17 files changed, 8796 insertions(+), 4696 deletions(-) create mode 100644 Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua delete mode 100644 Moose Development/Moose/Tasking/Task_SEAD.lua create mode 100644 Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.lua create mode 100644 Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.miz diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 798c5e881..3e9eea077 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -54,6 +54,8 @@ DATABASE = { PLAYERSJOINED = {}, CLIENTS = {}, AIRBASES = {}, + COUNTRY_ID = {}, + COUNTRY_NAME = {}, NavPoints = {}, } @@ -761,6 +763,9 @@ function DATABASE:_RegisterTemplates() 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 diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 831a24670..fe1848037 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -408,7 +408,7 @@ end -- @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, UnBound ) +function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Point = {} local Vec2 = self:GetVec2() @@ -424,8 +424,10 @@ function ZONE_RADIUS:BoundZone( Points, UnBound ) 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"] = "USA", + ["country"] = CountryName, ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -437,7 +439,7 @@ function ZONE_RADIUS:BoundZone( Points, UnBound ) ["heading"] = 0, } -- end of ["group"] - local Group = coalition.addStaticObject( country.id.USA, Tire ) + local Group = coalition.addStaticObject( CountryID, Tire ) if UnBound and UnBound == true then Group:destroy() end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index d1b9d443a..5ec75945f 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -310,6 +310,7 @@ do -- DETECTION_BASE -- Create FSM transitions. self:SetStartState( "Stopped" ) + self.CountryID = DetectionSetGroup:GetFirst():GetCountry() self:AddTransition( "Stopped", "Start", "Detecting") @@ -1781,7 +1782,7 @@ do -- DETECTION_AREAS self:IdentifyDetectedObject( DetectedObject ) AreaExists = true - DetectedArea.Zone:BoundZone( 30, true) + DetectedArea.Zone:BoundZone( 12, self.CountryID, true) -- Assign the Unit as the new center unit of the detected area. DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) @@ -1834,7 +1835,7 @@ do -- DETECTION_AREAS end end else - DetectedArea.Zone:BoundZone( 30, true) + DetectedArea.Zone:BoundZone( 12, self.CountryID, true) self:RemoveDetectedItem( DetectedAreaID ) self:AddChangeArea( DetectedArea, "RA" ) end @@ -1926,7 +1927,7 @@ do -- DETECTION_AREAS end if DETECTION_AREAS._BoundDetectedZones or self._BoundDetectedZones then - DetectedZone:BoundZone( 30 ) + DetectedZone:BoundZone( 12, self.CountryID ) end end diff --git a/Moose Development/Moose/Moose.lua b/Moose Development/Moose/Moose.lua index 8b715ee9f..0b7d4658b 100644 --- a/Moose Development/Moose/Moose.lua +++ b/Moose Development/Moose/Moose.lua @@ -59,7 +59,7 @@ Include.File( "Tasking/CommandCenter" ) Include.File( "Tasking/Mission" ) Include.File( "Tasking/Task" ) Include.File( "Tasking/DetectionManager" ) -Include.File( "Tasking/Task_SEAD" ) +Include.File( "Tasking/Task_A2G_Dispatcher") Include.File( "Tasking/Task_A2G" ) diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 466b65de7..ddbaa4301 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -34,23 +34,6 @@ -- ------------------------------- -- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- --- === --- --- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} --- ================================================================ --- The @{#DETECTION_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) DETECTION_DISPATCHER constructor: --- -------------------------------------- --- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. -- -- === -- @@ -252,260 +235,3 @@ do -- DETECTION_REPORTING end -do -- DETECTION_DISPATCHER - - --- DETECTION_DISPATCHER class. - -- @type DETECTION_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 - DETECTION_DISPATCHER = { - ClassName = "DETECTION_DISPATCHER", - Mission = nil, - CommandCenter = nil, - Detection = nil, - } - - - --- DETECTION_DISPATCHER constructor. - -- @param #DETECTION_DISPATCHER self - -- @param Set#SET_GROUP SetGroup - -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_DISPATCHER self - function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_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 #DETECTION_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 DETECTION_DISPATCHER:EvaluateSEAD( DetectedItem ) - self:F( { DetectedItem.AreaID } ) - - 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 #DETECTION_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) - self:F( { DetectedArea.AreaID } ) - - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) - - 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 #DETECTION_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) - self:F( { DetectedArea.AreaID } ) - - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) - - 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 DetectedArea is Changed AND the state of the Task is "Planned". - -- @param #DETECTION_DISPATCHER self - -- @param Tasking.Mission#MISSION Mission - -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem - -- @return Tasking.Task#TASK - function DETECTION_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 #DETECTION_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function DETECTION_DISPATCHER:ProcessDetected( Detection ) - self:F2() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - --- 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 DetectedArea", DetectedItem.AreaID, DetectedSet:Count(), tostring( DetectedItem ) } ) - DetectedSet:Flush() - - local AreaID = DetectedItem.AreaID - - -- Evaluate SEAD Tasking - local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) - 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, "SEAD." .. AreaID, TargetSetUnit ) - Task:SetTargetZone( DetectedZone ) - SEADTask = Mission:AddTask( Task ) - - end - end - if SEADTask and SEADTask:IsStatePlanned() then - self:E( "Planned" ) - --SEADTask:SetPlannedMenu() - TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() - end - --- -- Evaluate CAS Tasking --- local CASTask = Mission:GetTask( "CAS." .. AreaID ) --- 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 --- CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedItem.NearestFAC ) ) --- end --- end --- if CASTask and CASTask:IsStatePlanned() then --- --CASTask:SetPlannedMenu() --- TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() --- end --- --- -- Evaluate BAI Tasking --- local BAITask = Mission:GetTask( "BAI." .. AreaID ) --- 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 --- BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedItem.NearestFAC ) ) --- end --- end --- if BAITask and BAITask:IsStatePlanned() then --- --BAITask:SetPlannedMenu() --- TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() --- end - --- if #TaskMsg > 0 then --- --- local ThreatLevel = Detection:GetTreatLevelA2G( DetectedItem ) --- --- local DetectedAreaVec3 = DetectedZone:GetVec3() --- local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) --- local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) --- AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", --- DetectedItemID, --- DetectedAreaPointLL, --- string.rep( "■", ThreatLevel ), --- ThreatLevel --- ) --- --- -- Loop through the changes ... --- local ChangeText = Detection:GetChangeText( DetectedItem ) --- --- if ChangeText ~= "" then --- ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) --- end --- end - - -- 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() - - if #TaskMsg > 0 then - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self.CommandCenter:MessageToGroup( - string.format( "HQ Reporting - Target areas for mission '%s':\nTasks:\n%s ", - self.Mission:GetName(), - table.concat( TaskMsg, "\n" ) - ), self:GetReportDisplayTime(), TaskGroup - ) - end - end - end - - return true - end - -end \ No newline at end of file diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua index bc21ddd48..cf277252f 100644 --- a/Moose Development/Moose/Tasking/Task_A2G.lua +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -1,28 +1,53 @@ ---- (AI) (SP) (MP) Tasking for Air to Ground Processes. +--- This module contains the TASK_A2G classes. -- --- 1) @{#TASK_A2G} class, extends @{Task#TASK} --- ================================================= --- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, --- located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK}. +-- # 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 SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- * **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: FlightControl - Design and Programming --- +-- +-- # **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", @@ -33,45 +58,311 @@ do -- TASK_A2G -- @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 #string TaskType BAI or CAS -- @param Set#SET_UNIT UnitSetTargets - -- @param Core.Zone#ZONE_BASE TargetZone + -- @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, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) + 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.TargetZone = TargetZone - self.FACUnit = FACUnit - - local A2GUnitProcess = self:GetUnitProcess() - A2GUnitProcess:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "Attack the Area" ), { Assigned = "Route", Rejected = "Eject" } ) - A2GUnitProcess:AddProcess ( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) - A2GUnitProcess:AddTransition( "Rejected", "Eject", "Planned" ) - A2GUnitProcess:AddTransition( "Arrived", "Update", "Updated" ) - A2GUnitProcess:AddProcess ( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "Attack" ), { Accounted = "Success" } ) - A2GUnitProcess:AddProcess ( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) - --Fsm:AddProcess ( "Updated", "JTAC", PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - A2GUnitProcess:AddTransition( "Accounted", "Success", "Success" ) - A2GUnitProcess:AddTransition( "Failed", "Fail", "Failed" ) + Mission:AddTask( self ) - function A2GUnitProcess:onenterUpdated( TaskUnit ) - self:E( { self } ) - self:Account() - self:Smoke() + 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, 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 + --- @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 diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua new file mode 100644 index 000000000..010f06388 --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -0,0 +1,302 @@ +--- **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.AreaID } ) + + 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.DetectedArea DetectedArea + -- @return Tasking.Task#TASK + function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedArea ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + 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.DetectedArea DetectedArea + -- @return Tasking.Task#TASK + function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) + self:F( { DetectedArea.AreaID } ) + + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + + -- Determine if the set has radar targets. If it does, construct a SEAD task. + local GroundUnitCount = DetectedSet:HasGroundUnits() + local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) + + 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 DetectedArea 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_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} 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 + + --- 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 DetectedArea", DetectedItem.AreaID, DetectedSet:Count(), tostring( DetectedItem ) } ) + DetectedSet:Flush() + + local AreaID = DetectedItem.AreaID + + -- Evaluate SEAD Tasking + local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) + 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, "SEAD." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + SEADTask = Mission:AddTask( Task ) + + end + end + if SEADTask and SEADTask:IsStatePlanned() then + TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate CAS Tasking + local CASTask = Mission:GetTask( "CAS." .. AreaID ) + 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, "CAS." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + CASTask = Mission:AddTask( Task ) + end + end + if CASTask and CASTask:IsStatePlanned() then + TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() + end + + -- Evaluate BAI Tasking + local BAITask = Mission:GetTask( "BAI." .. AreaID ) + 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, "BAI." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + BAITask = Mission:AddTask( Task ) + end + end + if BAITask and BAITask:IsStatePlanned() then + TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() + end + +-- if #TaskMsg > 0 then +-- +-- local ThreatLevel = Detection:GetTreatLevelA2G( DetectedItem ) +-- +-- local DetectedAreaVec3 = DetectedZone:GetVec3() +-- local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) +-- local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) +-- AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", +-- DetectedItemID, +-- DetectedAreaPointLL, +-- string.rep( "■", ThreatLevel ), +-- ThreatLevel +-- ) +-- +-- -- Loop through the changes ... +-- local ChangeText = Detection:GetChangeText( DetectedItem ) +-- +-- if ChangeText ~= "" then +-- ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) +-- end +-- end + + -- 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() + + if #TaskMsg > 0 then + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if not TaskGroup:GetState( TaskGroup, "Assigned" ) then + self.CommandCenter:MessageToGroup( + string.format( "HQ Reporting - Target areas for mission '%s':\nTasks:\n%s ", + self.Mission:GetName(), + table.concat( TaskMsg, "\n" ) + ), self:GetReportDisplayTime(), TaskGroup + ) + end + end + end + + return true + end + +end \ No newline at end of file diff --git a/Moose Development/Moose/Tasking/Task_SEAD.lua b/Moose Development/Moose/Tasking/Task_SEAD.lua deleted file mode 100644 index c6ee123d2..000000000 --- a/Moose Development/Moose/Tasking/Task_SEAD.lua +++ /dev/null @@ -1,258 +0,0 @@ ---- This module contains the TASK_SEAD classes. --- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK} --- ================================================= --- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, --- based on the tasking capabilities defined in @{Task#TASK}. --- The TASK_SEAD is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_SEAD - - - -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:New( Mission, SetGroup, TaskName, "SEAD" ) ) -- Tasking.Task#TASK_SEAD - self:F() - - self.TargetSetUnit = TargetSetUnit - - 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, "SEAD" ), { Accounted = "Success" } ) - --Fsm:AddProcess ( "Accounting", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) - 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_SEAD#TASK_SEAD 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_SEAD 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_SEAD 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_SEAD#TASK_SEAD 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_SEAD#TASK_SEAD 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_SEAD self - function TASK_SEAD:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - --- @param #TASK_SEAD 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_SEAD: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_SEAD 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_SEAD: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_SEAD 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_SEAD: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_SEAD self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. - function TASK_SEAD: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_SEAD 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_SEAD: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_SEAD self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Point#POINT_VEC2 The PointVec2 object where the Target is located on the map. - function TASK_SEAD: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_SEAD 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_SEAD: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_SEAD self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. - function TASK_SEAD:GetTargetZone( TaskUnit ) - - local ProcessUnit = self:GetUnitProcess( TaskUnit ) - - local ActRouteTarget = ProcessUnit:GetProcess( "Engaging", "RouteToTargetZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE - return ActRouteTarget:GetZone() - end - -end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index ad0fc58be..697ac1616 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -540,14 +540,14 @@ end function UNIT:GetThreatLevel() local Attributes = self:GetDesc().attributes - self:E( Attributes ) + self:T( Attributes ) local ThreatLevel = 0 local ThreatText = "" if self:IsGround() then - self:E( "Ground" ) + self:T( "Ground" ) local ThreatLevels = { "Unarmed", @@ -585,7 +585,7 @@ function UNIT:GetThreatLevel() if self:IsAir() then - self:E( "Air" ) + self:T( "Air" ) local ThreatLevels = { "Unarmed", @@ -619,7 +619,7 @@ function UNIT:GetThreatLevel() if self:IsShip() then - self:E( "Ship" ) + self:T( "Ship" ) --["Aircraft Carriers"] = {"Heavy armed ships",}, --["Cruisers"] = {"Heavy armed ships",}, 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 529b1f195..212de98c7 100644 --- a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua +++ b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua @@ -1,32 +1,761 @@ -<<<<<<< HEAD -env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170306_1309' ) - -======= env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170308_2139' ) ->>>>>>> refs/remotes/origin/master +env.info( 'Moose Generation Timestamp: 20170309_1321' ) local base = _G Include = {} - +Include.Files = {} Include.File = function( IncludeFile ) - 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" ) +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) else - env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath ) - return f() + return tostring(tbl) + 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 -<<<<<<< HEAD -Include.ProgramPath = "Scripts/Moose/" -======= + +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) @@ -3116,6 +3845,13 @@ function SCHEDULER:Remove( ScheduleID ) _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) end +--- Clears all pending schedules. +-- @param #SCHEDULER self +function SCHEDULER:Clear() + self:F3( ) + + _SCHEDULEDISPATCHER:Clear( self ) +end @@ -3313,11 +4049,15 @@ function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) if CallID then local Schedule = self.Schedule[Scheduler] - Schedule[CallID].ScheduleID = timer.scheduleFunction( - Schedule[CallID].CallHandler, - CallID, - timer.getTime() + Schedule[CallID].Start - ) + -- 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 @@ -3330,7 +4070,12 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) if CallID then local Schedule = self.Schedule[Scheduler] - timer.removeFunction( Schedule[CallID].ScheduleID ) + -- 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 @@ -3338,6 +4083,14 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) 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. @@ -3841,11 +4594,11 @@ end -- @param EventClass The instance of the class for which the event is. -- @param #function OnEventFunction -- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, OnEventFunction ) +function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EventID ) self:F2( EventTemplate.name ) for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - OnEventFunction( self, EventUnit.name, EventFunction, EventClass ) + self:OnEventForUnit( EventUnit.name, EventFunction, EventClass, EventID ) end return self end @@ -3918,51 +4671,11 @@ do -- OnBirth function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnBirthForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Birth ) return self end - --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnBirth( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_BIRTH ) - - return self - end - - --- Set a new listener for an S_EVENT_BIRTH event. - -- @param #EVENT self - -- @param #string EventDCSUnitName The id of the unit for the event to be handled. - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_BIRTH ) - - return self - end - - --- Stop listening to S_EVENT_BIRTH event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnBirthRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_BIRTH ) - - return self - end - - end do -- OnCrash @@ -3976,49 +4689,10 @@ do -- OnCrash function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnCrashForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Crash ) return self end - - --- Set a new listener for an S_EVENT_CRASH event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnCrash( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_CRASH ) - - return self - end - - --- Set a new listener for an S_EVENT_CRASH event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_CRASH ) - - return self - end - - --- Stop listening to S_EVENT_CRASH event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnCrashRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_CRASH ) - - return self - end end @@ -4033,96 +4707,13 @@ do -- OnDead function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnDeadForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Dead ) return self end - --- Set a new listener for an S_EVENT_DEAD event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnDead( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_DEAD ) - - return self - end - - - --- Set a new listener for an S_EVENT_DEAD event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_DEAD ) - - return self - end - - --- Stop listening to S_EVENT_DEAD event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnDeadRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_DEAD ) - - return self - end - - end -do -- OnPilotDead - - --- Set a new listener for an S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPilotDead( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - - --- Set a new listener for an S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - - --- Stop listening to S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPilotDeadRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - -end do -- OnLand --- Create an OnLand event handler for a group @@ -4134,38 +4725,11 @@ do -- OnLand function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnLandForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Land ) return self end - --- Set a new listener for an S_EVENT_LAND event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_LAND ) - - return self - end - - --- Stop listening to S_EVENT_LAND event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnLandRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_LAND ) - - return self - end - - end do -- OnTakeOff @@ -4178,38 +4742,11 @@ do -- OnTakeOff function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnTakeOffForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Takeoff ) return self end - --- Set a new listener for an S_EVENT_TAKEOFF event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_TAKEOFF ) - - return self - end - - --- Stop listening to S_EVENT_TAKEOFF event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnTakeOffRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_TAKEOFF ) - - return self - end - - end do -- OnEngineShutDown @@ -4223,210 +4760,11 @@ do -- OnEngineShutDown function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnEngineShutDownForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.EngineShutdown ) return self end - --- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) - - return self - end - - --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnEngineShutDownRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) - - return self - end - -end - -do -- OnEngineStartUp - - --- Set a new listener for an S_EVENT_ENGINE_STARTUP event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_STARTUP ) - - return self - end - - --- Stop listening to S_EVENT_ENGINE_STARTUP event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnEngineStartUpRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_ENGINE_STARTUP ) - - return self - end - -end - -do -- OnShot - --- Set a new listener for an S_EVENT_SHOT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnShot( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_SHOT ) - - return self - end - - --- Set a new listener for an S_EVENT_SHOT event for a unit. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_SHOT ) - - return self - end - - --- Stop listening to S_EVENT_SHOT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnShotRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_SHOT ) - - return self - end - - -end - -do -- OnHit - - --- Set a new listener for an S_EVENT_HIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnHit( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_HIT ) - - return self - end - - --- Set a new listener for an S_EVENT_HIT event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_HIT ) - - return self - end - - --- Stop listening to S_EVENT_HIT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnHitRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_HIT ) - - return self - end - -end - -do -- OnPlayerEnterUnit - - --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPlayerEnterUnit( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) - - return self - end - - --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPlayerEnterRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) - - return self - end - -end - -do -- OnPlayerLeaveUnit - --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPlayerLeaveUnit( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) - - return self - end - - --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPlayerLeaveRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) - - return self - end - end @@ -4549,7 +4887,9 @@ function EVENT:onEvent( Event ) local PriorityBegin = PriorityOrder == -1 and 5 or 1 local PriorityEnd = PriorityOrder == -1 and 1 or 5 - self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) + if Event.IniObjectCategory ~= 3 then + self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) + end for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do @@ -4570,8 +4910,10 @@ function EVENT:onEvent( Event ) -- First test if a EventFunction is Set, otherwise search for the default function if EventData.EventUnit[Event.IniDCSUnitName].EventFunction then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - + 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 ) @@ -4584,8 +4926,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4599,7 +4943,9 @@ function EVENT:onEvent( Event ) -- First test if a EventFunction is Set, otherwise search for the default function if EventData.EventUnit[Event.TgtDCSUnitName].EventFunction then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) + if Event.IniObjectCategory ~= 3 then + self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) + end local Result, Value = xpcall( function() @@ -4613,8 +4959,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4632,9 +4980,11 @@ function EVENT:onEvent( Event ) 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 - - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - + + 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 ) @@ -4647,8 +4997,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4660,8 +5012,10 @@ function EVENT:onEvent( Event ) if EventData.EventGroup[Event.TgtGroupName] then if EventData.EventGroup[Event.TgtGroupName].EventFunction then - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) - + 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 ) @@ -4674,7 +5028,9 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) + if Event.IniObjectCategory ~= 3 then + self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) + end local Result, Value = xpcall( function() @@ -4696,8 +5052,9 @@ function EVENT:onEvent( Event ) if EventData.EventFunction then -- There is an EventFunction defined, so call the EventFunction. - self:E( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4709,8 +5066,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -5885,6 +6244,58 @@ function ZONE_BASE:GetVec2() 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. @@ -5899,6 +6310,13 @@ 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. @@ -5985,8 +6403,9 @@ 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 ) +function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Point = {} local Vec2 = self:GetVec2() @@ -6002,8 +6421,10 @@ function ZONE_RADIUS:BoundZone( Points ) 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"] = "USA", + ["country"] = CountryName, ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -6015,7 +6436,10 @@ function ZONE_RADIUS:BoundZone( Points ) ["heading"] = 0, } -- end of ["group"] - coalition.addStaticObject( country.id.USA, Tire ) + local Group = coalition.addStaticObject( CountryID, Tire ) + if UnBound and UnBound == true then + Group:destroy() + end end return self @@ -6448,8 +6872,9 @@ 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( ) +function ZONE_POLYGON_BASE:BoundZone( UnBound ) local i local j @@ -6478,8 +6903,11 @@ function ZONE_POLYGON_BASE:BoundZone( ) ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), ["heading"] = 0, } -- end of ["group"] - - coalition.addStaticObject( country.id.USA, Tire ) + + local Group = coalition.addStaticObject( country.id.USA, Tire ) + if UnBound and UnBound == true then + Group:destroy() + end end j = i @@ -6715,6 +7143,8 @@ DATABASE = { PLAYERSJOINED = {}, CLIENTS = {}, AIRBASES = {}, + COUNTRY_ID = {}, + COUNTRY_NAME = {}, NavPoints = {}, } @@ -7422,6 +7852,9 @@ function DATABASE:_RegisterTemplates() 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 @@ -7705,6 +8138,7 @@ 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. @@ -7723,10 +8157,14 @@ function SET_BASE:New( 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 ) @@ -7778,6 +8216,8 @@ function SET_BASE:Add( ObjectName, Object ) 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. @@ -7829,7 +8269,15 @@ function SET_BASE:Remove( ObjectName ) 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 @@ -7849,12 +8297,50 @@ function SET_BASE:Get( ObjectName ) 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.List.Count + return #self.Index end @@ -8117,7 +8603,8 @@ function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArgumen return false end - self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + Schedule() return self end @@ -8190,7 +8677,7 @@ end --- SET_GROUP class -- @type SET_GROUP --- @extends #SET_BASE +-- @extends Core.Set#SET_BASE SET_GROUP = { ClassName = "SET_GROUP", Filter = { @@ -11416,10 +11903,20 @@ do -- FSM 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 Value = self[handler]( self, unpack(params) ) + local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) return Value end end @@ -11618,8 +12115,66 @@ do -- FSM_CONTROLLABLE 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 @@ -11687,6 +12242,27 @@ do -- FSM_PROCESS 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 @@ -11711,7 +12287,7 @@ do -- FSM_PROCESS -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do - self:T( { Process} ) + self:E( { Process} ) local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) end @@ -13205,48 +13781,35 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At return DCSTask end - --- (AIR) Attack the Unit. -- @param #CONTROLLABLE self --- @param Wrapper.Unit#UNIT AttackUnit The unit. --- @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 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, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- AttackUnit = { - -- id = 'AttackUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- } - -- } +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 = { - altitudeEnabled = true, unitId = AttackUnit:GetID(), - attackQtyLimit = AttackQtyLimit or false, - attackQty = AttackQty or 2, + groupAttack = GroupAttack or false, + visible = Visible or false, expend = WeaponExpend or "Auto", - altitude = 2000, - directionEnabled = true, - groupAttack = true, - --weaponType = WeaponType or 1073741822, - direction = Direction or 0, - } + 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 ) @@ -13860,7 +14423,7 @@ function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, end ---- (AIR) Attack the Unit. +--- (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. @@ -15092,7 +15655,7 @@ GROUP = { -- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name -- @return #GROUP self function GROUP:Register( GroupName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) + self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) self:F2( GroupName ) self.GroupName = GroupName @@ -16447,14 +17010,14 @@ end function UNIT:GetThreatLevel() local Attributes = self:GetDesc().attributes - self:E( Attributes ) + self:T( Attributes ) local ThreatLevel = 0 local ThreatText = "" if self:IsGround() then - self:E( "Ground" ) + self:T( "Ground" ) local ThreatLevels = { "Unarmed", @@ -16492,7 +17055,7 @@ function UNIT:GetThreatLevel() if self:IsAir() then - self:E( "Air" ) + self:T( "Air" ) local ThreatLevels = { "Unarmed", @@ -16526,7 +17089,7 @@ function UNIT:GetThreatLevel() if self:IsShip() then - self:E( "Ship" ) + self:T( "Ship" ) --["Aircraft Carriers"] = {"Heavy armed ships",}, --["Cruisers"] = {"Heavy armed ships",}, @@ -17385,7 +17948,7 @@ function STATIC:FindByName( StaticName, RaiseError ) self.StaticName = StaticName if StaticFound then - StaticFound:F( { StaticName } ) + StaticFound:F3( { StaticName } ) return StaticFound end @@ -18589,9 +19152,10 @@ function SCORING:_EventOnDeadOrCrash( Event ) :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 ) @@ -19229,7 +19793,9 @@ CLEANUP = { -- 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() ) +function CLEANUP:New( ZoneNames, TimeInterval ) + + local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP self:F( { ZoneNames, TimeInterval } ) if type( ZoneNames ) == 'table' then @@ -19241,7 +19807,7 @@ function CLEANUP:New( ZoneNames, TimeInterval ) local self = BASE:Inherit( self, self.TimeInterval = TimeInterval end - _EVENTDISPATCHER:OnBirth( self._OnEventBirth, self ) + self:HandleEvent( EVENTS.Birth ) self.CleanUpScheduler = SCHEDULER:New( self, self._CleanUpScheduler, {}, 1, TimeInterval ) @@ -19302,32 +19868,24 @@ function CLEANUP:_DestroyMissile( MissileObject ) end end -function CLEANUP:_OnEventBirth( Event ) - self:F( { Event } ) +--- @param #CLEANUP self +-- @param Core.Event#EVENTDATA EventData +function CLEANUP:_OnEventBirth( EventData ) + self:F( { EventData } ) - 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 - - _EVENTDISPATCHER:OnEngineShutDownForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnEngineStartUpForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnHitForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnPilotDeadForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnDeadForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnCrashForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnShotForUnit( Event.IniDCSUnitName, self._EventShot, self ) - - --self:AddEvent( world.event.S_EVENT_ENGINE_SHUTDOWN, self._EventAddForCleanUp ) - --self:AddEvent( world.event.S_EVENT_ENGINE_STARTUP, self._EventAddForCleanUp ) --- self:AddEvent( world.event.S_EVENT_HIT, self._EventAddForCleanUp ) -- , self._EventHitCleanUp ) --- self:AddEvent( world.event.S_EVENT_CRASH, self._EventCrash ) -- , self._EventHitCleanUp ) --- --self:AddEvent( world.event.S_EVENT_DEAD, self._EventCrash ) --- self:AddEvent( world.event.S_EVENT_SHOT, self._EventShot ) --- --- self:EnableEvents() + 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 @@ -21137,10 +21695,11 @@ end -- 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 +-- @module Movement --- the MOVEMENT class --- @type +-- @type MOVEMENT +-- @extends Core.Base#BASE MOVEMENT = { ClassName = "MOVEMENT", } @@ -21154,7 +21713,7 @@ MOVEMENT = { -- 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() ) + local self = BASE:Inherit( self, BASE:New() ) -- #MOVEMENT self:F( { MovePrefixes, MoveMaximum } ) if type( MovePrefixes ) == 'table' then @@ -21167,7 +21726,7 @@ function MOVEMENT:New( MovePrefixes, MoveMaximum ) 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. - _EVENTDISPATCHER:OnBirth( self.OnBirth, self ) + self:HandleEvent( EVENTS.Birth ) -- self:AddEvent( world.event.S_EVENT_BIRTH, self.OnBirth ) -- @@ -21194,24 +21753,26 @@ 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. -function MOVEMENT:OnBirth( Event ) - self:F( { Event } ) +-- @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 Event.IniDCSUnit then - self:T( "Birth object : " .. Event.IniDCSUnitName ) - if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then + 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( Event.IniDCSUnitName, MovePrefix, 1, true ) then + if string.find( EventData.IniDCSUnitName, MovePrefix, 1, true ) then self.AliveUnits = self.AliveUnits + 1 - self.MoveUnits[Event.IniDCSUnitName] = Event.IniDCSGroupName + self.MoveUnits[EventData.IniDCSUnitName] = EventData.IniDCSGroupName self:T( self.AliveUnits ) end end end end - _EVENTDISPATCHER:OnCrashForUnit( Event.IniDCSUnitName, self.OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForUnit( Event.IniDCSUnitName, self.OnDeadOrCrash, self ) + + EventData.IniUnit:HandleEvent( EVENTS.DEAD, self.OnDeadOrCrash ) end end @@ -21298,25 +21859,28 @@ function SEAD:New( SEADGroupPrefixes ) else self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes end - _EVENTDISPATCHER:OnShot( self.EventShot, self ) + + 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 -function SEAD:EventShot( Event ) - self:F( { Event } ) +-- @param #SEAD +-- @param Core.Event#EVENTDATA EventData +function SEAD:OnEventShot( EventData ) + self:F( { EventData } ) - local SEADUnit = Event.IniDCSUnit - local SEADUnitName = Event.IniDCSUnitName - local SEADWeapon = Event.Weapon -- Identify the weapon fired - local SEADWeaponName = Event.WeaponName -- return weapon type + 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 = Event.Weapon:getTarget() -- Identify target + local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMimname = Unit.getName(_targetMim) local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimgroupName = _targetMimgroup:getName() @@ -21475,7 +22039,7 @@ end -- -- ESCORT initialization methods. -- ============================== --- The following menus are created within the RADIO MENU of an active unit hosted by a player: +-- 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. @@ -21519,6 +22083,7 @@ end -- @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 @@ -21567,14 +22132,22 @@ ESCORT = { -- -- 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() ) + + 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 = {} @@ -21584,7 +22157,7 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) 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()].Targets = {} + self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection = self.EscortGroup.Detection end self.EscortMenu = MENU_CLIENT:New( self.EscortClient, self.EscortName ) @@ -21609,13 +22182,29 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) self.FollowDistance = 100 self.CT1 = 0 self.GT1 = 0 - self.FollowScheduler = SCHEDULER:New( self, self._FollowScheduler, {}, 1, .5, .01 ) - self.EscortMode = ESCORT.MODE.MISSION - self.FollowScheduler:Stop() + 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 + + +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 @@ -21673,7 +22262,7 @@ function ESCORT:MenuFollowAt( Distance ) self.EscortMenuJoinUpAndFollow = {} end - self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_CLIENT_COMMAND:New( self.EscortClient, "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, { ParamSelf = self, ParamDistance = Distance } ) + 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 @@ -21731,11 +22320,10 @@ function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) MenuText, self.EscortMenuHold, ESCORT._HoldPosition, - { ParamSelf = self, - ParamOrbitGroup = self.EscortGroup, - ParamHeight = Height, - ParamSeconds = Seconds - } + self, + self.EscortGroup, + Height, + Seconds ) end @@ -21852,9 +22440,8 @@ function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) MenuText, self.EscortMenuScan, ESCORT._ScanTargets, - { ParamSelf = self, - ParamScanDuration = 30 - } + self, + 30 ) end @@ -21884,11 +22471,11 @@ function ESCORT:MenuFlare( MenuTextFormat ) end if not self.EscortMenuFlare then - self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, { ParamSelf = self } ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Green, ParamMessage = "Released a green flare!" } ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Red, ParamMessage = "Released a red flare!" } ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.White, ParamMessage = "Released a white flare!" } ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Yellow, ParamMessage = "Released a yellow flare!" } ) + 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 @@ -21917,12 +22504,12 @@ function ESCORT:MenuSmoke( MenuTextFormat ) end if not self.EscortMenuSmoke then - self.EscortMenuSmoke = MENU_CLIENT:New( self.EscortClient, "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, { ParamSelf = self } ) - self.EscortMenuSmokeGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Green, ParamMessage = "Releasing green smoke!" } ) - self.EscortMenuSmokeRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Red, ParamMessage = "Releasing red smoke!" } ) - self.EscortMenuSmokeWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.White, ParamMessage = "Releasing white smoke!" } ) - self.EscortMenuSmokeOrange = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Orange, ParamMessage = "Releasing orange smoke!" } ) - self.EscortMenuSmokeBlue = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Blue, ParamMessage = "Releasing blue smoke!" } ) + 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 @@ -21947,9 +22534,9 @@ function ESCORT:MenuReportTargets( Seconds ) end -- Report Targets - self.EscortMenuReportNearbyTargetsNow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, { ParamSelf = self } ) - self.EscortMenuReportNearbyTargetsOn = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, { ParamSelf = self, ParamReportTargets = true } ) - self.EscortMenuReportNearbyTargetsOff = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, { ParamSelf = self, ParamReportTargets = false, } ) + 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 ) @@ -21986,16 +22573,16 @@ function ESCORT:MenuROE( MenuTextFormat ) -- 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEHoldFire(), ParamMessage = "Holding weapons!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEReturnFire(), ParamMessage = "Returning fire!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEOpenFire(), ParamMessage = "Opening fire on designated targets!!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEWeaponFree(), ParamMessage = "Opening fire on targets of opportunity!" } ) + 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 @@ -22015,16 +22602,16 @@ function ESCORT:MenuEvasion( MenuTextFormat ) -- 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTNoReaction(), ParamMessage = "Fighting until death!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTPassiveDefense(), ParamMessage = "Defending using jammers, chaff and flares!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTEvadeFire(), ParamMessage = "Evading on enemy fire!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTVertical(), ParamMessage = "Evading on enemy fire with vertical manoeuvres!" } ) + 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 @@ -22049,18 +22636,14 @@ end --- @param #MENUPARAM MenuParam -function ESCORT._HoldPosition( MenuParam ) +function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local OrbitGroup = MenuParam.ParamOrbitGroup -- Wrapper.Group#GROUP local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - local OrbitHeight = MenuParam.ParamHeight - local OrbitSeconds = MenuParam.ParamSeconds -- Not implemented yet - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) local PointFrom = {} local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() @@ -22093,13 +22676,12 @@ function ESCORT._HoldPosition( MenuParam ) end --- @param #MENUPARAM MenuParam -function ESCORT._JoinUpAndFollow( MenuParam ) +function ESCORT:_JoinUpAndFollow( Distance ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - self.Distance = MenuParam.ParamDistance + self.Distance = Distance self:JoinUpAndFollow( EscortGroup, EscortClient, self.Distance ) end @@ -22112,7 +22694,7 @@ end function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self:F( { EscortGroup, EscortClient, Distance } ) - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) EscortGroup:OptionROEHoldFire() EscortGroup:OptionROTPassiveDefense() @@ -22121,44 +22703,35 @@ function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self.CT1 = 0 self.GT1 = 0 - self.FollowScheduler:Start() + self.FollowScheduler:Start( self.FollowSchedule ) EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._Flare( MenuParam ) +function ESCORT:_Flare( Color, Message ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local Color = MenuParam.ParamColor - local Message = MenuParam.ParamMessage - EscortGroup:GetUnit(1):Flare( Color ) EscortGroup:MessageToClient( Message, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._Smoke( MenuParam ) +function ESCORT:_Smoke( Color, Message ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local Color = MenuParam.ParamColor - local Message = MenuParam.ParamMessage - EscortGroup:GetUnit(1):Smoke( Color ) EscortGroup:MessageToClient( Message, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._ReportNearbyTargetsNow( MenuParam ) +function ESCORT:_ReportNearbyTargetsNow() - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient @@ -22166,17 +22739,16 @@ function ESCORT._ReportNearbyTargetsNow( MenuParam ) end -function ESCORT._SwitchReportNearbyTargets( MenuParam ) +function ESCORT:_SwitchReportNearbyTargets( ReportTargets ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - self.ReportTargets = MenuParam.ParamReportTargets + self.ReportTargets = ReportTargets if self.ReportTargets then if not self.ReportTargetsScheduler then - self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, 30 ) + self.ReportTargetsScheduler:Schedule( self, self._ReportTargetsScheduler, {}, 1, 30 ) end else routines.removeFunction( self.ReportTargetsScheduler ) @@ -22185,40 +22757,31 @@ function ESCORT._SwitchReportNearbyTargets( MenuParam ) end --- @param #MENUPARAM MenuParam -function ESCORT._ScanTargets( MenuParam ) +function ESCORT:_ScanTargets( ScanDuration ) - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP local EscortClient = self.EscortClient - local ScanDuration = MenuParam.ParamScanDuration - - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) if EscortGroup:IsHelicopter() then - SCHEDULER:New( EscortGroup, EscortGroup.PushTask, - { EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ) - }, - 1 - ) + EscortGroup:PushTask( + EscortGroup:TaskControlled( + EscortGroup:TaskOrbitCircle( 200, 20 ), + EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) + ), 1 ) elseif EscortGroup:IsAirPlane() then - SCHEDULER:New( EscortGroup, EscortGroup.PushTask, - { EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ) - }, - 1 - ) + 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.FollowScheduler:Start( self.FollowSchedule ) end end @@ -22235,124 +22798,157 @@ function _Resume( EscortGroup ) end ---- @param #MENUPARAM MenuParam -function ESCORT._AttackTarget( MenuParam ) +--- @param #ESCORT self +-- @param #number DetectedItemID +function ESCORT:_AttackTarget( DetectedItemID ) - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP + self:E( EscortGroup ) local EscortClient = self.EscortClient - local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT - self.FollowScheduler:Stop() - - self:T( AttackUnit ) + self.FollowScheduler:Stop( self.FollowSchedule ) if EscortGroup:IsAir() then EscortGroup:OptionROEOpenFire() EscortGroup:OptionROTPassiveDefense() EscortGroup:SetState( EscortGroup, "Escort", self ) - SCHEDULER:New( EscortGroup, - EscortGroup.PushTask, - { EscortGroup:TaskCombo( - { EscortGroup:TaskAttackUnit( AttackUnit ), - EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } ) - } - ) - }, 10 + + 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 - SCHEDULER:New( EscortGroup, - EscortGroup.PushTask, - { EscortGroup:TaskCombo( - { EscortGroup:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) - } - ) - }, 10 + + 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 #MENUPARAM MenuParam -function ESCORT._AssistTarget( MenuParam ) +--- +-- @param #number DetectedItemID +function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItemID ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local EscortGroupAttack = MenuParam.ParamEscortGroup - local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT - self.FollowScheduler:Stop() - - self:T( AttackUnit ) + self.FollowScheduler:Stop( self.FollowSchedule ) if EscortGroupAttack:IsAir() then EscortGroupAttack:OptionROEOpenFire() EscortGroupAttack:OptionROTVertical() - SCHDULER:New( EscortGroupAttack, - EscortGroupAttack.PushTask, - { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskAttackUnit( AttackUnit ), - EscortGroupAttack:TaskOrbitCircle( 500, 350 ) - } - ) - }, 10 + + 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 - SCHEDULER:New( EscortGroupAttack, - EscortGroupAttack.PushTask, - { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) - } - ) - }, 10 + 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( MenuParam ) +function ESCORT:_ROE( EscortROEFunction, EscortROEMessage ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local EscortROEFunction = MenuParam.ParamFunction - local EscortROEMessage = MenuParam.ParamMessage - pcall( function() EscortROEFunction() end ) EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._ROT( MenuParam ) +function ESCORT:_ROT( EscortROTFunction, EscortROTMessage ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local EscortROTFunction = MenuParam.ParamFunction - local EscortROTMessage = MenuParam.ParamMessage - pcall( function() EscortROTFunction() end ) EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._ResumeMission( MenuParam ) +function ESCORT:_ResumeMission( WayPoint ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local WayPoint = MenuParam.ParamWayPoint - - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) local WayPoints = EscortGroup:GetTaskRoute() self:T( WayPoint, WayPoints ) @@ -22496,176 +23092,244 @@ function ESCORT:_ReportTargetsScheduler() self:F( self.EscortGroup:GetName() ) if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - local EscortGroupName = self.EscortGroup:GetName() - local EscortTargets = self.EscortGroup:GetDetectedTargets() - local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets + if true then - 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 EscortGroupName = self.EscortGroup:GetName() + + self.EscortMenuAttackNearbyTargets:RemoveSubMenus() - 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 + if self.EscortMenuTargetAssistance then + self.EscortMenuTargetAssistance:RemoveSubMenus() 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 } ) + local DetectedItems = self.Detection:GetDetectedItems() + self:E( DetectedItems ) - -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. - self.EscortMenuAttackNearbyTargets:RemoveSubMenus() + local DetectedTargets = false + + local DetectedMsgs = {} + + for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do - if self.EscortMenuTargetAssistance then - self.EscortMenuTargetAssistance:RemoveSubMenus() - end + local ClientEscortTargets = EscortGroupData.Detection - --for MenuIndex = 1, #self.EscortMenuAttackTargets do - -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) - -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() - --end + 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 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, - { ParamSelf = self, - ParamEscortGroup = EscortGroupData.EscortGroup, - ParamUnit = ClientEscortTargetData.AttackUnit - } - ) - end - end + if ClientEscortGroupName == EscortGroupName then + + DetectedMsgs[#DetectedMsgs+1] = DetectedItemReportSummary + + MENU_CLIENT_COMMAND:New( self.EscortClient, + DetectedItemReportSummary, + self.EscortMenuAttackNearbyTargets, + ESCORT._AttackTarget, + self, + DetectedItemID + ) else - ClientEscortTargetData = nil + 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 - - if EscortTargetMessages ~= "" and self.ReportTargets == true then - self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) + 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!", 20, self.EscortClient ) + 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 - - 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 return false @@ -22843,7 +23507,7 @@ function MISSILETRAINER:New( Distance, Briefing ) self.Distance = Distance / 1000 - _EVENTDISPATCHER:OnShot( self._EventShot, self ) + self:HandleEvent( EVENTS.Shot ) self.DBClients = SET_CLIENT:New():FilterStart() @@ -23121,14 +23785,14 @@ 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 Event -function MISSILETRAINER:_EventShot( Event ) - self:F( { Event } ) +-- @param Core.Event#EVENTDATA EventData +function MISSILETRAINER:OnEventShot( EVentData ) + self:F( { EVentData } ) - local TrainerSourceDCSUnit = Event.IniDCSUnit - local TrainerSourceDCSUnitName = Event.IniDCSUnitName - local TrainerWeapon = Event.Weapon -- Identify the weapon fired - local TrainerWeaponName = Event.WeaponName -- return weapon type + 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 ) @@ -24576,17 +25240,17 @@ end -- -- === -- --- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} --- ========================================================== --- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). +-- # 1) @{#DETECTION_BASE} class, extends @{Fsm#FSM} -- --- 1.1) DETECTION_BASE constructor --- ------------------------------- --- Construct a new DETECTION_BASE instance using the @{Detection#DETECTION_BASE.New}() method. +-- 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 -- --- 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. @@ -24594,969 +25258,1920 @@ end -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: -- --- * @{Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using 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) Obtain objects detected by DETECTION_BASE --- ---------------------------------------------- --- DETECTION_BASE builds @{Set}s of objects detected. These @{Set#SET_BASE}s can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSets}(). --- The method will return a list (table) of @{Set#SET_BASE} objects. +-- ## 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_AREAS} class, extends @{Detection#DETECTION_BASE} --- =============================================================================== +-- # 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. -- --- 2.1) Retrieve the Detected Unit sets and Detected Zones --- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. +-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones -- --- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. --- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). --- If you want to obtain a specific set from the DetectedSets, use the method @{Detection#DETECTION_BASE.GetDetectedSet}() with a given index. +-- 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. -- --- 1.4) Flare or Smoke detected units --- ---------------------------------- +-- ## 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. -- --- 1.5) Flare or Smoke detected zones --- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones 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 : Concept & Testing +-- * Mechanist : Early concept of DETECTION_AREAS. -- -- ### Authors: -- --- * FlightControl : Design & Programming +-- * 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.Base#BASE -DETECTION_BASE = { - ClassName = "DETECTION_BASE", - DetectionSetGroup = nil, - DetectionRange = nil, - DetectedObjects = {}, - DetectionRun = 0, - DetectedObjectsIdentified = {}, -} - ---- @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 - ---- DETECTION constructor. --- @param #DETECTION_BASE self --- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @return #DETECTION_BASE self -function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) + --- 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 = {}, + } - self.DetectionSetGroup = DetectionSetGroup - self.DetectionRange = DetectionRange + --- @type DETECTION_BASE.DetectedObjects + -- @list <#DETECTION_BASE.DetectedObject> - self:InitDetectVisual( false ) - self:InitDetectOptical( false ) - self:InitDetectRadar( false ) - self:InitDetectRWR( false ) - self:InitDetectIRST( false ) - self:InitDetectDLINK( false ) + --- @type DETECTION_BASE.DetectedObject + -- @field #string Name + -- @field #boolean Visible + -- @field #string Type + -- @field #number Distance + -- @field #boolean Identified - return self -end - ---- 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 - ---- 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( 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:F3( ObjectName ) + --- @type DETECTION_BASE.DetectedItems + -- @list <#DETECTION_BASE.DetectedItem> - if ObjectName then - local DetectedObject = self.DetectedObjects[ObjectName] + --- @type DETECTION_BASE.DetectedItem + -- @field Core.Set#SET_UNIT Set + + + --- 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.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 - -- 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 + --- @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 - end - - return nil -end - ---- Get the detected @{Set#SET_BASE}s. --- @param #DETECTION_BASE self --- @return #DETECTION_BASE.DetectedSets DetectedSets -function DETECTION_BASE:GetDetectedSets() - - local DetectionSets = self.DetectedSets - return DetectionSets -end - ---- Get the amount of SETs with detected objects. --- @param #DETECTION_BASE self --- @return #number Count -function DETECTION_BASE:GetDetectedSetCount() - - local DetectionSetCount = #self.DetectedSets - return DetectionSetCount -end - ---- Get a SET of detected objects using a given numeric index. --- @param #DETECTION_BASE self --- @param #number Index --- @return Core.Set#SET_BASE -function DETECTION_BASE:GetDetectedSet( Index ) - - local DetectionSet = self.DetectedSets[Index] - if DetectionSet then - return DetectionSet - end - - 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 - - ---- Form @{Set}s of detected @{Unit#UNIT}s in an array of @{Set#SET_BASE}s. --- @param #DETECTION_BASE self -function DETECTION_BASE:_DetectionScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - self.DetectionRun = self.DetectionRun + 1 - - self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - - for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - local DetectionGroup = DetectionGroupData -- Wrapper.Group#GROUP - - if DetectionGroup:IsAlive() then - - local DetectionGroupName = DetectionGroup:GetName() + + --- @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}) - local DetectionDetectedTargets = DetectionGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) + self.DetectionRun = self.DetectionRun + 1 - for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do - local DetectionObject = DetectionDetectedTarget.object -- Dcs.DCSWrapper.Object#Object - self:T2( DetectionObject ) + local HasDetectedObjects = false + + if DetectionGroup:IsAlive() then + + self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) - if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then - - local DetectionDetectedObjectName = DetectionObject:getName() - - local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() - local DetectionGroupVec3 = DetectionGroup:GetVec3() - - local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupVec3.x )^2 + - ( DetectionDetectedObjectPositionVec3.y - DetectionGroupVec3.y )^2 + - ( DetectionDetectedObjectPositionVec3.z - DetectionGroupVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) - - if Distance <= self.DetectionRange then - - if not self.DetectedObjects[DetectionDetectedObjectName] then - self.DetectedObjects[DetectionDetectedObjectName] = {} - end - self.DetectedObjects[DetectionDetectedObjectName].Name = DetectionDetectedObjectName - self.DetectedObjects[DetectionDetectedObjectName].Visible = DetectionDetectedTarget.visible - self.DetectedObjects[DetectionDetectedObjectName].Type = DetectionDetectedTarget.type - self.DetectedObjects[DetectionDetectedObjectName].Distance = DetectionDetectedTarget.distance - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[DetectionDetectedObjectName] then - self.DetectedObjects[DetectionDetectedObjectName] = nil - end - end - end - end - - self:T2( self.DetectedObjects ) - - -- okay, now we have a list of detected object names ... - -- Sort the table based on distance ... - table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) - end - end - - if self.DetectedObjects then - self:CreateDetectionSets() - end - - return true -end - - - ---- 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_AREAS.DetectedAreas DetectedAreas 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 Functional.Detection#DETECTION_BASE -DETECTION_AREAS = { - ClassName = "DETECTION_AREAS", - DetectedAreas = { n = 0 }, - DetectionZoneRange = nil, -} - ---- @type DETECTION_AREAS.DetectedAreas --- @list <#DETECTION_AREAS.DetectedArea> - ---- @type DETECTION_AREAS.DetectedArea --- @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 AreaID -- 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_AREAS constructor. --- @param Functional.Detection#DETECTION_AREAS self --- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Functional.Detection#DETECTION_AREAS self -function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) ) - - self.DetectionZoneRange = DetectionZoneRange - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - - self:Schedule( 10, 10 ) - - return self -end - ---- Add a detected @{#DETECTION_AREAS.DetectedArea}. --- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. --- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. --- @return #DETECTION_AREAS.DetectedArea DetectedArea -function DETECTION_AREAS:AddDetectedArea( Set, Zone ) - local DetectedAreas = self:GetDetectedAreas() - DetectedAreas.n = self:GetDetectedAreaCount() + 1 - DetectedAreas[DetectedAreas.n] = {} - local DetectedArea = DetectedAreas[DetectedAreas.n] - DetectedArea.Set = Set - DetectedArea.Zone = Zone - DetectedArea.Removed = false - DetectedArea.AreaID = DetectedAreas.n - - return DetectedArea -end - ---- Remove a detected @{#DETECTION_AREAS.DetectedArea} with a given Index. --- @param #DETECTION_AREAS self --- @param #number Index The Index of the detection are to be removed. --- @return #nil -function DETECTION_AREAS:RemoveDetectedArea( Index ) - local DetectedAreas = self:GetDetectedAreas() - local DetectedAreaCount = self:GetDetectedAreaCount() - local DetectedArea = DetectedAreas[Index] - local DetectedAreaSet = DetectedArea.Set - DetectedArea[Index] = nil - return nil -end - - ---- Get the detected @{#DETECTION_AREAS.DetectedAreas}. --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS.DetectedAreas DetectedAreas -function DETECTION_AREAS:GetDetectedAreas() - - local DetectedAreas = self.DetectedAreas - return DetectedAreas -end - ---- Get the amount of @{#DETECTION_AREAS.DetectedAreas}. --- @param #DETECTION_AREAS self --- @return #number DetectedAreaCount -function DETECTION_AREAS:GetDetectedAreaCount() - - local DetectedAreaCount = self.DetectedAreas.n - return DetectedAreaCount -end - ---- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. --- @param #DETECTION_AREAS self --- @param #number Index --- @return Core.Set#SET_UNIT DetectedSet -function DETECTION_AREAS:GetDetectedSet( Index ) - - local DetectedSetUnit = self.DetectedAreas[Index].Set - if DetectedSetUnit then - return DetectedSetUnit - end - - return nil -end - ---- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. --- @param #DETECTION_AREAS self --- @param #number Index --- @return Core.Zone#ZONE_UNIT DetectedZone -function DETECTION_AREAS:GetDetectedZone( Index ) - - local DetectedZone = self.DetectedAreas[Index].Zone - if DetectedZone then - return DetectedZone - end - - return nil -end - ---- Background worker function to determine if there are friendlies nearby ... --- @param #DETECTION_AREAS self --- @param Wrapper.Unit#UNIT ReportUnit -function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) - self:F2() - - local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = ReportGroupData.DetectedArea.Set - local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT - - DetectedArea.FriendliesNearBy = false - - local SphereSearch = { - id = world.VolumeType.SPHERE, - params = { - point = DetectedZoneUnit:GetVec3(), - radius = 6000, - } + 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 - } - - --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit - -- @param Wrapper.Group#GROUP ReportGroup - -- @param Set#SET_GROUP ReportSetGroup - local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = ReportGroupData.DetectedArea.Set - local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT - local ReportSetGroup = ReportGroupData.ReportSetGroup - - local EnemyCoalition = DetectedZoneUnit:GetCoalition() - - local FoundUnitCoalition = FoundDCSUnit:getCoalition() - local FoundUnitName = FoundDCSUnit:getName() - local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() - local EnemyUnitName = DetectedZoneUnit:GetName() - local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - - self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - - if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then - DetectedArea.FriendliesNearBy = true - return false - end - - return true - end - - world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) - -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( DetectedArea ) - - self:T3( DetectedArea.FriendliesNearBy ) - return DetectedArea.FriendliesNearBy or false -end - ---- Calculate the maxium A2G threat level of the DetectedArea. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea -function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G - -end - ---- Find the nearest FAC of the DetectedArea. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return Wrapper.Unit#UNIT The nearest FAC unit -function DETECTION_AREAS:NearestFAC( DetectedArea ) - - 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 - - DetectedArea.NearestFAC = NearestFAC - -end - ---- Returns the A2G threat level of the units in the DetectedArea --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #number a scale from 0 to 10. -function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) - - self:T3( DetectedArea.MaxThreatLevelA2G ) - return DetectedArea.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 - ---- Add a change to the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @param #string ChangeCode --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) - - DetectedArea.Changed = true - local AreaID = DetectedArea.AreaID - - DetectedArea.Changes = DetectedArea.Changes or {} - DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} - DetectedArea.Changes[ChangeCode].AreaID = AreaID - DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType - - self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) - - return self -end - - ---- Add a change to the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @param #string ChangeCode --- @param #string ChangeUnitType --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) - - DetectedArea.Changed = true - local AreaID = DetectedArea.AreaID - - DetectedArea.Changes = DetectedArea.Changes or {} - DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} - DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 - DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 - DetectedArea.Changes[ChangeCode].AreaID = AreaID - - self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) - - return self -end - ---- Make text documenting the changes of the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #string The Changes text -function DETECTION_AREAS:GetChangeText( DetectedArea ) - self:F( DetectedArea ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do - - if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." - end - - if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target." - end - - if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." - end - - if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." - end - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "AreaID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "AreaID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - -end - - ---- Accepts changes from the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AcceptChanges( DetectedArea ) - - DetectedArea.Changed = false - DetectedArea.Changes = {} - - return self -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() - - -- 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 DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - if DetectedArea then - - local DetectedSet = DetectedArea.Set - - local AreaExists = false -- This flag will determine of the detected area is still existing. + local DetectionAccepted = true - -- First test if the center unit is detected in the detection area. - self:T3( DetectedArea.Zone.ZoneUNIT.UnitName ) - local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) - self:T3( { "Detecting Zone Object", DetectedArea.AreaID, DetectedArea.Zone, 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( DetectedArea.Zone.ZoneUNIT.UnitName ) - - self:AddChangeArea( DetectedArea, '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 - - -- Assign the Unit as the new center unit of the detected area. - DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - - self:AddChangeArea( DetectedArea, "AAU", DetectedArea.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 DetectedArea.Zone - if DetectedUnit:IsInZone( DetectedArea.Zone ) then - - -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. - self:IdentifyDetectedObject( DetectedObject ) - - else - -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. - DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + 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( { 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 - - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) - DetectedSet:Remove( DetectedUnitName ) - -- The DetectedObject has been identified, because it does not exist ... - -- self:IdentifyDetectedObject( DetectedObject ) + 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 - else - self:RemoveDetectedArea( DetectedAreaID ) - self:AddChangeArea( DetectedArea, "RA" ) + + 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:CreateDetectionSets() + 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 + do -- Initialization methods + + --- Detect Visual. + -- @param #DETECTION_BASE self + -- @param #boolean DetectVisual + -- @return #DETECTION_BASE self + function DETECTION_BASE:InitDetectVisual( DetectVisual ) - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) + self.DetectVisual = DetectVisual + end - if DetectedObject then + --- 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 - -- We found an unidentified unit outside of any existing detection area. + 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 + + --- 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( 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:F3( 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 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:AddDetectedItem( Set, Zone ) + + local DetectedItem = {} + DetectedItem.Set = Set or SET_UNIT:New() + DetectedItem.Zone = Zone + + self.DetectedItemCount = self.DetectedItemCount + 1 + self.DetectedItems[self.DetectedItemCount] = DetectedItem + + 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 + + --- 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 ) + + self.DetectedItems = {} + + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + self:T( { "Detected Unit #", DetectedUnitName } ) + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - local AddedToDetectionArea = false - - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - if DetectedArea then - self:T( "Detection Area #" .. DetectedArea.AreaID ) - local DetectedSet = DetectedArea.Set - if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then - self:IdentifyDetectedObject( DetectedObject ) - DetectedSet:AddUnit( DetectedUnit ) - AddedToDetectionArea = true - self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) - end - end - end - - if AddedToDetectionArea == false then + if DetectedUnit then - -- New detection area - local DetectedArea = self:AddDetectedArea( - SET_UNIT:New(), - ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - ) - --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) - DetectedArea.Set:AddUnit( DetectedUnit ) - self:AddChangeArea( DetectedArea, "AA", DetectedUnit:GetTypeName() ) - end + local DetectedItem = self:AddDetectedItem() + DetectedItem.Type = DetectedObjectData.Type + DetectedItem.Name = DetectedObjectData.Name + DetectedItem.Visible = DetectedObjectData.Visible + DetectedItem.Distance = DetectedObjectData.Distance + DetectedItem.Set:AddUnit( DetectedUnit ) + 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. + --- 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 ) - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level - self:NearestFAC( DetectedArea ) - - 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 #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end + 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 - ) - if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) + + self:T( ReportSummary ) + + return ReportSummary end - if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) + 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 + + --- Adds a new DetectedItem to the DetectedItems list. + -- The DetectedItem is a table and contains a SET_UNIT in the field Set. + -- @param #DETECTION_TYPES self + -- @param #string TypeName + -- @return #DETECTION_TYPES.DetectedItem + function DETECTION_TYPES:AddDetectedItem( TypeName ) + + local DetectedItem = {} + DetectedItem.Set = SET_UNIT:New() + + self.DetectedItems[TypeName] = DetectedItem + + 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_TYPES self + -- @param #string TypeName + function DETECTION_TYPES:RemoveDetectedItem( TypeName ) + + self.DetectedItems[TypeName] = nil + end + + --- Get the amount of SETs with detected objects. + -- @param #DETECTION_TYPES self + -- @return #number Count + function DETECTION_TYPES:GetDetectedItemsCount() + + local DetectedCount = 0 + return DetectedCount + end + + --- Get a detected item using a given numeric index. + -- @param #DETECTION_TYPES self + -- @param #string TypeName + -- @return DETECTION_TYPES.DetectedItem + function DETECTION_TYPES:GetDetectedItem( TypeName ) + + local DetectedItem = self.DetectedItems[TypeName] + 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_TYPES self + -- @param #string TypeName + -- @return Core.Set#SET_UNIT DetectedSet + function DETECTION_TYPES:GetDetectedSet( TypeName ) + + local DetectedItem = self:GetDetectedItem( TypeName ) + local DetectedSetUnit = DetectedItem.Set + if DetectedSetUnit then + return DetectedSetUnit + end + + return nil + 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 ) + + self.DetectedItems = {} + + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + 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 ) + end + 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_AREAS.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, + } + + --- @type DETECTION_AREAS.DetectedItems + -- @list <#DETECTION_AREAS.DetectedItem> + + --- @type DETECTION_AREAS.DetectedItem + -- @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 AreaID -- 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_AREAS constructor. + -- @param Functional.Detection#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 Functional.Detection#DETECTION_AREAS self + 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 + + --- Add a detected @{#DETECTION_AREAS.DetectedItem}. + -- @param #DETECTION_AREAS self + -- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. + -- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. + -- @return #DETECTION_AREAS.DetectedItem DetectedItem + function DETECTION_AREAS:AddDetectedItem( Set, Zone ) + self:F( { Set, Zone } ) + + local DetectedItem = self:GetParent( self ).AddDetectedItem( self, Set, Zone ) + + DetectedItem.Removed = false + DetectedItem.AreaID = #self.DetectedItems + 1 + + self:T( { #self.DetectedItems, DetectedItem } ) + + return DetectedItem + + 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 DetectedAreaPointLL = DetectedItemPointVec3:ToStringLL( 3, true ) + local ReportSummary = string.format( + "Area #%d - %s - Threat Level [%s] (%2d)", + Index, + DetectedAreaPointLL, + string.rep( "■", ThreatLevelA2G ), + ThreatLevelA2G + ) + + return ReportSummary + end + + return nil + end + + --- Background worker function to determine if there are friendlies nearby ... + -- @param #DETECTION_AREAS self + -- @param Wrapper.Unit#UNIT ReportUnit + function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) + self:F2() + + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT + + DetectedArea.FriendliesNearBy = false + + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = DetectedZoneUnit: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 DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT + local ReportSetGroup = ReportGroupData.ReportSetGroup + + local EnemyCoalition = DetectedZoneUnit:GetCoalition() + + local FoundUnitCoalition = FoundDCSUnit:getCoalition() + local FoundUnitName = FoundDCSUnit:getName() + local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() + local EnemyUnitName = DetectedZoneUnit:GetName() + local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil + + self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then + DetectedArea.FriendliesNearBy = true + return false + end + + return true + end + + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) + + 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( DetectedArea ) + + self:T3( DetectedArea.FriendliesNearBy ) + return DetectedArea.FriendliesNearBy or false + end + + --- Calculate the maxium A2G threat level of the DetectedArea. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) + + local MaxThreatLevelA2G = 0 + for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT + local ThreatLevelA2G = ThreatUnit:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + end + end + + self:T3( MaxThreatLevelA2G ) + DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G + + end + + --- Find the nearest FAC of the DetectedArea. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return Wrapper.Unit#UNIT The nearest FAC unit + function DETECTION_AREAS:NearestFAC( DetectedArea ) + + 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 + + DetectedArea.NearestFAC = NearestFAC + + end + + --- Returns the A2G threat level of the units in the DetectedArea + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return #number a scale from 0 to 10. + function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) + + self:T3( DetectedArea.MaxThreatLevelA2G ) + return DetectedArea.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 + + --- Add a change to the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @param #string ChangeCode + -- @return #DETECTION_AREAS self + function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode].AreaID = AreaID + DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) + + return self + end + + + --- Add a change to the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @param #string ChangeCode + -- @param #string ChangeUnitType + -- @return #DETECTION_AREAS self + function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 + DetectedArea.Changes[ChangeCode].AreaID = AreaID + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) + + return self + end + + --- Make text documenting the changes of the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return #string The Changes text + function DETECTION_AREAS:GetChangeText( DetectedArea ) + self:F( DetectedArea ) + + local MT = {} + + for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do + + if ChangeCode == "AA" then + MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." + end + + if ChangeCode == "RAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target." + end + + if ChangeCode == "AAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "RA" then + MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." + end + + if ChangeCode == "AU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + if ChangeCode == "RU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + end + + return table.concat( MT, "\n" ) + + end + + + --- Accepts changes from the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return #DETECTION_AREAS self + function DETECTION_AREAS:AcceptChanges( DetectedArea ) + + DetectedArea.Changed = false + DetectedArea.Changes = {} + + return self + 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 DetectedAreaID, DetectedAreaData in pairs( self.DetectedItems ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + + self:T( { "Detected Area ID:", DetectedAreaID } ) + + + local DetectedSet = DetectedArea.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:", DetectedArea.Zone.ZoneUNIT.UnitName } ) + local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) + self:T3( { "Detected Zone Object:", DetectedArea.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( DetectedArea.Zone.ZoneUNIT.UnitName ) + + self:AddChangeArea( DetectedArea, '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 + + DetectedArea.Zone:BoundZone( 12, self.CountryID, true) + + -- Assign the Unit as the new center unit of the detected area. + DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) + + self:AddChangeArea( DetectedArea, "AAU", DetectedArea.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 DetectedArea.Zone + if DetectedUnit:IsInZone( DetectedArea.Zone ) then + + -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. + self:IdentifyDetectedObject( DetectedObject ) + + else + -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. + DetectedSet:Remove( DetectedUnitName ) + self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + end + + else + -- There was no DetectedObject, remove DetectedUnit from the Set. + self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) + DetectedSet:Remove( DetectedUnitName ) + + -- The DetectedObject has been identified, because it does not exist ... + -- self:IdentifyDetectedObject( DetectedObject ) + end + end + else + DetectedArea.Zone:BoundZone( 12, self.CountryID, true) + self:RemoveDetectedItem( DetectedAreaID ) + self:AddChangeArea( DetectedArea, "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 DetectedAreaID, DetectedAreaData in pairs( self.DetectedItems ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + self:T( "Detection Area #" .. DetectedArea.AreaID ) + local DetectedSet = DetectedArea.Set + if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then + self:IdentifyDetectedObject( DetectedObject ) + DetectedSet:AddUnit( DetectedUnit ) + AddedToDetectionArea = true + self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) + end + end + end + + if AddedToDetectionArea == false then + + -- New detection area + local DetectedArea = self:AddDetectedItem( + SET_UNIT:New(), + ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) + ) + --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) + DetectedArea.Set:AddUnit( DetectedUnit ) + self:AddChangeArea( DetectedArea, "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 DetectedAreaID, DetectedAreaData in pairs( self.DetectedItems ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table + self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level + self:NearestFAC( DetectedArea ) + + 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 #" .. DetectedArea.AreaID .. ":" .. 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.** @@ -26962,11 +28577,6 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @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#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. -- @return #boolean Return false to cancel Transition. @@ -26977,20 +28587,25 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @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#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. --- 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#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. --- 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#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. --- OnLeave Transition Handler for State Engaging. -- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging @@ -27198,7 +28813,7 @@ function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) if Detected == true then self:E( {"Target: ", DetectedUnit } ) self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:EnRouteTaskEngageUnit( DetectedUnit, 1, true, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) + local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) self.Controllable:PushTask( AttackTask, 1 ) end end @@ -27220,8 +28835,8 @@ end -- @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#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 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 ) @@ -27256,28 +28871,28 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, EngageSpeed, 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 - +-- 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. @@ -27332,9 +28947,9 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, EngageSpeed, --- NOW ROUTE THE GROUP! self.Controllable:WayPointExecute( 1 ) - self:SetDetectionInterval( 10 ) + self:SetDetectionInterval( 2 ) self:SetDetectionActivated() - self:__Target( -10 ) -- Start Targetting + self:__Target( -2 ) -- Start Targetting end end @@ -29107,7 +30722,7 @@ do -- ACT_ASSIGN_ACCEPT self:Message( "You are assigned to the task " .. self.Task:GetName() ) - self.Task:Assign() + self.Task:Assign( ProcessUnit, self.Task ) end end -- ACT_ASSIGN_ACCEPT @@ -29306,7 +30921,7 @@ do -- ACT_ROUTE -- @type ACT_ROUTE -- @field Tasking.Task#TASK TASK -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone + -- @field Core.Zone#ZONE_BASE Zone -- @extends Core.Fsm#FSM_PROCESS ACT_ROUTE = { ClassName = "ACT_ROUTE", @@ -29402,6 +31017,113 @@ do -- ACT_ROUTE 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 @@ -29409,7 +31131,7 @@ do -- ACT_ROUTE_ZONE -- @type ACT_ROUTE_ZONE -- @field Tasking.Task#TASK TASK -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone + -- @field Core.Zone#ZONE_BASE Zone -- @extends #ACT_ROUTE ACT_ROUTE_ZONE = { ClassName = "ACT_ROUTE_ZONE", @@ -29418,11 +31140,11 @@ do -- 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 TargetZone - function ACT_ROUTE_ZONE:New( TargetZone ) + -- @param Core.Zone#ZONE_BASE Zone + function ACT_ROUTE_ZONE:New( Zone ) local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE - self.TargetZone = TargetZone + self.Zone = Zone self.DisplayInterval = 30 self.DisplayCount = 30 @@ -29434,7 +31156,7 @@ do -- ACT_ROUTE_ZONE function ACT_ROUTE_ZONE:Init( FsmRoute ) - self.TargetZone = FsmRoute.TargetZone + self.Zone = FsmRoute.Zone self.DisplayInterval = 30 self.DisplayCount = 30 @@ -29442,18 +31164,32 @@ do -- ACT_ROUTE_ZONE 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.TargetZone ) then + if ProcessUnit:IsInZone( self.Zone ) then local RouteText = "You have arrived within the zone." self:Message( RouteText ) end - return ProcessUnit:IsInZone( self.TargetZone ) + return ProcessUnit:IsInZone( self.Zone ) end --- Task Events @@ -29466,11 +31202,11 @@ do -- ACT_ROUTE_ZONE -- @param #string To function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, From, Event, To ) - local ZoneVec2 = self.TargetZone:GetVec2() + 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 to target." + local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km." self:Message( RouteText ) end @@ -29708,7 +31444,6 @@ do -- ACT_ACCOUNT_DEADS if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then local TaskGroup = ProcessUnit:GetGroup() - self.TargetSetUnit:RemoveUnitsByName( EventData.IniUnitName ) 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 @@ -29721,7 +31456,7 @@ do -- ACT_ACCOUNT_DEADS -- @param #string To function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, From, Event, To, EventData ) - if self.TargetSetUnit:Count() > 0 then + if self.TargetSetUnit:Count() > 1 then self:__More( 1 ) else self:__NoMore( 1 ) @@ -29736,7 +31471,7 @@ do -- ACT_ACCOUNT_DEADS self:T( { "EventDead", EventData } ) if EventData.IniDCSUnit then - self:__Event( 1, EventData ) + self:Event( EventData ) end end @@ -29868,6 +31603,17 @@ do -- ACT_ASSIST 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 @@ -30016,22 +31762,23 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self - --- @param Core.Event#EVENTDATA EventData + -- @param Core.Event#EVENTDATA EventData function( self, EventData ) - self:E( { EventData } ) - 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() + 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 @@ -30172,6 +31919,14 @@ function COMMANDCENTER:HasGroup( MissionGroup ) 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 @@ -30195,6 +31950,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) 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 @@ -31370,7 +33126,6 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() - Mission:AddTask( self ) return self end @@ -31378,9 +33133,13 @@ end --- Get the Task FSM Process Template -- @param #TASK self -- @return Core.Fsm#FSM_PROCESS -function TASK:GetUnitProcess() +function TASK:GetUnitProcess( TaskUnit ) - return self.FsmTemplate + if TaskUnit then + return self:GetStateMachine( TaskUnit ) + else + return self.FsmTemplate + end end --- Sets the Task FSM Process Template @@ -31847,15 +33606,26 @@ 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:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + 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 @@ -32253,23 +34023,6 @@ end -- Reporting -- ------------------------------- -- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- --- === --- --- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} --- ================================================================ --- The @{#DETECTION_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) DETECTION_DISPATCHER constructor: --- -------------------------------------- --- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. -- -- === -- @@ -32307,6 +34060,8 @@ do -- DETECTION MANAGER self:SetReportInterval( 30 ) self:SetReportDisplayTime( 25 ) + Detection:__Start( 5 ) + return self end @@ -32469,32 +34224,77 @@ do -- DETECTION_REPORTING end -do -- DETECTION_DISPATCHER +--- **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 - --- DETECTION_DISPATCHER class. - -- @type DETECTION_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 - DETECTION_DISPATCHER = { - ClassName = "DETECTION_DISPATCHER", + TASK_A2G_DISPATCHER = { + ClassName = "TASK_A2G_DISPATCHER", Mission = nil, CommandCenter = nil, Detection = nil, } - --- DETECTION_DISPATCHER constructor. - -- @param #DETECTION_DISPATCHER self + --- TASK_A2G_DISPATCHER constructor. + -- @param #TASK_A2G_DISPATCHER self -- @param Set#SET_GROUP SetGroup -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_DISPATCHER self - function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, 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 ) ) -- #DETECTION_DISPATCHER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER self.Detection = Detection self.CommandCenter = CommandCenter @@ -32506,15 +34306,15 @@ do -- DETECTION_DISPATCHER --- Creates a SEAD task when there are targets for it. - -- @param #DETECTION_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @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 DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) - self:F( { DetectedArea.AreaID } ) + function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) + self:F( { DetectedItem.AreaID } ) - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone + 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() @@ -32534,10 +34334,10 @@ do -- DETECTION_DISPATCHER end --- Creates a CAS task when there are targets for it. - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) + function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedArea ) self:F( { DetectedArea.AreaID } ) local DetectedSet = DetectedArea.Set @@ -32562,10 +34362,10 @@ do -- DETECTION_DISPATCHER end --- Creates a BAI task when there are targets for it. - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) + function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) self:F( { DetectedArea.AreaID } ) local DetectedSet = DetectedArea.Set @@ -32591,15 +34391,15 @@ do -- DETECTION_DISPATCHER --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Tasking.Mission#MISSION Mission -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) + function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedItem ) if Task then - if Task:IsStatePlanned() and DetectedArea.Changed == true then + if Task:IsStatePlanned() and DetectedItem.Changed == true then self:E( "Removing Tasking: " .. Task:GetTaskName() ) Task = Mission:RemoveTask( Task ) end @@ -32610,10 +34410,10 @@ do -- DETECTION_DISPATCHER --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function DETECTION_DISPATCHER:ProcessDetected( Detection ) + function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) self:F2() local AreaMsg = {} @@ -32623,98 +34423,99 @@ do -- DETECTION_DISPATCHER local Mission = self.Mission --- First we need to the detected targets. - for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do + for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) + 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 DetectedArea", DetectedItem.AreaID, DetectedSet:Count(), tostring( DetectedItem ) } ) DetectedSet:Flush() - local AreaID = DetectedArea.AreaID + local AreaID = DetectedItem.AreaID -- Evaluate SEAD Tasking local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) - SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedArea ) + SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedItem ) if not SEADTask then - local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ) + local Task = TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + SEADTask = Mission:AddTask( Task ) + end end if SEADTask and SEADTask:IsStatePlanned() then - self:E( "Planned" ) - --SEADTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() end -- Evaluate CAS Tasking local CASTask = Mission:GetTask( "CAS." .. AreaID ) - CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedArea ) + CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedItem ) if not CASTask then - local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) + local Task = TASK_CAS:New( Mission, self.SetGroup, "CAS." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + CASTask = Mission:AddTask( Task ) end end if CASTask and CASTask:IsStatePlanned() then - --CASTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() end -- Evaluate BAI Tasking local BAITask = Mission:GetTask( "BAI." .. AreaID ) - BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedArea ) + BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedItem ) if not BAITask then - local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) + local Task = TASK_BAI:New( Mission, self.SetGroup, "BAI." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + BAITask = Mission:AddTask( Task ) end end if BAITask and BAITask:IsStatePlanned() then - --BAITask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() end - if #TaskMsg > 0 then - - local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) - - local DetectedAreaVec3 = DetectedZone:GetVec3() - local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) - local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) - AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", - DetectedAreaID, - DetectedAreaPointLL, - string.rep( "■", ThreatLevel ), - ThreatLevel - ) - - -- Loop through the changes ... - local ChangeText = Detection:GetChangeText( DetectedArea ) - - if ChangeText ~= "" then - ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) - end - end +-- if #TaskMsg > 0 then +-- +-- local ThreatLevel = Detection:GetTreatLevelA2G( DetectedItem ) +-- +-- local DetectedAreaVec3 = DetectedZone:GetVec3() +-- local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) +-- local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) +-- AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", +-- DetectedItemID, +-- DetectedAreaPointLL, +-- string.rep( "■", ThreatLevel ), +-- ThreatLevel +-- ) +-- +-- -- Loop through the changes ... +-- local ChangeText = Detection:GetChangeText( DetectedItem ) +-- +-- if ChangeText ~= "" then +-- ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) +-- end +-- end -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedArea ) + Detection:AcceptChanges( DetectedItem ) end -- TODO set menus using the HQ coordinator Mission:GetCommandCenter():SetMenu() - if #AreaMsg > 0 then + if #TaskMsg > 0 then for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do if not TaskGroup:GetState( TaskGroup, "Assigned" ) then self.CommandCenter:MessageToGroup( - string.format( "HQ Reporting - Target areas for mission '%s':\nAreas:\n%s\n\nTasks:\n%s\n\nChanges:\n%s ", + string.format( "HQ Reporting - Target areas for mission '%s':\nTasks:\n%s ", self.Mission:GetName(), - table.concat( AreaMsg, "\n" ), - table.concat( TaskMsg, "\n" ), - table.concat( ChangeMsg, "\n" ) + table.concat( TaskMsg, "\n" ) ), self:GetReportDisplayTime(), TaskGroup ) end @@ -32724,26 +34525,286 @@ do -- DETECTION_DISPATCHER return true end -end--- This module contains the TASK_SEAD classes. +end--- This module contains the TASK_A2G classes. -- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK} --- ================================================= --- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, +-- # 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_SEAD is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: +-- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- * **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: FlightControl - Design and Programming --- --- @module Task_SEAD +-- +-- # **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 + + 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, 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 @@ -32762,130 +34823,76 @@ do -- TASK_SEAD -- @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 Core.Zone#ZONE_BASE TargetZone + -- @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, TargetZone ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, "SEAD" ) ) -- Tasking.Task_SEAD#TASK_SEAD + 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() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - local Fsm = self:GetUnitProcess() - - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Route", Rejected = "Eject" } ) - Fsm:AddProcess ( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) - Fsm:AddTransition( "Rejected", "Eject", "Planned" ) - Fsm:AddTransition( "Arrived", "Update", "Updated" ) - Fsm:AddProcess ( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "SEAD" ), { Accounted = "Success" } ) - Fsm:AddProcess ( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) - Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - function Fsm:onenterUpdated( TaskUnit ) - self:E( { self } ) - self:Account() - self:Smoke() - end - --- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) --- _EVENTDISPATCHER:OnDead( self._EventDead, self ) --- _EVENTDISPATCHER:OnCrash( self._EventDead, self ) --- _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - return self - end - - --- @param #TASK_SEAD self - function TASK_SEAD:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - -end ---- (AI) (SP) (MP) Tasking for Air to Ground Processes. --- --- 1) @{#TASK_A2G} class, extends @{Task#TASK} --- ================================================= --- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, --- located at a Target Zone, 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 SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_A2G + end +end -do -- TASK_A2G +do -- TASK_BAI - --- The TASK_A2G class - -- @type TASK_A2G + --- The TASK_BAI class + -- @type TASK_BAI + -- @field Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - TASK_A2G = { - ClassName = "TASK_A2G", + TASK_BAI = { + ClassName = "TASK_BAI", } - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self + --- 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 #string TaskType BAI or CAS -- @param Set#SET_UNIT UnitSetTargets - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) + -- @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() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - self.FACUnit = FACUnit - local A2GUnitProcess = self:GetUnitProcess() - - A2GUnitProcess:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "Attack the Area" ), { Assigned = "Route", Rejected = "Eject" } ) - A2GUnitProcess:AddProcess ( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) - A2GUnitProcess:AddTransition( "Rejected", "Eject", "Planned" ) - A2GUnitProcess:AddTransition( "Arrived", "Update", "Updated" ) - A2GUnitProcess:AddProcess ( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "Attack" ), { Accounted = "Success" } ) - A2GUnitProcess:AddProcess ( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) - --Fsm:AddProcess ( "Updated", "JTAC", PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - A2GUnitProcess:AddTransition( "Accounted", "Success", "Success" ) - A2GUnitProcess:AddTransition( "Failed", "Fail", "Failed" ) - - function A2GUnitProcess:onenterUpdated( TaskUnit ) - self:E( { self } ) - self:Account() - self:Smoke() - end - - - - --_EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - --_EVENTDISPATCHER:OnDead( self._EventDead, self ) - --_EVENTDISPATCHER:OnCrash( self._EventDead, self ) - --_EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - return self - end + 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", + } - --- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - end - - + --- 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 @@ -32947,7 +34954,7 @@ Include.File( "Tasking/CommandCenter" ) Include.File( "Tasking/Mission" ) Include.File( "Tasking/Task" ) Include.File( "Tasking/DetectionManager" ) -Include.File( "Tasking/Task_SEAD" ) +Include.File( "Tasking/Task_A2G_Dispatcher") Include.File( "Tasking/Task_A2G" ) @@ -32961,13 +34968,9 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Database#DATABASE ->>>>>>> refs/remotes/origin/master -env.info( "Include.ProgramPath = " .. Include.ProgramPath) -Include.Files = {} -Include.File( "Moose" ) -BASE:TraceOnOff( true ) +BASE:TraceOnOff( false ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index 529b1f195..212de98c7 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,32 +1,761 @@ -<<<<<<< HEAD -env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170306_1309' ) - -======= env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170308_2139' ) ->>>>>>> refs/remotes/origin/master +env.info( 'Moose Generation Timestamp: 20170309_1321' ) local base = _G Include = {} - +Include.Files = {} Include.File = function( IncludeFile ) - 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" ) +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) else - env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath ) - return f() + return tostring(tbl) + 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 -<<<<<<< HEAD -Include.ProgramPath = "Scripts/Moose/" -======= + +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) @@ -3116,6 +3845,13 @@ function SCHEDULER:Remove( ScheduleID ) _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) end +--- Clears all pending schedules. +-- @param #SCHEDULER self +function SCHEDULER:Clear() + self:F3( ) + + _SCHEDULEDISPATCHER:Clear( self ) +end @@ -3313,11 +4049,15 @@ function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) if CallID then local Schedule = self.Schedule[Scheduler] - Schedule[CallID].ScheduleID = timer.scheduleFunction( - Schedule[CallID].CallHandler, - CallID, - timer.getTime() + Schedule[CallID].Start - ) + -- 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 @@ -3330,7 +4070,12 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) if CallID then local Schedule = self.Schedule[Scheduler] - timer.removeFunction( Schedule[CallID].ScheduleID ) + -- 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 @@ -3338,6 +4083,14 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) 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. @@ -3841,11 +4594,11 @@ end -- @param EventClass The instance of the class for which the event is. -- @param #function OnEventFunction -- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, OnEventFunction ) +function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EventID ) self:F2( EventTemplate.name ) for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - OnEventFunction( self, EventUnit.name, EventFunction, EventClass ) + self:OnEventForUnit( EventUnit.name, EventFunction, EventClass, EventID ) end return self end @@ -3918,51 +4671,11 @@ do -- OnBirth function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnBirthForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Birth ) return self end - --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnBirth( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_BIRTH ) - - return self - end - - --- Set a new listener for an S_EVENT_BIRTH event. - -- @param #EVENT self - -- @param #string EventDCSUnitName The id of the unit for the event to be handled. - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_BIRTH ) - - return self - end - - --- Stop listening to S_EVENT_BIRTH event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnBirthRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_BIRTH ) - - return self - end - - end do -- OnCrash @@ -3976,49 +4689,10 @@ do -- OnCrash function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnCrashForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Crash ) return self end - - --- Set a new listener for an S_EVENT_CRASH event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnCrash( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_CRASH ) - - return self - end - - --- Set a new listener for an S_EVENT_CRASH event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_CRASH ) - - return self - end - - --- Stop listening to S_EVENT_CRASH event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnCrashRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_CRASH ) - - return self - end end @@ -4033,96 +4707,13 @@ do -- OnDead function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnDeadForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Dead ) return self end - --- Set a new listener for an S_EVENT_DEAD event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnDead( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_DEAD ) - - return self - end - - - --- Set a new listener for an S_EVENT_DEAD event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_DEAD ) - - return self - end - - --- Stop listening to S_EVENT_DEAD event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnDeadRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_DEAD ) - - return self - end - - end -do -- OnPilotDead - - --- Set a new listener for an S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPilotDead( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - - --- Set a new listener for an S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - - --- Stop listening to S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPilotDeadRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - -end do -- OnLand --- Create an OnLand event handler for a group @@ -4134,38 +4725,11 @@ do -- OnLand function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnLandForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Land ) return self end - --- Set a new listener for an S_EVENT_LAND event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_LAND ) - - return self - end - - --- Stop listening to S_EVENT_LAND event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnLandRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_LAND ) - - return self - end - - end do -- OnTakeOff @@ -4178,38 +4742,11 @@ do -- OnTakeOff function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnTakeOffForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.Takeoff ) return self end - --- Set a new listener for an S_EVENT_TAKEOFF event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_TAKEOFF ) - - return self - end - - --- Stop listening to S_EVENT_TAKEOFF event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnTakeOffRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_TAKEOFF ) - - return self - end - - end do -- OnEngineShutDown @@ -4223,210 +4760,11 @@ do -- OnEngineShutDown function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnEngineShutDownForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EVENTS.EngineShutdown ) return self end - --- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) - - return self - end - - --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnEngineShutDownRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) - - return self - end - -end - -do -- OnEngineStartUp - - --- Set a new listener for an S_EVENT_ENGINE_STARTUP event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_STARTUP ) - - return self - end - - --- Stop listening to S_EVENT_ENGINE_STARTUP event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnEngineStartUpRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_ENGINE_STARTUP ) - - return self - end - -end - -do -- OnShot - --- Set a new listener for an S_EVENT_SHOT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnShot( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_SHOT ) - - return self - end - - --- Set a new listener for an S_EVENT_SHOT event for a unit. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_SHOT ) - - return self - end - - --- Stop listening to S_EVENT_SHOT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnShotRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_SHOT ) - - return self - end - - -end - -do -- OnHit - - --- Set a new listener for an S_EVENT_HIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnHit( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_HIT ) - - return self - end - - --- Set a new listener for an S_EVENT_HIT event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventClass ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_HIT ) - - return self - end - - --- Stop listening to S_EVENT_HIT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnHitRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_HIT ) - - return self - end - -end - -do -- OnPlayerEnterUnit - - --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPlayerEnterUnit( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) - - return self - end - - --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPlayerEnterRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) - - return self - end - -end - -do -- OnPlayerLeaveUnit - --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventClass The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPlayerLeaveUnit( EventFunction, EventClass ) - self:F2() - - self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) - - return self - end - - --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. - -- @param #EVENT self - -- @param Base#BASE EventClass - -- @return #EVENT - function EVENT:OnPlayerLeaveRemove( EventClass ) - self:F2() - - self:Remove( EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) - - return self - end - end @@ -4549,7 +4887,9 @@ function EVENT:onEvent( Event ) local PriorityBegin = PriorityOrder == -1 and 5 or 1 local PriorityEnd = PriorityOrder == -1 and 1 or 5 - self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) + if Event.IniObjectCategory ~= 3 then + self:E( { _EVENTMETA[Event.id].Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) + end for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do @@ -4570,8 +4910,10 @@ function EVENT:onEvent( Event ) -- First test if a EventFunction is Set, otherwise search for the default function if EventData.EventUnit[Event.IniDCSUnitName].EventFunction then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - + 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 ) @@ -4584,8 +4926,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4599,7 +4943,9 @@ function EVENT:onEvent( Event ) -- First test if a EventFunction is Set, otherwise search for the default function if EventData.EventUnit[Event.TgtDCSUnitName].EventFunction then - self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) + if Event.IniObjectCategory ~= 3 then + self:E( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) + end local Result, Value = xpcall( function() @@ -4613,8 +4959,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4632,9 +4980,11 @@ function EVENT:onEvent( Event ) 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 - - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } ) - + + 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 ) @@ -4647,8 +4997,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4660,8 +5012,10 @@ function EVENT:onEvent( Event ) if EventData.EventGroup[Event.TgtGroupName] then if EventData.EventGroup[Event.TgtGroupName].EventFunction then - self:E( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.TgtUnitName, EventPriority } ) - + 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 ) @@ -4674,7 +5028,9 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) + if Event.IniObjectCategory ~= 3 then + self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } ) + end local Result, Value = xpcall( function() @@ -4696,8 +5052,9 @@ function EVENT:onEvent( Event ) if EventData.EventFunction then -- There is an EventFunction defined, so call the EventFunction. - self:E( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -4709,8 +5066,10 @@ function EVENT:onEvent( Event ) if EventFunction and type( EventFunction ) == "function" then -- Now call the default event function. - self:E( { "Calling " .. _EVENTMETA[Event.id].Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } ) - + 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 ) @@ -5885,6 +6244,58 @@ function ZONE_BASE:GetVec2() 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. @@ -5899,6 +6310,13 @@ 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. @@ -5985,8 +6403,9 @@ 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 ) +function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Point = {} local Vec2 = self:GetVec2() @@ -6002,8 +6421,10 @@ function ZONE_RADIUS:BoundZone( Points ) 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"] = "USA", + ["country"] = CountryName, ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -6015,7 +6436,10 @@ function ZONE_RADIUS:BoundZone( Points ) ["heading"] = 0, } -- end of ["group"] - coalition.addStaticObject( country.id.USA, Tire ) + local Group = coalition.addStaticObject( CountryID, Tire ) + if UnBound and UnBound == true then + Group:destroy() + end end return self @@ -6448,8 +6872,9 @@ 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( ) +function ZONE_POLYGON_BASE:BoundZone( UnBound ) local i local j @@ -6478,8 +6903,11 @@ function ZONE_POLYGON_BASE:BoundZone( ) ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), ["heading"] = 0, } -- end of ["group"] - - coalition.addStaticObject( country.id.USA, Tire ) + + local Group = coalition.addStaticObject( country.id.USA, Tire ) + if UnBound and UnBound == true then + Group:destroy() + end end j = i @@ -6715,6 +7143,8 @@ DATABASE = { PLAYERSJOINED = {}, CLIENTS = {}, AIRBASES = {}, + COUNTRY_ID = {}, + COUNTRY_NAME = {}, NavPoints = {}, } @@ -7422,6 +7852,9 @@ function DATABASE:_RegisterTemplates() 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 @@ -7705,6 +8138,7 @@ 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. @@ -7723,10 +8157,14 @@ function SET_BASE:New( 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 ) @@ -7778,6 +8216,8 @@ function SET_BASE:Add( ObjectName, Object ) 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. @@ -7829,7 +8269,15 @@ function SET_BASE:Remove( ObjectName ) 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 @@ -7849,12 +8297,50 @@ function SET_BASE:Get( ObjectName ) 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.List.Count + return #self.Index end @@ -8117,7 +8603,8 @@ function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArgumen return false end - self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + Schedule() return self end @@ -8190,7 +8677,7 @@ end --- SET_GROUP class -- @type SET_GROUP --- @extends #SET_BASE +-- @extends Core.Set#SET_BASE SET_GROUP = { ClassName = "SET_GROUP", Filter = { @@ -11416,10 +11903,20 @@ do -- FSM 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 Value = self[handler]( self, unpack(params) ) + local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) return Value end end @@ -11618,8 +12115,66 @@ do -- FSM_CONTROLLABLE 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 @@ -11687,6 +12242,27 @@ do -- FSM_PROCESS 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 @@ -11711,7 +12287,7 @@ do -- FSM_PROCESS -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do - self:T( { Process} ) + self:E( { Process} ) local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) end @@ -13205,48 +13781,35 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At return DCSTask end - --- (AIR) Attack the Unit. -- @param #CONTROLLABLE self --- @param Wrapper.Unit#UNIT AttackUnit The unit. --- @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 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, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- AttackUnit = { - -- id = 'AttackUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- } - -- } +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 = { - altitudeEnabled = true, unitId = AttackUnit:GetID(), - attackQtyLimit = AttackQtyLimit or false, - attackQty = AttackQty or 2, + groupAttack = GroupAttack or false, + visible = Visible or false, expend = WeaponExpend or "Auto", - altitude = 2000, - directionEnabled = true, - groupAttack = true, - --weaponType = WeaponType or 1073741822, - direction = Direction or 0, - } + 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 ) @@ -13860,7 +14423,7 @@ function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, end ---- (AIR) Attack the Unit. +--- (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. @@ -15092,7 +15655,7 @@ GROUP = { -- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name -- @return #GROUP self function GROUP:Register( GroupName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) + self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) self:F2( GroupName ) self.GroupName = GroupName @@ -16447,14 +17010,14 @@ end function UNIT:GetThreatLevel() local Attributes = self:GetDesc().attributes - self:E( Attributes ) + self:T( Attributes ) local ThreatLevel = 0 local ThreatText = "" if self:IsGround() then - self:E( "Ground" ) + self:T( "Ground" ) local ThreatLevels = { "Unarmed", @@ -16492,7 +17055,7 @@ function UNIT:GetThreatLevel() if self:IsAir() then - self:E( "Air" ) + self:T( "Air" ) local ThreatLevels = { "Unarmed", @@ -16526,7 +17089,7 @@ function UNIT:GetThreatLevel() if self:IsShip() then - self:E( "Ship" ) + self:T( "Ship" ) --["Aircraft Carriers"] = {"Heavy armed ships",}, --["Cruisers"] = {"Heavy armed ships",}, @@ -17385,7 +17948,7 @@ function STATIC:FindByName( StaticName, RaiseError ) self.StaticName = StaticName if StaticFound then - StaticFound:F( { StaticName } ) + StaticFound:F3( { StaticName } ) return StaticFound end @@ -18589,9 +19152,10 @@ function SCORING:_EventOnDeadOrCrash( Event ) :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 ) @@ -19229,7 +19793,9 @@ CLEANUP = { -- 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() ) +function CLEANUP:New( ZoneNames, TimeInterval ) + + local self = BASE:Inherit( self, BASE:New() ) -- #CLEANUP self:F( { ZoneNames, TimeInterval } ) if type( ZoneNames ) == 'table' then @@ -19241,7 +19807,7 @@ function CLEANUP:New( ZoneNames, TimeInterval ) local self = BASE:Inherit( self, self.TimeInterval = TimeInterval end - _EVENTDISPATCHER:OnBirth( self._OnEventBirth, self ) + self:HandleEvent( EVENTS.Birth ) self.CleanUpScheduler = SCHEDULER:New( self, self._CleanUpScheduler, {}, 1, TimeInterval ) @@ -19302,32 +19868,24 @@ function CLEANUP:_DestroyMissile( MissileObject ) end end -function CLEANUP:_OnEventBirth( Event ) - self:F( { Event } ) +--- @param #CLEANUP self +-- @param Core.Event#EVENTDATA EventData +function CLEANUP:_OnEventBirth( EventData ) + self:F( { EventData } ) - 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 - - _EVENTDISPATCHER:OnEngineShutDownForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnEngineStartUpForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnHitForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnPilotDeadForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnDeadForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnCrashForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnShotForUnit( Event.IniDCSUnitName, self._EventShot, self ) - - --self:AddEvent( world.event.S_EVENT_ENGINE_SHUTDOWN, self._EventAddForCleanUp ) - --self:AddEvent( world.event.S_EVENT_ENGINE_STARTUP, self._EventAddForCleanUp ) --- self:AddEvent( world.event.S_EVENT_HIT, self._EventAddForCleanUp ) -- , self._EventHitCleanUp ) --- self:AddEvent( world.event.S_EVENT_CRASH, self._EventCrash ) -- , self._EventHitCleanUp ) --- --self:AddEvent( world.event.S_EVENT_DEAD, self._EventCrash ) --- self:AddEvent( world.event.S_EVENT_SHOT, self._EventShot ) --- --- self:EnableEvents() + 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 @@ -21137,10 +21695,11 @@ end -- 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 +-- @module Movement --- the MOVEMENT class --- @type +-- @type MOVEMENT +-- @extends Core.Base#BASE MOVEMENT = { ClassName = "MOVEMENT", } @@ -21154,7 +21713,7 @@ MOVEMENT = { -- 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() ) + local self = BASE:Inherit( self, BASE:New() ) -- #MOVEMENT self:F( { MovePrefixes, MoveMaximum } ) if type( MovePrefixes ) == 'table' then @@ -21167,7 +21726,7 @@ function MOVEMENT:New( MovePrefixes, MoveMaximum ) 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. - _EVENTDISPATCHER:OnBirth( self.OnBirth, self ) + self:HandleEvent( EVENTS.Birth ) -- self:AddEvent( world.event.S_EVENT_BIRTH, self.OnBirth ) -- @@ -21194,24 +21753,26 @@ 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. -function MOVEMENT:OnBirth( Event ) - self:F( { Event } ) +-- @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 Event.IniDCSUnit then - self:T( "Birth object : " .. Event.IniDCSUnitName ) - if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then + 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( Event.IniDCSUnitName, MovePrefix, 1, true ) then + if string.find( EventData.IniDCSUnitName, MovePrefix, 1, true ) then self.AliveUnits = self.AliveUnits + 1 - self.MoveUnits[Event.IniDCSUnitName] = Event.IniDCSGroupName + self.MoveUnits[EventData.IniDCSUnitName] = EventData.IniDCSGroupName self:T( self.AliveUnits ) end end end end - _EVENTDISPATCHER:OnCrashForUnit( Event.IniDCSUnitName, self.OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForUnit( Event.IniDCSUnitName, self.OnDeadOrCrash, self ) + + EventData.IniUnit:HandleEvent( EVENTS.DEAD, self.OnDeadOrCrash ) end end @@ -21298,25 +21859,28 @@ function SEAD:New( SEADGroupPrefixes ) else self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes end - _EVENTDISPATCHER:OnShot( self.EventShot, self ) + + 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 -function SEAD:EventShot( Event ) - self:F( { Event } ) +-- @param #SEAD +-- @param Core.Event#EVENTDATA EventData +function SEAD:OnEventShot( EventData ) + self:F( { EventData } ) - local SEADUnit = Event.IniDCSUnit - local SEADUnitName = Event.IniDCSUnitName - local SEADWeapon = Event.Weapon -- Identify the weapon fired - local SEADWeaponName = Event.WeaponName -- return weapon type + 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 = Event.Weapon:getTarget() -- Identify target + local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMimname = Unit.getName(_targetMim) local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimgroupName = _targetMimgroup:getName() @@ -21475,7 +22039,7 @@ end -- -- ESCORT initialization methods. -- ============================== --- The following menus are created within the RADIO MENU of an active unit hosted by a player: +-- 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. @@ -21519,6 +22083,7 @@ end -- @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 @@ -21567,14 +22132,22 @@ ESCORT = { -- -- 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() ) + + 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 = {} @@ -21584,7 +22157,7 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) 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()].Targets = {} + self.EscortClient._EscortGroups[EscortGroup:GetName()].Detection = self.EscortGroup.Detection end self.EscortMenu = MENU_CLIENT:New( self.EscortClient, self.EscortName ) @@ -21609,13 +22182,29 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) self.FollowDistance = 100 self.CT1 = 0 self.GT1 = 0 - self.FollowScheduler = SCHEDULER:New( self, self._FollowScheduler, {}, 1, .5, .01 ) - self.EscortMode = ESCORT.MODE.MISSION - self.FollowScheduler:Stop() + 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 + + +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 @@ -21673,7 +22262,7 @@ function ESCORT:MenuFollowAt( Distance ) self.EscortMenuJoinUpAndFollow = {} end - self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_CLIENT_COMMAND:New( self.EscortClient, "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, { ParamSelf = self, ParamDistance = Distance } ) + 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 @@ -21731,11 +22320,10 @@ function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) MenuText, self.EscortMenuHold, ESCORT._HoldPosition, - { ParamSelf = self, - ParamOrbitGroup = self.EscortGroup, - ParamHeight = Height, - ParamSeconds = Seconds - } + self, + self.EscortGroup, + Height, + Seconds ) end @@ -21852,9 +22440,8 @@ function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) MenuText, self.EscortMenuScan, ESCORT._ScanTargets, - { ParamSelf = self, - ParamScanDuration = 30 - } + self, + 30 ) end @@ -21884,11 +22471,11 @@ function ESCORT:MenuFlare( MenuTextFormat ) end if not self.EscortMenuFlare then - self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, { ParamSelf = self } ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Green, ParamMessage = "Released a green flare!" } ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Red, ParamMessage = "Released a red flare!" } ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.White, ParamMessage = "Released a white flare!" } ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Yellow, ParamMessage = "Released a yellow flare!" } ) + 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 @@ -21917,12 +22504,12 @@ function ESCORT:MenuSmoke( MenuTextFormat ) end if not self.EscortMenuSmoke then - self.EscortMenuSmoke = MENU_CLIENT:New( self.EscortClient, "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, { ParamSelf = self } ) - self.EscortMenuSmokeGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Green, ParamMessage = "Releasing green smoke!" } ) - self.EscortMenuSmokeRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Red, ParamMessage = "Releasing red smoke!" } ) - self.EscortMenuSmokeWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.White, ParamMessage = "Releasing white smoke!" } ) - self.EscortMenuSmokeOrange = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Orange, ParamMessage = "Releasing orange smoke!" } ) - self.EscortMenuSmokeBlue = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Blue, ParamMessage = "Releasing blue smoke!" } ) + 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 @@ -21947,9 +22534,9 @@ function ESCORT:MenuReportTargets( Seconds ) end -- Report Targets - self.EscortMenuReportNearbyTargetsNow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, { ParamSelf = self } ) - self.EscortMenuReportNearbyTargetsOn = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, { ParamSelf = self, ParamReportTargets = true } ) - self.EscortMenuReportNearbyTargetsOff = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, { ParamSelf = self, ParamReportTargets = false, } ) + 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 ) @@ -21986,16 +22573,16 @@ function ESCORT:MenuROE( MenuTextFormat ) -- 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEHoldFire(), ParamMessage = "Holding weapons!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEReturnFire(), ParamMessage = "Returning fire!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEOpenFire(), ParamMessage = "Opening fire on designated targets!!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEWeaponFree(), ParamMessage = "Opening fire on targets of opportunity!" } ) + 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 @@ -22015,16 +22602,16 @@ function ESCORT:MenuEvasion( MenuTextFormat ) -- 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTNoReaction(), ParamMessage = "Fighting until death!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTPassiveDefense(), ParamMessage = "Defending using jammers, chaff and flares!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTEvadeFire(), ParamMessage = "Evading on enemy fire!" } ) + 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, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTVertical(), ParamMessage = "Evading on enemy fire with vertical manoeuvres!" } ) + 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 @@ -22049,18 +22636,14 @@ end --- @param #MENUPARAM MenuParam -function ESCORT._HoldPosition( MenuParam ) +function ESCORT:_HoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local OrbitGroup = MenuParam.ParamOrbitGroup -- Wrapper.Group#GROUP local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - local OrbitHeight = MenuParam.ParamHeight - local OrbitSeconds = MenuParam.ParamSeconds -- Not implemented yet - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) local PointFrom = {} local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() @@ -22093,13 +22676,12 @@ function ESCORT._HoldPosition( MenuParam ) end --- @param #MENUPARAM MenuParam -function ESCORT._JoinUpAndFollow( MenuParam ) +function ESCORT:_JoinUpAndFollow( Distance ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - self.Distance = MenuParam.ParamDistance + self.Distance = Distance self:JoinUpAndFollow( EscortGroup, EscortClient, self.Distance ) end @@ -22112,7 +22694,7 @@ end function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self:F( { EscortGroup, EscortClient, Distance } ) - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) EscortGroup:OptionROEHoldFire() EscortGroup:OptionROTPassiveDefense() @@ -22121,44 +22703,35 @@ function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self.CT1 = 0 self.GT1 = 0 - self.FollowScheduler:Start() + self.FollowScheduler:Start( self.FollowSchedule ) EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._Flare( MenuParam ) +function ESCORT:_Flare( Color, Message ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local Color = MenuParam.ParamColor - local Message = MenuParam.ParamMessage - EscortGroup:GetUnit(1):Flare( Color ) EscortGroup:MessageToClient( Message, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._Smoke( MenuParam ) +function ESCORT:_Smoke( Color, Message ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local Color = MenuParam.ParamColor - local Message = MenuParam.ParamMessage - EscortGroup:GetUnit(1):Smoke( Color ) EscortGroup:MessageToClient( Message, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._ReportNearbyTargetsNow( MenuParam ) +function ESCORT:_ReportNearbyTargetsNow() - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient @@ -22166,17 +22739,16 @@ function ESCORT._ReportNearbyTargetsNow( MenuParam ) end -function ESCORT._SwitchReportNearbyTargets( MenuParam ) +function ESCORT:_SwitchReportNearbyTargets( ReportTargets ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - self.ReportTargets = MenuParam.ParamReportTargets + self.ReportTargets = ReportTargets if self.ReportTargets then if not self.ReportTargetsScheduler then - self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, 30 ) + self.ReportTargetsScheduler:Schedule( self, self._ReportTargetsScheduler, {}, 1, 30 ) end else routines.removeFunction( self.ReportTargetsScheduler ) @@ -22185,40 +22757,31 @@ function ESCORT._SwitchReportNearbyTargets( MenuParam ) end --- @param #MENUPARAM MenuParam -function ESCORT._ScanTargets( MenuParam ) +function ESCORT:_ScanTargets( ScanDuration ) - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP local EscortClient = self.EscortClient - local ScanDuration = MenuParam.ParamScanDuration - - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) if EscortGroup:IsHelicopter() then - SCHEDULER:New( EscortGroup, EscortGroup.PushTask, - { EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ) - }, - 1 - ) + EscortGroup:PushTask( + EscortGroup:TaskControlled( + EscortGroup:TaskOrbitCircle( 200, 20 ), + EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) + ), 1 ) elseif EscortGroup:IsAirPlane() then - SCHEDULER:New( EscortGroup, EscortGroup.PushTask, - { EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ) - }, - 1 - ) + 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.FollowScheduler:Start( self.FollowSchedule ) end end @@ -22235,124 +22798,157 @@ function _Resume( EscortGroup ) end ---- @param #MENUPARAM MenuParam -function ESCORT._AttackTarget( MenuParam ) +--- @param #ESCORT self +-- @param #number DetectedItemID +function ESCORT:_AttackTarget( DetectedItemID ) - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP + self:E( EscortGroup ) local EscortClient = self.EscortClient - local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT - self.FollowScheduler:Stop() - - self:T( AttackUnit ) + self.FollowScheduler:Stop( self.FollowSchedule ) if EscortGroup:IsAir() then EscortGroup:OptionROEOpenFire() EscortGroup:OptionROTPassiveDefense() EscortGroup:SetState( EscortGroup, "Escort", self ) - SCHEDULER:New( EscortGroup, - EscortGroup.PushTask, - { EscortGroup:TaskCombo( - { EscortGroup:TaskAttackUnit( AttackUnit ), - EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } ) - } - ) - }, 10 + + 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 - SCHEDULER:New( EscortGroup, - EscortGroup.PushTask, - { EscortGroup:TaskCombo( - { EscortGroup:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) - } - ) - }, 10 + + 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 #MENUPARAM MenuParam -function ESCORT._AssistTarget( MenuParam ) +--- +-- @param #number DetectedItemID +function ESCORT:_AssistTarget( EscortGroupAttack, DetectedItemID ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local EscortGroupAttack = MenuParam.ParamEscortGroup - local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT - self.FollowScheduler:Stop() - - self:T( AttackUnit ) + self.FollowScheduler:Stop( self.FollowSchedule ) if EscortGroupAttack:IsAir() then EscortGroupAttack:OptionROEOpenFire() EscortGroupAttack:OptionROTVertical() - SCHDULER:New( EscortGroupAttack, - EscortGroupAttack.PushTask, - { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskAttackUnit( AttackUnit ), - EscortGroupAttack:TaskOrbitCircle( 500, 350 ) - } - ) - }, 10 + + 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 - SCHEDULER:New( EscortGroupAttack, - EscortGroupAttack.PushTask, - { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) - } - ) - }, 10 + 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( MenuParam ) +function ESCORT:_ROE( EscortROEFunction, EscortROEMessage ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local EscortROEFunction = MenuParam.ParamFunction - local EscortROEMessage = MenuParam.ParamMessage - pcall( function() EscortROEFunction() end ) EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._ROT( MenuParam ) +function ESCORT:_ROT( EscortROTFunction, EscortROTMessage ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local EscortROTFunction = MenuParam.ParamFunction - local EscortROTMessage = MenuParam.ParamMessage - pcall( function() EscortROTFunction() end ) EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient ) end --- @param #MENUPARAM MenuParam -function ESCORT._ResumeMission( MenuParam ) +function ESCORT:_ResumeMission( WayPoint ) - local self = MenuParam.ParamSelf local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local WayPoint = MenuParam.ParamWayPoint - - self.FollowScheduler:Stop() + self.FollowScheduler:Stop( self.FollowSchedule ) local WayPoints = EscortGroup:GetTaskRoute() self:T( WayPoint, WayPoints ) @@ -22496,176 +23092,244 @@ function ESCORT:_ReportTargetsScheduler() self:F( self.EscortGroup:GetName() ) if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - local EscortGroupName = self.EscortGroup:GetName() - local EscortTargets = self.EscortGroup:GetDetectedTargets() - local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets + if true then - 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 EscortGroupName = self.EscortGroup:GetName() + + self.EscortMenuAttackNearbyTargets:RemoveSubMenus() - 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 + if self.EscortMenuTargetAssistance then + self.EscortMenuTargetAssistance:RemoveSubMenus() 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 } ) + local DetectedItems = self.Detection:GetDetectedItems() + self:E( DetectedItems ) - -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. - self.EscortMenuAttackNearbyTargets:RemoveSubMenus() + local DetectedTargets = false + + local DetectedMsgs = {} + + for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do - if self.EscortMenuTargetAssistance then - self.EscortMenuTargetAssistance:RemoveSubMenus() - end + local ClientEscortTargets = EscortGroupData.Detection - --for MenuIndex = 1, #self.EscortMenuAttackTargets do - -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) - -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() - --end + 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 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, - { ParamSelf = self, - ParamEscortGroup = EscortGroupData.EscortGroup, - ParamUnit = ClientEscortTargetData.AttackUnit - } - ) - end - end + if ClientEscortGroupName == EscortGroupName then + + DetectedMsgs[#DetectedMsgs+1] = DetectedItemReportSummary + + MENU_CLIENT_COMMAND:New( self.EscortClient, + DetectedItemReportSummary, + self.EscortMenuAttackNearbyTargets, + ESCORT._AttackTarget, + self, + DetectedItemID + ) else - ClientEscortTargetData = nil + 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 - - if EscortTargetMessages ~= "" and self.ReportTargets == true then - self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) + 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!", 20, self.EscortClient ) + 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 - - 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 return false @@ -22843,7 +23507,7 @@ function MISSILETRAINER:New( Distance, Briefing ) self.Distance = Distance / 1000 - _EVENTDISPATCHER:OnShot( self._EventShot, self ) + self:HandleEvent( EVENTS.Shot ) self.DBClients = SET_CLIENT:New():FilterStart() @@ -23121,14 +23785,14 @@ 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 Event -function MISSILETRAINER:_EventShot( Event ) - self:F( { Event } ) +-- @param Core.Event#EVENTDATA EventData +function MISSILETRAINER:OnEventShot( EVentData ) + self:F( { EVentData } ) - local TrainerSourceDCSUnit = Event.IniDCSUnit - local TrainerSourceDCSUnitName = Event.IniDCSUnitName - local TrainerWeapon = Event.Weapon -- Identify the weapon fired - local TrainerWeaponName = Event.WeaponName -- return weapon type + 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 ) @@ -24576,17 +25240,17 @@ end -- -- === -- --- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} --- ========================================================== --- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). +-- # 1) @{#DETECTION_BASE} class, extends @{Fsm#FSM} -- --- 1.1) DETECTION_BASE constructor --- ------------------------------- --- Construct a new DETECTION_BASE instance using the @{Detection#DETECTION_BASE.New}() method. +-- 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 -- --- 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. @@ -24594,969 +25258,1920 @@ end -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: -- --- * @{Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using 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) Obtain objects detected by DETECTION_BASE --- ---------------------------------------------- --- DETECTION_BASE builds @{Set}s of objects detected. These @{Set#SET_BASE}s can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSets}(). --- The method will return a list (table) of @{Set#SET_BASE} objects. +-- ## 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_AREAS} class, extends @{Detection#DETECTION_BASE} --- =============================================================================== +-- # 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. -- --- 2.1) Retrieve the Detected Unit sets and Detected Zones --- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. +-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones -- --- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. --- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). --- If you want to obtain a specific set from the DetectedSets, use the method @{Detection#DETECTION_BASE.GetDetectedSet}() with a given index. +-- 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. -- --- 1.4) Flare or Smoke detected units --- ---------------------------------- +-- ## 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. -- --- 1.5) Flare or Smoke detected zones --- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones 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 : Concept & Testing +-- * Mechanist : Early concept of DETECTION_AREAS. -- -- ### Authors: -- --- * FlightControl : Design & Programming +-- * 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.Base#BASE -DETECTION_BASE = { - ClassName = "DETECTION_BASE", - DetectionSetGroup = nil, - DetectionRange = nil, - DetectedObjects = {}, - DetectionRun = 0, - DetectedObjectsIdentified = {}, -} - ---- @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 - ---- DETECTION constructor. --- @param #DETECTION_BASE self --- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @return #DETECTION_BASE self -function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) + --- 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 = {}, + } - self.DetectionSetGroup = DetectionSetGroup - self.DetectionRange = DetectionRange + --- @type DETECTION_BASE.DetectedObjects + -- @list <#DETECTION_BASE.DetectedObject> - self:InitDetectVisual( false ) - self:InitDetectOptical( false ) - self:InitDetectRadar( false ) - self:InitDetectRWR( false ) - self:InitDetectIRST( false ) - self:InitDetectDLINK( false ) + --- @type DETECTION_BASE.DetectedObject + -- @field #string Name + -- @field #boolean Visible + -- @field #string Type + -- @field #number Distance + -- @field #boolean Identified - return self -end - ---- 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 - ---- 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( 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:F3( ObjectName ) + --- @type DETECTION_BASE.DetectedItems + -- @list <#DETECTION_BASE.DetectedItem> - if ObjectName then - local DetectedObject = self.DetectedObjects[ObjectName] + --- @type DETECTION_BASE.DetectedItem + -- @field Core.Set#SET_UNIT Set + + + --- 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.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 - -- 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 + --- @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 - end - - return nil -end - ---- Get the detected @{Set#SET_BASE}s. --- @param #DETECTION_BASE self --- @return #DETECTION_BASE.DetectedSets DetectedSets -function DETECTION_BASE:GetDetectedSets() - - local DetectionSets = self.DetectedSets - return DetectionSets -end - ---- Get the amount of SETs with detected objects. --- @param #DETECTION_BASE self --- @return #number Count -function DETECTION_BASE:GetDetectedSetCount() - - local DetectionSetCount = #self.DetectedSets - return DetectionSetCount -end - ---- Get a SET of detected objects using a given numeric index. --- @param #DETECTION_BASE self --- @param #number Index --- @return Core.Set#SET_BASE -function DETECTION_BASE:GetDetectedSet( Index ) - - local DetectionSet = self.DetectedSets[Index] - if DetectionSet then - return DetectionSet - end - - 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 - - ---- Form @{Set}s of detected @{Unit#UNIT}s in an array of @{Set#SET_BASE}s. --- @param #DETECTION_BASE self -function DETECTION_BASE:_DetectionScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - self.DetectionRun = self.DetectionRun + 1 - - self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - - for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - local DetectionGroup = DetectionGroupData -- Wrapper.Group#GROUP - - if DetectionGroup:IsAlive() then - - local DetectionGroupName = DetectionGroup:GetName() + + --- @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}) - local DetectionDetectedTargets = DetectionGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) + self.DetectionRun = self.DetectionRun + 1 - for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do - local DetectionObject = DetectionDetectedTarget.object -- Dcs.DCSWrapper.Object#Object - self:T2( DetectionObject ) + local HasDetectedObjects = false + + if DetectionGroup:IsAlive() then + + self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) - if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then - - local DetectionDetectedObjectName = DetectionObject:getName() - - local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() - local DetectionGroupVec3 = DetectionGroup:GetVec3() - - local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupVec3.x )^2 + - ( DetectionDetectedObjectPositionVec3.y - DetectionGroupVec3.y )^2 + - ( DetectionDetectedObjectPositionVec3.z - DetectionGroupVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) - - if Distance <= self.DetectionRange then - - if not self.DetectedObjects[DetectionDetectedObjectName] then - self.DetectedObjects[DetectionDetectedObjectName] = {} - end - self.DetectedObjects[DetectionDetectedObjectName].Name = DetectionDetectedObjectName - self.DetectedObjects[DetectionDetectedObjectName].Visible = DetectionDetectedTarget.visible - self.DetectedObjects[DetectionDetectedObjectName].Type = DetectionDetectedTarget.type - self.DetectedObjects[DetectionDetectedObjectName].Distance = DetectionDetectedTarget.distance - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[DetectionDetectedObjectName] then - self.DetectedObjects[DetectionDetectedObjectName] = nil - end - end - end - end - - self:T2( self.DetectedObjects ) - - -- okay, now we have a list of detected object names ... - -- Sort the table based on distance ... - table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) - end - end - - if self.DetectedObjects then - self:CreateDetectionSets() - end - - return true -end - - - ---- 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_AREAS.DetectedAreas DetectedAreas 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 Functional.Detection#DETECTION_BASE -DETECTION_AREAS = { - ClassName = "DETECTION_AREAS", - DetectedAreas = { n = 0 }, - DetectionZoneRange = nil, -} - ---- @type DETECTION_AREAS.DetectedAreas --- @list <#DETECTION_AREAS.DetectedArea> - ---- @type DETECTION_AREAS.DetectedArea --- @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 AreaID -- 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_AREAS constructor. --- @param Functional.Detection#DETECTION_AREAS self --- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Functional.Detection#DETECTION_AREAS self -function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) ) - - self.DetectionZoneRange = DetectionZoneRange - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - - self:Schedule( 10, 10 ) - - return self -end - ---- Add a detected @{#DETECTION_AREAS.DetectedArea}. --- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. --- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. --- @return #DETECTION_AREAS.DetectedArea DetectedArea -function DETECTION_AREAS:AddDetectedArea( Set, Zone ) - local DetectedAreas = self:GetDetectedAreas() - DetectedAreas.n = self:GetDetectedAreaCount() + 1 - DetectedAreas[DetectedAreas.n] = {} - local DetectedArea = DetectedAreas[DetectedAreas.n] - DetectedArea.Set = Set - DetectedArea.Zone = Zone - DetectedArea.Removed = false - DetectedArea.AreaID = DetectedAreas.n - - return DetectedArea -end - ---- Remove a detected @{#DETECTION_AREAS.DetectedArea} with a given Index. --- @param #DETECTION_AREAS self --- @param #number Index The Index of the detection are to be removed. --- @return #nil -function DETECTION_AREAS:RemoveDetectedArea( Index ) - local DetectedAreas = self:GetDetectedAreas() - local DetectedAreaCount = self:GetDetectedAreaCount() - local DetectedArea = DetectedAreas[Index] - local DetectedAreaSet = DetectedArea.Set - DetectedArea[Index] = nil - return nil -end - - ---- Get the detected @{#DETECTION_AREAS.DetectedAreas}. --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS.DetectedAreas DetectedAreas -function DETECTION_AREAS:GetDetectedAreas() - - local DetectedAreas = self.DetectedAreas - return DetectedAreas -end - ---- Get the amount of @{#DETECTION_AREAS.DetectedAreas}. --- @param #DETECTION_AREAS self --- @return #number DetectedAreaCount -function DETECTION_AREAS:GetDetectedAreaCount() - - local DetectedAreaCount = self.DetectedAreas.n - return DetectedAreaCount -end - ---- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. --- @param #DETECTION_AREAS self --- @param #number Index --- @return Core.Set#SET_UNIT DetectedSet -function DETECTION_AREAS:GetDetectedSet( Index ) - - local DetectedSetUnit = self.DetectedAreas[Index].Set - if DetectedSetUnit then - return DetectedSetUnit - end - - return nil -end - ---- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. --- @param #DETECTION_AREAS self --- @param #number Index --- @return Core.Zone#ZONE_UNIT DetectedZone -function DETECTION_AREAS:GetDetectedZone( Index ) - - local DetectedZone = self.DetectedAreas[Index].Zone - if DetectedZone then - return DetectedZone - end - - return nil -end - ---- Background worker function to determine if there are friendlies nearby ... --- @param #DETECTION_AREAS self --- @param Wrapper.Unit#UNIT ReportUnit -function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) - self:F2() - - local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = ReportGroupData.DetectedArea.Set - local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT - - DetectedArea.FriendliesNearBy = false - - local SphereSearch = { - id = world.VolumeType.SPHERE, - params = { - point = DetectedZoneUnit:GetVec3(), - radius = 6000, - } + 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 - } - - --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit - -- @param Wrapper.Group#GROUP ReportGroup - -- @param Set#SET_GROUP ReportSetGroup - local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = ReportGroupData.DetectedArea.Set - local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT - local ReportSetGroup = ReportGroupData.ReportSetGroup - - local EnemyCoalition = DetectedZoneUnit:GetCoalition() - - local FoundUnitCoalition = FoundDCSUnit:getCoalition() - local FoundUnitName = FoundDCSUnit:getName() - local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() - local EnemyUnitName = DetectedZoneUnit:GetName() - local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - - self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - - if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then - DetectedArea.FriendliesNearBy = true - return false - end - - return true - end - - world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) - -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( DetectedArea ) - - self:T3( DetectedArea.FriendliesNearBy ) - return DetectedArea.FriendliesNearBy or false -end - ---- Calculate the maxium A2G threat level of the DetectedArea. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea -function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do - local ThreatUnit = UnitData -- Wrapper.Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G - -end - ---- Find the nearest FAC of the DetectedArea. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return Wrapper.Unit#UNIT The nearest FAC unit -function DETECTION_AREAS:NearestFAC( DetectedArea ) - - 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 - - DetectedArea.NearestFAC = NearestFAC - -end - ---- Returns the A2G threat level of the units in the DetectedArea --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #number a scale from 0 to 10. -function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) - - self:T3( DetectedArea.MaxThreatLevelA2G ) - return DetectedArea.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 - ---- Add a change to the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @param #string ChangeCode --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) - - DetectedArea.Changed = true - local AreaID = DetectedArea.AreaID - - DetectedArea.Changes = DetectedArea.Changes or {} - DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} - DetectedArea.Changes[ChangeCode].AreaID = AreaID - DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType - - self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) - - return self -end - - ---- Add a change to the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @param #string ChangeCode --- @param #string ChangeUnitType --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) - - DetectedArea.Changed = true - local AreaID = DetectedArea.AreaID - - DetectedArea.Changes = DetectedArea.Changes or {} - DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} - DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 - DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 - DetectedArea.Changes[ChangeCode].AreaID = AreaID - - self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) - - return self -end - ---- Make text documenting the changes of the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #string The Changes text -function DETECTION_AREAS:GetChangeText( DetectedArea ) - self:F( DetectedArea ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do - - if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." - end - - if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target." - end - - if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." - end - - if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." - end - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "AreaID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "AreaID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - -end - - ---- Accepts changes from the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AcceptChanges( DetectedArea ) - - DetectedArea.Changed = false - DetectedArea.Changes = {} - - return self -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() - - -- 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 DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - if DetectedArea then - - local DetectedSet = DetectedArea.Set - - local AreaExists = false -- This flag will determine of the detected area is still existing. + local DetectionAccepted = true - -- First test if the center unit is detected in the detection area. - self:T3( DetectedArea.Zone.ZoneUNIT.UnitName ) - local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) - self:T3( { "Detecting Zone Object", DetectedArea.AreaID, DetectedArea.Zone, 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( DetectedArea.Zone.ZoneUNIT.UnitName ) - - self:AddChangeArea( DetectedArea, '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 - - -- Assign the Unit as the new center unit of the detected area. - DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - - self:AddChangeArea( DetectedArea, "AAU", DetectedArea.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 DetectedArea.Zone - if DetectedUnit:IsInZone( DetectedArea.Zone ) then - - -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. - self:IdentifyDetectedObject( DetectedObject ) - - else - -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. - DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + 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( { 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 - - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) - DetectedSet:Remove( DetectedUnitName ) - -- The DetectedObject has been identified, because it does not exist ... - -- self:IdentifyDetectedObject( DetectedObject ) + 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 - else - self:RemoveDetectedArea( DetectedAreaID ) - self:AddChangeArea( DetectedArea, "RA" ) + + 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:CreateDetectionSets() + 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 + do -- Initialization methods + + --- Detect Visual. + -- @param #DETECTION_BASE self + -- @param #boolean DetectVisual + -- @return #DETECTION_BASE self + function DETECTION_BASE:InitDetectVisual( DetectVisual ) - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) + self.DetectVisual = DetectVisual + end - if DetectedObject then + --- 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 - -- We found an unidentified unit outside of any existing detection area. + 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 + + --- 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( 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:F3( 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 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:AddDetectedItem( Set, Zone ) + + local DetectedItem = {} + DetectedItem.Set = Set or SET_UNIT:New() + DetectedItem.Zone = Zone + + self.DetectedItemCount = self.DetectedItemCount + 1 + self.DetectedItems[self.DetectedItemCount] = DetectedItem + + 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 + + --- 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 ) + + self.DetectedItems = {} + + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + self:T( { "Detected Unit #", DetectedUnitName } ) + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT - local AddedToDetectionArea = false - - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - if DetectedArea then - self:T( "Detection Area #" .. DetectedArea.AreaID ) - local DetectedSet = DetectedArea.Set - if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then - self:IdentifyDetectedObject( DetectedObject ) - DetectedSet:AddUnit( DetectedUnit ) - AddedToDetectionArea = true - self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) - end - end - end - - if AddedToDetectionArea == false then + if DetectedUnit then - -- New detection area - local DetectedArea = self:AddDetectedArea( - SET_UNIT:New(), - ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - ) - --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) - DetectedArea.Set:AddUnit( DetectedUnit ) - self:AddChangeArea( DetectedArea, "AA", DetectedUnit:GetTypeName() ) - end + local DetectedItem = self:AddDetectedItem() + DetectedItem.Type = DetectedObjectData.Type + DetectedItem.Name = DetectedObjectData.Name + DetectedItem.Visible = DetectedObjectData.Visible + DetectedItem.Distance = DetectedObjectData.Distance + DetectedItem.Set:AddUnit( DetectedUnit ) + 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. + --- 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 ) - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level - self:NearestFAC( DetectedArea ) - - 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 #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end + 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 - ) - if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) + + self:T( ReportSummary ) + + return ReportSummary end - if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) + 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 + + --- Adds a new DetectedItem to the DetectedItems list. + -- The DetectedItem is a table and contains a SET_UNIT in the field Set. + -- @param #DETECTION_TYPES self + -- @param #string TypeName + -- @return #DETECTION_TYPES.DetectedItem + function DETECTION_TYPES:AddDetectedItem( TypeName ) + + local DetectedItem = {} + DetectedItem.Set = SET_UNIT:New() + + self.DetectedItems[TypeName] = DetectedItem + + 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_TYPES self + -- @param #string TypeName + function DETECTION_TYPES:RemoveDetectedItem( TypeName ) + + self.DetectedItems[TypeName] = nil + end + + --- Get the amount of SETs with detected objects. + -- @param #DETECTION_TYPES self + -- @return #number Count + function DETECTION_TYPES:GetDetectedItemsCount() + + local DetectedCount = 0 + return DetectedCount + end + + --- Get a detected item using a given numeric index. + -- @param #DETECTION_TYPES self + -- @param #string TypeName + -- @return DETECTION_TYPES.DetectedItem + function DETECTION_TYPES:GetDetectedItem( TypeName ) + + local DetectedItem = self.DetectedItems[TypeName] + 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_TYPES self + -- @param #string TypeName + -- @return Core.Set#SET_UNIT DetectedSet + function DETECTION_TYPES:GetDetectedSet( TypeName ) + + local DetectedItem = self:GetDetectedItem( TypeName ) + local DetectedSetUnit = DetectedItem.Set + if DetectedSetUnit then + return DetectedSetUnit + end + + return nil + 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 ) + + self.DetectedItems = {} + + for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do + + 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 ) + end + 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_AREAS.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, + } + + --- @type DETECTION_AREAS.DetectedItems + -- @list <#DETECTION_AREAS.DetectedItem> + + --- @type DETECTION_AREAS.DetectedItem + -- @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 AreaID -- 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_AREAS constructor. + -- @param Functional.Detection#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 Functional.Detection#DETECTION_AREAS self + 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 + + --- Add a detected @{#DETECTION_AREAS.DetectedItem}. + -- @param #DETECTION_AREAS self + -- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. + -- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. + -- @return #DETECTION_AREAS.DetectedItem DetectedItem + function DETECTION_AREAS:AddDetectedItem( Set, Zone ) + self:F( { Set, Zone } ) + + local DetectedItem = self:GetParent( self ).AddDetectedItem( self, Set, Zone ) + + DetectedItem.Removed = false + DetectedItem.AreaID = #self.DetectedItems + 1 + + self:T( { #self.DetectedItems, DetectedItem } ) + + return DetectedItem + + 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 DetectedAreaPointLL = DetectedItemPointVec3:ToStringLL( 3, true ) + local ReportSummary = string.format( + "Area #%d - %s - Threat Level [%s] (%2d)", + Index, + DetectedAreaPointLL, + string.rep( "■", ThreatLevelA2G ), + ThreatLevelA2G + ) + + return ReportSummary + end + + return nil + end + + --- Background worker function to determine if there are friendlies nearby ... + -- @param #DETECTION_AREAS self + -- @param Wrapper.Unit#UNIT ReportUnit + function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) + self:F2() + + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT + + DetectedArea.FriendliesNearBy = false + + local SphereSearch = { + id = world.VolumeType.SPHERE, + params = { + point = DetectedZoneUnit: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 DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea + local DetectedSet = ReportGroupData.DetectedArea.Set + local DetectedZone = ReportGroupData.DetectedArea.Zone + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT + local ReportSetGroup = ReportGroupData.ReportSetGroup + + local EnemyCoalition = DetectedZoneUnit:GetCoalition() + + local FoundUnitCoalition = FoundDCSUnit:getCoalition() + local FoundUnitName = FoundDCSUnit:getName() + local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() + local EnemyUnitName = DetectedZoneUnit:GetName() + local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil + + self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) + + if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then + DetectedArea.FriendliesNearBy = true + return false + end + + return true + end + + world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) + + 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( DetectedArea ) + + self:T3( DetectedArea.FriendliesNearBy ) + return DetectedArea.FriendliesNearBy or false + end + + --- Calculate the maxium A2G threat level of the DetectedArea. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) + + local MaxThreatLevelA2G = 0 + for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT + local ThreatLevelA2G = ThreatUnit:GetThreatLevel() + if ThreatLevelA2G > MaxThreatLevelA2G then + MaxThreatLevelA2G = ThreatLevelA2G + end + end + + self:T3( MaxThreatLevelA2G ) + DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G + + end + + --- Find the nearest FAC of the DetectedArea. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return Wrapper.Unit#UNIT The nearest FAC unit + function DETECTION_AREAS:NearestFAC( DetectedArea ) + + 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 + + DetectedArea.NearestFAC = NearestFAC + + end + + --- Returns the A2G threat level of the units in the DetectedArea + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return #number a scale from 0 to 10. + function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) + + self:T3( DetectedArea.MaxThreatLevelA2G ) + return DetectedArea.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 + + --- Add a change to the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @param #string ChangeCode + -- @return #DETECTION_AREAS self + function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode].AreaID = AreaID + DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) + + return self + end + + + --- Add a change to the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @param #string ChangeCode + -- @param #string ChangeUnitType + -- @return #DETECTION_AREAS self + function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) + + DetectedArea.Changed = true + local AreaID = DetectedArea.AreaID + + DetectedArea.Changes = DetectedArea.Changes or {} + DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 + DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 + DetectedArea.Changes[ChangeCode].AreaID = AreaID + + self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) + + return self + end + + --- Make text documenting the changes of the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return #string The Changes text + function DETECTION_AREAS:GetChangeText( DetectedArea ) + self:F( DetectedArea ) + + local MT = {} + + for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do + + if ChangeCode == "AA" then + MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." + end + + if ChangeCode == "RAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target." + end + + if ChangeCode == "AAU" then + MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." + end + + if ChangeCode == "RA" then + MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." + end + + if ChangeCode == "AU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + if ChangeCode == "RU" then + local MTUT = {} + for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do + if ChangeUnitType ~= "AreaID" then + MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType + end + end + MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." + end + + end + + return table.concat( MT, "\n" ) + + end + + + --- Accepts changes from the detected zone. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_AREAS.DetectedArea DetectedArea + -- @return #DETECTION_AREAS self + function DETECTION_AREAS:AcceptChanges( DetectedArea ) + + DetectedArea.Changed = false + DetectedArea.Changes = {} + + return self + 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 DetectedAreaID, DetectedAreaData in pairs( self.DetectedItems ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + + self:T( { "Detected Area ID:", DetectedAreaID } ) + + + local DetectedSet = DetectedArea.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:", DetectedArea.Zone.ZoneUNIT.UnitName } ) + local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) + self:T3( { "Detected Zone Object:", DetectedArea.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( DetectedArea.Zone.ZoneUNIT.UnitName ) + + self:AddChangeArea( DetectedArea, '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 + + DetectedArea.Zone:BoundZone( 12, self.CountryID, true) + + -- Assign the Unit as the new center unit of the detected area. + DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) + + self:AddChangeArea( DetectedArea, "AAU", DetectedArea.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 DetectedArea.Zone + if DetectedUnit:IsInZone( DetectedArea.Zone ) then + + -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. + self:IdentifyDetectedObject( DetectedObject ) + + else + -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. + DetectedSet:Remove( DetectedUnitName ) + self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) + end + + else + -- There was no DetectedObject, remove DetectedUnit from the Set. + self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) + DetectedSet:Remove( DetectedUnitName ) + + -- The DetectedObject has been identified, because it does not exist ... + -- self:IdentifyDetectedObject( DetectedObject ) + end + end + else + DetectedArea.Zone:BoundZone( 12, self.CountryID, true) + self:RemoveDetectedItem( DetectedAreaID ) + self:AddChangeArea( DetectedArea, "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 DetectedAreaID, DetectedAreaData in pairs( self.DetectedItems ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + if DetectedArea then + self:T( "Detection Area #" .. DetectedArea.AreaID ) + local DetectedSet = DetectedArea.Set + if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then + self:IdentifyDetectedObject( DetectedObject ) + DetectedSet:AddUnit( DetectedUnit ) + AddedToDetectionArea = true + self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) + end + end + end + + if AddedToDetectionArea == false then + + -- New detection area + local DetectedArea = self:AddDetectedItem( + SET_UNIT:New(), + ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) + ) + --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) + DetectedArea.Set:AddUnit( DetectedUnit ) + self:AddChangeArea( DetectedArea, "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 DetectedAreaID, DetectedAreaData in pairs( self.DetectedItems ) do + + local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea + local DetectedSet = DetectedArea.Set + local DetectedZone = DetectedArea.Zone + + self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table + self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level + self:NearestFAC( DetectedArea ) + + 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 #" .. DetectedArea.AreaID .. ":" .. 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.** @@ -26962,11 +28577,6 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @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#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. -- @return #boolean Return false to cancel Transition. @@ -26977,20 +28587,25 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @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#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. --- 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#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. --- 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#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. --- OnLeave Transition Handler for State Engaging. -- @function [parent=#AI_CAS_ZONE] OnLeaveEngaging @@ -27198,7 +28813,7 @@ function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) if Detected == true then self:E( {"Target: ", DetectedUnit } ) self.DetectedUnits[DetectedUnit] = false - local AttackTask = Controllable:EnRouteTaskEngageUnit( DetectedUnit, 1, true, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) + local AttackTask = Controllable:TaskAttackUnit( DetectedUnit, false, self.EngageWeaponExpend, self.EngageAttackQty, self.EngageDirection, self.EngageAltitude, nil ) self.Controllable:PushTask( AttackTask, 1 ) end end @@ -27220,8 +28835,8 @@ end -- @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#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 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 ) @@ -27256,28 +28871,28 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, EngageSpeed, 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 - +-- 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. @@ -27332,9 +28947,9 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, EngageSpeed, --- NOW ROUTE THE GROUP! self.Controllable:WayPointExecute( 1 ) - self:SetDetectionInterval( 10 ) + self:SetDetectionInterval( 2 ) self:SetDetectionActivated() - self:__Target( -10 ) -- Start Targetting + self:__Target( -2 ) -- Start Targetting end end @@ -29107,7 +30722,7 @@ do -- ACT_ASSIGN_ACCEPT self:Message( "You are assigned to the task " .. self.Task:GetName() ) - self.Task:Assign() + self.Task:Assign( ProcessUnit, self.Task ) end end -- ACT_ASSIGN_ACCEPT @@ -29306,7 +30921,7 @@ do -- ACT_ROUTE -- @type ACT_ROUTE -- @field Tasking.Task#TASK TASK -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone + -- @field Core.Zone#ZONE_BASE Zone -- @extends Core.Fsm#FSM_PROCESS ACT_ROUTE = { ClassName = "ACT_ROUTE", @@ -29402,6 +31017,113 @@ do -- ACT_ROUTE 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 @@ -29409,7 +31131,7 @@ do -- ACT_ROUTE_ZONE -- @type ACT_ROUTE_ZONE -- @field Tasking.Task#TASK TASK -- @field Wrapper.Unit#UNIT ProcessUnit - -- @field Core.Zone#ZONE_BASE TargetZone + -- @field Core.Zone#ZONE_BASE Zone -- @extends #ACT_ROUTE ACT_ROUTE_ZONE = { ClassName = "ACT_ROUTE_ZONE", @@ -29418,11 +31140,11 @@ do -- 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 TargetZone - function ACT_ROUTE_ZONE:New( TargetZone ) + -- @param Core.Zone#ZONE_BASE Zone + function ACT_ROUTE_ZONE:New( Zone ) local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE - self.TargetZone = TargetZone + self.Zone = Zone self.DisplayInterval = 30 self.DisplayCount = 30 @@ -29434,7 +31156,7 @@ do -- ACT_ROUTE_ZONE function ACT_ROUTE_ZONE:Init( FsmRoute ) - self.TargetZone = FsmRoute.TargetZone + self.Zone = FsmRoute.Zone self.DisplayInterval = 30 self.DisplayCount = 30 @@ -29442,18 +31164,32 @@ do -- ACT_ROUTE_ZONE 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.TargetZone ) then + if ProcessUnit:IsInZone( self.Zone ) then local RouteText = "You have arrived within the zone." self:Message( RouteText ) end - return ProcessUnit:IsInZone( self.TargetZone ) + return ProcessUnit:IsInZone( self.Zone ) end --- Task Events @@ -29466,11 +31202,11 @@ do -- ACT_ROUTE_ZONE -- @param #string To function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, From, Event, To ) - local ZoneVec2 = self.TargetZone:GetVec2() + 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 to target." + local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km." self:Message( RouteText ) end @@ -29708,7 +31444,6 @@ do -- ACT_ACCOUNT_DEADS if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then local TaskGroup = ProcessUnit:GetGroup() - self.TargetSetUnit:RemoveUnitsByName( EventData.IniUnitName ) 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 @@ -29721,7 +31456,7 @@ do -- ACT_ACCOUNT_DEADS -- @param #string To function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, From, Event, To, EventData ) - if self.TargetSetUnit:Count() > 0 then + if self.TargetSetUnit:Count() > 1 then self:__More( 1 ) else self:__NoMore( 1 ) @@ -29736,7 +31471,7 @@ do -- ACT_ACCOUNT_DEADS self:T( { "EventDead", EventData } ) if EventData.IniDCSUnit then - self:__Event( 1, EventData ) + self:Event( EventData ) end end @@ -29868,6 +31603,17 @@ do -- ACT_ASSIST 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 @@ -30016,22 +31762,23 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self - --- @param Core.Event#EVENTDATA EventData + -- @param Core.Event#EVENTDATA EventData function( self, EventData ) - self:E( { EventData } ) - 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() + 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 @@ -30172,6 +31919,14 @@ function COMMANDCENTER:HasGroup( MissionGroup ) 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 @@ -30195,6 +31950,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) 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 @@ -31370,7 +33126,6 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() - Mission:AddTask( self ) return self end @@ -31378,9 +33133,13 @@ end --- Get the Task FSM Process Template -- @param #TASK self -- @return Core.Fsm#FSM_PROCESS -function TASK:GetUnitProcess() +function TASK:GetUnitProcess( TaskUnit ) - return self.FsmTemplate + if TaskUnit then + return self:GetStateMachine( TaskUnit ) + else + return self.FsmTemplate + end end --- Sets the Task FSM Process Template @@ -31847,15 +33606,26 @@ 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:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + 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 @@ -32253,23 +34023,6 @@ end -- Reporting -- ------------------------------- -- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- --- === --- --- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} --- ================================================================ --- The @{#DETECTION_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) DETECTION_DISPATCHER constructor: --- -------------------------------------- --- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. -- -- === -- @@ -32307,6 +34060,8 @@ do -- DETECTION MANAGER self:SetReportInterval( 30 ) self:SetReportDisplayTime( 25 ) + Detection:__Start( 5 ) + return self end @@ -32469,32 +34224,77 @@ do -- DETECTION_REPORTING end -do -- DETECTION_DISPATCHER +--- **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 - --- DETECTION_DISPATCHER class. - -- @type DETECTION_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 - DETECTION_DISPATCHER = { - ClassName = "DETECTION_DISPATCHER", + TASK_A2G_DISPATCHER = { + ClassName = "TASK_A2G_DISPATCHER", Mission = nil, CommandCenter = nil, Detection = nil, } - --- DETECTION_DISPATCHER constructor. - -- @param #DETECTION_DISPATCHER self + --- TASK_A2G_DISPATCHER constructor. + -- @param #TASK_A2G_DISPATCHER self -- @param Set#SET_GROUP SetGroup -- @param Functional.Detection#DETECTION_BASE Detection - -- @return #DETECTION_DISPATCHER self - function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, 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 ) ) -- #DETECTION_DISPATCHER + local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #TASK_A2G_DISPATCHER self.Detection = Detection self.CommandCenter = CommandCenter @@ -32506,15 +34306,15 @@ do -- DETECTION_DISPATCHER --- Creates a SEAD task when there are targets for it. - -- @param #DETECTION_DISPATCHER self - -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @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 DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) - self:F( { DetectedArea.AreaID } ) + function TASK_A2G_DISPATCHER:EvaluateSEAD( DetectedItem ) + self:F( { DetectedItem.AreaID } ) - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone + 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() @@ -32534,10 +34334,10 @@ do -- DETECTION_DISPATCHER end --- Creates a CAS task when there are targets for it. - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) + function TASK_A2G_DISPATCHER:EvaluateCAS( DetectedArea ) self:F( { DetectedArea.AreaID } ) local DetectedSet = DetectedArea.Set @@ -32562,10 +34362,10 @@ do -- DETECTION_DISPATCHER end --- Creates a BAI task when there are targets for it. - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) + function TASK_A2G_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) self:F( { DetectedArea.AreaID } ) local DetectedSet = DetectedArea.Set @@ -32591,15 +34391,15 @@ do -- DETECTION_DISPATCHER --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Tasking.Mission#MISSION Mission -- @param Tasking.Task#TASK Task - -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @param Functional.Detection#DETECTION_AREAS.DetectedItem DetectedItem -- @return Tasking.Task#TASK - function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) + function TASK_A2G_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedItem ) if Task then - if Task:IsStatePlanned() and DetectedArea.Changed == true then + if Task:IsStatePlanned() and DetectedItem.Changed == true then self:E( "Removing Tasking: " .. Task:GetTaskName() ) Task = Mission:RemoveTask( Task ) end @@ -32610,10 +34410,10 @@ do -- DETECTION_DISPATCHER --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_DISPATCHER self + -- @param #TASK_A2G_DISPATCHER self -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function DETECTION_DISPATCHER:ProcessDetected( Detection ) + function TASK_A2G_DISPATCHER:ProcessDetected( Detection ) self:F2() local AreaMsg = {} @@ -32623,98 +34423,99 @@ do -- DETECTION_DISPATCHER local Mission = self.Mission --- First we need to the detected targets. - for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do + for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) + 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 DetectedArea", DetectedItem.AreaID, DetectedSet:Count(), tostring( DetectedItem ) } ) DetectedSet:Flush() - local AreaID = DetectedArea.AreaID + local AreaID = DetectedItem.AreaID -- Evaluate SEAD Tasking local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) - SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedArea ) + SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedItem ) if not SEADTask then - local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ) + local Task = TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + SEADTask = Mission:AddTask( Task ) + end end if SEADTask and SEADTask:IsStatePlanned() then - self:E( "Planned" ) - --SEADTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() end -- Evaluate CAS Tasking local CASTask = Mission:GetTask( "CAS." .. AreaID ) - CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedArea ) + CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedItem ) if not CASTask then - local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) + local Task = TASK_CAS:New( Mission, self.SetGroup, "CAS." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + CASTask = Mission:AddTask( Task ) end end if CASTask and CASTask:IsStatePlanned() then - --CASTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() end -- Evaluate BAI Tasking local BAITask = Mission:GetTask( "BAI." .. AreaID ) - BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedArea ) + BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedItem ) if not BAITask then - local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... + local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) + local Task = TASK_BAI:New( Mission, self.SetGroup, "BAI." .. AreaID, TargetSetUnit ) + Task:SetTargetZone( DetectedZone ) + BAITask = Mission:AddTask( Task ) end end if BAITask and BAITask:IsStatePlanned() then - --BAITask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() end - if #TaskMsg > 0 then - - local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) - - local DetectedAreaVec3 = DetectedZone:GetVec3() - local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) - local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) - AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", - DetectedAreaID, - DetectedAreaPointLL, - string.rep( "■", ThreatLevel ), - ThreatLevel - ) - - -- Loop through the changes ... - local ChangeText = Detection:GetChangeText( DetectedArea ) - - if ChangeText ~= "" then - ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) - end - end +-- if #TaskMsg > 0 then +-- +-- local ThreatLevel = Detection:GetTreatLevelA2G( DetectedItem ) +-- +-- local DetectedAreaVec3 = DetectedZone:GetVec3() +-- local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) +-- local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) +-- AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", +-- DetectedItemID, +-- DetectedAreaPointLL, +-- string.rep( "■", ThreatLevel ), +-- ThreatLevel +-- ) +-- +-- -- Loop through the changes ... +-- local ChangeText = Detection:GetChangeText( DetectedItem ) +-- +-- if ChangeText ~= "" then +-- ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) +-- end +-- end -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedArea ) + Detection:AcceptChanges( DetectedItem ) end -- TODO set menus using the HQ coordinator Mission:GetCommandCenter():SetMenu() - if #AreaMsg > 0 then + if #TaskMsg > 0 then for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do if not TaskGroup:GetState( TaskGroup, "Assigned" ) then self.CommandCenter:MessageToGroup( - string.format( "HQ Reporting - Target areas for mission '%s':\nAreas:\n%s\n\nTasks:\n%s\n\nChanges:\n%s ", + string.format( "HQ Reporting - Target areas for mission '%s':\nTasks:\n%s ", self.Mission:GetName(), - table.concat( AreaMsg, "\n" ), - table.concat( TaskMsg, "\n" ), - table.concat( ChangeMsg, "\n" ) + table.concat( TaskMsg, "\n" ) ), self:GetReportDisplayTime(), TaskGroup ) end @@ -32724,26 +34525,286 @@ do -- DETECTION_DISPATCHER return true end -end--- This module contains the TASK_SEAD classes. +end--- This module contains the TASK_A2G classes. -- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK} --- ================================================= --- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, +-- # 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_SEAD is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: +-- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- * **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: FlightControl - Design and Programming --- --- @module Task_SEAD +-- +-- # **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 + + 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, 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 @@ -32762,130 +34823,76 @@ do -- TASK_SEAD -- @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 Core.Zone#ZONE_BASE TargetZone + -- @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, TargetZone ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, "SEAD" ) ) -- Tasking.Task_SEAD#TASK_SEAD + 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() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - local Fsm = self:GetUnitProcess() - - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Route", Rejected = "Eject" } ) - Fsm:AddProcess ( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) - Fsm:AddTransition( "Rejected", "Eject", "Planned" ) - Fsm:AddTransition( "Arrived", "Update", "Updated" ) - Fsm:AddProcess ( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "SEAD" ), { Accounted = "Success" } ) - Fsm:AddProcess ( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) - Fsm:AddTransition( "Accounted", "Success", "Success" ) - Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - function Fsm:onenterUpdated( TaskUnit ) - self:E( { self } ) - self:Account() - self:Smoke() - end - --- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) --- _EVENTDISPATCHER:OnDead( self._EventDead, self ) --- _EVENTDISPATCHER:OnCrash( self._EventDead, self ) --- _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - return self - end - - --- @param #TASK_SEAD self - function TASK_SEAD:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - -end ---- (AI) (SP) (MP) Tasking for Air to Ground Processes. --- --- 1) @{#TASK_A2G} class, extends @{Task#TASK} --- ================================================= --- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, --- located at a Target Zone, 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 SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_A2G + end +end -do -- TASK_A2G +do -- TASK_BAI - --- The TASK_A2G class - -- @type TASK_A2G + --- The TASK_BAI class + -- @type TASK_BAI + -- @field Set#SET_UNIT TargetSetUnit -- @extends Tasking.Task#TASK - TASK_A2G = { - ClassName = "TASK_A2G", + TASK_BAI = { + ClassName = "TASK_BAI", } - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self + --- 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 #string TaskType BAI or CAS -- @param Set#SET_UNIT UnitSetTargets - -- @param Core.Zone#ZONE_BASE TargetZone - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) + -- @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() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - self.FACUnit = FACUnit - local A2GUnitProcess = self:GetUnitProcess() - - A2GUnitProcess:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "Attack the Area" ), { Assigned = "Route", Rejected = "Eject" } ) - A2GUnitProcess:AddProcess ( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) - A2GUnitProcess:AddTransition( "Rejected", "Eject", "Planned" ) - A2GUnitProcess:AddTransition( "Arrived", "Update", "Updated" ) - A2GUnitProcess:AddProcess ( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "Attack" ), { Accounted = "Success" } ) - A2GUnitProcess:AddProcess ( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) - --Fsm:AddProcess ( "Updated", "JTAC", PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - A2GUnitProcess:AddTransition( "Accounted", "Success", "Success" ) - A2GUnitProcess:AddTransition( "Failed", "Fail", "Failed" ) - - function A2GUnitProcess:onenterUpdated( TaskUnit ) - self:E( { self } ) - self:Account() - self:Smoke() - end - - - - --_EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - --_EVENTDISPATCHER:OnDead( self._EventDead, self ) - --_EVENTDISPATCHER:OnCrash( self._EventDead, self ) - --_EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - return self - end + 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", + } - --- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - end - - + --- 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 @@ -32947,7 +34954,7 @@ Include.File( "Tasking/CommandCenter" ) Include.File( "Tasking/Mission" ) Include.File( "Tasking/Task" ) Include.File( "Tasking/DetectionManager" ) -Include.File( "Tasking/Task_SEAD" ) +Include.File( "Tasking/Task_A2G_Dispatcher") Include.File( "Tasking/Task_A2G" ) @@ -32961,13 +34968,9 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Database#DATABASE ->>>>>>> refs/remotes/origin/master -env.info( "Include.ProgramPath = " .. Include.ProgramPath) -Include.Files = {} -Include.File( "Moose" ) -BASE:TraceOnOff( true ) +BASE:TraceOnOff( false ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Mission Setup/Moose_Create.bat b/Moose Mission Setup/Moose_Create.bat index 4e3a251d4..1c2850150 100644 --- a/Moose Mission Setup/Moose_Create.bat +++ b/Moose Mission Setup/Moose_Create.bat @@ -97,7 +97,7 @@ COPY /b Moose.lua + %1\Tasking\CommandCenter.lua Moose.lua COPY /b Moose.lua + %1\Tasking\Mission.lua Moose.lua COPY /b Moose.lua + %1\Tasking\Task.lua Moose.lua COPY /b Moose.lua + %1\Tasking\DetectionManager.lua Moose.lua -COPY /b Moose.lua + %1\Tasking\Task_SEAD.lua Moose.lua +COPY /b Moose.lua + %1\Tasking\Task_A2G_Dispatcher.lua Moose.lua COPY /b Moose.lua + %1\Tasking\Task_A2G.lua Moose.lua COPY /b Moose.lua + %1\Moose.lua Moose.lua diff --git a/Moose Test Missions/DET - Detection/DET-255 - Detection AEAS with Destroys/DET-255 - Detection AEAS with Destroys.miz b/Moose Test Missions/DET - Detection/DET-255 - Detection AEAS with Destroys/DET-255 - Detection AEAS with Destroys.miz index 35ec96850099f66a805b9ea05cb37ae26a7aac9e..6b41226f680b66731e021e6016faa52ad8611628 100644 GIT binary patch delta 296 zcmV+@0oVSq%>l8^0kHck1Z;P(S(E=OGXiZelQb-9f9*!WO;A@lHsvXz@P<^?1a=F3 zv}7r*G97<@wPODvr}~goEY;K=GzoLwcWf&sz_^4qzI;7FZ9(oaQ&8&^)WU3)L@U(T zqd&LFj9u{k6J`~WryONO@pzq;9a=uE zkgIxVe@K;a#W(?$(_e!Y7J>`#+!?QEW*VbKnkUs-&}pI}Hoj?zYpP2dpcQa-NgphM zWhEF8ua;t&+RSQ}l8^0kHck1Zr4&;FJF=GXf1ZlQb-9f9yur!ChVH*p#P;!W&Xm6WA^E z(UPUK%5?ns)r$Rxoa#eTu~bug&?L-x-?6Qn0OJzc`117xwFSAyOhK(vPz$qF60J~U zkN(^yGj_rEPncEc0uj7`cFNxf{w`3lL2_@dXw`m+5A(Uv6u diff --git a/Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.lua b/Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.lua new file mode 100644 index 000000000..aef0c629e --- /dev/null +++ b/Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.lua @@ -0,0 +1,25 @@ +--- +-- Name: DET-900 - Detection Test with RED FACA +-- Author: FlightControl +-- Date Created: 06 Mar 2017 +-- +-- # Situation: +-- +-- A red FACA is detecting targets while airborne. +-- Targets are grouped within areas. A detection range and zone range is given to group the detected units. +-- This demo will group blue vehicles in areas. +-- Upon the detection capabilities of the red FACA, the blue vehicles will be grouped when detected. +-- All blue vehicles have ROE on hold. +-- +-- # Test cases: +-- +-- 1. Observe the tyres put around the detected areas formed +-- 2. Observe the smoking of the units detected +-- 3. Observe the areas being flexibly changed very detection run. + +local FACSetGroup = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterStart() + +local FACDetection = DETECTION_AREAS:New( FACSetGroup, 2000, 250 ):BoundDetectedZones():SmokeDetectedUnits() + + +FACDetection:__Start( 5 ) diff --git a/Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.miz b/Moose Test Missions/DET - Detection/DET-900 - Detection Test with RED FACA/DET-900 - Detection Test with RED FACA.miz new file mode 100644 index 0000000000000000000000000000000000000000..fadc9859563c63cef01b2552b0822a02ab54dd36 GIT binary patch literal 259004 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 z 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%0Dk2Go+T&UuINVf#$8>Tlkx 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

$pmCCYu=uIId;JeX~Cp;}pO z@9?|+X&LL?KS~p}7>#RZdiA^YZt>u~>08ssbz+xelU0A8t8L4K9DX+UriWW6O)#k*)7ts9v8N>RLoC9_YZ%DQ&Nvd5X8*-I`KWb4sL8iboii)XAy9HD(wG z$UI%o@64vF=fV{NDMhr~Lv3H0tcn^xO&=X~ibd=SZD!C5<~I93{oyF8*GsOEK5TclbrVLY^aT)W&p`m?q8el@_F*qt$07cI4Hv8VIc2&?SONxa5xVx=2aH+h%mL0m)NJ6 zC5cG{wFA_I=uotk{n%G5U0Lus1%NByL`O*`)R8LUlnglIo?XRsrCU>hJCxs$orAXIZh z(J;N^gAbLxNbdjGo9>!R+QAOHV>U&@*;`cPGm6K-smG!%bpuAY@<(vD_?AkkH_1Qd zB#0q-Ls+b=%Pa`sztAM0p<3ZcEv54@A+M|BA|PV~gC(g0P?;60nULMd;WLMkrl{}* zOL(q4jE+@tb-9xWEc+3m;33EMAt|uXM*{L*F%)~#sZDBMa^SFa_6w&~oZ|z^ZR$%Q zT-N5=b?QqZbZhaiQpI{0;ex4H>i|UBNwOM201YELv^PT&Zg`X~LAwr3xqO?c{d$D{>2s<#Biq|gC2HK^Eq$oY& zmaSbb0Y;Q)iU=&2sUb)_Wj4tn#@Y!?%!8@Ez)M*X%nu$5#KPLWxZRAQZ6Q*}OrfiO3Nd#{D&XdtH1^RI)BcEwLH!Ht^dkoUe$2y*!SKN#uv*dqn!!dB@hera?H% z3k!!a`xnAVZ|YbiLlY^@<@~~w{KE`V`M2cF0uRk*la27H3%^RrjrKAcaw;Tnl}SmP zhRw(X9r7=a62uQ7Wx}Guijt0cf8db5k)1Tx1d`Ob|6ldgQ?1`>BXs%c*D= zJ;e;Dl#$3>IK@n(Z+#?_c;8KjEMFSNkc)pS{n^fkg?GeQgVkKEB8)G{B3tw0te1R! z1S*X+XE_9jNE7m=4l5jw-CV9FOsTe4BlSCP_-(QLut}*w(n~3n%n-|0#Wk1dls>#X z((M-+$sAI5I^}7EU%V+CzRG{`H!-^$C{Jebp0*Fwhv7_en#*;CDRs+~4=*UkA1cpl zFus->j$ptexMgJw6+Y-hA|=@NP#JvqjN$EYB1y zwz2qZL#t;!rW0J9HMDi-dD#U`%KG$BmG}c+rJ@UopI~Q31@l2gQ*T|!=Bq^R8C~f) z$Hak#*oTz0*L_v<5kLL`X_rl^sJ*-tw3A%($!{vLu`nHrX$Awvuj5kM)MYs^fsS!?47Uhc72iZ7iP(E4{to-NavHgaa% zOv)4UABJpyokN?B8Vk9686mrw278&nidwItiT?GTXOcJg9qKa zz>7j&FXPCoTX~)fgcCwoFa`C~4*U4@!)jC}F*uB?P+3%h)e3QZ)v~${)J1~J62g~A z{E#I|?I4?)cA<|3Hc|sj6E`}#N1@Ia4k3mUg+2w@1qi~JQjusA{5bIB6N(2P1p6J$ z$Kfc_BqQN=Gi2K{iX)@U<3PJ0%GW%}*dg86SPZX<0l}4#)(ZO_%S5}-5XcVT#jbZn z#if)0tLPg0?wojS9L0nisxZXL!!V=yu*2(u*B94zTe=BRIF6@fj&2LX3$RAME2ywb z25c)`M9T$*?0OA=r?|9djMnK6UE?TsGV%pi89d0{ojI)w3?W~981u_}gs_Cl&>(Qg zaL&>Ltb-!geN^$`AXv)p@G0#&T)vnXeu=l~dTdM}g@M-I`BubjaI-(-B|(Ta_o&HF zqMc1Q>* z!EhkVe6oo3FKD0Xn{m>&bPS6*0iL?rJuAF69UA~>Xt z)c+zctm%TjcQW}~u7Rh8Z&r+wA;qsOm7MH_-pAqsnv7&SAj+(90$sg~3;Nd}xMKb9 zf0%pi&(3Np?wE~@ClsR9iN_m*6W#8a$HaNR1T1}Wn2oQfeWXjYnaSouJ!QO8tf1bp z3iLpUtiMnwS)((5g)|XzrC{&uy6Y3~@#`H%)?)7Ea*)*Ld;PCE%h{&xN^y{6VRadY zj{>Jn_ZT90Eq&^##$<5FO0 zR(@c7J`1;xBzu~5jGDT>l3AXo(M&Orw5j{9rkdm6oF3ZsrD7o;l{7ll=B3{7QL%lB zNC}RZUe>vRCoU8M(jwecIEQOj7Ec$|Bl8A_*&VAEA;qU|i|P#rOr;`}&w9vr!!cCe zTD|XA2iB~Ns!_zJ8pji!p5FJCL_^x#ZA1knR7GJ7#aHw(tkoz`1 z6^eGTqs0L8@D(N7xGxy#|IG+I)b*$xSh?nK@ zQlM%9i*D8apzdPGt)zBYwY*kn(MgR@j_XNrZouTl<|D&qubHy*hS*q>7ee}}EDlFf z3s9kb26vy}dtu>CJYEEI?7hxTjU5}Vd&Q=G2WHZayeJ$W))r^OfnPH8tqy5h9g#9o zGd(xVl&kaLs`D^8HO$OXwB zk#cN{2M@NfdfS(HdfV80ugg+xbMy+Nf|OhIq*i=qTE$b+bM&_H_EellHK%s|7U#)* z&5x7waFfaW=d;UPVGYabD~ z&4=>DAG&{Aeh{@C_SLMrgZd1mS_^gHURKD%V+Qa+$=-aA5^Gv1j_+D z6a)4ohx(GA=9wD(%CX{Uf*`D%DH$>WsIp3&RJ8K=6@?lD-2gk-(AUW+zumfi<3r^k zMdwNC!x*-jKHW@PLivAng91-5vM|L1u~S7TS$5F6BDc#)Ksq*KnrKI+q11&~O_(q- z5-$B4d{Y=g&`6R5>zW|678*aC1CzL*Q+4t^hTkAi{8G5|I0063%qC1#P43e2(X1l) zBhx+V2{?bdD0FfZ@-#psT@dF@5j=-n{SIdx72p-qT_lyLq-*o4La|FwxPqs@gO18^tVg*4fiaj;P5Xmq1> zWfSfG-1_s-n!lPB80h(XyXRlI&Lh>ot9IO9zof1s0RWNY007bRs-3mFm$m!f zwK@Z3mqkHb@x`F09_mP0cm*TPG^@LW*9m-mMOa2N-N%K@m|7iSS(PD3z9l zy6}gtStY0#l2m*F2{zk$P|AP$yvp!v2d6O<()Av>k!iSOI&Vx;{b)8uE79Poofuna z5_;bs3R-JLSQlCl|DY#GvNT_AhtJ@aEG|l2J(N=eunA;xtytMv9iY`)00toGOwp*$ z&9~%o6FX6tTiHPxbv;62ExC)eiY{}|cw?JpTBt2acryCn=%Zz7Ny&ED(SL-O=H#3s z_LI|fk26Pb#WAWBoCsned>(|ab}I5Lkm@Q%lNnraYtkS`9PU$?s6j-i=D3s8Cbh)% z4@=}U1v#{X8*$erk{Yfj9t6xFt8{*X&-c|AB;KypP zeM`V5Z`{zAQVRO4518vjJ)di)hW473ux2LbrzKlR+O-xN+W42>V_XVtCx(rocxrY} zTNZ1)jpbR^KoL0oD*o1G?@{MT#hT{*9+5b)Yc=D7EIWPRQ-Y(Qns_2sTExf1FvF~> z<_^@J>yD#E+BBV)4TTT;Th15!29;ej9n^P`F_JPVLrvZuzWPupvEbVPdNb{ zx7VRGSmk=Svrzeguntk8_9I`9A_IJ);~ndD zkKaC-+8mczK-Y#UweGM#W6pL#Nkz1_ z!fZ}D%WsoS$k0PatG0tkCW9!>UGT?iTNlz?z8}cB&IKOxzkVfeQA}G9zO`NXm5G?K zhEuI$tHyc#JhlAlPvQ`9+38T7g88NnSXBa03Dh^8C2r!K(-BQsjXlD=dXKp|UX-$| zcuO%Ksr8HH&?XdpzKsj48?lv{G;E)AQnTPZmXX6q$j)LRpLVNzcMVK(cJoLs(#7-? zwq|gzm=qj@)sn{ih9;EAOeoVM0u8Ps!_}?U_|vwOh~V3TB0IJcLLwVlYl5~&`;!fW z=wlcXb^x0Olq`S+_tRSdo~V@Hjnwwc#fOQj58mP$({(sRp9Z3ioDStn-z7Q=L^my0 z7U)%_&H8AKI9@x-_Er5_+{e&Y{ce71!oB1mg5x#P{`x0a?yIp!$2*r|T}D|f+K$Fa z)?_vANudc=&V1e2i8gr;LU;7_c80}(%50yj3Ko!P!|Lf(>GX(i-FNX8rQHCE!pDY* zhB@15I{g6^&g4yEVb6Qm0>3%iabR`iH>-`WOx3X!{O+*xTj~}6`p*^y@f-LzjGwi` z)6w`HxE!jB(Kp1DPrzkT|C>gMYlp+5UWb$Z5gvI2oOMas{% zq~vz?QPB|)7zZ+b>WSsg`}`8J>;wld3K4wp({obav<(jot1AivKL#Az zIZ1S!^f;Z6Hc>fIP-$iVH;K~R-RC(-j!@dJ3rsl!_9pokh`Hu7kay;;TGk*J zPj^e}=P0W;RZu(Fh-6Ix0GLOAw|`tg)&c(M# o=Q+;vUCe)Q^7fy3_5b!X8p=qp?->BVfW6LPnNoiWBmM&Z59@uv1ONa4 literal 0 HcmV?d00001 diff --git a/Moose Test Missions/TAD - Task Dispatching/TAD-100 - A2G Task Dispatching DETECTION_AREAS/TAD-100 - A2G Task Dispatching DETECTION_AREAS.lua b/Moose Test Missions/TAD - Task Dispatching/TAD-100 - A2G Task Dispatching DETECTION_AREAS/TAD-100 - A2G Task Dispatching DETECTION_AREAS.lua index 0b2db4599..67b247d1c 100644 --- a/Moose Test Missions/TAD - Task Dispatching/TAD-100 - A2G Task Dispatching DETECTION_AREAS/TAD-100 - A2G Task Dispatching DETECTION_AREAS.lua +++ b/Moose Test Missions/TAD - Task Dispatching/TAD-100 - A2G Task Dispatching DETECTION_AREAS/TAD-100 - A2G Task Dispatching DETECTION_AREAS.lua @@ -26,9 +26,9 @@ local Mission = MISSION :AddScoring( Scoring ) local FACSet = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterCoalitions("red"):FilterStart() -local FACDetection = DETECTION_AREAS:New( FACSet, 500, 3000 ) -FACDetection:BoundDetectedZones() +local FACAreas = DETECTION_AREAS:New( FACSet, 500, 3000 ) +FACAreas:BoundDetectedZones() local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Attack" ):FilterStart() -local TaskDispatcher = DETECTION_DISPATCHER:New( Mission, HQ, AttackGroups, FACDetection ) +local TaskDispatcher = TASK_A2G_DISPATCHER:New( Mission, HQ, AttackGroups, FACAreas ) diff --git a/Moose Test Missions/TAD - Task Dispatching/TAD-100 - A2G Task Dispatching DETECTION_AREAS/TAD-100 - A2G Task Dispatching DETECTION_AREAS.miz b/Moose Test Missions/TAD - Task Dispatching/TAD-100 - A2G Task Dispatching DETECTION_AREAS/TAD-100 - A2G Task Dispatching DETECTION_AREAS.miz index 370060b38d016f09019429e2e8ce3985306d9aef..3c17d54f84fff4ef6bb6d9241bf3c1992178aeee 100644 GIT binary patch delta 253942 zcmV)8K*qn=$^yBq5U^M(4go8%W5aR-05k6s02Tm~kfRuX?0su@8^@98XZjrdhcNFY0MH!7JTWVu%{omF$*Z%!{eRE@-YmTyhG*TB4)Y(?& zzdZTo$u|dSe>5H7_lJ+>tM@z^#YIgTBn_u&ADe0`$)NNOqBTH(tE;Q(uTh?4(?aFh zv`o^tz*P0;aWo{y5ttFoV^WuA?H5Da#*h|B#v&+@~#D5A^w=j`2n z8eNRyL3#qrYeQU*o($DsQVzH!De-vM#2{XaH#&8-nZS{qyM?CBnTflTop%VD#QH z6}sww#WYe=Ozx_gq7h#_FmpCtJ^+ji%A+v;Y{rEWdnqlUygycC*Rt zDGquFqBn@+3Ff*ynO*!n?w7zAOU&w{EW4gg&PxO!ZAy-=qR#sO!>PEG3{`nMiFK0t z?`^eA0G5#{QY^BR$K^Cn6&Jyj82Aw?y6v5RaqgQ+t-3-411Hdu;FrcXKH5vQu(xeN zEHeZ)PtrmBuB+ZgqiGBuY7!-RVR|#j2o+Q7jQdZOItY)ysdmS~N^plMIgZPSs~2n8t;DJFe10<2T`ETCQZ9s#JXN;6>f@i8dhDBys(9BUD6hk zgtMm_#`kI+3${MAqzU2aF0iC!XX^hTb4L%Dy@cK-@n}$RHyAmn>mz4~2suQ=<#al} zh;wItLm94h_EcBbc%8N8lqfSEbpk&R+M8_whXMq8GoBWux`>hG2*`u63FZTTto=#f zvew;DG@~w*g%PPzw%sP$LLM_H)3()7FpNRgMV5`?C~b8*F69O4*;5TZq$}2+Q?}LB zbZO!*;>sQ@S^cPNOP4!$sUt0N9TES2_ZZ#QB7K`ZN?&1z>wBCgBO{7H`k*=$gG!*T zqRAu%tsqAw??2{s1)zKty>Uc;tDYl6lqMuH&d+Jd^kl~BYGd1`{QROYlnR%n0|1?* z*Z6m#;6EZ>R~@Loql}0KgLnWEPvBt(^yF+N?RhOv&0?y%V{)${yU&! z;H&{MOXsrc_3DK9BM%vm7y#w2$d;}(r5z4x6n0%>RSvrcJ3v5gwzM#RWawbomWoPN z3AwJ)dy@DD!T;8qn zXS~P|xm-lUn44Z!L{3Znic0B|+f^tFY#bP|Kn4UUW;jm}TK*gS z4<&%^w_Prarp2-dmdZ+hj^Xoa77MUyI&QHBpsPbxr?NXSV^EkO(sQe5Sl4hn$B8{q z*2XZ%t?&NP>MDj{whqC78r43aZ<+1ImwD__HbV8=8Cv>YS^vY@GidFqH%Sb*3b5m7 zkVNTxU;^kF0Br&WI~>@Xo@Or(U2vT0z~ck}^y9`nNYCTATm;sCT5oghhd(qydwF=8 z9a2FT*2_Z&&dbA{~fmF9slVWA_r?s`&BqBiL z$<-VpScK`H6`p@bRGXS?r*f= zuT2pe__Oyu6d50X-a&hm1zY-C{rmk#M~KWUdHa!exJjvQqC!ReateFeTiDp>8Op%HrGwsOT>cuP zwm>a)U?BzAox4&agjG+j_aAHEGfO!{325;?OXJfFso_olOts`l;~Tvf)Jp~xaJ6Yb zP+?+kk|k+rr~_4dh^)=(z0>Kpe)_FWfA6QicPIndLar(z;qrw!sA+<-b{XfraZ>b= zg#QncP5-ceKUB_ebQ8jj7&oHq<6N^s0y_gfg}MS(2FzDuJQG*_EYCr`0gZBy;1A?q zI!k53MxzXL9GKrFl!bqK&1??9_maE~g}PkXjwpZzjA>~EeIebzLcRB00{9M#aRL1M zdkYo-z62%fYL`(&Mlo=KkXZVUYR%Bng|VU6VVcE%lW9rmpsM9`DxVFpUIxW*$roA) z{h(llWP_vtwEJ`p%1f!bh|8N8cBth|#=R|6djkrN;}nmsXK zj&VwLFK@&q7cfOP<`vy=Ru)qb2|tc5d1f<2V8d0xLr?QHp4gKA-sZ#9zb}n87pKu? z6OHbF*Bi;WwHddI^iTh9b#-$OWgJd~ag-*L=_umESQig-BjF(jTQ*nRLDD)gzuOx^ z9&-To?|DTP#3}JsCkoP(_+BTX*bN0PHe5_E?$q#VUBjz&4X@5=7^Z!cA5KSQ(0*5q zA+e_U-n$jdF)jCQr-G6OxZ+o}q z*Zcq4==|4O@5g$WFoR#GN%>cr6d`C{7r5EVJocM6wCTNrZQ7;7ApB|g13y(m2lK3d zj7n0L17tuQg9W6F>KfcYp2T&epA~H`WhMh>6iF%>1}#&`=9#G*Let28+RN2BH?LN} zfo9uZMRMSeX&&`&aixr-{uTUH;AzLR6c=RNp(|V+fRTZIpc4PkMZNV$_kva*037~u zeB#m^zfp(o%Q6nXP@h^0HC!y$dwqI;hzb;+K+(YyCF5Uv$z>i*uBz(nf)$;t@X%q6 zQV}sMGiY_g#R)#s0rjM}FNA5Yul-m1$5reX@WLA1S}rO6^EfTN6w=o=#! z%Hih-xkDAnWtt3=ew3oNb(xfaz)1#w{jky7+)%__SOa&8Z{E~!%8M5+pTO$cRUH@=2mgTmh6gW&$-kFs^Nv_aY`Dx9ClBHEB z)Q~Npp#VCoQo%rh&I6J8-UYSh0Ue*=-86Gcmo{CNFZA_YHX6Lp_%WhFVovh=1Zf>x z6%tNpd8p<3wuQVZkP%reJbVmv-?)=9LRAbX&BY)~=V8ELRt0j10dpOH!Pr(mS-x2^ zeOuF?E{X7n+icS`9Kj`kqcL35pKeDhaJXAAO3(9gc~a&$lKnh}#gC>cOCCD7)92hjYzZZ&|m#H04&O7FDTCEe2$!77IT4Q9rfn;An(@%+wM%{G)zqLI3YM zwS;8%np#eCpLS}64Slw$Wg8BsSJRAn$Yk+a^CnDn4g@-t%Hw}blRO@5sR*}cIb8`o z`G#h%&BKEb$mKH-#5S9hblL&cMm9&a@wB(h6d9qS`ixh7z!|sMRBT9ZP?drb(*li> zIz1m4fXS1-Ldzw8D3wp?wih;Ci~z3H*j$453G_9l`jZ@Zj7&^#lF?di5t*`6xm@r;{c4k+ma z+#EKNVXask4qi^j+yzKmT|G%aTEh+th!($%K{Q;!BptxgQ*4%_FC0tx2ig+~aGWs3S=tjfd zux(T1YVRh?M}x3CZGr60NYnK^9%pakrrsxPe}mk19p83Me~l?9HyAtB3uERCEa`L| zMWagzB!y!gR0HS?nj|CZWW;5(kp%Gl6UP$9zt*IMp|TFqykS#eHano1fPxyQ$bZzG zsKe}kI|fW%A-5f*yGh<3#mw+sTEwQH-X&=o=g0J6X91m30p8|1h5yX~ST=6kasWi{ zvveM?Ojs7Ehz0+o*6mpjRovjkv?699jgM{FV$L8)BjA36BE|i10n6cYTXI&tF0Nq9 z-@8ffL6WJ=bys}k>IKvouS!)^sGAtpRkWFZh7}f+Cp`Ik)+nih4yS`%6g7reh`4(i zp$p(NeWyuo&l6WP=HF}4!pD-qd1R%SZwL^y;X!iCyK95Rys|`O$MDlL!?}daNf+i_ zdF7l1C&g)0Tw|*&e^3oQObXOhT3w=Flt<%27odqBo3ACgV5+S0`h7>6nj=69$02$P({p>C%yw7lR}4{DxbfAmb_?CDp5cF$Th~n|^y$(Hy!8W+awp;b}K#c zjSKt^x|V!(aJS*n!ALWo?sU%9YNxo_Nm0SQ`~7-^4xk=sX26ip#iFq z)*4&DvvI2mPW0?ib^}uf)3hN4=R?J8I6Tj@Xwbq%&`njGYL;fNxg*Fw#(b?tdmz&~ zi0N#AX)`=n1+RTz_y*`ay1*@0HA6LJNP0a2$Jj_+>wTnb1J;`Al}fO;9cO7{Z}%j- zuIk&kvAMs-^{vHy5D`H2Q9KNPx-B`XF=y1&);{OInAsrNR@b-KoR!aM^4LG4sS=!Q z%VS8QIkmZn+d;k_19e^i)YB!-(Def5o)q? zhrdoC@iWR-yEjJBLy{18qD}HIBF!u!z0d*7$s22HYZbWI9|J*u2a?5qc6-UK#gliC zWW)!p=rZKCQF*RM=y+%E;K*e*7sy4FYZ%207(eeEAGI7l@cLJ6sUpD(Z`Em7a>7cb zOCRP5Qwcw+QoLwd+!|M$AJ^78Pihyb8nmzp!J}@es7?k0*I@4rE~o0T)n(A?eRPY< z6%CA^tQykKIlXrJtJZUWsCa}=l?-B<7aS-hBTw`VTaY|8EAw138*r%5ra=(Zlv`Bx zxRNK7JE-dYj}kdQ{w{I8N{O@ayTth{5@%6?^T{L*?RZbKV>)S~3a;FF8|BW4mfpu> zDpv$m>{#w@OsRXY3j7p06Cr);MXQ}h)H%?lun9K1j z8*S@S`jc!?ol~|Ib5a-9d86GRcaQGRn)Ki5Gy~tE@0*Q&663(?VK%D`XA`ZO_`8NE@Aw zT7gv#P4lHscp69#js$Mw6`whu)NsF_4fj^y;s~ETrEw0Qw)=-hZB2u&dp@JN5=PWA z6Z07@`0ci+6Lbf|0DA8_WZ2r)s)dvFz9W{d>Q8#suc-BZ-lk!V#qs@c)_T`^LyMhm z7ptsy)miJkpYfhDK6afG4%@%Gv}tK%4mR!wh=;o#jGny@CB?>%4W!uk@nbm1X3E{Y z3jv_T0cc*WGMv4Nk~8n%pxdls4NDj=VEE?YBpwaD1M|sVR7U!Oo!gZe<@CV_g(i7G zP;ALzbRGYHKYq1DLPdY~%)&A@-+SN<0opvobD%iGvVyfv>!+=hIbe_)I$~UqsE#q3hoWYSvaT@1&l%#)+Mv3FVbcD+PSrTYDN!e3I4ctUh8wp6hDV*bV*u;l0=(KozMb(f7QVtt+Cu&j?+$hvZh ziT;v*w4)(>F+0de2TKaozl!_UPvqLYpQS~40Nte@G<(eDJ3Tl(ndZZ&AJeATxq0{Y zRRV-?@iKf&p}A@`Gnr>pwAc9TZ6ZkvAWhpNQ&)F|Ef~DJdv}&riRXLSvb5vjY$hFo zL(1X5KH>Z}glPAs_8rJAnB0xj)D6!|)527LHqJtPa?{_~-?uoo4vf#bWDwBF1+Du_ z2B!Sd*`Wr{O2R%Q)R}=?b;qY|Icf~2{A<@xm{fMGdXbWiLES3VHF~YPUWvU`-3p_n zUF|AhY@ZnG&n@jAOQL(*%x!lwx}d-}bhwwi_s!x&=~AKDgQs8A+Z&*0U`nN+P&`?G z*!sY3!_$N;A&l_>zQZfp-jfqRj|`-!?AiJoyJeRwW0!pL>(T2MduSqhnPtFv7t<0Y z7v$$L8k0qka@iHBl?`I(FwiAlo#a_IEKsT;DCfZl3Imwfsj(TlnqInYC%c4{ox6mh zyCR_TtUo$ntUPf+C)%%**ViT)g0g^r%i*ev1cP;Z{~bO+=JbxG$T+$s-+H5@EXm1I zrqDJe%@WkYZhAfHtoD9LwQ&?)$wR7kr#I2ByZ#3*_MY~^NE{gMvJ@2qNjh#;D9d(ZQk3(;eb@d$6Tai;L6lhfHe9+ zfLT?2*rgwJDRqR?`X(u6R_L*RxC^;K)8dhMVbkPcPb3e1K9ogf+W`w7%;F^|Rr@%x z!ZI=tv;lK?B4QsP$Q62+^LPj)Q19PV_*<5KW)D37AHhmS%!gY8ZDsf$nn~zcf%x|7 zDn9iZ$jlwY=)a~+N_5Yiq{bmA)iiGiOyx_M=)NoNRYlpw|s`Im=(;qy&gj*fZc z!|k>|`m;8o%2LWPD~*TM_1bv1(1b&abrQ=oTC{M8c{nTBxJKc+CQf{MmVZX(jGwRe z#1N5bB02oUjO4j)5fz7BK7=k@(4aLQYNJWlhl}LMp@{3mMRMMMw9wh5Nk9PC&k9uN zxW((9IaqHawl!Nbc$Kh!#6+Qzw|0$eoC`l6w*kq9ryu%A(D5|i#5G%r+|g_?jd8@# z2k4ARMHBoH1?|7c47``@{i^2KBgl4VQgVJcAtg1KjFS2pCL*^qZxU)OA3P168ALX} z#{`8R-lJvCGPKP3`(EBlPR^Vq|Uua5DxUcp}W5;tiNB51F)2viniq zOeSwMQ&TD2M&OTsExn&|4uEX4XHOjzDnL$G~$7} zxYe!xZwJV>*&jPYB7K&Pv2YPNx*3S48^aU651h&?K(ismJMRV%Z(L3)p3V6n< z!xux;uRprYg0Qt4aziDrfSV_q^~%1ShX}UQ6cvJEDCi-A<&>Wv(0Yz3vgLNy4q?II zGQ^I~YwoJBnRMEq^u{R+@L=75zi;X9_gxSYr992+>i^8aYx4jRI!#N0#i*! z^*6-4u$iJ{suwC&^#?ZhKXjSg=fs6^)rSoa8nmu|KTMx?@DM5Ti&QzZp@ZpmKT7!> z{w3&EZ}H~QImWS#k`arz2_OdnP@tI~=AX$tCO2%_%*vU~-1payaOkoS=jg4{*UdTA zeW5wYLw680Tf&BoDA$pVsM&}5+g{xDKBIjte8+i=`%XTUXk@JU8_xSP7Vk$jeh$Nh z8_Ah}JIUJp;H0`4oK$y$lh3maoXp$7_@$kN25&r zhOgRNPwZ9OWE97Md@AJ7TE@&Er3^i4fXK0he^> zyJa*~@jFO?-FKGX>e{*T;cL)2xUrPPG z(Fcty{o57%;BRZ?n#MP$jFPoZz#Ur~5d{wV?z^JpT+U&*Fk+DJXbT{`tB^}%}_pBjr&(=(#PNe819F? zK;<#TTF`Dk1{qOb?%_+&AV!k{3k{-wi)56P?!jyQWN)}m`^+*xYo?77peu;N-@glk zhX{jUH~jkr%3+-#kn`#r@7T#N#=bK%NIsf=wusoVz`IIioff5uhji(5aqnKW-lHD^-Tz>+8_K*q@ z%79@uy5?&x^2Y=vfnmr)C#D%G)%zTRQS760lxi2FY5XrI_@*OZ*4tY9kI<}G(}isx zaf@j7y@q7YEZ_SXqob22D6|{xT}V?sevP^055H+uY_VswjN@?6rg;dP+y1Op3kD2L z2LA}>UxClUC3dHQ$%5-hJJ>aUL)=uh?!wW1%<3J|n3V0n{(C-Od|HV)bMI%b3cj){ z57pM5DLdXivU2857`d-@>Y z_v58UQv8e%nX>!+_;~Gi0=p}Ns>iqlUPb~4i{Q28wlX$ZYsOwMe9`kVU%gE5C^S(!s zp~3N>cXVhD6n_`0k8m~V_^B`Ci8jU%9FTK=45)&^bl+gP^igjMNYbhM{`s)=O6Ry)`iAr)Iq zHUQIQyq@bf>sRP?AkOhx)3oVQ8I4)jI6=Q<*O+x|i&!dQt5B1FG-T>0Z-TBmprin( z$!GHOjV(v`njclBF z@s)<&i|E~H4ht2%MmyM3603n{wgna#DpAI3kwdnW_vD(bf=$VTy{`SR4C{h&pE}#3 z$aCfkTX>NS<8}vsU6rl(oS;@qVMiQF5dt2GFr`W%F;?~=ImX8qu<%_!sWSog9QtWO z=WVsT+@=422!MW6j(d79%QNb$8+NZVr+JhX=%~KFLw3jsk$Nc0s-vd}53d1=*~f9g zF(EjJM@U=JSCHG2EZ~ULaRcpf5qE3swPP z>WRo?e9Xuml>%P&^0+HrQp33@IWh?7jc4o2B!-qP*WfI0is3wP zKJg6JnGFFvZ9pH=v;HY*2-Y&RU*BQqnsNMKvY|QJFhZds9khA>od;5j zrLSe6zNTTSAEfoFSguR~DQrM#F`2Zjj6`|qRoYrcjj;|+#~b6tfML+PQFfX1mz5j- zNmhiKR-V{*#f!Ia73BkIKp?Z2c9n~!HRfd;<-~%2?38onTGLg8C^O`p*PXemPHhoq z*{RL{!s`y{f^`c|%{sgA(jzM7Vv7$eln-5hv|@ViHONto>((Kw9)A@w&Z6_#M4dZz zYIY3^twnAHS&?jl*;51`cCX=Vr0%yweRU)FHH=>40#&!wtD7R;I>nV9zEp(+exLQq z!+O?#n&mcik7dgNFM@vlw)mt3+1#)0kT`wrw)kIk8&uORCP=XEaAM7-=MH6~sd<9P>tzqX2MZp6+m+g6&1FWQ18HPce`B?O zBf8~*rtlaPIXy_FsOnzY``VK4wJ2G1M%)=)1BKE(oa?d(@oL8V<~r}~8Mqew0Wq5A ztGicNbk6!JcxB!O+c;6RXV2!k`K)IZUmdrh2G5MH`GtCuGpQ;Q7^%T}pROS~AK%@& zXAMx)l))1IIRyE+b2R?|H{%G8wk|GzcKE%l#(1-M5z}d~L^i}(u31VNsl`|K;p!s35EQ6d8OHX2RiZ{=V%pvX)`&LPOs9l58fZr_CRq=kGC(Y-Ht8Nm!hU459voSvlEp* z%-_g7fdBVF62~|QD?1)&>he!9|A#b5C8R}F;J4D<^__*RB+eLurb!5 z=c))O^8KofGQMTDelD%%i*^cs3PIDc*K&(~wuKQ`n3(X`2Aw(>#Q6HUjEd_HAF~yR zcvL7|n4%dJWY3t3KTsRUo-NU^5emo{jk5HTb^-i&JClc#ANdvzjpCv{+7RJBBaZU^ z71~V+A_OoRjj|grg?_&jq>^+pb-a{ZVxetid-5a*nm~zu{Lgrz3Q}dVesI7HYrgT3*oc2eU(favd}+A z+>rM+zDoLfQCuR|pd%tu5p(n;brro88|8xW(|FRTQT&*;*FSeSc(lB1iFHEJ2CJ2O zPUsI8MEhwSkUy!ljx{5tF>T?HQtr1NvNSA|eoi8sfBNH5i^(8=bD?0TovLQOI#qBY zZSzdk+{Q0e6Mk{csNcZ!&SSECMH9*)S%KEKf*~WzIBbg&`9QLuyr)f>+V!j3fN@1e zCB(C>{o{Qg<)|Q2j$is1qBXSYaL8JOLDIcoAm*G^mrTd{} zmqG1F++V}6Fz%`17((4gx$-J6pzkfB?y(-KJ2gWIZBRhtn)q|+pf`!iJR8y8q3-Dv z|EFL4j3>H(pc99hBCS-eW?(J!Wv#B^L#|1yWD^_~U-2Zp2Rw(R6~5S4(+` z1{r7Nf0bu%lR@n6>Rp6#4-xjoQG?-$Ycp!9;NJE-S^vUSorGlaOIw*hpv zV5n)QysS%pnvFjlQcQ<~*CT95i(bTzq0mWfl={|x0Cu6%QQ0!(C#SrQQaS#xu!cj7 z4)~n)4=Uj%)xyitN|m&!#u5*C&^KWRR%Gqyx~l74$3_3&(Dtbr@`b~KJ3a6ER5lM- zv|D?Nv=?>&lZ7y*1iFxSG!k1-kDpi4Ds*~N>kZMirLMD5EYPf0B@kdb51u!|t)o=o zXDft%(+&d2RvKohMn^OeOZ+hAY-1HuW6=s6h`m!!6x$!}gV_wYWh|M!j+&ES(m_0? zpr-klB^(VswqAuQT>9(oEbG#tYnZNUxyrR6UlQTUGasj%N7D(1z9mk`rm)OjMEz^0 zJIt^BXayHFg}ub4uNS28%_~wl zbh1g1b*tUZs+W&v?ugzg#>j}op!6SS}@L88oz+glQ>U6oq8XC zw+%`&TJBizD4Xa>CWcOm$~LFkSLw*Rr@a57XSQ@!-1;rYx4VNK8i!UF7p^4?GCJn) z8Z^FU1V1omam|x{o=nPCm(y$=(XIR0cpOp86-IE;h;bvb2OQJ9|}k**h6ln)Mz%VXjxoec?!Y*m~R zJ0p_Os!YbQn}0HiZqgI>q^W0r8@72Er&ExOS3wdMSqm(4FmI>bS|501rWzLXGU=@wG0HTxodLUCOv4(qEXZ2^6L}i ziII{LPuJGg30uF}UJ;i^6TXb~bGmBhpvPIgoh0uigWmDcewg|;8ewvO@95S3%k$?4 z$NTD`ZS3#to*o>%Jcs{Ie}%8p+9oTddi#If+1s}$=%YD@NdN8Ms0T<+ro|PmAngFf z54@`J$P<-eHFQ=&a~Tn~hl1!}uifdQ@AjBhJm?5E(fciqCRw^q$-SKyFS;rs@c*~G zeUXe4?auMzM=S5`oa*s^ggG-j^+pVk57}SFX$CW^9r*NUeOGL$R@ZVki4yJSQl$4!%@@pw6u>~@m;78hmhjH0)Q%YMNxI*(4Sk_jTQ-Y|_YUts|| ziE3(ZEK+;pf8rFh^!+R|&@}hB6EN(}#^>$L#%C2L8xIsGN&1$5y&>U=eW8O~+(17b zC{B%@z_D?jI!sN4 zUU72ocxQM2{1pEFi-L`N7nm`dK!XE&j53%?9QJMX)7l!A5}V*TjQ+#D82tcU&|Qct zvgjsCD3XSLPlirK^C>ZIac^!vjjrRP;cz~`OTphc{=po7|MP#o+BrEn-+8%rzJId2 z^J;$)2KvLj7zhV#6-Mpe?D1O|0@m`F9BZjhs*Yx{~kK@yy=d%YC zT3(#4^6zC zQSvsNbCvFY*plHKK8-1y?(8}2SJ}shHidGYrWmD`pqcv~udvDAN(xG}_dd>{zV~dy z1>il)aWd*h`JmW~qd~j+%BEHud>iL)llX?o-fm(CznDYEv7F*tS;sCn&27P&$*Cco zF^%L$LVjIieOBQ%@BF!( zxywgGJin*gYTdD1V7lPn=nUDL4a+zimb>m0tkhVZbCA3IL|b3fA+qsF5Rv!(4TnIp zG1~kj7|q-s%|_^lM?~l@YV2&Retbl%Dofz(rhj_Grfb)%+0FmkBR20WEVCQ^!y`6o zRZ(ewGjLG)MLNv|o0tC3q(`qv&W&!*z0HNj>bxSnp5WWYpQTb|L1p)K5$DfG(PjJJf9yh)8lbPx{2YyC z-bVTfBogg84M|sVCBGR*O^_8Qs8%zyPrqA#+Z-%+D=&x*g4j?;lmxFA*|bEj`4+YA z`dUqABk2OIZ|zXkvCPn*GU9c6!=+!H>4T{fthK1`by~dS-gOhjD%NZj6c=B538vKC zk{b(`++4Wi4-1$4(Y4ZcZ5)Df-cxb3IYfT6_7LHt_#j(}#r`haW)&E6;beM&1%MTQ zceNRR`-wK;l7wE73}V>3?TNA1((=jcRT!fvEt6GTWr6Y0KBQRI1=uYg{PD!eQe)5V zhFK$G%g+f9Hg&O6poSQfBzoLZP2=+WPK~n)Qm2|}x-|6P$qg+}B+Hcjgm_ofx4UAE z_m^J^UsCVxiZC7ie-4aM>q1+5K2kM*3ZbV3E{;5%-hK|Wk7IJ>#S-fd_<4)EMy$p` zMw2)&0xaxD9^;$PgEAg_{ix|d6Kod|FLv&#I=U#bQC!Bx5|pDbUB?rA?XU8ff;$7h z4Re5mA3bkj+`Y=OB0flm5xvoBtJ8Qy1bmgp!{ps5JHXJ1hta#hb2pj0Eq5w^tEUfl zOSQDwUk&_Sd>{tm^l43AeT{>35TltS=p9$R9H^X)+xP;b5qPa_V}Rm}c1Q(i%qPRg z8U1Ri&G~+Hq;OD!i0+===#1xR-D(dZg4`EbHc{rm5a;RsGEkfYtDjCIo z6KuL)sZi6z8MX07&m-4R^2{%P{>IZ6W|ll_Q8v-C^+v6N_Vh=ObXKvr3!Lpcwfen8 z6j%y}Mh8B2wlGbjSKVjLrjU6%f46R}QJp3{omdE73F~6i=Pv7($Jrb*m;JGsRuCTh6J#0s6ZW)YTS0=O~^0RPuu3Bx%p)FyUPd*)1W?(pS{#x;Jjxsd%-2joaEb-an{~C+_V% z)6p>_65}AO8Q2ENyDA^Ut}*$+(?$`!UDpn1eN7ty&1Mdm3e1c$C-#dz%oVN9uQ{_p zavJ6X=kkc^uNP5K{ubTRLoJ=QpI%0n@iA1M||n2_@A;zSTGC6lXw(yt!81t68b*=QUPjp)TKimKr*qSf_Api_Y;4uMpq znQBREqbg^pel|K0l=}xJqtP<_a%dHJ5@T3f*E zxD`aRuBmi3eWUxjIQi6fYa0WfhFxg3Gpms)xp=o!GS^RGjok5~hR@N(TvCg~<=t$2 zk)f1a?)qV!7WtzE>D_C7xO)}+gV^7zy$7_3f5;ol{i{EqW&9JTsFCX}z&*yv{S!^g zOwWi1f?^JTVt%_1`?!DD%>~W1hXJ}jPr82|IAZt*y*2zcDQP=`YQ*kUlwQVz!zi6n z%=^a~1N>uUX1IcoQUBw1g=Nko0~Iw(6J#m)6MI?>A8gLjcd8IdNA=mRszZ;^ok{j! zro%svz|*G zhTs9-MkIJiWg4haxkK<`ek+O3W^QQgc}&F)Cus4oxRlo57uu@uj|7!I_z@cHI{hLS zE4q4ry+=Q1Po-LZ%dkPq(zkJr3b6(vN~&6>zL`{1PnQ*FjG2m*DLCzy(;R)TlVWNL z*BGjpyx^SMqz8k-DmhX?oX+#HsZQvXKqW{OdU;ucq?nANTO^;9NO)IaN{oxk=UGmh z{i&$?9}Ff=1SOcF&VZ5&4+n}U^)f(A>Rh{jcNsIG@tV19yU_`cIx7RB*|>;Px?=T4 zp%FDY@m&3rDUCF7t`r*i4}r0t7T#gpmHl+hFF$$)lSdk**>u|(joNo3*5Uri$<8nP zTQB3Ax|+)ZdRcY0K+>6Od{w>2pHt%J7q9ngZT~zQ!bipZG*jr&#^AJ2CuU&y^Jp4> zKR${G^}#seIjrk$$X_k)vG|aWXyj9XoVLTIL?(MDFq-#{kI17@Rn50@!mYxFwZ?Ol zh2Z&)s;d#Wka8huFH&9_JCX`;HIA3OqPJd)GhIrB=xAg=n_ns8Ior?mT|zlmU{d5p^$B>7=>gTk|jylJxK@0@vw{kVjcQHOJofLxJ2{7YzY`m3;7Z~VOAw< z)2T(D3sjRuTvua+J8~#lPlvixI*_iZQJmG3HdpM6mF_V;m=ufG5`qXV7Oy9N6cJ+R z1p>WOd&M;uXo+8&>A1@*`d9H_I*N5(oB8KNl5>0~_pwXf&=yq9q%DK z+WNySYE3m3K-s&bbCSY4I`|{?W%ugygluF3rf>{COprL=5Mw5WbY&`R(ZHbrGVpIb zCJV8rY{q7CCt5m5BQ;8$yH4PBT%BC=!sa;O?Aur0u!=zJMk%=tZ=2G8qU^QTJ0Y`r z;nin#^yn`XHtvKRnzob~Xqg|-6ig~>g(S!lZP4<>i*T|4L8C&&<4JkjQ?xMRaYIxp zzT@|rUotI+wzzfl)V18eJJc-=q@kV@N5~&Jk>p%omI}RA1W{d_kn|3(dq3^3IV56a z4b$PLp~$SNhM1Jh^_zQtLrWMbN=PH-W2&ND&cfRQ+j{k76|oF#$L;jF@=?a=%z*cR{*C-{(Npi;D)hSgnJ-00(5L#j2XI1KwDFtcF=6w%sLTvAW_^ z+UZ(g-dKHJ;jdpts$0@7wpQtiTPCs%Or zv>8{CaNG#E8dt@h*1=sf@mi2B8uXgB#n9tGTA%)K;+`B z6!y?H04c#$ml*GVTKUB_n3LqRvoV;i)$s!R-kAgA;mD`NHbunyd;so#&L%8UJ=}Q+ zkTBUCvS@9G5&Yh1?T;0?<_wX;^`T|7Q3`6x_?oD08_O)j^$13+;dCoA%M-h|e3O4U zg3t{&hzYZybDxgu&EJ5Xb^8UE8zqM+BBJ9{NeA1G`YjrNkvj@f8dzQFwU~V#aVi@@ zK3jPX=Y6BHT9YBFXWlB|967lZJ-|($;y4q9dXYsK+vx3j zZ(WOje6@Fqu|UTeVoP?#QIZyF5a+OS9w>~gk(3FB4~ed0e7Tp6ImYC{$*FzEhp{OD z)Xi1WzoIyQqVcR5mK`Zt5adc|0uuXo4>|)zJ}b)ZQj}mZI^+w>^SDe?5Kr zRP9X5t1K_JlucG@1=dWM#D&^YdvTFmrs{vGS9x{`XpE6lvSAHwJ*jL!lWBBvF{MYN zTRwb#{w&7R(9AKvSp@U}T?5iV>d!*C*BF9?M*xX``e%v^d2)F4m;K$N7e~ijc9_JY z0rVk8&anx{7%akm{&tmML=~HGl%w5`n}W9!Prd;RYzvM#cQMYc6AT?Nk!>JEa&sw(YR+y{+;-16|4m)h zx^iAfb&YKr-vqr-^^I)XqFjnXBcWAQ7h|S>yHZv6P6vcnw(1!Zh3a(>D1Q@xPJp0v zrHIRM41(RoLqKg;$HXIb5MhP5OS@fy-kpqK9=Cu6b?~6` z0#i0L=NtvGg@Zq{Dph5T^0kE!0GlfxpK>QVd zT{Q;j%+I7JBUq>vzIX>hFipC<-@9 zzJhpzN)_KS49y533j<7_e;43j8Dae)ahEPe*nNkKZKKr|w{`ijs3e~~)$nu(s1GgM zqHul}xYjZ~QD>031B{WjfEFkK3)??`#uw^W@U^6dTq5-{+p8`IBvQKi*;5Ti<9XIv zq*$3Tt58D=!F{vs07hX19UV`L5?Aa<4169D!Q7A9brGSuWcc%*Si4-K#{tH$ejFs$ zFXVdx@;VsJFUYz8?In)z#ad;Y1pL`k-A*_F2xO58t*%G6CO-9IlL<9CcdjUZZJUa+ z`1rl{jl$;lVD^yyQ_!J-60nsq(hvk-+A3B2HqO-*27ODrDo1tkKNjj8+B|=K9drb~ zH|>Tv^GHsepVN|F6;!A)H=$x`Em{EvpGq7+Gf9e3&|y~s{}F+^>OlP+B~moN9ZXWX zP-8i-QqG^IK{#*b%9(t5Pt+cNFvj;DWk<_u9CW>|SI50^JcsQLJB4aoUmkm8d0gFY z$;P%drPnN|E7|3k^;Fs}&{@%Jkz~jr#*k6T`mUTaxn1nk;s6`ui|J*Lj#MxlDc=hO zBjD*YbBOfOhz3!TykVBoT8(xnqf2X)QCIPlZ3mZnwb`MH)Saa`udddAXwAz-G>jR6 zWksa6BcnCwpS=-q_2+^b1tu;08xN;@%YTFaku0%&w+ngEw2T+QB3{Wc6QP>Lg0Y&8 zdrXYt4q2VL?U@;g!Wk_+1B>SM70new_s;)lbyd*`8Yrs6Yc@4Z>eFm5zJzVOOXLW* z9q#B-!piy|)}GO2=_E0KU^I~9Xpltd0zd@7GXNF0N_F9I3IO9It2=~rw>EyS)V6&%0rdZp zW~EnuJn@mA6f2uQt*zCPfB=jqSM%!kehO+*tAPE)Yx){f-O7}IK%IYvID|l;7Xboe~rfp6~P}O#UR6a50ZIVrSqn!Ue{w*=4Npv%gSs3t z-0%uhgO<9{;Dtt#u{BiZM89v4)^V#t(n;KhV$3DScn{HTTcE1~2vH>-G3P3_dEGbe zq#3Ge^OWW{pQR1WbNy8TRBfKQkSb%q8E>62omz8JToSPXrCDMuL_wztQRBJhq`1AV zg~)w`QG1?`%af8$e~0^d9F;MLFQ+w-y`sy}SH|;R23pZ6nZXX{^&huovJji3nH4Bl zv}aaY+a^pFdOBa#s~EkTWI%3TFhM6Dna>)q!dV%pD5V1ir+=! z$*8$OQZl&`YM(v&M)3GGY*k357ydvy;5Yv=>sXI%Row?Uf8~%@sNCObOIkF$!X;dq z-*#z9MeQi%?PO=P%5!->cFf6(Vn-b3VW&l~7K*;4(2QW$9FOtc3C4+fK6%{i6{Ezu zX-8%2rX2%uryUDM`O!S>8X#)4+-WBe`A74#13KJw+6n3IKkb}0KhLxa8~m)(&Nd!S zy;-yA-t)+7f6kjrHPge}GT;^5^V#=kCkfJ(_@3@fAX}yJWmID5j$|+)>gSkEwwYIP zL{EG@Ht34?-nVg3n)ErZsOtgWnQ`dgp-asFBH$RY@} z!j-;PYrP*~3-^pZ9lf>gT4<2zWAFgAz3hZ{d#Z5Je;RgGoaZzXqe0=9fLSxF?r_;Z z6q#NItob1ySE+7>rE$+n&b1i;!gxUHMdu64k z_M`q4-?zfVz~h;^f?^C%@Z~?Y;oQ}Kd-ijbrZ9vD zf7?rkXpUS)Ac+|tLA2*rB?_kz87W{@ zKB^!ods~$8xUTqB66aCgzq$qe0spNniU&|)fBAstQYZ`PzAl&3n*v>qVF7Uj4MH;R zljQ?-lTNWN@uF**!~6{KE~Xa+8IREGl1%OGp5XC!S~8L7r@Dd;jby$s4Wwy@OI~JW zyhR|1>fAx#%m^rXQD7%ZNhHD+^u}czNBpY!B35KKGDvcOJG%8uMbvr^c!$C25yGGW ze};R~TLJAc2Uh3;I{|M^*;6X;%Fj^|6TkFc#y1~bcq)bg<0HznQuu%w;UgYFTxA2{ zvXdBX`Sq%VM~tew-s98Q-36tB3>0e>f9masa_DZdHt8#+e`J831#UYj_}vjLI-% zQAlK@^ssSFGk6O2_-5$zG^q3qZ)r_0dOlasVR0~V5Gb(Q1Sj!;z?l9y@Ow14tS_PQ z3uehU`vyg*`;UfO`)#%SE1JnHD@Otz#|17#I$l7Q4@@aZ8}y6bD7)=?;x#ycil$!L96*~Bh*v^p86Ty93oxIxl?Ik}* zU#1v2%MN;6fh-73gfqG=n4Zx%Dg=s4=zeHriF5`wap7vC5&f6d^#{ zAA0bo(m zx72GbD@f!Z{(yO|^@7OS;n3m+^t=Rtma?BQI#}+3JW|h5^uur_eUg2!Ui7okbeyUm ze$X_9^%rTXhRG=IRZuytf1sjcr9qvfZ^gUyPmsJN<$)G>vY5ydGVMVH!b4A;J`}rs z2fJu=6W!|d$Q8efBb2KB`(WfT%!Xx{5$H0bIwsjP9Kl626=^lo%zf%D61reaOg>Cf z>Gazyfz@z2y7m~2^_$BWbm&Ny%MsocK#{5*wNrpj*1zgf@17Gsf1Mguy^3IS6%9D- zv?dIae_15s=_tb8);{elf0dnO z>(8BFs+K(m$!jU~RLy&!iAVm65JRBzd1ipg}F43pAu1iQr$wW$YbKFn8*ceyXDZ8I&NrLmE^C}f3WWBO50(5ZxA;E#zrdCjoncy zYoR$PG0o^rDR&XCK4J8Hs4#?D4)MUqt{P}12T?HW`bo|WY{N?$Ko|@?N~jgftB=Of zrFj+hN7~KfeoQBZfZd-zP?@Luf8Bq1y0>$>^U=Bj?8@kx6N(6RpI}eUbMGSK_jQ5; zp-YIsf9!QAjr$?!0c|tv*^BHdU|yCP?fU7oC8N&AhF|PSmRXRA<$)Q=a~!`#GMg*e zm7PZEHST6#zdShgw_}*2cHI4#Q)Q#M%FkXE`wRa46=%pz?9Txj9XxZ7Md0DA1o?|G z9=PW?aR71w`>I%`C8G`9V`TJcVK`6i?v-n7e>pRtt!PZ~Sq(Ja@k?$jRMPF7yhJm! z=i|YE!Ax!qzcR0K$DlH{)$^$LXHucmZ#f$8<~@9({uIZ6_Q|TQD4wkM0{&%`_M$Sf zH4Iuzq@$F}V7z!+oI7qS$^QHVUu)5;tF}WKIo|(71lstb1lqU1CO8Kaezf2DYEz^@~0j&v}e--H_Dky>{hQT!lzO$R(Ne3Z0s0mcgfG$2c zg0cLuLw8B$W+c21psf(?qL(r9W?U>g>J$uVADAR5#!NN7t2CD$LeL10gK5}Dap|A} zLogU8DLFfX1-l^dcmpSFOW&-c7I=A<(76<_6U;-pl(NSGUFVstW=bBr2q6C@f4+tF zp8}-9*I&^p+BVaq#h=6!J)P8e*$z(SsHT8!M2g_>wljj&ozCW<^U#=HfHVV28>my} zbPt^49cG@uEJM8Ljb|+IE+TA%p2Y#Au5%G%#+}hJ7b|`@3SU!{$p_G2^Lk4&`WAPv zh~jk|Po!-arBR0a=BS_x2&mSNf9;(ep;l-z6rld2;IumfGYUv!3?^bbNWJZ|lYW-9 zsA=g%l+|0>Qvnn@2S~%qzC(eZz-f2{a~nx-HYN8~|?IT;VD)FA6m$Fg^h6++?60$sMmFpU*W zcDlgq13tqkh_7Bid0_2GD=s+d!Kw$?;RP#53FuKN!tmV$ZzlOr>H=THlKMVmyCSK0 zeoR4)2j@){VJJtilMSlUU;hbzRu2zX_x85b?u(t1ldYANm+_7Ee~h@Y(pIlH97sn$ z%q^tg`O#>g%bnX|i-5JIQ6^%qEk>x~U+_tf9RP<^)iHg2`s>m035RhydU<+$@bl}_ zgQJ%x?$B2K9t4gRUWRwhjV>5gRy-#OD=YQR7;Gi&A^|VH+2-iUH@p!#x95Y#zlxhf z$5x60l)A~dh#NORe{KeAYW&`_lFnyr#(#k1i-M)Zr|PPE$h2$hP}mOvCh{kHJ}^oN zRjFt?y-HbV=als~305<^xY2j&;VXU|6sM#*nLD~h=+viejt^o{-&V_*!O?ddYS8!j ztDH|Ck!qB}M-}#4G#94oN0)@pjj|ik@%hp5;SRE>*JE53e|NA?+6s}6t4(2pI>#7(r7(3t3s(l-PAK>yRT2g5y9iE)_;Yld3mK&SqQoZL}=e z!he?9PnpFHf3mh!YuhGB1`q58;#!08WgK_eu9Qhf$*k=6Z4hvnl#%E4-*X8eeYb@= zRAMyj$@5ZtGevz^awf9*$)V|}d^MLG9a&dO3?_7=MEyVCi!~=O9adN6Vtv`kS{5^4 zor&{=7bbN7-)Vvk+MN%cVW`uiy`wF4bRFGx?Tg8ne|7~WY(!GLx$gCPQu;S!Lp&p8 zbsZF`cei|;S!(r$&&AKrFZ-~8h@)$9;(*3REqv-v`<_v99aF9aO=>Gl$QYk$0_On# zT=P8*V8j<$Q`m!y)IsZQEISrJa*!1LDJ{Tsxr2p`ghHPec%pVIZJeE1?qbx*BMJR% z%8evZSy!%9&#=pq(l_bPw9ax4PRx;X&-pvLq%di zJ8qs`wwzj2xW<37C)A~V3DQJAD`W|xdyhj7S^y3q6WMBq0)WRCg&Lob@@;aIUqqMj zY9C{nQUJqM*08ZU_4X>ut_6ZVBTCY@*)@(Af4hmps{-9UeI5)_Cb|I@(-X6Geokm& zL{vgorU$;PGWN7q?uwbyH7rfqtRvIHQ!CJ-&AKaMy~L99r>kT%(6ISKpE5*7k*@>9 z>bS1?%4wCWT6l@8;`gfJl8c2C+lX(xIttXGwRq@a2SGG zfBb`8w=wX*5wS0OtB5aAk|TSi=Sxc_vqqqapE8k)r{tDQ+Xm90v*$-@rf2n%j zzw-cvBjeE9pv<15LX!p-(W~BZ5MM=LOe<8@;#^kfnoe-K7LUOt3}*Z2@)e7L%Q$;4 zOwRfG=HTSj>&2`^QF6;c^;VkM#DIMb1!>mat>Ya;w`px5PDtNO95IRgz=tD6BBf zgj5IJ6IB@2IDx71L9nrn>n_K+e?b;|`h0iLTacVj1)o#*9;vB^6}F)Ax`@{NT=vj= z>Yi5;5BGiyh`ve}TK~cXmvlkLr^iCncScRa^#|l69FU+D8J}4Lv+Y-@86)22(MHFi zOe{tni}4&U!nO0(+ePYWYd4whwBE91WgpI^D_mH|Y0Y*pi;Q9@grs&*f7u>`V5SL9 z)7B=B9X>NQm)Kc&du_!eo1vJS2i#DGJkdZ^-!g&iXncI;6IyPte?hz5q~G@IcV&OX zv*He8@H(v-h4}GCVK!Yn`=+*w{Q^AKzF(iEeAV>E80!=+fsT04krl`fTa@0?$Sd#N z(lE(mdLbM~lPvA2pJ8W=e=o|g^cXhOuy<0IQ?gveVCMZOO`#TFM{*rJbjieOP}}@n z-16aF?*w+wC?Lr>v>6zK`5A8%wu#DblyC}E3Ma#YW9ZvdSouz0Hms^uDXK&=H%j=d zu;ja9A&#_72byGixh3~NiMf};>4hZaa{;cW4-e>LOZ`Uw+6u6>f5L{yUX_Amo8t=n z;3kfo!e|Kq6?$qxYW|vh0b?} z`YhB{s=Ihs_px|l#N?NnNBt~K#jcY~chCxy7K8ymvmv+sfAWlDIE_d1hn=n=(8Pon zYpAj)NsePIF2006KTYfkct3u}3yi36YAo9CFVI~&txUv}+T{o6#C3woGz&UXp;er) zF_IWbQ}7AeCyvOF2zW&ckc>)c)qy}2PGiVqcnMJ@u-;ALL(h1D714misW(W-jALPf zzrvbmTKYvqe~rji>P$bHVA(>G*sf-zdeVwEpBe&%wXchaU4z&a4GU|iMwilcy5@No ztzX!ztZO~KG{n6XS-}x73|MV~Tz6RUZA2ggGl|j~>#TFgp=!dR4 zCuKJIBK^O0mYxD%&670Kri?r`i;+OrUUZ5jdHF@uE$A})ssX8xj0(2!y_$}J7y4{8 zhTCb)7K^Bw0}(~Z2%24ZkBBWk=Q>|n1s5qp?nu&0y1QLO>MnpEi%ASJY4(?iAnJyj^4&!kQCGp z*^CVnx{fpow%L7x_)IRZR89}UhjbX%Q-@hWf4cUVv@_h_nQb2(Xcnsd%g*c7KXkgz zDW^PKa85xNn}mZ*>+r=@mhnTW%86brqhj_QwoBgO>X#f76gy@}Q1s~2WInMLeV81X z^s^kDX=Ld@Z?p^P1xs-M2!Hk>UnTTiDfc-PJwC(N7xHB2Y21X7-r>H1o@v~&*Drc| zf4e6b6SsIuF+2GjG7+D* zwhjOQC?AZWw*!KP4h^%tisHr6CKS_)(6c37xUhk&Ro2?J?_G;`*TtZ#-Dzr=#wK>_ zIXZf}2Y`>t4F$~HO}5rcO9@)2-DLS_f6!z4oUon1`OgQ(r@wZwNXb!NZM#&@(0toL z19MzrZhLrPE+e!pD3VQ@#&X-C;?fcseJ?R)SsGqgIkqA`YtjkiKP_uEG7ojDo_Q8v zk9#q|?(xpay%=D@krM;xT{%kf$AXJniKOcP*tZB{^pO(R-!%4mz2ITBIkv$Ye|@S| zI1ZX>++ZxuMT~jO=YsxtYGP2PYiCXahvy|c*4ZE~&Ui4A!JDphOOK&gCmWm2q4uV7 zuqSu3!h1GQE*U@Ub2r@(xlZYx=+bEF=LC^<1K+BabU?AGzFYf*pA7iHb;1H29p<(c zsP1qDjLw){z0#MN=+JKi5J+KLf8VYFW$9taACxZ`dw?EFAtEh8ECH1ie|x<{pOk8OqnAXoSN4H%DRKNqGy!4A$l<)?+u|j z4>tA|L0scAZ_(=MT{!JX>@#qo6@}pH1;*7_*Bn8MM%7gpQ+nNs8MGpXHZ9x^nyqUF`JaA?%era&_+ z5|~j}jLqmdT-)3k)3bJl;RRO-p7(7_g?hVRtVS}L-`bt|CEmyzsHR-tHV%I|qtF@P ziEs#0U3Tkq1E;EA)a%kmYaHYnIL>Gz@H_vD<6OX~F+XISyTDHc9s z_@a~RXy)WkyhW*X_aZKDK z3;43(3E=K1j`Dy^^r@dd+>=;$Ak{457*r@uIQGJiAhEhCP9_U&@jz)WGCap(x1(+H zU07V<;V<7$v*TC1e;MdF%Kl+=9UITJLZT@d{c1;;t`Asu1?Nh}7iAzXf;NZ4E=MW3 zm|pVA&t4x21xr6Zsz5nGZgC=NTvG8J0M8t{I6chJK9Jr(Mrb_5!RzsXW>>W%_VR&# zXFFZj5n@5!Gs*wm5M{z39!AqqslDl0_rvlG$(F}J7U`58e}E?R)-yMEfiTYa_7iOt znOuyU#i(b|9=m8`*}*NwC15|uZFZA{CG<<@xA7>OAbpzpkyg4f`z@0MV^q%5f9+EA zDz3T}6_C>c5aE?zTal+)${?r3R06H?Rh+VA;? z=J&y-w)-5B-RuG>wR;)u7wWDvVuxJe(vnp+0oY_D)Oo-WHrrnh zyzbW_p_Ww;tj1J3aiv^21BM3CF!Vb9ROEz&nN3SMvK5aLHAJGEwmheCSb({m{(fV8 zk*FG8H$|*g?U9NFCvl2tWMe<`>dqMWzmgclbq;#&+yXaKXCH z_W9ot-Xyz#Q~+dFMP0`Ul5X*kk*!7Ixf~J%t=2Kaoc8Q_aqzNw`MaM6ZTNrZ*>~&3 zf7W*!YsGhKKMs1|747ee&a&#d-Rzpyft8*w&wh*DHEhuhojJh10Rh>l?2hxL)lKqxq%mAp>!YIkwceHAkv#;cG<7edNQ8DId%_R`LF{jgMp9 z5bfmGWwvk*uFoMX4~Nyl#9$ZdWNBe(g_IdYqyf8;*Z z5S*VrM3r0mU6Xr&CdY1xW@>SC6*^0YTUuJ0ufWOO;{SBjP18D5+0E8?0Xkac#<^bh$g;8SygcfXGB=}#`MPX9RXOB5%bajX>di(K2oa~}+JME{Ej>QLJdi`Zb zaDUbSnl<)5u5!z3y_~ZHdKc*O*D~^g= z@zBkdEvaV#^AeqMJD$4nu}qDzfD_(yrM@>5jJ-|nP%$Q4TuvwAxZ0vte+h6i0nixI z1!ML}m~oU$LyWT#*d8T1lHwc$#H46(9Dw7mtsgeA)Zx2+-$=@{jB+fdeD=|id0cBK zQz@OWd;9lIEW2>lr#!@MAhk;30rz=&X6*Ln#)VK)zm_kk2;xodlgivP3RzW}rU5S&2O0?$P zU)8EI6tzv>uQSUqLFL;gPtQA z739e`w%xj*B_@|6mzAfZW2ERtI7!`}?pA{7e@-US%S$Z~qZHXHI9h}U8^x^rCS zvEV~vITYeD{n*GBC_#~&>?G`>C5Udt&MXrD+OLlFVq~VHf8P%?0}Q@K+@I=qWV4)W zMuIaJbmP1RRgGJ5SR#URONTjjE9uA>9~kfTF8_<0FkE^+`Z#Kqrj4lIhnIB)XGQS?%W}Vu>sd$dxsGQwG_d)9roU_G^`3GdyeF_K-lV( z&p}Ufed9c1={h($$5St6EgmH6zqoOQ;-$ zPehD*e?t?46+1Mum*}Ozij3lBMMTmHdpgioiXg8>4mfJ88`SoKM~bG7o5;D(tQS#k_e?@7*>kCM8m!(f_Bh?2vn}bjn4P4ab z8Wek#BTczfr0@(Fi`)b+(w?Aj`P4DCf5aTu93>l_j04MrXXu5~9QD)OR>H#9zh>tk ztgaUsU8-k!Hod$mF(5hcT4dEd4cY`%;^(`pJKJj;4;?{*q7X#x^`np8=?rSaf?kks zBh;I{!(zLsbg6(?=~)uo2ZeWrmD{ixAbGy&oluS!N(C$CkJkn|Jsj&N(9i}2e~GVZ zv94Rqj40z?YYA~e-d)4dIEz{56c+*$~!rn`wvW)1_C2kZ` z_Vyqp+TvQGtCF%sJ`IafS>3v0yCoOJ9mi3IZO_K6v@M@oCOp@-Pu!*6T*OzBVr2%} zwKzB}W2YNim+R@MhN9n^K}+$Gf4%|HC$B%IsS^)1nP43MfSztM#(4W!6%&3OUfuR3 zGMIJH&v7(uhv>R}5Ep#a4pZFT*rQmfI5S}a(;1BhnsdY=y-vv0Yt8&%{EW$hz8$V! zG4q&1ULi+F;swWAJEXT`+-%=8juqNuT;R^sjkYjn?hsuu8tbS`CeFE2f3*)rIH3Z~hDTa522g`(SPP2W>i zG&M&DqhtIBoceE8B9waSCG3h(HtWSu?1-wQv&`u@1!3JP(czHAQ9nniPX@9C97xm^ zxqM~+Va1euaA1h18WD{Z%w*<^0yk*ABSj6fdnQiCW7L&-Z$&5!f5@vYI`aa_0bIh; zRiF5ld`+*&9WN_xquY8F+hv+yA41pIHFMCtUY|AI-Z6!#V>bxZ}%kyem0$uBTD3ko`%M1z6eb1 zr>+z?S&Eu;cmp7^f(R&bQ01cRBZj)W2`EH;A>(ojI)M9;fBDj%?cNn6(>@ z20EP3XPoof10_1R8#R^dd(+W<_Fbyfa?wM3Rs(Yu)B4SQe+;aYgR@NIZcTq<(7u_& zeZ5?J@_S|wcoV{OvjP`~)e<4G?ohhtSi^@Ra&aK`K47~lRu>0D;`fyOEt77KF9Gly zR{ZZ(TW-vN9BZ?QSpyXjqkq3wz4gWG%9Yr*ED3k0Xs)KV?J{x~oME}ymNRwl7G^OX z@Z4(7YYhM8f5^BvNA%j=)-$W)PPq*?*?s7+pXXU#2PMw)adD}kxZWt1sSY_|q>Y@e zv&GJ9!iwhQ$$yiPM)0iDg5@_P^o-^r>R-1zZrjEwhUkmji1%K{_m5H$9~GG>WHir- za0h8?xmYeHiadDE%iFq(I9D=ScZ~*>5Dkrp-?bUCf5%5gL$1ZeLB%Rc)yArqUExKT zuD7~cS2Mit3u~jZNOA8d*L>|xJtj4hujh)sYK=NE*Yo6@Q%6f3UCspjuw{&NF@+=r zMO;wSktepaNvu}T62vy2P3nr-V|ll*Neu_ybgrBhs3;fHQRz(3V3hqS%5}}&00nz) z2gjL|f8hwV1Grr^O(!_DYeoj&7`akmU_X4Up}+4Ycy`)mOuD`ZvogadD-Z0$=fv59 z@CQyFd)2S8H4H2T-D=+}kCWZOCtUdjMB8A@!de_TNFo-IpCHV|3#I2)Q9#OkfI@5H zBZB!FzXc>Em;x@Qf82ID@xAO2gEqo>z#Vq;f0f^RKTGfnQpZtw)nnt7wx5H?#cgLY zuV@I*)Q0MG!@{*ixvXEb6QZ!zTU&Qi9D!}5j@Ff({&hV<)a;S%BEx?IZYLVS?#De~ zOXGJXTkw_cqgB_K&KMPbI^!-a+V18dgp?Z$jD-XnC08DkavUU4tjwZ8<0=ASG zf4757p74l}L5b?(!wvKb*JE;JlM&hgpoMdU!ZjRjnW1~g zUe=tbdj2-&5PzNqQtzF4R$r@>Z0Mq1svpq9g%v6;4m0G4d`45t&g}->b-9bOyk7g( z)T3+JNiJbqVEQZ*`pbdkT6vT7WBZJ(e?YB?vF`PHwW_Knw6plvDzA0r^ion(7CmPr z3+p@Ej;>QqkAh99ZUw8jxJy#-{N7AuJ5B8vhjC)+_TL;d#*-sWoTbPb1T9^Yx4!Gc z#kqFIH1U|0^t&&?v(T{5vQZ7knmae)Is@b+>(-iDMum9`$@Z;XAUksCm5#>ee`%QW z*p-=vPx`8QWL@pfVw^5g=+p970}ni$y~N_hvPk2Rc|9XNypMP6q1!Qir?awxmk=u} zpiEJ;F1+f1VNiR!Cy>X+$BqRI6lA~g5#&y$7o()OS}mrqhUduzf}!fpb>6pUKjT$j zt`5f0W&Eby>;3Ij9v1+X3|xx8e^IM{+e@PL-v4>^OQ-4X4}BAG2&G?jRIa19asD=m zZ`{UsQ&Y@pXCr9mH?{?cR6gYkS@D(MCGP;)lxZQ01exCV6b3b7%MsTnKD#%-Kj}@bPa2)e?Z>{hAGONgUGot0(y6(=Vm}psY0&-3}7hd!iAw&mFRSpzkFQQoiE5!Ml=KKxllg@f8Ky z>))n*)&YoC@!M=f5yk_GfA`$$YIbSC9o(~X=#KRLA)qf3jU($hH;ayGh-bypj)0IpQ3$2p7rB`goquL z#mVtwl^kNq@+13d!mu6cI!Vbrx-qiRkDMx$D^Q34Lau?^iaN&tf2ZX5haqtI)qZAe z1U**isT~`lO zNU}>wKB5;hj$e)r-@dT}aegjyBrYM#Zp=`2Oo}8;9Dn7Le?oq3H>&AmcyW2Jmm*0DhiL zQ4B&0Sbq?V0K+KApiUk7g^A+;8)s%LQNX*2cAs7W;b5kO8FeaW10|yj$Hg>Dkw2t^ zfinvolf)G@e_GD+-vb<54Q-ZkicqtVI>?DhG|KjwY9hnYaEjYj8f^J{*Cg=L+{Cr+ z!thMma{8ywu&Z<=Cg}C2&q&>OGCgVVaKURyKfk(GU5qV%`|sH>gZ*=pPd?z_iO<7B z(ZvMw<0%B|BqXg6_`d@PL@q~zH-eJR5u#SOj}nqSf3!3w1P-WW1~ef8XFs#o&{{zR zLV@dXa0;CauQ4hOp9NNa8!J(}pI#RLhaEZz0$WG&fY2B+J3k-xhZq;i zp+h9Z-c#iG#6W&fVrPk+&}?nPohjMu(iYNbp9W|Ir-duME7sSKh2#z2FQ_HRpLD8tYOX6$NBPSUU$yTZX!V@gxn`a0oV zHK5aQJi1+9|6LFkZsU4j+M^WU6|`Fw2tMHie|U2=Km&7MIOoHSMUj%|%rxzH4IQtq z10?EJY&MQVqq{xM$-xJwE2?Q4zSM$>*Jol7r45m_9kNXKDN{OU#&!uW7ShdD;{131 zcHHW1`gc%AP!@L}FnG1w0Njb~sQI*Fw|GUg>+3F5xJGk=A7=_VW8cyD-zaHqn(<<4 zf9%sE01hR(Pv!N?%*Pn@B4&`4{qz~@tkS8V(Q_wA3P35?^B=S_H6gq;+f!J9a8xm z_ptLdx8`P}nsRa*<=9$kWjq42K@Q6FO0-H7Q;ngoo`XF$oBi7)y}ni#N6^BoebE?l;SeT7CO9a1iE7(vHYPu5)e(F(Y${6qH3B+JGfff>}Z$=XXr z(B>CK(B>Qn`gt_V0{C(H>sFW_3E9n4ZxC8$N-~uk*4`vq3Z74&1SC%zH&9(i6w{hv z=vXbF(8eI+SFIV*?2&XH#Wh8rf5>%<&=hGMcQ8^hJKoWxS>5EYH;t8a1g;I{Wr~mB zvkVf~3#e>J_bb{!v^J@_0budX<4RONhe<&h+Uyv)T zWDP!3hk9k0XjvcQs@SFTgbT%JL8p2paH=dCVBi$vnnyuPI2?<16djH~g44nUgjqbF zO;DI~SBj4xPbxo43*K#iOs*7~;Hs5fjGw4IJtUFSIgJs_NMuPSOb|-J64-QiCS2|CXh|cnIun zHUT2iq^?N@Oz@$SK$C#*216@V1K{vWe*1?#c`b`)*yXi#8~iP!Yn!HHXxmfEjSB&n zIXDv-;Ywt}j$si z93GwQygo@@AD-+#Q+JKy{K}jiz%vx1$VVShgiQVpiK(R0msC#Ze|mhfb8@h&3x4;z z-zD~a1*1Xn`r8c)!noL8yaV|zcAib>67InLora0vyh0#G9xX?w*1MSyr_es^W5jVn zs_YOrYD!?Oj?5byr3J>+2^M(^3haCp>u^f(b^pzV1Fv z$SI&|(&se4<}+Lsy_>SJn&1Q>uCMDLJluB=R>};`FC^5FrbnGL{SR=xEX@oXmuAHd z#C#ni@3`?$ZOrL2OZfILb$KBbNE!ULPDSsInhg%jZbQ==e}6~K0h{Ti2D;d*?14@h z(F2R*ED;giFOwW1Z!0j0pWlM@J<}PISDCc%U)$dM_WJtHn}cNc#m?(r_LCO}$0vtJ zzpk&lQp{{T$R?v(6e5%`c#%Q63Pbax9swv+s4kLkMj33I3EbMquTh=o=QGIj?uoW) z6QnXCvKJg!e~k__<%)Q&F=P;`RX<%Tu2T?7&hpU!Yt5QuQvAf)am$g=lEE0tJm3$HbGfm?$t; z`sC=~=eH*Zhp&$%p;iXJu83xRy1wrCZm+Mu>s(H!*Tu6Reo$xOObsndFGg8!GW+2w zgZMqYf9uQZ|9iIm@BjP9tv{`WJ-yX0=SZ-R&D!%DQR7CjCPSEzhc}kcZ;oJ%UwZsd zF4Tb0P}KxHHSp=LlX?{|0JoV-2Srs8fqpBL;T z&p+ts58D;(P7V+M@*bJk;Y)W3*c`NW3DjT@-w=L=>N?$kNPa#zIw2x?F}q5~&(q-u zc4GxzUc7y^^ZNbsor9MOr9@ui3&;rse+Zpb4RinXF9)yp388P_$flRG=^(#h`mAdD z;_b=a;opcrNwV382&Q<2+m2Dr%bj2Mj}(X#^>2LSvaMfc>Bp=|>zDgGf8F<6@8(w+ zM>XrK0WhRtQaZN1&Mwrmy39YFnK;2YQU}aOHK!;h=x$B-J_Vq55f9)3y zJ?Ly1WcjuwPWGNtL-1He2Z-+Vt{Ue5C&wyIBDnTDXEaCqQz8d>!;f zNed4F!NtK#>MC7TsXG0QU6uOy?6#;vJ65oxE`g$Mq8R61&3};t=k)tuye@fV^+};lNarq9z)!!dWp#~{6<_?fGBMl0@H%yDqyj}G~u>Rht=4v9B_coe|pml^I27?p3ib8 z_snC}P@3^r8cst73s4&{*Z@p{qDI_Rf{<|d{Sw;npZK86L1;bkSyLKTEPr{8DS`d3 zb_48i>@CM#tiSZ!jE3e>vJ}I8yx@9gYLO#c=jp z@&v2VtsTmKBL*Q=xa}CD)e*;;qX6D!%c53zzI_Xd=lZ>i9W)_gP&T(CUQlDlsTSZh zIUA+Qy4@)$l;ZK@Qi5;7%pE;=JC-h0?Qy>DbkJf%tX`jINUXxI9TRuagz@@%`*!@- zeI1B%VZWE=e4jPQeeaC1k7u%8^z_|0?^Z9)|_MqRB@AmGS0V`#lP$1Z5;aKaNHnk4*Zv%c38q>i* zez&Q0!U5X7;n6v#(8VT=Hp(c+7}e#fKYP_W<+|^Be^3OqMR%r&2qLnvA;VG^_H{;D z8)y&0j7%-8!R9JkT6kg6`8xbJK*Oq_DLoTe*EO%88oM_4xXv%=uCs?Ln1Df3SHLLc zYl>mYX(S^Bfo-8(;=;5d_cWqvHO^fO6_BLK-}ZO@!r3IiEJOGK00PS~xG}F#mX4jf zc%p=fe-9!FqY3d1ZFLJ1BQ7|+?Z#1HV^|zzn3!mQKDcMM zRPe?|9&m_Me^=dF01%qgDW%<1f$>wP`a8A=j3L0Utph=mtzf&ZPVKN|x2>DDUK1JE zu6aMH@9WHJc6?pK zFiYECR*JC#KaHoD__o8I<|gv?bc1oW>Q_~`Cifbwu+}gdy}7S=EdlzfXySQ}vTVo9 zulqF-iD;H=w`Tz?j!_#xz^TSUdwLSKtIY_?J{9;BRR;C7krd9YW2g8}OH_z~Q-g;P ze_4rimV|e#H6pl)n@cg6#VpkBXB z@wN-B9bn59amk&_jW@{K*9|c^$6*r=HmJqiadJ4e4(}!?11%`Tj=65aEl959TglZm zn7ldVcKM)+2~m%J55^ZR_nqJ@H=r;Hf9CcAE=vwYdAw$BHe(0MH(wh$zr0ABUd`@d&qx9$Vzi07(!y>I88`ybBw&;$E>uYerxYu`0_cnJh8!$ z%Yv?(lsTJnZoLtvhR`%NPP78yzl<|4*-FCS9LEJ+)Q}?S-LugNcHcC&wA`a$0-xiplnE2+d%3ZYpCHf+uMU6@gzOv=DV7 zI#rC;Xhqwh06jp$zoub&Uz`y?z?I>jkbl@Cw~jXCZZW(>dMCrNAi+~noZE zby`td}FyI2CX6F85! zMcf8W-iW`sMBh>-wy?~r^Ne^x>s+EXI!E?s$(`h44)F+ux<^N7+Z3iFa#H+8M}PCu z5#sObq9a;z0XjrQB^rWDw<-Z+Z^u4F}fPMbFWM?y_FGXa|+pD*6f@ z2=3*#eRh$V(vC=yJa_Q1B=JU|-6l(2$Ln%1AOACq6+=X?1)5g0Baf+2XMb8h66%T6 zJ+ZEmK;Y`&Gz0)gbz{Ofdg)))mcj2Fcr;CRUmn1lc=8HzjEsgKve7NQ73e1fWMJTp zXEG^<$4h`}N8C-m|Gt|{Co^a4P7>e}3U&0Q6=$+#OaqmdzCIfk`=5rzv{TN78|$bH zBS0FWxn5_yOhqx^Xy+^xIe!yh*B0(6HcFQeJ*0*)EIc#AWNIq}Y1O(@hMSx6U~A*_ z7;dbK!#(>v$8Ecp75&ayPdi~+UR%l(y+EwO5FI^EX2heS(KpzmoTGZ_D?Z< zC5b;@5zWEvI9$;c6KvxOF~JMs5N;bLDH%nf+%7~?I>JL#ChK!PsR4gmB{; z1|b6=gaK4Ah`>?aXn+4!9OW%H@PqHe=sDhCCw`UdJm(5m9N>?Bi6f`C+rH}zJ-psu zZfC}hZVpl+mxT&ATf??fFI2@D4}A)cWn)9u2`sv3j5yRyy|I&~NZZqD$4KAI8#F3C zEDW{!8G=ZvRvr}ajK%ZrM=)i48fr=LEo1F4#ng`_1x{JU{ePeBWXr)i>i70TUUN7T z;WpQ`Km+9G6Qw)SyNDXgSgHyeX$Mzbi2xs}HdYm^A*kVsJC!9ua0AWt2go*klO-3q z%z}odrfPP*F z!aE(alVj|E%zw3K6qz!k9BHeCKE~iu8VJR$$K&WBE8yj;IhG+|d5Ww95jb7vJ@GR) z0-|ghVN;!y;rOR{6mSv?I;t+U4iWX65mPbe2>ylKCG!0=1O#v{FOB$5mpe#(Q&4@34bQ4yct3s>@1x?d|ciygX}z= zji!d|{T|v3(z7Ki3*$cUcHC3`V>#){t|B?GcPvvE-WXTp`$))vqzhGWRaXSp^;oW9 znRo6sqko(mLhPS%4LWp=vO+CDH~DKin!zFa=^8dsCs5zMQK=hQxI^ie-hRmfKA%ar zrwk4V=YJtbJY$J!qaq7^N6J0X@teoFw|I&uhhyN&MV^4uoMOV~kRk>NTvIH+lQ}7# zxS<={xfjE{+o?2QJPjYv)Zx2085>~0ARwB8XRWWWspN-=reTTknY~YdDH~Hik z5t)OAz-;H=X2Fey?`;+MQKc)m|A^~1*nkbT*nixcY@Th_V57NPt^EdyZlT&qmp+v~ zv^Zw`qv}x}Gu|M7Gsld$ug-tWJdR_fa*4hjE8mV4@1JkS%GY+RIO=iSe{S{43nE@V z=e!}3Zyxznrc>iuB>Qe~VJzL2osz(3%jUcn60hL23?z8NtiNNHuHsI~;zN``qL+<2_fwcB==N{ z9Y`%;)Lt*c3-xG^j<&bV$1M`KUchh1#(%eCKuZ!UT6K``LK^ZNL@Nt3{A#vtR<@4khv|9A(-Z7!=T|mJT^neL&#Q*IXz*24YFxA zxl(%`n~m}dH6HuWJ<2|&5CJZqP5Rje7$vY-3Jn?DpLCQacT}D+zj@I#?hM{*XMeLJ zP($T#5ZgW|`rcAAb?>*xkZ4eO_Q!|ovi;O|f|G3DrKBkNl$UD>Osyw)-Id1dd1gL#anZuVbVNVpF%;a&uV7y=g*78cuCk4c9O|16~Mfq&-Pj19NrG6adn z8!?KExn&IRL(Dwfx5Ts@p}FNBtbZ}I!1f|wsL9*_&0p-i-UA=lX#NJSK%W(G1*Gs| zaQ*AQIG{KUg`q(38juwz;RF^R?5V;hrNrJ6T0bAt*gy~N`wg4X8?R!=8QYBA^(p4h zHp@iA{1U(E%#y$mELhK#5d?+uPgrhZ53~R|<5Y z{YVwC^g=QDS)sEWz;N|wd(;XRB^`i_2uTjqV5zpO2?qwsrb^1Y91b{Jg&M7rLO%h+ z$-$92McxL3r3q5+D5a{=UwJ`ofPy_z# z6SW!;Mi~0O>Wa?=`5Rp&=|wsm_mW@p+4qXk<2iV9IUHp?qnQ%?#`)w*EdXGO;~}d8 zO+Xm4Glk3qQ~VBHV4vy-4vGfEw}3Rs(&9Eb&r%dRVFs7!M}ro^fqz-{%pAFl_P2IT zFkL8CXMA^AbXQ%g!O}3!6`@>ms(VI^!rOS*2iM@U3`!_w!$8xEf>C4^B!D@~)aZat zXG;3T6@fVrJsm>Kh3hU4$J{UqMVBAb$q>zFaeKmTLs8|mf{WNww914_1LFy5;(}#Y z|2s!1Lgp|Pq7E=xe1OZR zx=1SKwz7)}UsrU;!9{WC0oH{gYycflC%DLp8kAH99dPPcxY-uP3?P_2S-+}~<4Lh> zv${#;`s(TeInHS3Tj~~2o2+^xaPI zCK0JBJ1ZdA#(z{5=mz?>VmTYLsdL~*H~Hj4(or{{K|cO|ihLe<-RiIV>S<_G3EDh@c&6v&Hw zf(#v+6~LZvBhw(!1I{nC+OuoQAcV=e^uC*LS7&5dfq%et0svQ_nVL;atX&|qr%?LS zXCgPsAp4Inz@E{J28LkgA`q$e$R#`f|0I*f1(nKEc2(|}46r9Hbr0EK-NB$3A1Mns$%bjc9#*riC7U)hJ&RnlTAY`odzL^SqlQ?Rk!?g2}>_lzAS|B z;%aVe7QSjyJ;vy-jQ@0?*}P%i-V}T4?sKQ=OfU0+KBLW*404dk$~qYuiKB2YDsZW- z`Us>{5bFAjS}o$LW5ut~HB^EsbC&FPUcMAdhkwzLW@q$6taUn@=2z*IrAeHdKQBr+ za_6k@W@&}Z@mRIe&VF2Qv$voCZ)^@Fl+W-oF&L2xWh}l+VrINgS${UQfz}G0kX~;M zsxTKl=|Q)mE8XZTqAxSgGMzbmb8AhrWEK+}aJP9FoF`aHKE5S6o4uLbGjEF#w?BX? z27f1})0_}Z2hB*(F3eXze0FYSvfCo|R&_J^LQJlf6xwk*OBD~x;Y!18Nnl?Smn+Y; zhR^+x`5f6d)bhC2{0+m87CrGuPG7X1b2wI)ASK^+6)~v)z?(Lg3^KJK`K`{XL!%{@ z=qp<8mn8=Gfb4kbtdK0=8&N5(+gCuDG=HZuHU80Of36ng%do!ql7Ag6&lze9Li(Cm zojb=GHus|z=TKvIA2(H@Ym<7mKC0Og^C%<+_@mFvSoQlQF0W44%NOk^Bx*b^5S+bWE!;5k9zmhkT{9=+`UFm01daS?72Qzgm1uBs_FboH> zA?Qz7jfkI#c&La?6+{#Is%c&rDI@{{+bDQe3Z|S)6cQZSn^YkUgMW*Um=YMGAvzV| zwbE9H5=@$|sXuYl30|MXjlLAoEXBL1sUYJ(ET3lNqpjuDs(f=cn# zMC2=gfr1ei+ZFlr=pM0#D4pIbh*Q?nw6{x)6CexQJ4>&kd}neoLu(74o`1!NMOle@ zr!n%Chn5rr%m0V{AC_LbT$f_Hj$&wJPq-g)kNO8AAR4(NBBY}7mdQBUFKk#T?OIU*r0;M zB{h3xF_PxlIp{Vt&4rK3si4GuTP}VXLdof9Ouq!7w*h z^PyU(s^isMkJW)T9DmF|WHJmY-agH#S5(ocnd6eqWQ$on=YNXh*=-sU6L&-oGPHRv zN?FKo<9^6)q3n8+6$J-<4FWb|`OY{=_1}6r#`lCRWdXVQ7%h5}!`J(^!G+=ns@8Fy zT+K$)Ay{>#Mv%Ok6;o(&gGofyQjXC%5oHzCCL=ZDSqA!*>{AMhn_PnFC78@1gQ*lD zN(&a`B)V4X8-GHt*K03qEOaMPm+tVR-_j? zha>qTq6gtsZtiEP=hI>s-1R60%SB?y9KwW(73cU9dPb92 z;wXb?_J1yU5NEZkRSXAN?`U7i$17w|PX0!b@lz*eryi>#=XjbIEL!(VgT zgJ^Voyz>h)cvG*NJpD0Qdv=nmA$QhnZCamauhfC8hURtxExekW^))k{t+|XhYJ%G_ zE7`QfssjolOl2$NmJ` z&>^>gG2p5eD@hP3ha^t4OT)5b|5!oyq%zD7LxD%7_3CNRaMqWe|aVdber&yRXAjQZhu%0 zNp=(}VW2RVUT5iqcrMpuk8}3CVLRDknnET<4Mj1?>|-{$b?((<=1#Znf#H@A;@|_s z49bboUz52h2f9rAeRYzJr)UE^$TJGgLom266dB>HrwLAPhW$*N5>tZp(eNKB5){Jh z8N_9wm<$c{njW(Ab*7Q_pmCoD96ri>v0GbLf~S;x#u?l2mqu>#VE|A z(taOOa56Pu+*3~12{8>#;2;@=X9*%!QNpaGn_N(mI zVj)JR<|_1;Ij;wO2k=p6G|}{wX5>drmUWK-WzUEZjEKVDAB_6|5E{m^9s`bdU+nL_ z)jOXkAxUON&!t*#h}_ukQUrZ``>V@0~C< zf18A{`Nass?|G?&AqH2e>B1T@^%ncK)`_7c>IfMWM;umE5y-N3Dt~;q@!UZn#D9K~ zN3XD=#hr?G4;7TGDIuiY5Z*5!6iz1T7~`u=Srdes(C;ce!M>|>1pBVJAJ})|8oz)q zj@KP4X}%LLU{&}Czb{$#WF(zZDQ0uQIKx(AaZ02Ppo!nvy+5z1g@gO4yb_*8I0au{9U z?2_EhIk4RMECXIh4^97G7Sjx>(A(e1=Uv8{=B!AW~RiSk-Soqp+8(;Bh_dr*Q`d9kj^kt*@b;DX8EaAFQZ-fvYeBdfF z&rRaHc`gzyZWF+8croJ!99ZF-{e}Rr zko^YZdlB;u?PGEis;UP%dHT#Fk#xKIeCzaEcKhC37Y0v$egLRwXMaaLXI zfg=10lAKA|@FwNluOo%~!L7M}z+TheJ`Rf^On-wVRJ#HQ_uw3o=z{D|)w(+FGfoH^ z;B{-jI#HBwZ-#}c#|JkmP*HNlYuQjIZMSL_oMi7k-NUxapVR{At2=WWx~F%16-&p} zfrYfZ1WawNpA;X!+w)J3Rt#0K{C0`IolCwP)+ya(eLso71gIw8#ZBt$%0$mm!@#acIX&+lnOKpcO*Y?uV>}C)&~16Q4i_Jc2>t+r#r6G9?M#!hy|vs_iVCK z=cX-k%r9ikz!PoY6|TE$X;sk9uArQq-7*Dh&J>Wh!(7EPjBu1R3)?JM4%d*ww`7}- z-&VnO4ZkOB1vRr0Lq29c2dKNkN~YeJ!G8s(nUdilL9SWXMv`_*C1zw&h9A_pzqQwD zioM9_+V_VOKuVcE#cDcTG5#8P-$hIe+%z~F2S>+OWhT1hb~V}4;-l7A6# zFEVBP$-0?LS@TN$Ma0c7BWgw-v}=?H?Vqz+8uz@_!%>ihld#1o7+Z|H#K#p~?e^;6XB9Uxzo)oZbMgRK+emuG#PX^g2Bo zg5n+E#4=b>R2Fz~g_C&<&$LSPM}J52d?GW5;qi`xg6H7;Djz68=$QqSBQ~=b)Z^B}7dLZ&`c~)rI3H#DTQ+hoOjZ_+ePSX!r;vheQ1s&~>Q-6#J9P~m?oFCqI1_(%>CWs8}ll0R>yIm%X9F+PIGoGEh3s(h0U1gVCyj2KcznhhSrR z-k^OygNEU{N-DDGz~^kLR%9-rtM!G)G@RyGapx}GM6a{{@O;=8Hnp@RoE3w+z%%aA zbo|rj|D(!Qp%cpA_YBwH{D0=;dJeR+v|yeM@5lHgE=i&n1b(!$ckuRDf~POC10?{T z-FnlbppLMAQR|7fhgrA}0bHjm6krV=0C1ApE(Q6#Vpg#~7=q3_IFYxn4^HGX;I}i0 zLQEo^z-~~#e){}&Je;m#W8yiLQ{h~_q8nj`QQeP2_6>S^bNKStUw;l?%f9$isJ9~f z?-?W#1p4&*N^fyD6mg7paqD9;LA7&6w-@Je@tN>e`X(C7(2za1G?vL;mO_WQ!X-G=u{OkEmu*P$KHPWY=39bW6|oav-V60MU>JJISf!{i9gt`Hf2{s;FECEEAZd}bYA6? zYsGP821}-KwHoD}UNORh;;&hM^I!~3oMFN_l|WhGkm^;y+NyxHRRPORerrwLn+1}u zC;1s2)wjuc3U5?&+H$zqBE!X-s1?nxhX2Trc1IbBhiBRJCVx{mZ9E{W@Nk?>k2Hig zwnLo8aYm<}?t-|K99Dj=_a~7N4FDn$tK*P%!N+to%glJ5AOSvA_t+;&N_z79ZUXV& zpFVl=dl0xHq!bjSyD;ReF1tIm4oU2@w>UPDx0!am|=}=o~OCSXQKf zz^HMhUBNGAKz{~<;ebr-oK5scLq|GetRlfFq#Ui@u|V{t9sASgz1`yzb+8tzSgypE ze4?(R@N^bD=);R=VDFJ2oAUh2@PpgLPVPr%(mB))lLlB=^YkO`HPoK1*681<;-W=aU??g_Er3jxDufSL_Yb|so4uzI#K;t%p~a?(m=PJ4N2CPxdH*IHe6MT*b3%oryUHGr zheHd^wSS8UlrkAHgkKy8a{tGG*r9JZLpO`sV(^AJn4y=rLc>WkV`0sKZPftVa)8-W z4>M+?ZCg4@E;GpUY_Bdp0u{do>f)Q0b1vALf7LfjL-pCGVSy5W+Ft|BPRsJCw`8%! zcbN1q*|e}MdUKD5Rw)Vjz}`R;AL37+@$>4N!+(Re~x2&TGp=z|{+|RT(KDR2pZwTMlQbs)wR#&sN_^=A`&Ivi!`z-ouc znSVh8uF@VoQdIxRMKx;_Vwd=Op-BP=(dRwsB_08GA5fX`ClKbo%wa3ncS%QX7^Hjf zlI*h3(OatosUF8x%{Uq#!_J{vf%VXC{kPz5t=y>2j*f5Gf3Cf%aVwC!zP|J3AlZGf z^ZJ+lGtj>+7as`5zaT=o>S-g?|zCFEPHtD8EPsd4Fc72OUtMI#b@LgQNft zn{+^Gk-ob2p3g?3TRLyf^U)~30eFfp&|QR`FFoae8u(z44c6E3nT6hfvQKP_0z2<4 z9}VzP0n~4T?%&f(>)?H?;c%LchJC*&{_90H$yh|Q(-i~TeDd_)HlA#5Z2tQ*B!9F2 zhioI(*QLU;v!({EcFRb?E@ZX$2Zh=Gy6XF0(ggHYbI@B2KohYw7&ljJIdT_##n{W> zn~a|Kc7x&Tnk~ogbx<*ZKnLX+IK4ded(rD$J|eKg6m~&x5ja~7;h0qmKv4T;4Avr~ zhg%fhW;1wy+IaHs8&7}q_Jc7DX@Aq`n6$t`Mcqlf53NVx1DvNls-=T19aj9)RX&)F zG8{7H#?=;>-b*RfM%HFZ=-c@)Q~XkUssJD~p?n>D^U<}(v#T?;K0c;)!dm$rE9F^7 z&w6>OJSX7#JjdIqc|iNuf3b66TTjQ*)0D2DTbY@GDzabXC{_PmwgnAhtbga~n?tAz zRw3MQvLRA7nYZ>&Pq7bm#3{6WlIr-*C_A63lk;Z0RuAS@g1Ma=%%@ERbE!nK(r_jy zr_HXLjK%?T8v~<}EJ)H*!fLB3TZ4?LL{UTf)P`3+Bx8YP3dnHLz(d2K+Tcv4AzLtN zb1ex*fA0xO)PHJ|D#svA7=IGgzq(eTN>83~brJ>=K!xKjO>^4p@KnT5dv+Q)W;tSf zG&O4E*u!c4LOYa(54!lT6RD(T+a7Pp&+=T!n5uL#Gwy2D0}5cm5ge#%2Px*0K+SoI zwzGj{M5Gw?;*v13MQ_t5MswL59Th`~;bKeRVyldcEsYD>I#ii$3x7RiepZZT8{70g zb(35D_dl#9BM+S#ME6Cjx6LNoWjq{3w*`q?&aB1uJ06H+E%^iY+D-nUo8BgOC5pff z%EW-58*lEfiVQ-Vd&}9}&FDc~3)>Kdv#w&*95Ch0z9l#NmVemnTkgpe*zBS8wPpTh zpU?QE!O2sWb$I)2iZiWK?{JuRK3mJ3kG~Vz z&u6Q0KH8MYw_ln|kioHfkfNh>FR?lT3q$)1cUE<4@4}@M|NCi^f8&42l`=9XgdDVK z;-|*xaH5sdv45Eb#*AP|iB`I)h~8TmXVvrMw^Mez=)qsb>hY_?zwGZGzC1jles%#E zqP>8h26$lj#)PaRo4P#GEpVqbx7}pZxh_$k6PkV#U7Lf9V5xJzcJ(^`7lp*9RoCtL zEm2!+N>Y_A7nLC`qqieUiUuEFK}^t8+Zw+v0Zq2-M1P1Y8qOR;BHQa;m_us#@CLn% zKH^KnirnDhkUVqbfZtB8(&=T7`lPDr^6u98kL~aVm>M5H-{H|X_r@cmdWukK4ceX0 z98~B`30hR6=<0cl=}EvSu@{A5#APyxL`X3+NLSPBT6wU#ndgrR=kJWZd3!|^f_C}4 zJB>MED1S;1$jb#Zrw3 zMX@#KqvZ`*CUhv|YC2lOWk|1(fk8w^Drb4PKYyI`N1jlrb%k%oB)A=%liCpKPj4p~ z$7DSdL5k;FQ!Bvns3Ayz8s_8{+=X@brYQ4?49w& zD3hFcFic1A@TA%B^>BUDx{9MKw}OJrNETKAyGcj=JNZF(V?+8#sH!?OTbJGZKqEqb zd4Fi#vr3KR4>W{+UUW2!HPu>Ang~z(jZ^DetJmPx#c7ejcU$5ARQ) znpjTC7(peC#S>=k+^q{On5rU%I}K}MC0o3 zpA1&IX4Q1y0~*De1M4zN;Vy^Z^vLdj&nd10T;OyCjW<``>8Js;S~_lq344oi%74%; z`w;EBI&z2Npw>*(0li^ilj*;=w+-(w0S72@1M?0qwv`F^5Mq^!-RB~FbKV=>QaNu+ z#u>aib`8s^yh1)YP8#B&4h#M27^wT`&@r3cfL)^%EMNQ^kbesgv3bb<`OBRnCV%Y7 zlK(mUydUxp$dWt%7+%ek6r}$vz<=sb2%i6D`4Y!C-<~plcpWkEt`SUhr|#zZ?9|nD z?&jWUv?IRlwWaPgT%t~JrRq1G|GpQ!&s6TUkc!{jmZI0BAf0*`sD6kRuO0HD;=i$r zGGE8I$gG!iMO+5fjC*BATpF8TuY9*#7_0648ru_A-R(U_R+1okv*D5X`gky#Bq7rQtZDX|M8Wq$(a+cKtA-akfBG|HqWM!Y9rKjbB8~6*E zBZNX3RSx`oS7B&Hbpd^3}vtPWiUwyyL0c3}4OEUz`7zs$6-6A{eF>LwV1$ zrQxhuD3PmFV<<+*Ge%M+`3}FfF>Te(q30tGNhn=Zziq4dwu;+SHN&%gvIZyIx%Xkz zC(H8Z2D`D1g|6LG)_<53sGv+ytF-~ zR+^g%$=x-^&Tc%CH%)CrqUl=oo0T?AhDJ==%~CiTQK5r1sQ(J_03S@tM%FCq!N zj14a?vPn;Y?!%ps#MZL5faCvSD|>6mCX;;9NmlSPW(P1WAz^?`=8JJJdMo;+E*QSL zfwxfcNV#=qnb@3v5L_@Tf4GY`d3ZI*`x+`rJrYTE|T=p_8w6s-*SHH2O zVlifoCwEglJ+;ItUl8;NB>Dnc^pghaq;a{U8>VJh17FHC6kWJ#J;Jfl;$r96n z;IL^Hj0 zuft6~5hHAOpAWrp`T$#9?V|u)dJ-5d3#PVkzPAQgg{J4rSN6^z2<)HQ@6pIz`QNy zvAC7^M(`TSZ(cC68c9y3a9mWB3u29L__U@EE78fhxqDNjICfSE2x zVx6WoL8!|S*=Wrg5Y58!8dFD9So4svkhE5URevQuIf%5P**r4TvhnJ7w+!^oA#l}L zufhqZn;(1n29H8Y0qntEuDwd|7PsZ7VBQu{5C&kogauq_SI?`%D zn>!xI{4kw~E+#EhQyNTa2T`=s5?3J!ATVr~^Epuuy~A4$riCOB_92TM^|BMsE_et= zxQ<1b-iVsp!vDZzcZgByl2r*zTk6V&r+>qDrfsPN*{gtnpTae7%QbJE`*6%s_W5X5 zTna)pu@MqX^n!VB!2(>Zc5jqlG$+IbRvpI_3}%Ku_ldJ>#CtD8yJ8C4?jnWEJ(m)A zpn5Mxyz{zk-SgTZQMl>_{qlxFDEqtk`#8Ix5WwKeZj>lkbgt4 zn{vt>s;5JF*iHW4^(ol<@b`B%jV}G0Mz@!Zrs;oq3#)3QomXl5lv@58Xn7lGc`JK{ z{mfV)BYcCRK@JXDe^cu=#Eh94WKkOd_gCcdqk_uyFqtCyB58Xs76hSgk9u*Wx6%xe z8GzxM9>}^z#SeBkYXL~586&d@K!5tR0#ex}(02-4d3(IGBHaPoI0V+Z1h#MrZ0Hu~ zdHntA6$zKW|MH6F_4(^`hKkUhg5avr45}Ty3Ts|BA$P#2Lm(BpYu3OqkQcBw9Q4%k zx!!@T`~xE%f+7#E9-a9BjATfE0&^LHPiF0bb+3GS5x2qx`1CwMWxnz7Z+|?zIS&sy zT>V#9!;ZQ(E@QXOW6pk@&nEp0GS^@pM!YGdAF?r;v^+~Lr_<}=*$+S5+}!lCev$Pi z*TY^mnEmh~zsjy5^WqOTlk*>rvO)pqU;c1>sScj^kU*;V;d%^`Ztrq>HEPKnzMTEM z!!26HwD&PJeiu`-ewkWMzV?h+hCm69lE9uOceMZpMM3|MXrQuEcngf zDq{n-4%JwF9c>h{v3d(em9plR3NtyNjC9G_a0*sq{SUV$kCB<3ljz5)O_C#gV;tu< ziqWch4RQ=?MZv5F!^!lPQZS9Pb0tm>f&bs6x0vc(s(YW4xMp{?EwaCUJ{FIbp?HFJ zWfj4w+H3B<7aeg=W`EP`7k_;4l)765ruXS-7yqMp6}^J`6>h1*EqLVId30;p9x?^| z9%78n_K+y4^4cl&B8WEf)UZ&Vrv~|zTF`f39LKgL4%{f#=xS2>u150d zDFfIf0Gmpn!K7dc`%!L2KXLk8YjJd;1@Jl zQaRN)C%Z4x8`Q_a@adbKZWBL{5Kd4dTA_fd(me&uVsL~GGsRCPxQ(dcGkU|O z?NUNAhayMBKK{$iv5490S2f%~#!HhP>&~=p`46_!R5Hkl;RWoFq@RurF<=!1*zBAZh3VOSz0CDhesl#*2ygN9JWX6&t_zJfG~VK?1$rJxg}gzzaDtGl`!*lUtJ_YtRuCp>BFrJ&-|J zbr*cl&fUkGQF@zADmzI>ieQs#thi`L|Cz%+uI%pbSY_eHS@QSrD-)ywk{Tz`U!Xv?bA(u_nu`!{`Z0LySxi)MJ>vhp0;5*db(B*&it1d%5-qBmW}c;iGn#kEb}rb`Gl<- zh9IlZOFI4udj_LE;u)xgk!y8c4?p3>$+P(v$D6M^ad+OVNG~!zmaU@PnGHSc_ejv) z`}t4{1M0YBc^5QeAUWW@7N#`PmICy~uXv5!o*_wU-`u-?4={d?N&gn`o(tTonl^xM( zx({MEEU?$5S6VY@s{JxcKYwOXSf7A)EsPT-6zs$FZ*S`yFUls1ZE(vM$Wmx3AucDJikfOeFURqXt-CpHPIGv1@U z(6)lHL1D+hbno);RX>2>s^RH)sw=+}^EOpI_3+N1ZuUt~K8awXJbwq(0ZlLrTTjg9!kOZiZl0%)JP!{cwup#lPim6bteim2Tx(Z}4S!z0=oCw9sF@hG2#s_OK4u#RIT;nNa+j8y(T6BM(-SYxnho9X zyTjEq9t0A^z7!2Ybj>G$ixd5EIh4)Na54Gs7;WoY#i?7hMBUMP+xlYS61dlGHHGWh z*O0hG(p)rNgBPaqim68FoZAG)I|rqUmO)bI@>SifC3gN2rGIvVg24PabUYO}1aD9x z@gYcFw(&C)Fox(p>mrDxtU2&w8wSxs#jD(-RGbf(=JFb@*&NV z3;vE$3%2Q4z<+^PEr(P{5p-(mOG2@=!*o)Gh{dTSHWNuBrO|s7LLO5q3K|Xe*-BRc zymRE=GOTTGWGTrR3=$H_zCJOPVTI0IR^B=Yq|4fpPFwBP+gp|7V*?pc&DxI!k0F`CFvi%_V$;5gu4g09)EN7`7JO$*YT~9JQr$r(LkC8 z3OH?v$!Ipr1AMxh4&i+Y6lJVcmU?ud#G>b6eEvzYPuK9ko9zGB&hE*}U+Gl|#zQs~ zvq?t&k@lyfB%^n#ah53x--OsVLkjAdpli3$tkJ{K@pXDL7D>A3H)}Kx_DW+@f6WRT zQ1bZA&VS$7ex)hjC9h8GN9$t3n#bs(W801k!hNxKih5%&P#_r!yLEzi@JbC<>}x;T zwptm=Clr=zbj)NN?mo;0eTZY5PJEGOEL!LjAYD~JLsX=OK_j|;k{rV6kJMN52 z+s)p4P190Ja$sWVbXL#{2Z5Iyl3x(PAY+J?`j=V%L(-q*MFG-V_7UBU@cHsnss@k+ z#eXu@)iL(tOEletzWY9J=A-`HGkRl^2@4@v^tk#qw<{&bexOS*q{D<`t7ynwRv(tWQ<6ECJjgC=$dNpZ@&K#`QEPsMRz<;e*c0d~PkffKTW*dzS*KyJB$ zw^^RLi!*F(O}X6fJ?aYPp9s5vV5QKw-P#VDl^nKI(c$$ToA;|)Ht%rw8m8r<-hXn5 z?K1JTf@F@gZGTU-0D^wy2Lm65{@w}}Tx$?9;6Lbs!x#3}z>3na+QX@!gyt}+h@=IS zApK|xvh3q@)&BhlGW$SqD|OY{6Crh9*|nMO^1AmhSlzu2EU~=a9U`ciJB7Vi;>?|O zuk0LmzzS8=T8Q`K*%j#_%@NVT-$I*)pzb!-vCR9+^q)=;X~VGeNBD}OQ%z=-fu z1?*tY1qCeWU=T)`aapH-x8Sz*+{AeT(Yi<>uCJM^QSg@MY_rlmFK~~jJ`r5$tt@8- z^i8$UDh1cFVyPZpg~(jeY&962AQ<`7Vv$gbe}Du&v3gN`EqeH-Y-yt;_%vSeCH2~D zu4W;jSK3IH^89DLaPRJ37JsjME|pY{f|vhZ1|dkR8WTt~_k#yRkMfO*9?zRCP3-;N z9Zc}O-htJr;XQIC7VVQki7uOIgTQduHAi)`F10ptuGjCpu@;=--QQSbg9;DhO`wMb zS3b%>CbBAGCDv*%;%fm;K}Y+05R5{$1~dAm&3}IRc3;P;0Lk3$tAByeK;a;*bncCQ z&A5r5vw#AP;lHtVUNa|s*@|wcpDD4jc~CRr6Moj_-&lWG>_Y}BFXgQ?tP}z{wz$xN z?)=Jc>{Fgw-4@73QZ*KO>~qJn`1`DY(%_|7*B4>!%%9Qxzm5@c)2+oXm3mc-ruE)8 zS=s!$a<5VKz%HYL*ncLY($Mybv|Ik}+S@(mOH5# z)NCx5sliIuh2;>i_Y~w*T(iSA^VW=o{}i*917rd)Xpc;hDYV9V)>@ zHfGRxIkX$iLxnL%W2ixbu ziNfqDBMo&ESGp1uQYBA`9Tr+|stYSFWHraWBW@N~%UCzsn5=~SMdo93GN9@l+^)L$ zy)ZrMlD9t_rng~x)TEy836t$@(XIvO;}$Dj4_uJP>-h83S;-Cv_;f~j&~+w33=>Mhz#w)h=vP@DU8Q} zS2lVED6*!n^yuu3U9F-YAXKETgM=(SC_7ZoGk^HZUw`j}1q6QS9gK%sEU}D5mJ+kH zF3j7>R!LPW8G;X%4=Q`K3(ip8&r^@pxxz`fpQw-~t9#1B5S!69R$-@22gzkKNS7?Y z#}ovP!!h}pS^{}U;t-y$4^AT5+(x?!g_h30YT~$DZ>;JSN2}Gide`UY$FN_ka#ry2FN zBE6?U?$l&m+Hs74y2TkVX!}Asmv$N#UsK|A5m%YaM^)C5+)!1YLTN|H>H+A+jOk(N zW`Wghv$9pT{PinIagR*^Dfz*RM`ofpnE+Di_1a1U%4gHz*pQLFfRGi} z6DxbuI)HyAC9jB;xv@*3mllyvxvn^W{E6r~U3EHKr~MB+UfD#ChW?AO$T9Mgrh5AR z`{e19C$O8IFtyt{U^qUZdG>C>E2G2ltJh>L_Y~K(&r7AlNE_7zv?FBobG#Ml4k(;l z$D&p~6JuRd5?Pev{$&Oco%F?-hR9)3&~md2t_y!ouj=;k!Av=U~Wo7PsR{vemW$-AvOB^x(w2%c?Yat z*a=gVzp^G~x|dH}uL1;=INp7+zxVd#{t<<-1mty1g)W40N}lwdJb7yWQ%XY_xX{hX zE|Gr?uCF7TS*L)GKGVcFB9D$G;;4Lv=t3hmDzMw{_p@s-<;O;5^mFo0I>ejs&|a@s z#Xjk4Yo=_)`ntHK%b^KQr0fy}Ij=rCs?h1R+mQveT9htW+%B)19`q2C~#x=ev%EoPki`T zayFV}-%|<+2)^0TU9Mp%Du%vyE(JQ`8CqmQZQnJ)&!uVlJcbN`+upm@DQ5zmLAVlk zee9 z(m$}4P;odObny>&6$8NBhc)gLS420Du1JH~obq5`T818Hkg2mb$g*n)0bu)ahtU-B z>Gwbg-(yG#hSUR21flhhrxRQZ6qqoxmx3@IWtiaW3IYZ|9~;>xweiNVuWx^Q^9=#Z zzo*5+9x1hj2>Nh*ys!9#qPBdZzE2nH=k)te?u0)r2<{faJy{USKiomxN^qgq3g^#u zJ56jhxk)%VuHGT`6iQ49(6PH=`cUwGsCEqf71=Wkp}b*)rfs4FT|3;xf|V{^0 z9tJ|p$yO<`C+PC;vqEif%7#242o4Yu_Cx-huJe!~qTu90)MQZV0xEmjTY(O-7bpzw3$Z1V^dcROHz;%l zvdE8$zYl0O;c>-d#>9W>Cw^3ictuBCtEyOvh_6MtQGNrWoS@&echx;=rW>oX7A}DI zg^i8It5lYmB$x#$C_1)Bvnl;Cnh`!mzUhf9ygu_C6@e+&on1mM;;!crPP|U%la8F` z8dv5*=X}JwT*IrJm>GizT{kPPn$=0m*Vn;LV0}H=NRIbUD8PT3Ns6FK`}t^;;SIci zluze6DH!M+l&A*T|5UBB+2{k>Do{0;*(kr@EK}?2NZ}@9R1r((g6YJLv+1fD38qe4 zc1AG{GM9zB;YpKqMjtLaTKQ0r-e#cFQCOr1jL@uh5RIIIsj!)*0ADhRl&VGfu231Y zzCO(;VB2tXs}O&MPqkQ&H7S%&4E0>_KA4!%3{=|{EL5jav7*|tV(Tg$j*?<{H5-{$ zG{S>19bq@=Z8sUElM8xsq?~GJkl9$xk@6mc$;_FOa!is$3-1cjY^Z61kOZX`KHyi4 zEuD>~FgBWs9w7`6Hqbd3LOp$AsEHxzoLwFad>ajdz(;>aMce&+JdkvS`M$tcN)2_A zUF9D$)0GM7qn3aZjWoPIK`60YjpRdi>(dA1(Q=8Zr+UA`o#1^x!r|PZ(yn%ZiOz3^ z+sT>W=9s3A&o>Ry|Gc-f0HhY?fANAuv6Z?U#eaFrw zgaMtEt>Aw|zM=Vct0vq_I>(vX$!7}hYcY1TDcKQc6)W*0hmXv`k{aeX3=afoi1cO( z6X~XH7{D>+R}vt`Mco!WHQN8Cic8Q@z*O&7ISkwklaK*L&tAQ1ZJF zl(Gr?j~NW@XnL6KsCxu%VAJ+l(sp}ceSSV=rmQ#x2OA@1F;JADnX*S4J#9?M{z^U* zqDnMox85WxARa6xW8|ctiV+u?$rcEK&LGC1>UpG}uTiK0v+A*8f|K+XsL+-WiR$qi zUN?X6aO5C%OVSM6%=Rb1L568FWR{qO2L%QtQq6KMEZ#f9yv`fna}NqIeVa?UlDep= z-E@l7@X?`g`>`|}+Q=lAX!rSItykTd+0XNLrv2s>khgaop((FQ|y(SL+7|*8xRRH zSv58MZz0A2K=}E(f>d$0yMl#eVTC#l2e)c#l5nn$LOY^2>P85(>`$4y8Dxqvkm~p#xKi^OodG{7Fymm~bd(5sy=!HaW?3-W2sH#_H5+dCx$MgsvH;p#GMDLlL`9UosHHJa>%wt{5{JV0)9Z&jlO^3g5a^@4 zeBdEiL9Ny=roqhv8A$BnVJUz2+r8uK7R(cu_t;lEO?!)So+u3-jB65uZ_N2c(P;_BxN<$*7M94Bs} z>)#*s^^YAPmVNuWk;8<4EX#RZyRgEGZr@JK)AN5e@LGv_dQLXNXGGZ3 z6>0Q--In5>o@q$llV$Y+pB|RjNLu34fcz7q5qg&@^Erc@OX$-O>ZLwCYs<{__G~2g z%_}l+w{yu1?&O*X4Q5VNh-PZ|qe~4gsB;GgDJgM3K0IdOjLnTuyg$CDP}k@N9+~R070*{dAQ>v@M7>XB{{6iB3l>)q^XGDWFg1Xcx^1 zh-tS33J`MX>&s!`e8z~9COFut2@QsLHs7g*7C*3!aoT_Q*MBj?-cCM)cgik|o?Rzb zbm2-DikRzxFCqShV0-2VjLrT!7!R^f`sbZSQTezPXKj~65+KUdeV%ogUTRsM5~}*< zF5Bf>8~%w3D>!R#PwGw1S1de~+%O_$DCOJ+?&{5o^de)XqpkhD*>ECiwa;(Rrs!mNm5CB`HW4i~L5fd?ES)dC_uzl|j}&UY`ArlFDxR3)&V$&5CJ<$E z*A*3=I4eC%_s(Z$KZb+&$&~YuwI1WFzL^XaaZhhM$!1-&w&%z-)$t@`+ZDUr&cLA4>R)R^J{OiRYaajr;FeX&$Y&__N=dcZMj@^RZdyMC~olL1zeNcZV!AH6Gv0kXHZe=5>Z&-(O5-BID);J$z zesd}@y++{L^fI=B<~05n3^yw(dpiPP_TK9n6q$NBF4V=nBbZay!!*=h29ZwfuAi4e z|29b>+DMPUtRDXSpZmKf%O*uC?NkwAuZ$rbrPfq&i4W(>|bW?wi zt%z|<^xbDcJQ%3+8LrvvLELo@0kKpI@Q*TvX~6z=)+nyt<}o&J+GFk$i90vqIk5l{ zFj#N>>7|o}K{J4<<1WvY4RheXoC@&4vJ?8tc&j_Pel(l7NyB_1)Z=(KvV}rcF`UHf z(8NSnJx8YH4+D9R9ECqAF$OUL&9Z+aO5}X~Zt}6ZJIZbXv#IRK6bc4u5GKn5*sEux z8^lO8Sh$2TZ~RM!W)nl1c8p{?8JIvJ8!B#ZOLF$83EA(2cY|cV49-?PQ@5fI;xog9o);%J8=^xR0&vj-bL6J zd3V2~43t41IebWZg`8i+O9_9Avx}f@bv(N|gN&2VRS0WX&~e8vW?qv1cJu2YkKccB zz-8ALL(qG4&hcqQ=+ICrm5IxLI;K2>P^=2pnzC+F0wiHHlVt^2dBUo)R`7ImYoTm{ z=$XMc3VtFm{h1A^zD|r9G8T_hx8uhMdsOsHQvpcxc{QHb`3+(VJJ)~c<0DUu#n)kR zkL~imP#FO_yo?<%2EW0GEHqlq<58N6J2V1Ugksh-R|Y|J zl&KdSrQ_nd>rO|;L-v1lozCx=+Ef1Yq?ABN_vifRrC<8ALfOVlTApT%`hxd1nc+8Z z+x0U0T1B)QkQtAFgW_P^AI%1te|2|6ZvT#5zFap=mX41(p@++dz~FoFfcj-{XwuqS zaUHUY3Rx(SS+pJ2u@$9WLp66N!GvhXJo2koVW3&OIA?93FZX&%CWJUq#3iqEfQsQ71Z z$59FqxCz(7WTSPJywz=z@W9N4!m$kp3f}PiR_i~7)%diBlW!RwRv zf8F2RylZ%Jqa1(kH%{=wU$g#ZvJ-;s^nQa85~q2Rsk36Hr_mqfg&3!`9_UqE1Mui{ zTLyKk@UZim5Wx>KY9MCJ9Wi`Y_v(GOLZ1W7>O45uFAZo4Bz}LhS;B;(jq^q?4wUk$YdHT= z8m_C&s-JFX!urpcZmWC_JN@`_v!cv5>Rtv!OvWl?;4rfK)SaZsuo{+Waf+^6+tPNnG zznp3g|BZjOEH!j4_A9z6h!_UUoy>-)=+qQw`v`0?EcW3x+tD(NZlIBGo@lmh85x1myO-%w-H94CZ)6PHQ8*F??iu}4{4jr- zKj`1ivA3}y4!*WY-kZF!x5$KRw^>bM_pR1hyM2G{o{?C$jX--awu%eUqafb6z^gjv z4?17X67MiN@_VDi($RdmOUcc?r{2z_rtvsJe~+R;AYRb!qwC zy{mICGr1B1>@b=1V2JZmk?5O}d29t@Gckmq9=KbC5SQyO((Sg@A znSJP`;#+R}-)e4qXFbXp={fI|W6}Q1pb{tS=c6yZVFf>No_c>(DxS8gP7HH8jDvs2 zgBNsWhAQ+xcH%1TU#CgBw?wov7D?o!=SfQ3Qr*P|42vzOov27h3%kibOsv*{N}434 zXdcn=gwj$ zz>*djt-m%J31+pPk4kDYTQ7@BIJ|#R9TTx;I(b!-P?!HW2rRhSvbn)&yJ` z>gD>VvceJ9T`vMpo|0~2qgipOQ)-c$ukc*W6{(M{ZQLloz%@YIvO#t>yFf2YC3zM3 zxTsez851|M{oUc{Hr7*`;sY=zWy_%OqaqBu<4ifCV*vSUWyO8{t1_E#Zxeqm9QjM3 zK`1iRua$1HB1Bd39&l(a48bhSF`g&_rp2OEUXNk&7(r2b7<1yDfQvC#-SB;VT`x=v zBj)dmrM|;2VaGlP(K~CGKN}}p_p|oLb-q$ae2$tVO729DuQGZp3-tQgO1JLYnSLef z>I!H6^O$)EGgZdfY&t=cDzAUm>;1PUM>{XM;$3K-W+0fhQGLLP^>p&i-p;|%o0mJU zZD%$$WV!N-{g(&3hi^{yk4$w)v4GBxUR7A0*LxHp*eG-^hu7tr$1e`v*eV!-1+u1= z>zy3E-934GwC_yUIBv5kYGod#kneUVht0h?Z|mF}A>$LVF{<6S2?&3?Xtng;ro)tS zLt%X|iUinVI1^7Nn>dzLsw485 z-OJ*a^QCpleYy?_Ua3z7<<@0FDAhq)D1CLvL6ibTc$EcFga_!+g;u?Lm3yB0y_O23 zfiOl6;1Cp!pBH@!yLnY_d@~$pHA93#@n~i`^|UYkS)#|{PU3$|)UGamE(HuQm1VpD zTeW1G&w-fyLo7cR2N$8@O#6+w0xM70~9N?HLCZAp6>s@)S+JU9Gu1sxrONmM75 zcDjLwFun6EKo@_A{3!vt)=hiDx+!ScfKSbz?@+7EKSYmlW(LxcgNhQHtr8(N zM^FzFo9HG&Yz`3lvqWqxNh7WC-;&g1S3y!U6;5??q%?p2yN1xJ*_JijX8AhGz4T=b z2yJ?q7g_RQoZpP~d+6ToadMMSMuYY|OhXn#2HB$EOHFYvTFJ8Ys9 zP47uB@w$JJ(M+;BZQ#ARx1}(nO#o4X?}=)}HG$N8e=2cwT{x)@9cJpq+nh(Hg3A26n4!q>~qhClD)xqSi3w zYcM)YBLx`(_sJm%lUYB7$i78>mB9lpb#AA3CTwyqz-5vZ{}4wG3}RAO#V_msuHlZW zG9C*kd`~X*ZI3j(LKCBFl4AVC7KZQn5v#vuc7}v-b26(--BZ`T%xqiT$!XBA>78hm zG;4pbo#@*tc?#5;Oc{fLM&RrG8iUQcjbOK>_FHYI2xQJAj^5_Qvgo@xWm8SX?>-I{ zypP$WNFO(aSC}HlNqos`yv!6)tr9^sKJJ(Q_{oW>{nW*EkDn|9Id7lsPAZY6tK#8P zc*i;Bc+Ha&Jh*<^7@;4gpXOR&7~lOvN)GsT(*Fsa@@0loRD$2tNt z-Fv1*eqq{(EfTSBJ04xVO3{pe%j$am?!-hj#|Kh;e|At*A;)tMDA_FHN$i+t`bKo( ztj0gOim40uK4*WRt$MGi!@q6QIk&0I8vkvJiY*#L%x`sj-d~chaUqA*D_AA3lwwVjF zzO?o|--l!0)wdSRluUwjsjc}O;Km9E5U*xY49Z<)nER| z?wrG|F&Go&p;_(i?fBrmvoefo=jbejiw-Sw`5O#50Nv9f`$Q3XcP|lzQL~q5BJfOJ zMG>KAbB`hzN`N4AdfE^@l%Lx*Iml7fP(uVrm>6P}%d}v|9VZ2g;CrBivdw=%kUNy0 z%gfAx9)q8K@yc9W-x`^Ddo z?ZHKt&fsMEE9bqBn{)caIj4VhTNygIRO$w6RB{3O>4jWDprDZ_2ttTCf;Zxey^uR=F=kJNwRHRLQ-rtmS&n_-(_EGa~#VV8U~ovH82& zd(G&jH%>F9#dnU|AQ69PAXi1t}s%2Nt;JEEAyyuQ2Q?Q%Mtk72zlopbYCL zqgUUpTJn;qrUBPU#?95%z%xdA#UNL3oMP}x*e9lPzw2QHJz^{!SbrEigF~wcV5r)p{`}2dT#N55O!Bq0iZ%#1x=mdYmwDdqeFx8ax04^|1 z2<@8#%=`{8H4IDN4=`#HUL7KYM%N1K{zAW-I>A(u8Qpn3{};Tjw|0JMKuy?NhdYhY z5S50>M_P+@2|EZgqt<=Az_9o#DV)+{`9HD6ybGL|B!z-QQ29DT5N?=^Cyst`7sB@ z9PjV!;lj+UHnD}Gvwn4;gHB*U;JbJtsY-Cz%+qhNkuJy0@1NDZFSz3u^nJ2=^s?&{ zl?C;aU(&VDPw-;jFYD@WC#+Ws`SrO9C+A7D3G6mreOAla0P=6%r|+?lb)7%oz9H-I zGJ?J?xPpIOcQZ62iYT;7>vhCsdlOlFbM)ioYEX^5|H9J9@=YAEA7S8#TKqX;-Ullntc36mS^~dF}@MWp&x8Uq&pGM%XdT#|=%P|HsW`!sbQncas1tvJz7_&QysXFzIl2MMhSfrJk5b={`Ag#JsU zXKsIYx`Co0%17bSj5|>2}^1L>^5Ulyp-pD-3cn4XSYZxgWHySf5J z6>JRP&OR7NIf8wOP%E(RS6w1sL8$FGi)m8N zTkM#-{s$l%;5!x?a2{Lkrz5l#*%I4rS>{(h(oVe(hK4HZ7)5#`kiGCE17F*LESi6e zm21v1;^NfB?mGv)VOA?ypXk8ZNxSK&KO1Rz{VM%5oLwb5o4+{QNi)GlwxZ>oWcm=J zP+eGBV8)exax3V>dV2e?LF9dJAlXU`rFI*bjb|GDIWQ-=4MQ__KZF<7C}hh3948FF zl<*xq|Aw@}bc`jJDdc6HplZ{5uj_w!4|ah>D#44?UcZ2JST4@NM$kS^iI*kGqDl0` zv(ie7I2fbJ1(lKaL_I|~Wnv6G;d$2-=es3O{OlxrY{S-lqJ;X#*|^9jC+gzZhA`ok z3mX=O?Cgjob=isx-uCy*meDER<1z)KceJy&bJX?G<4T1K!7e2N)bwmy+AV)2dXkfK zRzv(SB4~se+Nwc;BEAjqg5Fn`@Usk`F|&HZnMwk7pF#y8M~GIxY)0e{C|)jc(u-*` zfkQW_6*r*L&QyuHXR~Q$Xa`!VvFv3n6|oQAXF zp`n+*)vbD|g>MeBytl>s$j0f=)x9d6%OO?fRXbNGp1p7?QuX{*2Ht;ZMJe|ccjwX5 zNTaAb&8XySiMi9ztL_!u?OQ$7lq2mRi}8Z&*l&FIMrOZQXdU9AUofhUzMFq-0d)f7 z_M<)cxUGrx;I5|g6C*r0;LN$$I&Kbv`BEWsG}idmz@ApOk3M8hWZc0OZyYVB(SkP$ zkK?=6h^6LtC~ujiQ%rxw^7`)t?#e=`v#R!iOF(v)$b6Pqwe3O(j(jfkO;gMzdgap%S1lziJp$s2R7a4I%{9;xUA5X&L3V459~#nqj0n9kq=eR5Do^x#lZ6NTGTo&MEZY-tpiqpWw5YZv=S!U zDMVot01`bc8)#}>NXhKhOthIX-Urlw$5|hqXo36ZNd|@y_WH21r>b42XG3*L-}Z!Z z%T8~F)T!tMzmfB<}Rfv*@!Ec6u>Q}8Q&PkG?*SJdsd z09apj04yf6cnGXM6W7B8!Q$bBeXiv0sSbq2VCD;j#oH+u28#d(17W#E_Ynw-dT1C5 ziz^}RDh(wqVcS9E$Zf82|{+^ zg@lTdMX5L(7OI9g%9TWDs3?iZVjCVRNGOqER)&bOwR@5@8W9GOhK?7-+GZzW6n_iH zjH=-bb~t~|%7zK&#KexG@BVw2*bG|F>rU4$HhnKek+Rig94S$`Hj}EPH0CSY9%u9^cbIMCPqmHD(?DBPyHMYv^ehskC$jGr3}_-1edUlQx-_Ci zT1J*~>xhEH{ZAgV=CLjEvU1VSzskN1AY-V#y(l#-*#lZ|jpEV7PQT?_|8Y;k`j6F$ zbJ>6YwPn~1?fp5o)SQq;WWms;&LF0%ANK%KLgFW4NkV`c)qvL)LSQ zDr5ngq&~g?8TYkI>tfY>+n!%+dw$iiYwmZ4KF%mMO{qSNqgV8E4yq{|(eo>qXGU+o z+P9}y`X~q*)9WTB^C6z6Ud6lfPW6eD~!|KvkWZ+lOQ1{(O>O zQSFmV$u)`0t>k{F5AJ9_HYi~BTePwRuLEzJ9(2HQ1egqzY8ki;eYPL=-o@%@(tq6X$g%9{u%NF)#M^;-YvS)v_IL=jjjdO} z@1fONL3=x1--x=!8tKQWTAmSjS1BE90SAhuxls7$83QbzW>^ql7J^qqPcq|~;q}su zIbZz$?7jVW8#l5b`t!VV-v7{%-(G)H3Psz}WbUl)=x$_5jX)IlF{tmm}&rp0#GPa6{-rg@fwX08>rIMM|1c?tUZfj z;T%hzLr;xdyaxCs4HPv0CcqA2z4`GTDmz+ zryMNe;1L`XOMbY9kb|2OMnulCSTF;eJ@hwgaV?;Rb!c0KK<=`dCo zJni4^?D@X)K|SF<-_j*sFJFJXPkgG^tkx->EljSHm(+1}C-J+>u(a9VUDvk`@AeXj z(q=WgylWz4pLfx6Tf&d}y?bSM;rPy7wDf%E+Ld;E=fCScpTpS>T;F+uTDUrEVC{UJ z_2k(ur2D(mtMC6V3nLEjZri8_yi-NHz&js(A9yZgCwNiS^@0ykt8ssW7ZtmD=6pwZ z*~>LN;SDtyNBC>ggEl}hMvrto>gldY#OcLzh_5Gw9YJ%?6WQHtvt&+T2`c>OW%9KVoxSd^mf@jfwG2YG#z)zAar%vCyxH*moy zs37o?7Y(GFysW;^Pu_nVgLmgBzi4*v#8ckwv}W=jeC6-VSKiXq7jl+gNM-H4<+XUL z9p;tv+T-%(B;fg;zOS^Q(PyaKe!g& zwrk;iT~`T zXhzc!19Ml~`)+@gEEgK9S+XBu#m(LDSY24Y4%T|f>Za_q0ep=u5_-(&A$u+};dt>8&*47Tj)1sWEkbb3@ zWm{^zuZzm$i`NG+Yj8?tt};5AKcGmJzEkov%2=-tj{99)?K1mNlqGYn5w%&Mb2Kmv@NRXOoL(m1m7q*^l^|Fom&F_(SplB~ZDik1;kIP&QfYDdph}b& zJs($MnjSq$p7qo!+v$d3Q987uwDorIPE^#&RizXIYv@@QjT40TZrOIpT4Y6gZ?X?p zoz;IWv6Q{$+Q1T(NVn;=GQApUTkIp4O%T; zKEW$%{h|Q$AZI|%5mF>92-1(jhjxzAFZ6wIMXIFI1xJ^#EyJD&JH;L^3bl#Qo&cmU zb}+=CeS@1A5DMvRsMWC3kC-157ew(C#cqG!^OE3*#@yAnpXKzbljrYO;5rw4{xSaf zC-qr@ucokJ-e;rBo zMR>St=9Sj);t1~yhcSfJ=NQ83Ga!sOfC4=nf_bKzpQ>$?4nUQrS6h)tX97uoSYm%H zz_KhW0L7Lsmkq``oIt=61gf1Zs)C6~QB^+GY+l2#GXqHmKo5TbwvLY}#LCHCY^503 zR}gp3O4cUW@BZFNo;`hxQ1|>eaANt>gdENdXuVI#Cv~80oXK2F!Csm<@lRP7U4G zv_Hy*4b4ma9#~E5!xDCG1Aqc6G{X4dBA+#Z_yFaN5WXw3Y|;SX9LhBWXSLL^^8%=% zX|=F|hK@9Jt<^#>VzwiY8fF_?|Cj;&H2}E83XL$F*^Yo{oNa{Q%ytBVm~CgS6?@sI zP1`iv`+9fk9(bdRKJ-h)JnMgOm@Aojb{SpaKdvSR5e^Y{d>|OXbHxXA|CYa46zu5m z$3{dJ--yWKf&6?;k$jq_I`{B#=5WQS-s)Q#v3dfcX>y_%>;o5zXV8E zv_NW(V{@y*v+Uv;GW14VhQ`kdbiCll<)rA1xD<__<9)Dr{p-om8*w=rKgUsK0s9_F z(i?XwNpA>Adc&8bf9!uG8&98FQF=p)(wZ@|tz+lyL9+XD=goKf$;*S|{^8M&YikW` z-`xmGe$L7fUV1UDjEeJQSPbUlY{I@B&;b=zz2xmEg9`=4X@+Dv8K~p=Y_7H}Un&|2 zc2dk}o>ZlIl?|PlSOdg?o5KTt=tTmjygG^1YF6YyaaxRql+J(f%owJi;R*RD4o~VD z=CgE^5B#S1pO;G9KE0&nHp3N&Z9IMU@9R%D)&Jj0tf_5tYi-R)4V}dNJkWAMvZ{uG zhf^)bI^h1a{`Al5&o+E;eI-9v z6sEbC(_yEWB07KCHUWD2r!94QC`N9yLEKyLqbsiA_}fOJ7`qy^5?{|Rks7p=FaXI5 zj%n3*CgmPn>!WNsQY)zUYjT2+Pe{2W+a~;8TT_4WeW->>Hct>tphnMvg3Es6t#9&1 zPzSvhJ#UDfHx~B1apRtk)EpM$$jGVS4#wYe*o>8SchG-yS#2fmBE1*^`S~RIx8!YEoGXb&t^2tzIAd*itQgF&65zMvVmKc$S?Qa5U@X1`j%wp3w?*_( z35AMv=vQJtV)Ktu@|0$~Sd&X^a=A>CPuS!WH%TWAO}?LjQ5ak=;aBzO`1Rpm_jeCp z9UhTlzYBj#UQ>e?uXc{O0qRRZ1F3bO#RF$koxAj0y#Hl!Q?FgSMmuCv_difWC*I3k zA^gApk67kdtd}jOP_Ne@r49H_2)~5mHIY)!ca9DL2>iPuLcY9ln)?0pU3PeO_BJh1 zW>+oUFDv?W7bQZdKM?}fqQ8lT;~nwbn6A)|fuMixsIwKIz^6p_4Ih{6?kc)_HtKiJ zhFsFULDe(rgFYB)_LAf4)YP0#UVmRX%^kY%)mcFv8_ndgv0_8gh6Th=NhmZgb~O?v zl{ixgw~!nu5*3kD1aK>;K1h%5e5!M*N?PzJAvR9B_gXkiu~DfXpFShhxg1(>0%+B| zN8W!XHgu5nlE9|zx>t!}-&LnI^|D%v@(XL|_z%Y)LfM<5G+Aq#phpgkCg|T!KcwZb zY5~|iTH2z01c$Qg^A+7wzH^c-p^(9k-Q=>XdUyPe8UhY&;G*?l>Om0nNAnll;a{4+ zKB>PxHTh>RrVCOEQO*o>{$;5Jq2Pb|G{b)z(js?*saznm%jva6YHq+B);rH;^nqII z>Mo3J?{B4rK6Pt3Rg$mW`qJrAJKP}&pcwm6V%()~m%gXw22sHxPs}!}NyL2{%ehIQ z`p$;At>xV85YSco_MJa>n=p6y?Oha3%e5h1Jd_(#ggcZ_R$6{2KX#H!F%%v<>Bb#OD4i`ol%G0@I1;Qx)RdBe#k@PGF*duiNnU4JWm~z+d6pS98N7X3kFT2vSq%TqX}NLupKdbz2+i}4uN!HfPk=Nj(fof&r#HW4 zl1`GtIfTv~rNew)IZ;`o%jC^rU(`I9Ol-~T&s|(+Lo^8X;>w7LtXwOuz<+AbC1VfX zIG7N$j>MxY`|$+O&-E3rT-8Jo2xdmwNY)Z1dtLOVx&B29LWd(~%1|KE5kxXO6%>I7 z4e~Qn`#+Os!VBEO*htp(U&epM+VAW2d^F3aqdY4as2mEAgZ%q1+j7`JpB5IT_K;QA z-MIv-J)`-xyP7i3Zv?7@xz;#1y|g?m+3-J;rw;DADh7~$)RR9b;5950%u_? zKCXpBujz6u3BWgV`pw_z!p2P&i#h#fyXB2B60!4^KR5`O+`T^rY+bhMIt*(yg3qR5 z5EdJ)RkO`N6rhS7V-TC|7{u?Djur}D0fShAg8V)2nZW{TY6V*Cd2h{zdC5^Z?=!e1 zV5?T?t|N=2*AaR}SwDX*&$F3zWdJdFhTlTSwOe*iBWyqfLkMhhHEZO6>ICS8Mkuca zAFxVJ369%H%^#hbKSIq<@tQw5HTlFA8}2)Bu~J$=smBdI#~?9G%!Vm5we830`6Qd= zgJc4iE4N|2*)@G0aewH_7Q>`|+VQFz%-L2uj6k~!^Q zmD~?(6Iox6fC+y-S?Rlpc9p^qwET;sHMHwXDt&OLKAq2C7HR&#n@(-yr@)do)>MrS z2U|j()Vv&w-28!>je5P*S$jA3R8M^R8J~gVG@E_MDEUdqukkRy2Zw_>wapKrK7Taz zw70RRo7S8K5#|)(-ael?F6QMRt7T?9mQK=f`Nq0v2-JU?{2xUaU%RzFxwSrVE%@)# z-ZyoNy!o%Z$eX{pMZR=vUEY`!-ePVgbXt6#rI?253CQ;6`bqO;4Vvi3DtUg2j;+M6 znBRo0qD$zWD&XwC6N?rD^I|1nr|5E`EFEw(DMU=lMezaR%A|k!`Co)Sm2?W(zZ6-%KGqbe{#Nr0 z|5dB7xmdjQxQnBu5UtFLO82ILbWOKW;}5K$S)Z92#*K6Zoj+IttY^qBQ7znH+{xoa zN$p6p&Sr76tEc`JYi{dqv=>=6bKnUMkckw-j!3VFfCtaPVfR6W_NcUZHJ%^gI>e90 zK=Oa~&bv1Hw8_=uGU5ptVRO+S1iLWL=FJ*S^)+2Lq1yQT5i zUGKYpLGvKS2=WNGE?!VfkvT2^k0CpqffLU3NK!Aeusnbc+3cg?$Dy=S3~rYkb(d1UOb7C zpWGWL?>-SZ0D)8D(Vw4M{Z|O&e@A7RXKPK|wpn53YlVVn@l&J)-m^I^eyY1{Vb6cu z{@=Uqv7+Fv`q>RB6F7$6Dn%V#zA@W|;{~}Cr;kLG=zsgpX|2B0!S_Q;OlyH#TL2tN zv15~^N+4-T0@a;4p%ez zd1aPX?^YmHI(_&||G@u#Ude}8va(x@Pm4ZO?8;&!rAjo0{|Nw83p(&CMJj(@bwSa| z;Ku6Awz74+a@{voh%|I(jqPZha}KjAV0oy7R2y-nLd{42OtG7Yw$OwO+OQWQkwnylQ zq|Qa?{9x05Esqd3ZjTaHZ-dmKNFKL5R@AyZT2#LcUe8Smo8_^Bw(W6(+8#!jQ{a|2 zbwkR6m<{1mKAz7ml8$!8T21sPR5AItPUnjm;>>M6<)Im7XX#ui8oI7h%AVE;RESVV z;MFy~1%i~RQ^!v7bA^8{L-d(StOra;q{mZ|9oJur>uja_GqbdJYo;{vl~=z+MHSUXNu^Myul11fTbqrdgrH3!g&*1k3gFXG39@} zbQT-Z*e5*T2Tp#4*f2I)nuYIG+Js1UgC^*1K)fJWNraq!F+YDZk9O(o!RFmAeK?Ww|je;2riRVZ;v4?KU5(IB#S2@*a(skw8<&4U@Kier^H4rtJ(N1 z=9Fl&04G4$zr0P^CTbRc`ZkuiO|FT>%Y4mT6YZAS%QdldDi$aAB>9c&^c0u+w~4G+ zf@*&4C2pHuVWY*eUL(E2#;vATxF(W|(kpD#q*wT~(CdZ~#N&xAQa44GU~vca=bPz~hMMF0t`agVKa}el1FFsK5EF4%Fr7`sQgNeg#VZ z66qd(1*#9zILJH(tFo2XsHpjri$463D?&B z>Ioy-%?(Mzg}D@QScS5VB$lBDr!EGT#A(2-cX{h8?+CbmlY*SdA->?oB8cbByg#1K zE_IMYlWglbmunyJ$Jg~ERXS}syC^2$WpOh{()K6mJ#-}9Xmljqs9BO5JCgnw`n27> zBk7N=97%tyb0pocj-(1yU}+`_6z`9|-akIx`EH+|d4YE_>G=iTHEU}S+`T@_#}HRD z%g^!wx)Q*Dd!s;cYir}IsxT|Pq6I~umx=&X*>GM`9I<_klol8N^VQGKQ%t;YFjh@} z>GXR4cw1(bl1>QtKNzcj?B(gR-v564-KuTtym#@4o-}<;I6UHJi(fRw|8T41__CU1 zW58eWUl@We!7=TQa(FgFJoa3Hpt3U>^&}LUH4Ha@(vNj&Xj8RoN642z02~){a+9gD zL7|YNor}h36xCEKc#}LWJ@8<9=2vy#>Q=vT`{5!V;9W~T8RFXT3ofPtFC;IMSOoo6 zsG$ITyO499=x})Qsibh)@o}V{c%kKk7Cm(rY@o}{o2L-Yu2CaV6jHEhc@!RI19vG+ zVqJWH6BXWI;%auEqL}#o0?Jueigi>{C-J-7`6gJugnF_;Vr;c z?fFeXcif7o-x5z~XsNc6X=s;jla~%QNp7-#w=7&gez^w;si|qwLF!CxC7?g>J6zr3 zA^0Piwi(I6S-AL1t)T#5|D&JK;CAvE0EdB7W%Qcm@AReaX$IXG6Yu}Q$-B=*D_w=b zAGlxfVE5Cm{fuv)KegHU9WOXMwp|9$b6;_)d-d!L~z8MK#yd}ks*Odi1c;g zhCIHOT-nq3xomGfsQil%2U7kC8cSiIsMN?XbESKsd^7b!e&IQc5sPn9f1D*zLU zg&*J*!*8C_wO{p>>QkZD4JJKVm0iP1M!MFEY`O>Dr8r84FWn{6OzkqIXr5++6quQk zb{?yOcWTQZ_d}^%eN@P)y)&PG5W0(SPnW5Msy(HuL?!ZfhpK%*Jq5~fMY2ze^~M+^$%nS0bAvAeVMR} zbxu&A1X{n2HO78W47iv)o+0~7V4{Je&hptP>-BnnPR|s(Ut`I_4&lWT0Ts1Az>irV zOE#?j=4hZ$_U&iTI`zYPe2_XbrNOxqrj`HV%x7l`^WuG0eo(xBLJK!_m7fuC`a`ci z^{Pnh`6bBG9X-SNomuE@Gih@kI?Q@YOTQfl#k*kQ*Fb=s#lP&Jf2A?r^-6!S6^#sM z2`a0v8XWt6P3MrdpXkKozu};ypJ1^SJsj+rv@WUv%LY!lnyuQF(1dO#I`LDAvlhuy zWa}IbKXvf_e=FU8m6cWRPdp5hC+h#arsf30PQ5^cVm<@VFNzXT>$oH6-V$!zdW7(R z`oprv7N1v39Vyu6(RW9O-@oM}M4l&gCrE^dc%}@U=^HQpL>-~YgT**SO;lNvicEF~I$q{d3nPAWik&clL-iA6OE z8xdUUlc=J;CWd&6H@m|buqd>6XxM#v5inXMlLN+@i3O<2|itd z)TzMKKUTeeIvVU5&&_-W*5BEM)oJOTa&E7w;kta7ma1e>=2O($vdOust9FAsuB+bN z9y0fSqb*$Vd$a|Y!S()3a=1Acjw7c2*f#DKaTFoHg!LikGg}GonGRDozI&_3SPLE} zG9>q_UH|_0x5oFHXz$=GSRFfFN1cjLA}Uk1I%?;C=C5r^@*~XB;fWN$T%du}FP^=? zyE_5Q>*q_{M1?nh$cW(95BaC+cY#p#E1>WzPKk$rMK7`kxWrKSs(lT*4M%TLQ|lD< zF7J!Uc@ZyKme}JHL2N+m`z1GbYiYAhRXuI?Rw!30#mGWm3<*$NW-jD6Fa2F|2R?2u`H{Ii(nRn3=)JS({wh=N=1z+ zI4mF|K0C_?XqW7ObH=)A2n>2PnU=*MLm=q+SCsG0R1@jDw$MKkuvz3Axs#|`=nnX5-1V1E*DN1S1lEVYFR8`E@ukZ&QufYw+Kq6SmEO#2Ug4ylj%j4MOw zyh?R23N~C@<8BXTJ`{>h%Ne2selh%==huKv%K3=!#!#V&54^si zTDai|J%GEm5#6E{m8>bL9f%D{6}Me~HJ?t45`4f^2N!u(rlq1oFxY!oAQ`_PGCoE` zUK7Pa=Li@t`|_%b7wxbuQNmJFs%UxzX2*rH zyB+G3*}BprVBNus?IL~8c`nrVeDWTBUeur!8D}F{ZzbR2E=kYR9LNgkn-~j!G=!BL zWgji$a9G6;%s`U;k15ovXBQhDyMT#LPmA~96_%c(8`}j;QktkgrZnkgHWi?F(nESf z?`Abz$Vq5-*7=;DR(LrdD>9lDAC#;LfB&Smb(f`rAsk~DIXPfwO2jQLlaG84MJzHM z4A5zsUGe54?R@;{7M!G%$}7 z@-@2vlud(SHUrOPjTLd6odZNqO_3s4fP!r*(@5hDlfxqe254bmeSMUhX0(X_dOkTz zC$sXBre-=)Geb;K4SIZu+;DOnOX-0%fKFgWi!TH9av>C z;(@y~p$@~t!{dF`S31t%d{QJihw}>ENGrwFN2Hh7T`LbuJSz1;$y-rt*2L}3)rpQm z8g`T8wyc1nAg{`pBsYDqGVw#6MVLojdJ36h)#lJU^ZBLSf(pAiBrE%*@9*Py8Vm^y z<`8c8K${A=f6nv4J2*stF+Z)h57*X?9I9JCO@ZzDEQPyMbBUbCam;Y1n;4fmBq7C2 zTIFVxjPM9t_Ni?xg0Arb`C&>GUqNuEKsVCMX@6s z@3)`va_|b~;R!nOe*fm6e}&Gb73>{LfxO;qvU{XDI@vvZ(?2?V^=jw&D;+G-i(}m1 zIsWT1IAr(ELD7TwkqmWzxWy^Du4QyG^3cG>f)W^iBp0Boq*ING*(x$4y)sE>cPII8rKYW# zH3D=S2K@Hua91fI&Y1B(dl?Wup5Vafcza>iy|)LPo+%%Y6ZzS^M9MtP;Dl6=O76_WUBIL1>A*#rx77NP?7A-Qxo&l{c3^-#^O)%Ue5U~-d2p7ih`zR;&NinGErMk_C zRA=do{U(6E_zQ?|qYLi`6I2wUU5jbJ%A!&G+fwb!Pu)Yb1WmZ-i@isZdogdrh7?1W;i6)M=;NG3^E zmPKhoJ<2q%nB{6=42!B+F*WN;k=3=cCa1j2Xr1BFVZ3{*ocRz6Ig+m@7Ik^T_?cU6 zS-(`<>Z6$uji=5imXi&Rulm4RGFK;Ts`Xuek!6Fc=&`e5;nc{>T{V9cHDhTNtaBoA zx^Kb2kSHpYs0FMDprH)rYVyX(s5pnmj|#hG?F{n<;Z-a!&TX#7LKc)XIJ0QVeM-jJ zc4nDQMrVp~SR{1BW*kR>!C5|{8c8;m7nKQ&0(H2{(x;0PA^?K0JQhMq(MT&1rC`K= zEz__8sK8Dpq$ZH8Jo67wF-1ESi5xg~RGho44I87y+S)5snwHF3EMAsbyp+dZq#CW* zsahvZ(c}Ch$zWr3x?()xSSjTfq>9xZSg@cZ_PFxO1;!jy*;JvS`W{@Qs5$b;U@ow4 zVbvKILr{~{*$$=o1e8vplHW4N)N#pwFfN`NGJG>6E1KH?oa% z^dY^hjKFQOLIhS*y;;<#4CgXz+M5Sg4&-fv8`t5#h~#+;bM9mE>cl`aIusKO8F=pCmlr{mGurWI2lK+F~?ly_nb-v0Uu!9bsj zyowX%hh8}^r*~j_+>&`TiGV#NhT7S<>a1#zCI;3JmA^7NV+lY>+%6o8v46nLP4K0Q z$#FJ22CW=<3j`B8nQ=*BCxFF&nNee_WaW*bwiQ=M8ZrmLT(@XtBMV@#_h=-DS#Ax2 z6HA(`vYOWvUsU7GW5vPMnXwJEI1~#=Ocm{AXcX{9LEfzS-FqDKxEP~S%6cjs z;HY1TDYrPp_g2OBK-~X-V8_r_movp$-Ya!Mr)BY%5gD=32uy+mxT~Tks_^a)W{GZX zWEv5UmGTcPE!@&Uv$n=}(DkR2X|cA(7m)VOTHxj^ii@{K8;;!-_5UK6>Wj1puJ%Ml zl%aK*ovRZ7HU1)kesHuVc8jqsNp3N^H-VT=#Nk7cz-@0bLbZ~AQ7OVF^~=_wWkSnp^%jRedKL%1f)uxVK$>f7e^jhz z%$J$^h~C$F=4pm2XHHm{6m3`^_QZ5Y)d@cM@#YOo)zpza#z`|`4MFQKzC}K!2%-s* zl)*&G8ybdC+fkK&5Jb+{|A)EkFlIxmKvB&DzNV(xsB9{FJsUk^G}2i__)e^f-WE3_ytrcRf~T9eajgz^zt)QE{>dkX~!#aksD5*2H02 zt5O=1IMELRp<`Ezd_%yTZf^0mm(I31DbEU*0*wIKP^kV^%r`8D2v1bOyj$cW@plb3JC*tXObbf%-;vg^2xrNGp$i4dK)S=@+tJ zy3>Vw;%S$Ewzau|(o+w6AI)?r9eQwp*_2vhu4saL$#F)TRg15=G1wCF13i2E6HdGZawg+*U;t333ZyBoN%s?lapjg4AKCF_iA#<_Vgj8`0taGkBRBP z(*WU8KWB%z(`@!3BO_o1A?wiv!xd7;P*Kh}2a3|h6|UgM+k#Rm+I(4aD z2RV`GL{ila)HOZR|BN3bkQyIVOm7~q?It^a#D9b)CqhO)>*8(w1Ckz#s_d`Ge~F)@ zC!5yKM3F=P-&hQiI3yo~uLEEVigKPr#zWSn30?T{+H_fsbEao)trN_j?$qOEJE>+ojmBU{{S8&WQQCVXpV*p;VwA0Jcft5i+B1}1C+x5$Z|N-Dx)ud zJROa!Zh42p7_+QMtWY(;=Zq#2WIi6y+x` zRT3klQ_e&&Gw2ob*>uh}cX;BQb{J|lXJvXm7XA81fT5|4+HbsMBn$U?J<5;6qe+8d zHw=scLnH7#RO`N>7*|0TEG2R#q#(h6U-TNmZ?LcOWvfmwmwT+%w)`qQ_5ZjUQOeod zPEM%Z8!-P0D>W)Z_?fY~lDCiIo-?D{sQ>@nLI36V&$p7F!Gq}8)2GS0+naH9h~C`4 z7(uG_U2>6Ge~pfdPq3pWr=#NZ$(X+sN*(zh8&CVHbM+59$yZNQpX$$b^?!7KGpc@| z765hV8srJ1#nmio>JNcj%WA#RJQWhqfiO}*bQQ(5;3e4%$PefKc{-bqbE0y1fR61t z<)x(*kZMNS8iO|(d>EwkHXc7*hsOcN<>KFVyP8|$Gl$dB^(WFD=52!i%RUZBKG3MZ zB`utK3VVA*u@n|Fm}*!NE9xG?Og!w*8YQ z=DWsk#I}v)8$JYfZ^X?v9%D`4bWAmkp$LAdfwpQ2^_+%IuT~_`+OH!Q=pm8^F6}#i zx9VMqt{Y}M(!q{kb8(bTAU7A$y!21Wt(&8zkPEhOS&e~Z6iqmFiwX`H^(*@Cu@$wF zB2S~!RYY7=^}Yul=g5nHR$Q#Df91B2zw;dT~gx}DEYT-)9p499AiL-HXqNng1F=+{!W?1{tWWm;YT72-shAc7=A!3QGXW=`Y zh1;NAzm&4Hb;pK`9qN#zj7)#p^8`|Dn|4GpBRGni3N0j}!PD!1{Ck&wvX83z*6}2* zRoc5Qj=YC#l#F2ToxxezBMq#WpN{nF((16po%7XKUnMJBE33Vl;WKCs#CC09`0u9V zYg zr0?Y%?gNq_gbzvDMc;-GdF7qSAMi{@_d9TjLUjolfeuuEGW?CW3hClx{txb}mSvpF zF>FI%;?m9VLG5iT#@3bgArJtxOAmSSXtR~MLZ$qxU`9s8emJDtAmhg6iKo(?2SjBf zkgBcwtX9lM*|O-PA_*;cil7r%Mov#IH4 z)0ms!HL03^*%khGYEfvp9H?T!H9y5D<>(w+VT^0jvfyQNaf*37PqE#+c0X2V$6b=Pd8Yc#T-@Zly1|iF$iZE ztcnq97&o2c>qM>(mbq;LpDql9RqTtRgG|g1l8AQerw~WHM_pSBT_pMWtwCc`EAgYilG;akzNJa5MDCKu0z>z%+LHj>2BHLv*(B=`nZxI$(N{dmdke zfN!2+^xE2?BcoX7L?=81f`GQY=Z4nSz(HX5<<6V$_LG+f$Nj^jADvrc1p=>hMwfWq zNtY$yRjHkD>9_(YR9N+rx1%g&a?>Iv8Z}3Mmp$)VL<`nh;`D;;6m}5#0xnf)$zmnqTgq^@B;Q@m{8H^u+_sQ$P9`t|zW-d3{vYUlWP zYi$i4xMn$bZLO2M1z{9kt}3(XNVTrG;>qD?sOz2BYHh&U+F-?jQHu?(HWs_4+Xe7{ zrfTEqvp=mr+gN}0=PgCTxB!w>hs*wb|K;J)F(tZy@WF%U-}es=-yF-;2ZJ%%VDnQ{ zuqr?9_~*5?xB0v5a&668aTRx%kE>pms=eAP=TFA4E6d*WV*0=4+ke{l^X50Jfa3c+ zLvIt;nfu?=*x%;^@`O>GsZ^^twy_<52CJR9+6-k3AIEas8m~Y9&u=#Wr109KAl-k1 zTSkZ=N3Vx}bCI;pEczK6#nvHl$FKmwL+v=7L4Vx_E^$hIddmi@(BE}0+2B1Ey<-E1 zrSp`HDp~2m_bZ({D14#DMtk&hSDZBk{rfSs2z}N$Qy3}drV#VksL}Q93yRo(h~FMy z4Oe5__`tgV(!0o#PY<%d^K<%_TKvyfVU1ojKdtn0tIjvSffi%Dy4wF(RewBDN6KT- zgk1}^T|nHU^9>e^Q&o{emSXYob*e~L;l(rvxy zzjKDW8eK1ExMn|zA=$S+fx-BWsY2dh%y)u1Bu!V^dD7^D4RH=$cmqd&H#7$|v>_n! z6-FI;*LjJMXV-gvP?&wty6?Lh%hfP4rO?oQtn@>yE$NP0Wn{Ha zQ=?MjFwg+&5U%(rp!9l7>Zvk1h`xavIhh=yrISnTT3WW~I{@AFF}q3||f$_6OI2r$F@f z%i-26FDsgB4>6^W4-0CJ7aCmD(-#}~FEBn`z%;UL+-mKMVwSXZ*Q>V3T@@fhUB)98 zKlo3GKkX=DRJ=n26s{bYa~RRXB*e(f$)1QMgdP@w+YW|*yneM3B#>qKj2wkA)U{4d z(E38H;A<3$pYfkxBI2)6f+AHsq@)aN5UZk6s_D3v&aAEzqce9Nk_yxx8IY=9c!zX% z@6kfCw6>8OVcv@I`aCdKuhg;3f{b^wy`-ZZ_{(gRy-$s$os#ot_1A&AT6316Zl_;j zP3gH&&hcY^lmbLU-|4akoV zBJA4n@K5KEd9eYfOkK^Zi0+1(uO-gdSKn&j7WeD~Z`&>xRM%D8sKiZ#Y7Xt25G=A3 zrdRxZEQuD4)Ak~-UR+`b^hf)~58LG~wo6<}Yrlnm7Bq+mY5Kt0_7C02>FsNLpG9kZ zUY>w*!u>hl=?mkg&l7>8+=d$8Z5Qvvhh`Z-O@ZJ6@fuBRps7VICx}aPF-gw8Zb@^+ zQe51=gH`HnI|QLl$E@x780?>o{i+Iey-E{pi#{gLjIu^v^z`<4J}S zBp`5q-$M6RD;2t}aIlV42w43Lt$`qPdq-kxl3RtvK^q&$kZPVW5P0g{2Ux zx+X`!fYNeuVg(TFK3vtE7F>+?27N`qiYdg70P_frxi&*gqaK;nP1=q1j;ZWMqOiw;CX?Pu|xjIB{ z+W_r={yVPDUG|tw-uLp!S)up_wgWbQZtj>&+b!f7vOWAyO(*(`!fW7QWpPz^3mGf* z&{-5Xy}o;NG<1s*A6?X$r^l-vkv;Nc{Ni+J7$BTKu%L)ES4|u~>EFHmY$g+SiyDi2 zCLCdU@NV)q2>DVKK2Bk6I(BMT<$-Ik)0eLE$rOgoE0=(YM2)EHF4U?we(cMC8Ga3` z>{%M2?#x)aNc4duxtjv*7g~}Wq?3+IOu9PU3l->$y5_$0ZW`k2$gifdtAXF#)p1_h^&AVaz;6}IsjGt!EeUJS$-`_z;K;QWCTgO;ByTU*~e z;`jCS{}czUt#7{13PJSZ(fe9|+-|2Q#S;^|y~K=lv~(hw%t|pi#b8*sgDB4dy1)z= zwl_7Iaj-g}#w~*_CL_q%fl!H3Zj9%vr2y7Z*)BQj+QnRn7%mZp#|IrAWv{kpg)F39 zq8;hzQJk<`ojD|N@HRg=FW@CJe@j`!U@x4Yq$Lf!8j6(PL3Dvlrw%xO@Vm|;c3bcS zGV1P%t+mbD&1t$Cw)@$s@r&!|Ko!N&A*O!7ydQ?aS>yr2mhD%FECL=2J<(Izt+7C4 z2^#$DcKS<47s&4IYBrGtGf<6U*QhnL!g;05!5xPSsI{`gvPqyL#7oABl}ww5w?JPA z`nz%_F1j(nk+o!YR3ox~Gy&jLLe~dSOfdGK1;bf{$j+_6f#3l(pX4~WvH*aY@Sios z*yUTAX4K;Aioq7eM028)a)X31HXNy({d1mEU#GueU1r`9L#BKbq;VaIh^Kr>Sd9A%5Vjl^|s*hfy5|PBb2W^jaUI-&J+kwF$ zwnrTh&@EpEGYzziJsKUrqp?rLK9t?Wbtb>kUy`R-12*d+Y*yT$HmdOO&F=nJlH#4# z+sQbcUG&mZb&xxM1}J^)3Ud=ylEvBjYJm6FO&{DR`Rm| zA2)P-uEVAqhgkF3?MMiBat0e4g{KEM)uUfDxl(f7WwpndQB~`$6w9rgZl>2(F;i;2 zBN%AiJXU;v*R`t;Ea_bCg0f{nk?loHqCrdObGtFKhn-vsqI6sNIE&iBA&gkX9L4XSzOHm9t22!#m1LJ>$%3BENLU&sriYeTP+!@Prz>aY zDkkS#L%xvr8?VichI<{c38>0a(>ci0m(z@{Y|ub|jCO>_bSwVYog@X|XIt?XEjRW0 zMYz#eYbPIrNs^P^F1wL>JA#e_jk!(H{MU`D!LdwuxVvmbP;-;*`=D~1qM|Th{$jpP z4xqg1AW<}fsTP3&8)@b+Bxeip=E;5uh#@8hU%9Le1z{>Nx;fmL{Vdz!Et4&Jy!QAP zXG98r2nm#3cKL1?iP}URZ$h*HC0`-=e#O>N6XBzgmaLn@OW4vEUf=_EaO9(L*l_()&5zUwGQMYT<6erzzog6cMZ zf~N+$ru5UI7-dR1Iuho3OjYX}_qnZHQA$F7`wL1reIC}c?Rx&fE=qky;v22WvWsC6 z+Nn()d^3zrPOeWN+J_Qt2AqrpdN!>YDj)j)j_BKma>oogO{Im^koce_GMug_Aev(%2O7l6h5b>!H(XhJ3o3MudSp^2X}yY5q=z6?Z9M+~MYJHqe9yp_rYfgjMRGt{jPfcK9)9R>Nu!7Y{xx}|L;fII zaPJSC@-8?fegZPQ*G#h=dr3|qksUpYlvlFCT`->AVtQG>pPg&lj3OZoC*7G05BAhr zMdBwR%U`8Elj@mUJ!jJ32vAGDqoBPlMwjEFoL=MucHb%V3(WbajI$SiyR&Yl8(FJ| zg~5QBlyrR(jINst zqbqY-H0%ajFLu`-od?K&`#^ac>_q5P4lon?cVsF=PKanZSv!jFcoa2%(a#$HI#kVLS+{{L zKYjJ+^}+G+!Qq=GoVjWf+8W#!Lw~mzkCkk(i%!;$b`M{_-g&dPyZ@%Yf7CP-r&-N_ z>@qonuW4>ZtB~v;?j3HajYT1!^b7<(u#|r!^GfQ9wn{)jzxFfCSoZy!gMJU*LFd1} zJ39RSZO=_pbDZIS(_LdVap}294e{+~!1qe~yMI>}^QkVg4@A_@aCEj_Qy-m#f6%Ve z>dt5LdE5ND(&3%#SiCDTKtyJaeM%JOgz$DK9YRYiv`fVvXyD*nvPK$hW@}J-|3f>` z@WL8^a)5|GV98dJ4od9eGsLc~((?i<=nHTrOCj(8d|J?d2l`!>O(88Feld?wK$_;{ zRH+~DvKi66^}xm~!ZkzD<#O8ImKjjhwrxyUe@lvn`RWbrusI4_Ip@dV1?QZ1UFgdwr4SqeXpEoti$OaH$@T=L1J1j&{kF zYWmb5qIVx4Zfd70ldQxbM9eE<`)jp57T8vV;eJV$kUioXtW@zFqs z_Eku%0C;XBt6TkIXEf@>1a`++T4xUG7r*G)Fp>vGNywyFWd;vsa2iKNJONSBn;!#s zY*MFxdL$M_j_EU*|53|tlt|0ILWSIiE@5-HP&FIe-9CA_-q> zPU=g?s7Irr8%)E2S8YflA>$Zr6nJGsbR&->b_JZ7e`?PM)n(2xcR z%VlOeGC1K&;(dkA$a92ysk7bO=2&IMaXHxNa1;hN@%jJ|?b+hJ_;PAMTug4`xM4 zuXU|A{?9%tEY^@y$Y!*<_T@Ay@q=knccf;O8|NUFsNp-)=B-9gao(%W>K310VPiS0 z4KpmA-*Lf^%y2By+)_t=;~T?pCplExC_lRdd#bbO&KqYD{1rDj*uxRX6ypbbdVw5Z zb+4i6N4%)A-C*uIP*s#S=retPsC&N43L;u@Ch*9Y05(RBI%q2VxI-2{9N~kCNcIq* zR0({$(rE7hQROthE$BJR zVE-VI)e$#sYRw=kfcJ+B{Hm~Ym43HeA&O?zGw3_TPD z!c^Kix0smew__#;DQ9he@WAG!frZ&qgi;ol@*M3o({yYS(TKRz6mq{`Y7%LJ?7R(S z_r~ZSnIlWWwbIAI-u|2Z!HWYMi0jKBk7SjI4C0ClHjKJukTtollZh+>Aq#Wm2w3uM(XkU{`- zpi8a;bgm?r%LqvPzC1w1ng;DrddlRonNxC@;DEGagVsqo0E=>g^mHy?wRwX8#w` zuN9P?yqu2GNk;v6)nD$vI@mpY+uuL>g{!~JM){yn(-N%@t{HFkPQd*93)P3aR3$G+ z{G%<8Umm>uB{Bbl_r@;rX}oIx==b>sI7;iAD%1PW=Vq zw*>OYVGL2)2#%ppG>`Od#2r~iVtUX^J272K`XLBUbdUFe(p7{y{ZDnGycFaYGu8!b z;|I(_T3vwJ1TiLh`t@6gpw61`sUZc?%}&i!`QoV4cMGA^sqf48vXGG?QC}8p<<&k6 z&J+0~ZLRcwPZ+pqkOS#fuWOCmM{hV=a<@k3~V$vm0H-QKY{taSd47xDC2$ zG$?7Y2C2vN3YL~y;?PZQ+0fC#Qe=?z>_sj{V&T1JF{6?$Tx>P*H-E6#%=F>LO{1^1 z295TN7IIx)5r8z~B_dw9kao}9{SNlD1V?N5+4iF*rL)>I6Z%W-UX|^=8^^oevCe*7 ztn4m-wONf??d@9APp?uU3NAnXFqVH^e5c2Q!R~$u5(cgK@!(-yb3Rxg^#_Eh^Q9#a zm*f^0LzW^qhW3d6?d3x{5xA4!O5@Hb3;VlobVZX;LsbIfTACsNO%#PSzAQZ@;ko9_ zi8YY*#4$y(=`fq3m8zJ;&%hf9D7Ds%9w0$~Cbb$g@02#_P`XcqiSrSqN{g$=r5-e` zr$;FM6^>Jz2?z2LL3E1dZelIE$%l)4a6uP7r=xW84iY;lSu357X4e&>nmX(?%1|;0 zhyg!-EX2loCF=t0gRipp8Tyxt_D><%=OCM8<>d-PYP_y{dw6`%KRA3-e_i*spfG2D z+*7i8 z=YXiWtgBt8@Zv5C&bzpC>fGtY-8f6DdD(bvS3kBz4oLy<&ue5WR!4qf*0vaWISBK*iX-|uwA(`Tb?W<~ zuPik4wCy(o@mK%dS?x_dy(|jE6@+6|JYjf&ZtfAKn!b%IUcopODvH|pRp<>+Lu%RB zV^kQ;FG^>C#r>i$p!l}5)c|9ELpy4*H*r%UR z!6#M=w22*aookcr<|4BFV&{I0Y&Y$>|7(-&Jv;E3Y&F18GFeQmo03|8H*bkrH(OBa z=3;8~>9tgHsZuFiA!5@A5loF(4>G>|>cs2|759LUk8lcUd5_XzKChB{MbVNhTXEwM zgdL)gGpL%P3&a;6a3yN$tR`#p_cUMI_>WM~u;2pO#2~9mz9t}pqUvxr2C(Rero6cq zU#PLYTsF4*RNL;8#;(eLC#|t-P!Yro*Y9&%>85JJW}TX#c2fNw`q$Xc_x4P>z5=>{ zuC7N>>1dYE=GG}7vO2dz7JE6_cyWPS^m+$0vBSN(ba8)JxGKgxcfodAEHm^jo0mSR z@n3e3SF@_~2&JdRd$3)W^T~(w@}`6kI63>>M!UOh3OJ~CpqnFq?Bmzt4U#-qE7E|h z{%6uA&C9cNfKG<0-bfuCJw+5QZYY^^U%O^TW=M5fRn}5oaMa8QhatsolC>)yv(xd#x%!oi!tT+JvFe2msG=Za%;2pjVpgs<0Q5B zHjimM6WJ}oK6prfTEa^eCk`|ePHp|M_o-V~^3loHAM4;R$x|L_^_g?nQmi77Gbk5HO&Z&meJ5O+Y87(uk?L!nzKWS$lu<@-m({9H>m zdw+sOYK627>Cvx z54Eo#+cuCpyB-ZO(RQLUZ<`RQB2-P>(+F+hJj_RboZYoyyZA}ayJn!>DGQa9aF_lWF|;BAH9+KB*2 zU-LwO89Ie06(6YhcO=(5S1bD4BV)w+`jX9mD9M7_e_oAiZ;@|5oHI9658LhcB2oSJWGN

OXW=k%bwMWos{}+FgAcRE;&-g;a+Gv~)rpd4rKvoamI!LAYzu=yl^eV^uB1P{wxWTR>bD*D6s=Xbpuy{(C+j6z51a#%oIgtCCZ4kyQcScUR}_e zyG7|&NE#W85~sv$19ST$1eS6ZG!MiEJGFxYpK;sa3E z0B%5$zZtB~X$B-Y%w~#hW@cV;`e7D5zi_*1BKwirI~w-dS}NymZOu)kK&&RhYk-V*X0voauLECgB^^b6e`=xd z8}J^Vzb*jtI-Qaf;DItiTsqZyM(1>%k3hQX+HBkEyzR|;gVi=&;hV%B!qA0%NV~56 z^J09e`ZbOBGVsShp7bG0r;2j;(JXRpC(k<+SSQ0)kjY`P0X~D2uCR3=_s6Gc`A(wy z8vJO34T0)1`iY{$up-1X*DsXof3$-}f`$E>Az-{7)Vy0&pTewVF^N z;bAJEBP#eB!&#^|=7LVD0Yk>o(w_?Bg4lgto)+cA9el5u!e8v{o`h7Me4S|2`+DmE zuc?B$eS*43nkYCpb457>oe8SJCpz(=w%GPbC!1vBOQsqJ+-ia^bIb>ef9K*wUS>Pw zH7=(bJ||T{J_sPgrQ&4E5;nIHl8EQWI>F~||$r=3wk7IU zwt$5nWoPqjB>AoRV$b|ZrlbZnIZ70N2WtGAf*$)r7qtcOL0AY`f6$Q@j^UMKI38I1 zBo#w_C%LyoQA6|d(@SFZZMy6iVzx!cueEsPC}LwgV+zx63tnP-*}VH+#BPz3SZI+j zJQ}HoPpaLb^@oM%7x3nrC4qXt6XcPqven46#H;i&DIA6ZuOm7t{pg?bd|BD?GQ_Gg zuQ1F)HLtQsRg~50e0?Yl$IeJXkS+Vj26cNX8MF=AV z42z8H8ICrHhq}M|Py_>e5|9PSqe`v#5AZ+z?|FsBmf}NIf8QoUq-(oVGy5TfSyog* zXR{Ugkyw_PnLf1$Cq>YmW-?%bYzg~^qvZv1(OwBtHG0ME*i!PmouT#d*jvB__;8GHf#{BQIOpbS@)qq$#1Z1$)7mNqSqWBO z*KU9oD%^9pe^b+Z#0DCyb(&qI@AG0_3R_&wi9lCq!<_-kpXR7`3eY5^V80{YvRjPD z=^}MeRXQysMEu}FQlHB*MQc*uT2|^IDxuWJbD%$h>pcETYM~|#wi+yi%#~e)4G4Gq z_*aToP7Yp?#r~s`{Z489E=;&O$yzRp%*N9Z7`H5lf8A)gz6DVKsxOMh3{YV_Q&f1Y zG^klVRt><0`5{#@FzRyCOPn09Eoby|c`|YfJSFlOi37N;T9{p46W9hxP<%DtO! zJXUE2-t^4COoA;f!Qp(atqoiVYiriZd!dB~Rv5Tym5}g3mx=DEk0CcU*3u@|1=GZ} z_ljd&e>62!dqqOOb$|6V%Z-nfcrm#6A-&W}^xLciQ3KQhEK=)!Np>CwB+uy|y*WK3 z!)vQ4jm@arUQOuW0`j$xQ=4W<#&Rs%ez?d-+BPZPixNBOk6>awf?F`vo=%>z((4vX zGGTOdxc>v1re2g;cGaTx;WV3o8RBYjRU+t?f0g&&r^C$FB}NaYTsA-(FCAKwL{&}b zdevC4nX4Ityxgf>NC~_AOuS{w@iH@F+AK3OOEfYwfxXZPCc9?`kZ-Tu7e>9lgvu&WDIutwi$!n zWm>n+B~JYX8H4L5I66bYrG1z+IMTaue?gID6z&%E(k^Ron0`0}CjHB4}#gTI%{CJl#o?p_vQZH_pkPk2o>v_5>G-L&Z1UEDss5!iFXYgVq`rje%ZWW z`Td82G{i^tXWdGj;0r6dR>yY@iqGMdTuOhpF3g(YitJvXu`INBQS`Yiqjy$Zf4oId zZw(kXI5S>pu`3;}oe=$HgUK-a*u|o~be4j1 z(lpJ>s$;>gCc{E|W8#;eAK^b=e?R+00fTAVf*bda1>(9~3W{TZ4uRpfcA;95elD_f zG`oNYc2d`RQzc42(B;Wrww0v!bSWYcc?_Jxlp!kVX%5z7yznWtgC@8Iotq$aKhuGm z8^2Yi1E1(#eHINb# zm!c{)g~2A36v!CBPj_k*5#gJ*K5(efFJ|g5gEx^s6dvpm{R`R;2|t2rBbrIjulx7c zQS6;0Ki)`f+b2Ao-YygS3F@}V%wwIj^cH(tOsD8|#5FaMlp^Xyk&0Co?= zLjittl?b2D`nm5X{VT5q)rV~fUXY#<^7!q}4{xe2S|ITXfiBQeu=W-cyfQrZ#7%Y< zBvnw+lC(oak#w z0FArwI$SZ(GDx#{f7qhUTeMf{6_v*PQKC|*# zW%Ubs(6cI~1&owyOjI4am<{z1V^NtGH_~DtkP|B%scGEow#8yc>NzvA?Jd5Uap0(* z!x_4A>}`H=Bn#X1FQu2-V?K#$cHuw@Mib&68t|5-ejchgNrP@{PSi-vkYL?js4)1f z4d;q~!|%toe+241?ot`fN@eRNbhW*0@G+d*+a%$%K`q=lS1}UukPB$YA5)x&TDoG~ zh-`KcAeU6hj)qT<0Qj}9Zh>CZs5-X88p^0@2|+dYXaJ@uSbp_!@yb_r=__|hNwbIZ z5DLMLfeWF;q1+nrcm&3lfy3DEBvu|Ur*Vut0Ph;gf51V;h@!Q}&oV^84IgQL4g2ZF z%kALX5s1r_wgheQa+{FBmMTBiO;L^u*J_8~< zOO<$6OH%2;vI{*-CaZ2Et8g)o(u6l`K^yzzLR*o`5YAjW2n3&7q=Th&ECwK#-dl6G z;2xQrf2s!Tm$yJI^7?7NT3U#Or=lDo)+_H9Zt22p43*{(9t7M&W~E+V?(d4U)?6)lbncySzMml;Rv ze?<2=NRS*Xv-}3vnlQ+mNbxs1hc(g~SjWPxJB5P^HOs531uLry>KFArD}=VIcFSYB zjIWHHF=6yb`a8#eJx*R69$|31oSkE97+Dy3+-mU_TO~y#C}%r$!!S-Wj^4;TS&sQS zc?aS{DH%pkeDS{02qEP0w^yuyFfIy4XJs4gyH);Nwc+G90?F7Z-79gho>4MLrKZkv=ag{Ul4t-U%T0iTJFrX)>I1Fioa(af~6T z#YD#zajdlMzINiw18Mr1ve?8iZmK3`=;5f^>rIJmesJb|!2D8$;xB67e}e^I{k+J} zFPKA_JP7r~!h;`-)PF0Br(V=y^I#YoQ%hi_slQ?3F*r}GV3nSUp+?Vg!#!RBi||u( zoq;KJ=yF3f5G7q?qp<_jy zODo7XR&1V&6`fYc>k<{>e`ufH{JULonhNg5Nm`+MY9;a*&r!7FnJgZ)E@o))vcv4i zv$!h`)~<76b?xGHEz9YeJ=FCVx0M51_l`O@q8ca(Qr2MM3$+Xq(?lr=YFOsVYNV8q zGu8pL!s@+)y{;(zU`3K`HlWC2K5Dm>&N3e-F$qD|wuBUeWtiN)e=*+h7K_$W#e7&e zUGU^J!Z18xJJD=2iY85a1jH5px^m1KA^vD9AS;GJ+uAQ84Pc`?Mh61D6O772HvHZK z4y)I|H8%^!)CR;+M?*1T-%^jL4yK%gz0Rsp-yA9u`$3l*G72ciCmXwEbZLZa{D}?g z%1XAMVA{Ni3~Guhe<8M-dfZ*?Dns2m!(rT3=qRKO(a=ljGsX`uv8RIZ?u{RLxXRqK zIPc&H0dp%Rj-jah+-5kMy0RrB3?VmgKRx&C+YhS8gX;01dfZpl<3aenk?`wHqNIzE ztD-Z8O}~gn96@hmNB{x}S1>FW^R{5#E?(3s8l0BAOsgb2e>;P0d5}iR?tRMJXmd9d zp2JxeH#2^QaO{!Ydv`6^!-93h8P%1?(6XZS-*OCV?8fH4rI2s__XSXj-5r~ zr0Rt?IL(CYroHPO(d}Fu_1n(D56L>!a|HrvbogiTo?55*73k+@|wcvC>MYqvHpd zLID1{@?8VSe~73Dl{TNY5crzFHp;Y)y!`Cxzit1qE~sj$NQ$IWb#`PGiKGFF z=yHrX=qHW^12&4#KKN9A5Y=u|RC|yznxzbu*E-0of0j4fU0;y6wf{BVC6Bx2yA=$~r-a$^HX&y}$LxWkRV#a-t{gE33Q#dapC}%L!?%VyQ%`4Cfqz?? zz0CZ?r6zA)nygzak2M*$;$i?dj7u|I7#)8|6T14X*_*y_F4b$w8|32Am*T|*Wc4M1 zql&m@f9qpT%Xocxqt&|kF<_2uaSTeKyhP~=G)aJ9SvEHUuOCF1%&}9 zaaA2!^nqtZ4?>W>XcX741XsYx4tau@4?7jAe-BuN&P{sQINgnk z$JYX9-q%JI&~wE%1_#~%Vt(_QmpcCwgPYaPL9}J;f7hC-*3e}~!PH&{O1Ko)fzoE_ zmWO+_M6;__xck8npA!*yvnK*?mPNJ&s&!T3-&Y+M+!m)3%A$9XeS9fL-rweF(z)&d ze^PsL+;u`9?S!h~;b|DUbJrc40jQcIbkVb6mJV$W7N%kY=G`3D1Ft;VoofARF`(}WWqC;U>LIfgSE2l9cSc)yrul*cIZ(aC0iUpn%RW^Hvbxa`XDbof5=M@ z^3sF6^dK*Nx!YUvw(DqS{3QjZ+w3yD$)$nS-{(^KB6*1pJ@Wa=%b}v*>|ppWB{+H0 z)hssgl6p>Y|6&r<^&Y8QDzp5pZ~%kna+XJ+A3MZwf0?rA-ZB)}ng9j7IPu-u(%tM$ z*;~4K=SRy6x|LmMt*)_BS2IXne-*x#eLXWZJ&WsHnHwaQyDK-%1Tob z$H(1}eio{-dO|oQpAALxE2zT0PRf`dK{8z?QNn~97Wrnk`Qty6r!qz@pkEqEVy?91 zF_R?jSa_Cffj1h877B~S^`p5DzcQ>1uVPF#&b!5sympfsB!VPhju-C2f7{CIyv5vV zTkb0Lqh|ml@C$bryit3PcO>B1L2T{L>Ly33tQg0$*3|6ICJ^4GBqw^o4I!)9Eub$K z0VqfURLfp5T2l0DiaOD_OMPS6f+sCaWiF+)eSY0Me_5}9^Rv+-ctb+pS!$rJ5&->; zvk7V!r>cUNMwpZ!^(mh~f6{1!p@Ni`2p=!B5Tk>5AE1V)`5EbbREu;$egR8+vM^e^{Ox>`JtRMz1}~ zCwX;|4ew4@r4Bn0+Cj7XW;ZhAmYi=HkV0nRDR86~I7;bzv)~d-zxkgqp8l+YHk|ZX12ZNNyICJ@MA}p`3YwyI*Cvl0YE)vVmVNBYW^A|2&5RT={ zXYFi8hY@WMtL%Fgf8J1k+^+`UR`AW9T<_47s@2H+Z!-^y~Cv@|rI$j9(E_pkDBR7>G1Y@H731mfaCN z?0PB9#RNqEgA=BDp$ghCEZms}1-yOjcqu*WeUq#)NR7Bcf4C{iYc?zM)A<)!>e=9hj}6_?sOIe=;|)yLyvR~?v6;*bB5*XDNIz-&KGQ*7O&p3;o&r0O!SnbI(8T4JijbDlMEo?uWh_7m|?5@X>xH5@?<2OfZjz?x+aG!#J6l!M00>gKYFz(5oP4(uQ4cXVO7k{bw3IIHCCGiUD? zqxind@mU4%ks76!S-GwoVOZ1%G}+yq<8|ZKf1*Wi_IpVOGm7nHyn5P{Tjxa*sFGIg z({4#P=G(R4$~+V#H8ebw9(wV8Vb{F|b&1_Ez=P_1LN^EUx94P~KLfULmm_$WdX!Hb zN247WPo;DD4s{KD$&WQ`CbxV7>GD&3_Or{B_0iJoQ7M=FMEMhuW_i-0P7+cVTN9fMV>y5dR9EEtGQ7Y)Zw}M$Z?1UdO${QxITXlhPha8xk=_x zcdYL4g2Cy9>)yg~p=M?O&HB!6|3tC;{(kJ_s68lFNe!aMI$^V+{BGFDlE%fjieP@k zjY_T`X37s@jIwiUPGUBIEG#v)AOl7)e|0tIMVSq6!4#xjYFpTPT!#L(%vF6Q-YEvB zD$;N;4H0?RVq~7KBXSj@2Qg*_WI&w}c&*YK80(n$Tz%9nSBb}qjEE^eg^ET~Lh}PU zZvqAB`I*wwBM<=$p`v%hC-*1#z6KhtaHeR6;YGBVL8xoztR;6`1V&nzBm&8{f6oh@ z;zgWroPLC!rDh~bTUmT$#EHgMGtL{xqTVp-I9+@Ii;N!D|HWc#rutUo-6%s!YgrPQ zMg|zqyIx(SQ}r3F4R;lTi?Hh%if;-z3d|t)h#Rdxup`NiTyx8fSW+8Mb!_Qif__z@ zq!y{QR2-Z54up6V7miVF(u6Tg7WAFLdsTM8@_(4>n^R7_zTz6im%btyV132 z&FtNN5kwfw^#Q{v23Tk-a^?C9TRHXK4w7`ebMY z*jsovj3}hx-7sIpb1xKsYuet!D`*c2+=Bx5puk;6ffKqAGa0+1tiNkqQPHm|3Ao5R zbaoehf9WBA%#D=~dTPx6f0i=;J$ZEOiy{|MPmuB)^+HOE_T4f6(nAQ=_Wd!GKwNEP zkvh^sACa}2^^rxYMk_s`IcJTm*q~9^{O8Nw!?b+r%~lSb`y+QL>U?=-Dm=UecO5zZ z+ONR1P5E<6YJyZ0cAUcy961#)2V2<>JBqD>rRWUe^oEM7lZ4yBe~Qzxc%PFBIwsdo z=>?dcf&{{zq1Ea=NE;*=KaQ-^jtx60O>ssdiA>@9Z96YB&^z*8ZeDy&_7qwC`?ZjL zMDrReNev@QNd>pPmKy3^xuWm8N;NDSyME)MIXXfW#29>Mt0tZOAJ)rsn3k(PJJ|o> z=y%Bs)VQueKrkB-e{YZ8OlJaSj$L;#ZC_o z@ePaP8-8ET7!M&D_dG;HuwL;0U-ky@ZeliUU#+4yYHsZ!Q5>-gmHQmbAp`Uyn(K8> zvAU_a+qpee1`j^~Y{JHR;nESM8vk%YWm%c40p3NYH#Oue%YetLJNV!R`@Wb1SUIRrX&OOoo9&a$@uOc9RdO z+GnT>7+Gj2)5m7x)3ki&yGY+8wxc$YFraenw54HVH;on!L(zMyOj=m^hKT|ja|dZ` zf|Q`$8ceU3f4#T{8VtjM9)Bg*C3&l)YLJmvb0L!p;?T3%UM|fy;mpyH57<#XUlgV3 z4||9YNd9rK$0k$Rk<3eM4apS+Q}|hdV-=wDtJ@F>9BT}XhIVDYRN4nV)CW1u==MIv z72*3yYD@dN5c^ebXR&32+c)w>NXs&RuUf$wolgW9f8tVKcx%@ttjWx(W9k}~W1xg+ z=}wL`yEprVSq`&D8Q0~uA~&jehkAXz4)q!Xk&oOB_FybT=a$Z)h|qgPwbNwGk5uB4 zpwK10W#cKxHOoAWIwMZ5py}r?cM|%>iQ>%Lp`zn<3 zlx!oGe^bEkLYnfM@Ex-V08p<1ag zcY{Sh0aQ-(t(mrhwtV~@1u7^%M8}7bQ!Jm|Fie5-EG}SqUdMOTIWCP(5uIkLNSsZ zCuyBgchiz=>x?aViFC4h93QPMa!cZb;?{7Jwv{aRKfCMxr27{x>H(ne*bhpS<8<5S ze@sW*017~%P$(4YaqU)`lLn(TP}l+1ge5JvPO9IxD{DD9?IT4eYFfWI*1@*I)ht)H z#Q8WAH#h2tO#|Mv3Wb>rzipatDVXkf-`>SO7V|8d;Wr8a_K}VU2jPR< zMfV`@iW5wJ7KfPpT!TpqJ(7KLYVVz$+PBfXvYF!7lPV_c>(mOu# z7}Xb+cYBuitNcu>65)mA3-N_fequ&x?(3R6`iiEQ(7n8f{VWfKY#gI2wBL-mf3+=m zm!-+ij(F`H0hW`g5>9u`4Zvf{t8LwzyrlxnfbzSm{xd1Mqb$4H&9AQg!RdA^$FEAd zw}AC%(Uu5jcA}TQ``VdQgp{i}`zudMV?2a10 zgs8^7rNKIsDcr;OJ9?SbT-@nte=_I+E{U3n%ge%Hz4kMzfS_{kpLl?!Ny&ubuC&{I6k7vJgZ zY_;+Yu=-y?tfaWS;?#Kpd8(ft9lz`i4vr2X+25trkXzh<`VvFN)?A?nf4R)iWv0av zOGm(W*#Kf~|Sf;C+G-p7MmX{{BW>TK&OeQ;vw{YreF5=2BrkR6LmSSw-wOd$*MKdc)d~#NS z$YQF6^9jWpu@qu5sx#Vjf3c1Nu_xX~xSn1oOUNOml1P;-vJ}sk$zZWCxW)mWpC*6% zjWg!nhai@{{{F{L&9AjNoHq_0H-AO;7mgJE{oAh_Dy{MLwb2N(dj_nd<&wke!v##I8bPw%uy2lfA(?5Er30TB`wHK z*z!tgo7i#)8{t>sdj?g)n}%1zEcl65>Qi06p1l>ce0RB%$tlm&HX_Sn?HW|efNj0&Bs6du=V}--#+ruYEpO8Bp-U^*EEE0 zA8jcx5C8oABlZ8he<~la%EsewH^1vX{9*Iax8H95@aWH8rD=M}%ds~e&i^yJKKbct z4$oF6k01F9gu8%-^XP}}59Xf_Q_Ya?epp$*q&j4$mR#4(fC2zEGsDB((3OSX)OTTXe2#D%=yEwYo>jNdCa z3@SEUu-Ak$pDQH?ds2#Nhd|7Q*;Q<9u2xneD906deNS>arjxx+bW%%yd8yE3oNV^; zq(7XEufY8Ue@m9o=CEL~mlFh6>cIWV5aaR&7whJ211PwjnF6DPLj_5TzH0Bx(3*{> zq&{f~u79GZI+Hzl@1S;$j+LyCKz)0Fs2h3{>Zli>H&4TrMK#5q?OSI0MUH*t7|e7#^`($Sp{!S zveCFme>O|9Q;vcjV!$&<#tjvrfq@BiAV(a3*}xB`1~m}n7imF5+pN>R>A&cL$;69e z?2yeHNK#eiXFoxunF9sW8*~VgUm&!1Gi@|i#+I+H`Ie6B1KAiyj)twosLa-m=Cvs zijoLq|I>D{IBlz&Te9(xAX@!NY+TMroYPns+myL&>Ioapx6@$jm+yKbIU9I(=3@!~ zMEg%_j_3=DGw_oGSC|I-Q&-WnkB6(QOn-uL_5fG?U{6&bs9ysf@5h|hWgeS2TOa!oBQ~HPE zs`KsZMA7?2mv**xT}alGjV*UamO;IaK)qfPsu;NW)BM5b7{PY;N;aG2v$ndPXi@~U zVT3Ux73X5NowSsw67CXrKSdvdyaNqV>o8OD3i@85zf+X&y8HE{qz)jt38qp9ML8QlP4L>6i^txGUlG7*jBo``ZR5uW{DI z?tdH?3pHb_M69TLr0NWtRz3z7{bp4Uj{Ad#Kz}-JR&npe!Qp=fRb5GMkBNg*e+EnC5BhC;;qv-^sG{si{;zoW z2}~2V&r3I-A=yqqRUL2DJMP+a93*!93D=%7aW-*K&;a#tQJFxS@%Px_az$xi2ZbJ5 z$9yqS*60HyzaES)&F}x5=I_b--2V2{Wae%UDf4z>yWXB33 z6^y5@7IKTaO1RDIDj_-50s5&H3=*EMQR5@zFk{(S`iZYBGqF<#PV6_FPqk*zBO_jTQ9*-{apD#mM>8 zzZhS&*PM%ke)FB_4ASCOf5m0^Ox5I-9Pk2q8^-;OMMnnTv`NF&`aM^vxp7>4RppA= z`rAb|r)jn!t_A%Gum9cgsH-s)S0eaXqeXr^{p!TLFAyEdzQDmqIYz0M?6nWxIe^KC_FeAEueql%9 z3h~VxFu#;$et8yS*We42(VOjffAISF@Z{;i@&50?WNLfAGbR)3`A(RO4(4O^O?Sdy zj;^vV+$FL!fR|6eS~U8l2EVZV36QiR?*zk<*S5Mk>2PS^@X zCwn@}K1;@OS4@Luf1RAEY2Pv5u=xGLIXwCQ>GA$u?IU#{Lw=RJ75N8;`Ef>8X#XmE zhfsc)C)v9cZDq{8YJM@x(uQ$2ZoHcW#{boJ(|&Ik+^0^|{~tV8zXs9G71Zh;9-Zi~ zceK|ODU33<6uN?7iSN*ODw`JBok+riSJcTK_3^K=^#K3-f4u8FG~e;2bMgIU<2BoO zuX_Fd!H@eVd;3rK4|nhCprg%q=i05oKJ@dsUO(^dq}$i%z55%cMe&y%)@+CE|Jd8x zzsuWLCRd+NxOd<)1vt7)r+Kz`H@l-l?nmzDUwFTjWBiVHqQ?D~jo54>KKVNwb|<~V zy_5a^Ztv9!fA-_C_G1WwIcO_=%pZ$UNg$2kB!#fpdRZ^>{1?acSvPv0UX#JLYQP@7 zuQ{y;8|(e;x6)_M?Vr+Xa_ZrJ&{>!K1P}ytYX(7COURONJTyHQ4SM}swAcL)*>Ew> z)-Z_z=BvlpqxexOyes8!<232aE5Ksnb_E{uWfLd+EO1f@OxQ(%T^EdwP8_Y-a%dRcGKiP~<6Sx%W9uMK z*iE>To@o1HHpMRMp1bqi2sf$g17 z)*lv~v(1+`_syT=1JiN>jClen#LIfM{d7DXwTYUNmSLdbz(CTvAAbHzvMJbW`AFQE zgNsM29UE7N{6B~+)kYnriY$X}Rjp1-e+?MGthL-j_x58*7^FTDY~*D_q+|^@>JjbO z(>`!(*3!2-A-jewBd>X+0$9_oaESbG#9a4G^Q2C9L^8dZg3od%h_BR1AhX9rQGN1me=Q@2 z#tunj;t=5#clj6( zr$o>dHKfD~-dP3_c5jVr378V!Gk6q94@`vhpfjswD)GyQks%80X%F%64 zL76?tKz3p@K}CwkBxs5~40U^Se=^*%8z|XGwv00&?Ymh9$Gm^OnW%%IpQa~3%B|Sf z@evI(V1HS^jk^Hl;(hWmDEiI)#M-P%TC7^Q@)$k%8Pb!(U%Nc=_t+c+fi>Y$p)1vH$$&^^3jaaR0~s#nbx zez&@cBB-*drF{vp!bbcbmOWKFD@%tL!Mdm=fX612gk`pvlK8JPJ*3>HUTkjGR>X^r z2Wy5+58;v6jVXEZx8H0)0cxoaDoQ^#!kj8}=$wK>F&XrpIn1VQe-0S^1$k1jMrme7 z*VFV8RC<$ZI*Q@~a219xYtp=LyMymu95why!S z?KNJ6PBKg3qMfMIfHM;p3CUq=*1c3j@qpg_WyhZF;KVFu?gvm#+OhVgluZv1e-54e zGR@ypr~`#uNntP#f1E{&r_qXT*RF>GJ$_tkOMbDY;MWRqy=X1Q7;ynk0!ruw41fCLCTGJ+1T_wpK=!F=MXk9`!LK<(ufgM-68xjuH2ewOiO zbP60$Aadan>LhUmirPYtDQnBY;pn>Gt|dpOxN!qP1`~gpm^y_$gP*wtX6p}g2z@xW zV8mz&HJx0Ie?kce1tN7J+}hiAP_(k8XMa}& z{rR|k`}9Y4g&*k^=G)MU7J2AGt6yiYfsmPSnBVGQlB@WNM(F~pAEaB~ALC;^MvPqi zP94Id)E}(Pgi55rs$H&3nlF9efP$atYI2RC#pj9@e}R8)KDUSa-+qJcKDw_ImamnJ zMQast6agJFhdC&AP5%c|>}H#Ztsrk=bp-)&57WA9Br(m;vXU`+UA)3+m4YuI*4u61 zt1E3O@F^h20lQ>RW;HRgH!mh=M5oufs+Ke|%8ZA*l8P`m_zB8Uz+O5mUg{Nsey{S$ z^<_T0f4WctGH0`ep41G~#a=d7zxG%5=RRS8_cd0$f zL&U19Rqxo ze_!46=j{al)1m(uh4q@6L<*DXCgosWPeVYWr9XzPjfdab#+LZtRUH2ad#!8TUbcnm zE#4#~wAX{@N5_2%wE)NX!IRhU1mBnR1~LpDN1Oz-*iKxMc(cB~54-Z}dVT$E`(i%7 zDz?A*MjcLz%c7g5=b$WEd~=!M8`9Oqf7M?XJ6l^@-+sTP?oetB@XCFjPS@ABlU)$q z6)Q8ZkF!ikwzCX~8c#co&9$yOzTQG{h*>bQ_4TKd@%e?eYk)2&CwlVl$*Wm@K1(kz z70ug5e`6I1%3ky(MbL=@NUKCWnVlLS0pnNttyLzq-p*A0Y@DeLq(fnv`xf?ve^(pH z^c5PYe~wYP3&Y^pVJ$3ySe74+F%sUAA@8=jrBXwdzLaz==FBQcs^M!nIVY8#4l8Kz z2y#d3O6;y0Qi3*0NFrnXEZP>}jJ6SCf2o6L`(Ubsk1;F*0F9KeG?1|tivdo1jau4s`ZgKPyyKB>pt&5@S zZm>G^wqsy9FG~`Ox(0+?q3Cs})OE*2o5X4U6k~XaKy}z776nVkgSPFQ_X>k&tsA1b z#S|Pyp;3d~_^yn?SqhiZUyve2M($8QGg4>A}Q?X@>sZ{e3NRWi(#c;uy+-4T#({GM19L(4Sw z8fqT<+D=eK;5y+N|D@I3bg9JEEI>;>TTD0R7!U~W4{Tp3^`022e?LKu(fe_ck$G+- zwqc^}=?ib^2t7JFD0)y3qlOY*D)>os{mVIs;KkgnJm$v5KA@^iam(cChY2jV){=kh z*nnkb$sKU+Qp=(y>`FO zVPS@MAVCrYs-I&&e^fdF5pD{$ZAc+Q2&{_(bAceM85d1X8E@rzwx49>;pV~CHh?;a zR}Jj@Djm;?cCglJt1Fq)MxG@b4wOMqg4~^@#YH;-#LeI|gV$V$N@hSy8)vpYp$aTkZRQT+5Mr(lSNVEyBW|#(p=h>1i+|rh?-t`x z#>p{thxjSdYlJR!n@`Lw%uv0BbB7bGoM{$g7`kw2jseDBw`AH3<39hlJ09_p5NNd? z)A{&ptnE_*iQf^*;^KaM`4Il6UObxAPD~u5RTQHNf7g&&M^_MwmR-NshX(^s-Ghow zw)BI69g?BFG)oYwel5FsmI)o{@Z%XGbdyGUjbI3*Zlph%-Xg;%`#S2)&l0Hl0KmMD zZ3j^xQF@`I?_xMnhdDz!9a%LrcfOUt50JI)1xD{_zzj(_qfH!=de|0FTzWF!mn9}c z3x6aMe=@yS5soKb68%7CabhexAnr3kiU^i+e&`vGoSRep8y54Pfe3 zh#+-GKR;Io8F?Re*<>Z-mzxN-f_KFLo6ba@Wo+<)56`hD;^vx!K^2&gRNz5j#hQXf ze>+t7TQ1tWBlEYZY3~qFw6N}YHQ3)xCu(D#n_KkRcvj4<#B(bJ&)`PzW7?g`1k4{k z^?I4HmtV!4k%gKNZXeaZQQ6YBX_klRjjjz@X88qdtpvrs!^blBZhCsdPIkFt{NXsD z-DF8^9^yvJ34pcBm0nL?2B>6;UC?4Ff3XyYzEf7)T|3(#c$zjvwTlE#e!M!>iz7LC z$qGD@EAXfq_a$%kG~MVUWC`B!GxitYJp#=j4_gFygRkNc_+S4v%HjR=pdc$+V@~Pe z^QMNd)fd0re3o@xGf^6bF*gN+P$gt~8YPp#(i^zBD3rTgwUJYUHrGddyJC(nlhr z?^y5TFdmY5JPMzMDV|bnyWQB3`434S(wzIBjyB4oXT=DDiKc=gZfSk~ha?YUjsMR} z8@J*XW4ZR@f_w=QH97OH>1ZHLf4wxhFt2dvmRT-o5nq96PDzWq~BoeR<>Ij0Om7)NIBT$=RKe}(jDrznZZiD?zm z%#y^i{9@WT&hkA;t~6)cPP_|XINIfDd+FhVXJ_4biQeUa9aU(V2Kh4mFuq(|Chs^Q zapa9`A7T_Ov`x)ex5tmFG zy(<~yWMK)Of7>G)u&D#l&XDQr*cyI#g2nU-k}_WBi&@gW9RG?i6^De#OYAWU*Cs;Q z_Zz>CuWUGt(^>w$$n>+Pt*o7$ISLCxTwmuwudiDG`4v0*VHFs8k)<<5X8MrVHsM6I z0y7#0{^##7c&cL1N-kdkNLn$wne}4y)u}Ic~2=>O26()0$r6Uo} zPi#%q(sOdu{)L@n%pup2ILW9+Pf8tP*AC=ip3l@DXhP&(MR`L*zzc}M!FN4g5j|S^ zsK;z$PYyV89gahL%gKfmG|`<x8F|gn+gkSgm%mK9z<31M;|>D(+ps9l!Y~O&$6Y)O*V4nGPM}34or*NNQ39R z5xh41**0_W^@h;&wwvM&-|4=4UDM`6(BeZ)e~as&#jiCjF2ZT?lypOl{@=`!$3c~S z*AIoUhc|@nIJeq>5B+dgl*lO_LpISBWk}SvA8K)}neae6KWYvrwsb5eh~5RB$>4rj z{0cjB)d;JY9^4iJ3w>>mr=xi*#sgt>7Z`CNwJXxZr$K9Fb}cN@+7<$Qhp(h$8*M&! ze;Z+~Z?R@Ou}j>MUAsI0TtK70PWADY*)jh=P66Myx2K880)^`A-69c9_oZhobx%P^g; z?zJEw#Tysk+ioQzc91c{q&T<1gbRssgN-2Eu}S_FN6oQK3H9j_)5XztuS~@IIF<}{ zbRgQ(>qG|025IOtEwcOe@Dc&*oB5?ZfdJo#N$Tw?$jN{^?&_L^XbkdAp%G6paH43W z7=Qg{H`d4gbQJUu1Fo<@prL_*iAs>P&=$!X_`%em2BI8dW8%=fu^7WBbq&cc!4XCO zl}aaX9Ak&mXQ(qRI-z4F_NapsuS+ur3Yh%xGel4zZgw*S5E3U)6j3HEOa~^~v19J& zu6?^JTibWnGYQdt`>6Hp!22hV_dyX>pMRsTn+J)Ac!I|E-Ajm7()NUvPSWbG-)|+_ z^Vqht619Y7{@yMYr)@|jvB3t=)^4*}Gzt?){3cp>lQ*1br)rxLDH$+m{Q zo@{KnTdEA|bp-15l2FCK!zVI6Wn!rfv)L@4wUd@1ML-)WC6QD}ov=;GoV*J|eHPxy zaG}W?HLZ)wr1$Wd`U(27WRkthCVvqHWCL$71A2gD4#x`t$m?NHT4}{RH%=Z4 z-@yY8Ek3Xm%9qfEm6`MywWr^m(KiO&`GGdf!2BQ^@Vhjw-obRWR;w}^UVr@kkDV^# zo?Y_EpZxyg>L(m_iF4T4PRZzAgug8`G=;9Ottu+hvP9T~hsmSqamM-<{=>$Nwp7~n z%Xa+Qo*@avkH(PFs8G^R?fu0mDaJAF0@{RO)usj{Xhx4M?(hG&e>mXgUTZT)HxPE4 z@&%-mH8+V{Jk=#fZW|64vwxz*BEWh0kG%Fbh|R56O6!QAbZ=Z-fp7*EAZmfAMLxsj z4h1#`UU!&N*0c`bS<+Il&5mO=*;ab+SgjO-H4 zBZgxnZvMVdEziEuFM;z0VJhq!+)^G(pkeeLJCwB z|BFeyaG93L;e|y`3NL}61{u7Zh(CXz1YVL1mdW2Gl5N;6k*u$mrtn%{uS(*zzRqd9 zOtl*&@}k+yoZmKDHU3X^9xt3rZ5l6uw62GTnk-(DsBW3WOMVrDpQ=B6)gR`gjO{?q^nb=W&F1g3Y^s+Ev%$c@ zh4FyhF~Ii@_GoV{lWab>fxFlz4-F-XTs64DY%RY+f)U6Fq;_ofAq8j4j=K%HA%;&6 zB6@W>H^4^_?^W->)d2>i`=FdKu3!NMB$_IpnWoOdK3Yrqm-#Ph7*D}+C*goFrKj6Q z<+c?Jw*aWXKYz{;E>~(2{&X>(XMf@6zh}UK0d?4g!}t}|!xIF& zLC^}@8@jbKt3jn{3Sy>0;s(GgE^)rpbRE4K z9PG*u$GyGYvHWmw+#fgURHyEIy@hSZx{4(i`d%@rlG7XE9K8+#a`1d+XLOgwzYIhXIrVShAZ zH*PjznJ$tGn9^j<%RMY!NKG3pRzpdrl^?!%MK-&_7 zC2e9Ksdw$v`&B+88E=V5GvPN;>p9_$JG6nQ^Pmuh4~_gL8hK9Kz<1)t-V;&8FX2hi zpnZi`MT6EA9u^H+*Lz#U{4MIh*4MB;7@?Jtcmmecsex!J)xS%r)hBjCuzL6VP`XnQ zkeGdl!}6UShoeZMWWcT7=zqT#C}y?H1S^qYr=}^%Zt3f#Aw*fn678cbWr_Bdo+_Po z;EVU}#nLVrdl>ULWUbA&_8 z&)m03T2;53#Zbxe2!E{l+JZmcU!e1I@*g`DGJ}l{Vef1XE8Z!e>iBI#23*d989Ng7 z=%LY_;l#@~ppx@r904Xj29xJFMgz~kxWrfz{bYRR-Zwb!z^Ks;)VFAST>`~7Ta;SH z2HAj-gK0%|%x=-YG=KUx&04EP=YHvvj_z6LOvcV&oO`~hU3V>wd1L~B2L(R0LY#Yc z@4%tJGpcqzSW-Kn!{!?eL#Wrg&C1lI3|Q^r$c1qe254oLpCmx6O^^k^_1VVZ8VxWQ zkhjQ=+?@x0H&*9gwH#GXd3F}5<59cv8lMx=x%LsY+Yrom)qiq$J5%^^o?a)t?!hut z@QV5)b!E%B~psCPs@KeEt{)K7vpev4;9>ye0Z%O zIXBy=f!qx!Ga)9U4hhh4$+Er znxy9xqe+RP0OLu5Ur)2Md2*54E#Y z2BB1`&o=m*u6d?>me-NuxXAuzflqME8~_^&%N)>nj=OcueQ`$V*_?T!8c>2$s#G^e zWTGfKSATmp;=%xx-dh2s-zy>Xdm>{GQ9{f`)%0mRR0EIGd&Xx*z@iedCFr{+KQ-WE zEZC-(m$1ryETJVIo4UQr%Y3D41pq%SQ-U7dlf#a|VL~+5W$}%BsX%*#;#R08n}%Yg z*k+iY7UA5fi*sir4%*GU>oFg4|J1$?f*45+Uu`XUEV=J&*We!d0K>acK7BAZ=69-wI%3qxb;%}mr}<)TLh!L%#9fn<1}L;mb93f4 z9)JDnNc_27T6J0Mdf^Gu+4*Xz+jb|2TaXt=5agacImb3$j>&kp)Bb*M4|QBCAv6ha z#p|NLR>!%$J)==w^ycaaK3%{Qyu)wyvSQd?>!zld8vCoMAh==8S-0kbe$zrmaq^^h zxVLrk^yv7ccl@&T7WYE)+B>AhRd_Rs+<)rE50~odVWcZZDQ)K)q@i+%RMlDX7<`{o z@N>^U?aHYK$WH}^;B;}Df@_A?D_szBcPq?iYWI?<%3N*XtlA*T-((P5`NnfB5f3;r6@UE) zx8tXq^@?N=JXb%h1cF^cSbqD1u;#D`F)&9UDQjK2DLSP)W0p-)R8gmw5cUi(f40ab zNgJPFHoKc}fNpg+*8=qu+!k?Br)o~~boz@rH_4|Cq#pRc(P&Iw;sg@wAQ;*tptCv! z-4oca&PX6oJuRz8YCMD063x?v4u5pbpKow2W3x;)(43(6;~8$s zj6AFwtqipLk3i@27h{@OXxCmR$dkq@Dk0)&vJGPWG~!i=twHOalSEvjRSZJnUx$j) z$71mY!vV$3Z0)-t-=h881L%R#)uF${v>G8iAF2H8V3T)nZ z3Bt%sz{m_33=dy~A zWks;>1xs~RAD!dR-jS}jH+c5448V61K(E&;tM|RE*YCZA4lYx5V;yypgX83UF&>qp z_y?ykI!PYG!xRAOuv}RU;m;Dn%WMQ8zm@~BwJAZo$Q5bT0EqC3V1MI!p|$K5b*`3X zm&Wdu+Jr^4aKi!GE_9eNSun^x%=t-_cRd2MIsUwtS>0WW%3EPpDA8d-{#JAaY`vz9 z)aoSW(*f@R`R!;jN(LxU{(I7UwOhzC{r%wSkL0-q+9IG&RVj&9vm^{&UR?f?C>^Vb z&x(GIq$;h(%tQyX=IzR5pWC2y^_beP3S zk*V%vS1(uG)t^gy>g%3TH-sS`Z?d9;?qx<68O{&RaN6+!DGubIq<8qLx7Sw%Yr$Ao zGR~5*eqV|@Ajg}nP`sz_%E>|Qhh@8r^z^s_|GEUaq zHy&zan2$==&9KUFpo()wK5D@Mh8F4RVQEi>QLkuDu9c%^iq4TF&yhFc$JbkLJz>%w zT5c{$@5EAtEo;-c{9y%{&SvsU@2B2w->EW7rv=6Z!vxciU)nvHUiW?!r^J&S?h5i~ z0UzM(d^}8oB7e0SkRZq=u9im?C~F`rQ7*r}TukQUS+3;dr{HAmw9sFcIiI*q6ArQD zX=Da^R@cQ+XU`KQDNQm=DqlC~+C#+IMzj<*d;L=5Eob<4^-pp2D+aw3dw)^~trG6z zQn-qkn;sXn+}Gi~W*60>cqw*C+qKbvmtu~@7;cJR$$tu0#0DufH1k1^3ql#Q!HtbK zz!ZnG^lYB&rnA|YW}*0C>v@*GyH1b?CIzIRZgoC(sLAeZF)kX_OP%~&*A-| zLI3&e9YmyRP&F)~COug>;hzvbb}u^vgC+QgZBgDn{2! z?8)-O%Ax6{H+%V^B6d~XzonSJ*jG1zTh65AE?gdNIWvvzt>$o7z)Y=Db>%zr$cp2;tCaDPr-!h~gj#v$M=DkTF4mgr-Y1FW}?S>~=I_VW!cm*-X+7?wZ3CX>3 znl<&sH*WZGHIHS&WV=V@e!aU#6& z0FNolZu#(#|1C4XrhK^Z5Qzg<4w7PtFEdJNvC3f*#OiTAnfPfZt49g-SARK1fai=5 zuI?tNdZkkY#j}Li;9!4!p3iKcX<025%4~{VjSmwY(qhapl@3LOmdPhT*y>pKUlOI3 zvJCHufh8FjpOL4{EQ27`fu$zDp#_+|y+ACj$H0LaOF`jQCK)X_s{eo(=I16Ab*kL_;y6 zKv|(?EC=LYZB0pBUD42!hlrN#$!h49Yq6OcnuhUfaOV!`_L)1jMx?84mi~~`3y$lD zdnbsN5;CRv-Govl@HoXeuzjMFg}0(Rb)TD!Rkg{rj62n4l^Va7R%0O^yGEn1F@q<)*cT57Yk>a-h} z5ujm&v)YWTy6?te&40>rvEH45zR07B)Z^q%b^KSH#HljIna-M8y*{f+W-3|76VI3S967x++yklFhKI#9#n0DB53bzoh@SDg)(qFMRr zU@>t3M-}|<9Dit)p1dgkWyTp;!QTwBRgQW*BpbgvjwV8$tBpp80#ip3D9C(}cs9S5nJcsDMSs#fNg z3NzL!@_{kSCHB`A^3`&`pJT>aiKBtXu#(dJvQ^n-Ie)k8@0wYbXK};avfTP}%q|-r znwF(*oL|;i_1kBdWpJfAW~n^LGV3+FU7lIdN0@2$7Cqe}*DUv1nQhjg+@w6;tj>f+ zOh#rplWR#;G3OY4rVoC$Gb3sIuYcp#l;7;=@X1l{c&~r3x6fbh9l|7clFbhMc(MN! zs&3(r<9~x^&*9fY{Pm=_`=4M$jX$0qA0fo z)-Pu}#0_>BL}0SRVFv{&4!!7uB*Q5YE-mV%xPNXraE+k^ts0SWjxv(xY4e}z3f^gg zY%bMDonk5&=Yui>Nr}ct@RIlzfL=avb!s}u&%>es?=8B3>Z$A`EC}>>pYQLzezAYt z?TtoU+D;xEFVtUYVyR&;%X1}_^?MuNJ?JD4jue%rbcchVw%(lTnRV3RmBEq;PM2JU zzJCE|=yY6+=Op{+(B&+7j+`UwP0UtqyuP z^oMsk^lu~3&HYlq^FnqISvqV=hV%6@<9|`Tcmg;xb_+|YWMosTmJwgKpN4JCEmhR> z?(NxZme1On>Ds{LpsCoV_~kBC$FT86?2q8l>1n079$G_c%}Rp&3Cs&YUN(GZ4vdL& zn^Im;$QAyt$49wWtzX=SMNE2Nd~;6y+0bM<`7DTlSgp9o7n2cSaF&kYa>aU2Eq^DZ zQ;yP&bkI$nTmv#vP`4Tw`hG$XZpqy5{j>4*d&{x*dw0m$Zvwk9V_(g{<9vNZ18>UK zH9X55I^JgP@GiR*J70#G|5h;=``RKKDOBkSZNEWSy~~nqU$MC1g^8dD_3qUk8)_23 zB?u*U3QM0Ru5F#aGB!7`->Nn{uz#0ZShm*-K`$mDY3wI&RY=3x>!PN~ORc85O4w=)t1%K2!d4AS8i8Sl56?*cqlg!7L>iVW6kHHud zIDHKD@F?jp+nhLQcZ0R zP5Dh-GO`%;>M>$9mskeDB7t@`FPd(-Mi(}E$!c^{6Pal)A<8>G@}<_-j@Tk9MfD{s zltk17d{N<^Js0lLX=ycyp2@F1$Bof19;tD4SxxJ7i}>CH)}_Mmfk2z0f8L+t9KIpqhm^6Ki}ATj?UMrlz!O4 zR~wJFp6EjQfn^_s;EBU5cLU{)2>l`Kb6v{s!=3Yi!JgH^%>c066=ihtxz(_&mS+g6qk6woK> z@E2o8!uNC04a#2TF;W<|c=8Fpm{0ODHE$p^;L-&H==k{#tWlC6N!i z0eLE^u=*f~;>{mF!fh#2BSeuB zWE!Zv*H!Fy>N_ww)H+9#FD-Qxe34uzBhEaMs{>DQIXt(31uU*6cqX1MP+N$K%&qR@ z?xRMhh8LQj)45URGN1OzTrfz%_K5C9_K}7r!l`nxI)B_qwT2~fxNL0PGi(=|0n4*L zx5vdkynP#9uj;dFa0m`X%xzA2CsJ%e8%L`FknuASgD6KML@2O}DJRAjl=y&GJuf8| za6@e$T`*RDql+^sdJAv~+fhr&mAWPsWziO2i}5Jy9`EmgYoBb5E=CQTKY8&QoJ!Pb zdJoPUGk>mwQOEx9^38jLr!4crHye}zvgvlqhlTv<-aVb?9y&ui;F5lvZk&E}E9k;N zVCeTa%ZVW%Q`sjU%X>%{v%TN;7EiDzLzu1;zWOI=LMI8dRYh(<-N1mN+7g!Vbaw%^ zyvvq7VD5z=#Y`Op*r9m1NDLq!l4^3Wr)sfXwto*s7dvy1vp9szOB1pdYl#7iI!$+O z;{6lta5x8h2Jz0=y*_0VN3CZXSuv2CIsC$;bEGn64)nJPSOEXf(D8lpK0}iqGL9+o z30pgPY?`8ri>AA3pTScL)Ez0TLpnsqYS=`?V09QV*re@E2CXR73h69KU@l@ivJ3TO zaet2E2v;hgZ<7yP=(@Z5V}V8^#T#!`-*%m*?g*+@(d0HqdN>%Bx8%iKc1T*AyEIk8 z>YXyycx55g1!3?j2Za>`#PkEljwK-abLqN=@O%^y7x-B=YMjnvy^Y%glU#?Ovw-H_}C42SOqoc1(P!5 zJ*`skw+IR+K@rO&R2T+8Nlf6d!O`B)HeETj?1#5olKHv^dnBYj%ZLNfL{!H*1X1J> zx<<70!AGP@XO3>XdUs)k*NPz!`=%n2uuGh+!PD~|+!-!WPo%c52jZT%61t7O$A1Zp z7McF_TxL9820yIds>6srm6pXxL_oIe^5!@pwpo=BD3>a&W~hkR9abZS^fw|BlOt`D zd~bZ68cFI=gb7n0aYNyc;*z3Wp*%s;Jwtgk?lZAovEAA!%B8JDbz z9`Fqo#@WNvRrSJfU*Xz#m&0Wi$A7bdYL0)ai!fO9@-4YHLMrngr3sXZs(M(YT=KoalXUUCgt~3iQR^Mt`tV4<(ih zK;g^6K6mLmFMMa43iwmrKwc{<8Q2UEhe*y#5}cjixc6vxMC z(2e(yxDpi*m9V%hi{!-1k`?+V%$S)kc?*IcWPhg>zU-Nu73tk(K_s*B6*k2 z#-Oj44pY$7n*5#PaqK)dsy`KPJ--N$0x;0usBZT}UL|=7u2BFQDe67z>~!6n(~xoV zy#?98X)q)68<-NLHx>39x=U+-$Y}B7F}Xw3F5$Wl<1?;}CmC!T=P{m7vbCxqV@?Cb z%r&^N`E7@Mn$6#5?0+A{W7NGx7@2XUvPVLR@3=QJC7kL(jMP>E1@FwC6#_SSBDJp` za)ju`BQE{X@t3OO2z00KpNwZggAPt3GUzT4akH4w`H@V;nZMzO(LkF3=i6D3%J=6rGsTQp~s`UR~- zszsqwo92{N(jgHX^nujq8Ru=3(w?*Kf7z+6XO{SDNLc77WgDR?32;@0s@oE4Tsw6O z>@10%#zeS05`UZ1+bWT$|I(^C^CFofD;>4gOWB)IWt@Zlpf@ZOP}D4>0ndd!ovR`fxHU^d4u5d9@}L7;p)%}%6L5ei0sQZo zN><{3&%;1t6~--tuX3~hz1-^M{`g(o>KR>2dC~KrYrW_l%IbQP9@hEMJFRc)N$&!y z_NC|Pt=gBKXT2(4dLCt|FFlo)__=$H8~V935^v$@&iz(;x;xa>z|);ZMW51zTC-X5 zbOH*JU4LS!+Wu7d7I+(v+M?kneWnk-H+<=N^uPX%p^-1VCY-rUj1GD+4w_a90lv*z>xtmB%l^}$ZNCnp8cIW;e!&rAFBpz=t z4VUT!Zxa68cr|Q$i*NmSqKkxOU}?0w$ao`3yt(042U2Q(j{{IL;ryP=1hfS$~tj?bb5qW)`g=AChM$xAX zs%QSErKD#)sao;bUD|GlmKp)tBaK^1(0?AZY7x3*gtwNVU4pp15Ut^rO3_>)2ubBP zY$!o{OIp%VD6ikLyIV-?y5~xf-6iP?nSHDKaE(UBEr5@fGd{EKK9@!n3q}OQ6RA*4 zPc1Dos+UJc3Xu_dxk$;qCSaaG`JjTC&9dSupN^1aBIz=B!+@^%W(aoo6pwc?E`Kf* zr~+b9s$Q@2g*uM%QO2)DXL>v8N2F9v8eceZ8>GQVpa@g75?zmHYJW{26{dI;!cf(4 z5-MR{HMknj73cpo8z~7c@4!~iKBVA1*$MV9w^fYXn){pMp8nfqBiN5^3ataI9e{y( zBN+(=@&t(gf{LFR0Alf9=Ce#}PJeyOq$9c*iKKEHy2b&|^p z_{=?>izCmr+0Ahy#(~7ek-sl}bJXxSjjp*n?zFgs64q+1EK9z|$4;-9Oc}9veDrdk zUFTH!NU@4cr@ajuK&psWy**5Vx&v8>C*!MhR9+tfa_fw#JDl#BD^sT6kAG|eK-U3| zjY5(#y18lRG6?^oXOHYm5v1OC(!679+tiUaIcV#GA(PcDtRS z(PQP+TOeHDiUVZaDK>+lR9xuwbj^+mF7wf`(`{Zij$P-?$x(lww`WJ$3)$}e;r{X8 zOPjP_YBx>P^Y_vQ7^3@@gDrXrz%wkjqnl8QT;2&qi@2W_pJEAlKz zSCc2c@5owy4eo!(9Z%W87)&nY57JoHgINKjTnh(5F&IuuyovV}2cBdbWYM?1zW(7@3H1S^!C+7Ux(_W1(%EF{=zze0|FcMy&^S%rXQvqTuDiZ&Y5uD>PtvKHYjSXz zo@Z~{-R{p2$wdJ}Nw4_1zk76iaQN)!y>YtL{Xegst%-}Lqkn0za8@)#8U>dJ|Gb6>wBzOhCyGntm!P>-OzIl0e8UC$s*x2Y z(i#VjXGvCb-wQT*A7$#0&#%c!mZRSDgx+6_)diOyeJEeV$BwZkAmb&&q(8ds6aR-e z%{!WRMhUlx?|+}qQoIAA5=phcyoL!+vxO4$C&@gWooD8R;PLU*>o7Y{Lzt@ZRW?m0 zkUW_>z&9$6En#wb5nwn#E+_)L4;_!!Q&=Nd!%2=_4x?)&O^u67&BS>XI{j3K7P0Bi8G$r$upDUi87=VoYlO`{=v*{GNuF@KZyWTAbLrSHI&)9+uw&!)v< z#&#omAY>3S%r7t1B+t~%X*M2$x3(epc|HgBiwI=0V6s-TH#CG4vA(YEy^?$(JHr^F z(qJn+()IQ3&9?Tpc59pA(ZIk#BJ&577FJkTUyso@O#~hi>A9?=8sT0KyC*xL?qL+grbxwbzjjdLw~Ogs{x*(n*4@~{McJ$%nyR`gdo_2Cfkd#uydk$ z(|nbPh};bmZ;;}M_pK&OU3-q~1k`eR1{XP*M1O%}2yahQAIE}_791~z^~=L-j0Qi5 zzpiM>M&}SxvTW^;A&tj}_4Y{B}@PkUcT8)p`6*n?#~I`l8)+GTQMzM>BWl<#T#Yo+HV62jJIHx9&dOd|49 z2#+>zh}7>roPt<28nej+jbEWo8%Xd(6tyWlYV`5>xW3-8CjLaG?P5(+1*^-$vvirk z{@EMskL)G#x8MFNpJpEmONn&*f@P$`o_|+@EDwv^J*TzkFy=r`49+{Iop(GtecZuH zz&Tq?6rwQf3?@#W6YbHxN$)9p9fZ0jXw*5>f);%0lQVZ|k)oplzWC9v} zdM4~+xUd zzMACe$n=MSI!#;#4B7SM066UgQy(o(C-6ED8=>n;2ZueJEu5b+(i03HQq4^#YRT7< z{SQOM?t%YcZyi=0U5(%6AefxOL}0_v2Zh2J^@$--^>Zta#rE0109w#prkD8wVCqy# zXZi}F+tVY+a8VR-kgPU9k~Dm3mVW^k22RygztEG5Y?ojIBH=nBF{u!A>5J8WU}Ux02dd>WtG5=wkNW*!1+| za7*G-kRqd52M3`x;5$YB6D_&T6L7Ok)80Pt3(q8bVRU3uBCNv4{HRQHzs{Q>#8C4p}#$9fFt+_t`Rx* zOn6Tzf)!mW7<7@e%|WF9>8U)~FS zWL1xd?)TMW^6K1hR0O4LT;5JZuPFSVTl7fnO@}MnhjJm?6fIspZeXaf@m@O*X=_U6G(AH+v-bL<<*1Z8@6uW{!upp&Fso@C8oWN4 zuaI%7xBe_}t)6+uPk#rKW58~kAw4l1-d+V<=Fr4Kn;#g7IZe(X+lA1-p;waBxA+}v93MeDMHZGD`#0l6IB?yFnYU?J>ZmR~(G{7PIrlV5WanZA}|adT<{ z+9?aCoAjpF#~~D-U3VZWl7&lvWPb#TZqzF2%K`2{m-;~R zuHEp2d>=LwOH=RzRL}u&qK|V%@1Wo`pR#2aW{)MRV3Hc?7|z1s9wG@|cU^)SYX@!` z2wpkq$Nm30I@~|O%(|>eoonhv{8L!w98-J3hYs(!YclkUES$J(#V$hjG$n&UV`W2b zibJi&ac*5eT7UA^m9*33bTOWQwr|bbtw}~1dkZB~!ZWH?dGVllmj4aGuqOaa$zB*> zAf2(*x|)E7?VsS^u0bjTCOXVy!%SdtmE)a-6JF2AyN#AwT1lEgf?XCc2fDdP4Sm<> z6>WF}Yxbx5fHyf7>3mjM=sN=Po!n9V^VlxnVQt!EuYbV&!o4ETDvsQcf_DCzSev#A zE0NqEuR)Z*v++47f~~rjG|Aw7Jq(hJy232f#fa5akOJ2T=bZ$0fffIaI&p?~ zDYYQk3=L>SUzKP0Q!_k90I49b0cjxJ3mM8}zl?{!fQCK^H05a6=FQl|j$kE`9{uG4 z)QzA0i!URSwMR=^XLa;sswbC&%qDqvSIH$YV zfqO?0=+$uJbMVIRBUv{Fv|$t2N2W~~%29mp24~<8<_ydi^Q*<&>P`gmV8fa>v~gjy z*REX_8>pBo!`yk!>nh>x<8K&}h|b7-*5O0nMt_Cr4vTl%{8LYVfCiJ)@pw9%5i>;{ zbo$PkF0QZthgzE1Uw;0%kDs5ZkrhAxytmu``R5n;IVdDlZzFW2QXJ_510La)F6QbK z(OK2-MG*FrQGzUZ9Q`cwjFS2I@kAC!czp_%o;LfLE6B=1Dabky`WUyQ41`lEVR+K# zD1RR=koywd{m2{e+s-02-ZVu$p*ibiMlNVl+l8Jmv%i?|2b2hv$nE#&qLm z%A#sf-kHNoEJ+Dh1~Vm=x8X!oYhW3i94<`$n{DIGn<*29lLfg_deQ%Mm=tdy`2WhTU%J)@+cQqq+g$e1D2g zCV3&WO!gj!Z>0A8O$(upa^{Es(I$AbeDHRq#NFNJy~Ahw$@7E$;OO}8WSV0*ILP?3 z3BIrL^9i`pNRG}v1|&XomDrlW3x?w6BYoAL!CTMdnq+Ug;M06=Vtm0H$+qbjP>1ho z_zZc4PY$r8*@r90@kHnDsX9<8xqr@CI?OE2Pw2upIw@<2t_?%4DK>wuZiXxj*vZ;q zJ>1;-eq;0D#>4NnlLK|%2SpQ`38^(xSg?=+Jii`1KRWJHY{{d;!STV9*I?Gx7wCrV zw5y+};Zo`&`9|@L#bwdW(sL!e%@*HWW)SbJdv$U3*Tv4GA0GaB%>qOWI3TSg z02Aw>5o0hV$EXnosQv`^t&S25;eM(H`f8S+&r)?qac+#(QZlDtw~jR=%NLm5;JvdGokLH#npPg_!!$^`HXz&|5S!_Y*u-0*A-7X&D7f9*Q zvQ8BqEEDleg2Fhe(i`N}i{9ZrLVE?q38n|PB6xx99=#guAHUUJAb-RBN}V_*^`9Ld zy*}JST?sb$F;l|OqzpRwjCkH6ex1Sb>)pZYV;XB8#eT(OThgaQdyf?CKB8BOHD`D) zkJ?Nc6_K@-^Fkmnq%uo-xFJLxOpu=F*&IJ>pDg?MJm(7$D+`QQqYY&c^zCFz<|`M? zxNt;qa~swdkY}n?34gw7XYhkEZ!K#>>)Z%L^_G(n*QsXaZlY`_Z1u}Ylxf>Eunxv= z6wwAFPv18?+*sb5&gM2_5nAe+f*n%_?eA^s9gc3$!!@DRz%*~xgbMVK=OK0Q+qFz& zN8{ny3X2g&Bq^ALrLw@X&ifW?4&tBH47;uW3l+oEmRnj$Lx0O6CP1|>@`y}4uE5@= zC=k(Nmbj~`um-)n0t{Q0^U(3Z(iv{)b7#DYdF9l>WfR^EsHqUjBdxR`B~tq`{V={< zT$<00rnrDZip&a(CWY<1U@u#x)mm4M%WA)L5QuP~>U@0UU|`Qvs?_UmY=?2(P(|mm z<9*g`f?I_;jeimY_&I*U;&!tC*h7bK};E zIK8&dbwuvcHgv@9!=r$W-(6GtMV8WQkf0;>Le#vOxiW(~QcI@3hMdoFH~_@T(Athw zfi|U}XA5|Cx3&eYeZ!izWYtQzz@jx`6t|_#>Wtt|-G5%DXvUaX>vv;X=e~S5JpDLi zE>fK6MwILan!z+&k{-VyqS->pBcFW}*7YE8NZBJaJ=cL|3$(BkxTs!>E=HNUHO$<{Qqzyl z=~`%kz<+<%gFC5)Wb9aGnOdr$eNW6#^5SaEmM=(TK{i*&ga%^b)_`-)_aMp^g_?v@ zcZGezfVuW^5e>=D5#7f$`Gfirdeu>uiY>^hURPs~gtQ4k-ePmC(=c zu|V}`4-6A*rz=M}I-xe(tZdEG0NuLgjW(=qynl)Q)S8B|*gFup-Fk1h1dP*S*OCVe8+hWVJf z2Y-7V!M<(s7y*nD$kWCq!%G3V%Bu+IMRIj464w_Kh{@OlinkUy)qplk0O( zanD@Sv%A?d#%ix^XBXWuP#`H8epm4^)NQ@dt!$!xzN@C&Mt^7T zZR{gzH3K?q^3n9@>(70!MpH-ALSsVucVaui9lW!WCk{%bgdQ z!NHlFK&ZNt6L3a)d!v)jXz`40zkC6?%BmN!F;13QNV?WTT%}5!B$;?dGTkr z;>`NW*IOTsceC2JMAtIJKr^i%1%E#NZ$}6hB-f|X*5AKEsdeNje|{W*UgpZlfn}>z zGw@a7Y#E}!=~mDLiI2@lB4!{~Kgvd{VqR;5_2_BCxt(Nd+4Z>DfSaJz8E*`)cGx)X z#t6Qxq1?*W!#uXut;)k2tjgWbve_aAldN0Hhn`IPNxe|U+HCtXbv0@Fe1B&R2gtr2 z0xMu>s;HI1WqJ+wAr3vIeYMQXWtF|cQU`=IXAmQl)Gt2oh&t8Ro-ckg(r>0Ijl}2U z(ThVTMsd2#qL-PJlyonNp`^-AN!w~7A*Ccd&hUyrP;pWQQWE$ZzTd%Ld+9ul%>G36 zNv0rAXZa;2=QP!Y1<6zQD}NiL=}+4SMGul|I9cTby{@V4sdcF$4I)z27Pt+tPF4)P z>hLM7C{p0+c3tfy9MrXjs19W3m~fjwUh>G$^=EyqFfs>_*~m_-W02Hw#@ca5({Xz; z>5y0&v`iN&iXYxY1AP zY)XMHxQWTomFRS7rGFsYjc>VFMXxblVSe)fM5@_}UFw1QhX>0UP9r?mR{1)4yxAP0 zoMrR6U(;2M{yB+uTg+k}U|u!B`PHx11C~QZLZ-kM0IVE%3%qHD)VBy5#Gn!|L2Wzs zBx$wvn6Pzo98jm@OJCMo>#7AXj@UYgEN0`-J^=LZq5T0#hkuPVvt77t+=ytCDbJKv zOa0&OQSSxDoIZKAf7p95_`CWDnb9^LZ+5Dwk$~J7+nvA_qoyU-dRsFrIQ|^`RE@QE zNM6tJqdPJ9HeA&FQHw%m3)&+8cHE>926a&a&K@utJ%8h-%N%>F-;EssAg!D_aXpL)lKiX^@Q9mRFNz`&~G=^`WFO^i1D^td-T zKG;3j+h>n=yqfWb;;TCcr^P_DmPLq3LU$ePLBHAW9=$#uBrki%gM;3S{XK-echG-% z(C@2X{N_0}Q3StA08c=$zkZ(n9iA;I(6Kec-EPK{oL%#1vzC9YG&^XU))Z%fyNhl^ zIC9y|-W87_H#m84#X-Gb7agzVRg0$9sA}CLKi@KV_pokoVROg3m%{VhGfk3;uz}SF z*bvV&IkdoW4ZCbS8ppcJq;D8-ok4m|R^IIA1$pd7O6Uhu?wPr5Lx&Kif-SjCcnfV0 z06w-5Gn`tIM=F0zMw^(Q%^)5`kcn4=huEBPGjVIn!3|II`Gt+#3oD}|72fpTEKn#7 zU)j){O1sGsm~?_~r)rEr|Fm+k~SDaa(j3 zz(Lw(k3uv zde)VQeZ)9;BT)C&5x?r{N{v!YUD~uj4pR5isFBs&Nd=IznFn$g>#LnxQa%g886teA zMpGuZ&~iAaQ&g6-qq8=?kY(AU1)>zWG~CR)exB0Q;y z>~RA&wp4Y~)lF``E7+mk{MME2Uz6idAB+Z?K3jgNQy)jgPwacuVJMZCox zaoq;aizVilP#bjr@bw9LDG`O37VHi5fDOGLxUG*dHv`t`FD@@rR5@B$C%Z>4U-o|v z_sU>$6Uy))qnY8|Q>`M5uA{NjrQWRL5w|BbamRySI?%mro~qmLXql0kHa8ur2GD%0 zxGQdtRAKE_HAvf6^$2NeW^-_~%RIvBm@x9tO^bR957)BYX2VbXF1yWi-*mS*`v>vz ztPx%)qz?`he&4q%YmOcSDn)Aez|Mag?d3Sx^G}FR5uU_BqP80OjX!>LFKrNW4smO| zM#Oh|T?oJ1HFl?t&1G);!wuX;q$rXS0cKd=c z(B2dj?bj0(Xc-(<;WlTK`5^40oFsQy3%_|)BEZadTh&=BQ`df#GnA+`RlR4*jg7(4 z-qCjQQ$G8JgXQm1?kUI={bX^jt9koKqhMPszvg_EmC5AGv*+^a+rb94c%@AZ`v~eT zjhuV<*z92Et=H0yBY1u|)3jGFeY-nvkmn6@7@2jiYLZ8z*FV zrt(dra^QI1#RrGBlVnZr0V$FD9)e5jpSkLZP2=6Gr4~w4w~{1?Rdj2 z<&ss$CyP$7^c{bBJ09LTqQ6cI3!TX1I{v8LJ%&5X&N+XS86Cq=PB5YYwzPIC=`jbo^v;Sjlq^}q5q2p z|BUcV5A3hO)C?(Wx!(>H-^SU4>>SXB0RhQJY~FS1v0?|*kLh?Sf~;W@PI_~$S~Ckw zz83h3=h8;bSoU@5?o0>g!wkrfdlQ`A{dvP9y}RSY)cAcJ%wm7NeQh*j(L#86{MZD{ z8@KCfut8<7o5+&DJuI>+LvUO`%V|he4@-2#S+r@FLTsoDXOZQ)ka=|Taat&ej}z6} zQ(W3FYId8k`qsuY{8rWmAkFB@bkgk%~7uZ@heAIkR-8}4^L|1*AFm~^Lz=ZdN6QmfMu~R>0d-z#aEGbB7mY? zho9FH@hIcr)mo5u>w0Z*v+ZOf4iABK^*hY~3`w^|O=kTY9#M75dwf_{v=&_s)Ndc0 z(d(Qaiv)jqEZ3vTm1gkx9OlXT&*|5inG!iANEy)Tr7|Na#y%RPtS zpZ5ytt8l`*XY2XFK=^imYsbUKorq#+DUD92`#D$%vR zQK>8%Y5K=FyeMvZvyGOIP0QO7_{3a-rLm=M`%IMTsw1zZDYQry#f*+Cqb{Pe4uB zPoLJIRZTMv#fTri%P5disJi!qG>|+oO?n4bnBc?xuYdh(=qQHC;%4damn7NS@9lxg zYWIJ**MCk{Ab~^D9h!lN3^&V|gVdQXRM;WZ+b=XTQV5Ix*(?+ykFJghLdlD2%m^X& zq8gYXl)b1%4G~ITRHLQ{WiP5xV}!eXDy7FB^Iq$Wuh#q^@^Tt6K=|@<8hHS%eE6bp zn_6{D&tN)Y8HsfOB5p`tZA1=pHiqJ>d+mQTuZ!kY5nugG0MV9>_CqP;RJG(?Ivayl zywC+vTRE0v-3?q~RVM1vBKvhE0gB4rrYkX&lMS40P-P_7%+jX9imuM$#$SS%S&fwu zO2mpotqqma!omjYh@v3`I#!PZR+V0vnwp+}qG*d44(ej8?MQ#Y zr+6xV@pw7vp3WbcjZjF@(vbhsX5tl)P7@mM?Gk#GI!%V^igviamwL1 zyjt?f9o}=z`2b76H)V9}X76#qha>^hKx}BM=Hs@I92iDT7W?YHghp!Efm9S$L9tIx zB?S!{mkq)zCP>cfY2B0B&7R?Sr7nLUPvQ%RnMqAL{0UZrzPPf(Gqq5*O?#~ds4>IP zZ1eD75Wq;jfkdH!+EN0uD{z_4FS^5QJZUHGaQF&T*!x)Q-pe-I#Ha3-?RW>|3%-DD_&NeDY%WxXg>aZc~A{^KtKnfV?2jz@Xvz0Ra!-Fu@F9RPv1b_}h{F>6ej-p*@F{tJ7elU+Q>rc*%K>S0 z;|P}hk0RPE^G^RsP8kl9}`FGzlGWO^LM zaMlFx2%>7T<3SCSmJWY^iZv0rfpy3cH*IYNyf`esf#!3VAoig6F&jQSm{LMLm%Bm< zkYkh~-iRfxfGIYygJjm`WckLdybmlJa?|5-JgizX`W11#x_)lY6_GD4XTV5F2JhH{ zv8t0w9{A7C1tWYgVz(*(IqDwAg3Y^&tcZht2K;li+;d6Zxf*}Y`B{pRY^}OtmicKI z<`V3(6vj%V$QyCcPsBg#0yKU?ez}BQZfMKh)KE8wvG!_~j}~A}=EVhs41~t z9Gz9rHH5>T!XLhsh|FcEgSuX1Jcd{}dTuZ9fwRda3QDc&ma$B;lkn0ZML|`VsQG|gv&;(9J=4UR z?^PHDn%dIZG$aNf8$%FD(=>Sgq?}V+FOJjJ>|8ezub&q$%!a1pvgiN|*@tmKW@@}M zBa5!vm%99m0`eCO$|8B~yrQ5XjL9-G_{$T1D(3A=L5_dep$={hEtEXOB!sJ@TmQK> z1E+t&4#rrmmhVN2H$KI3Hdj>K%o#oW?ov}3LLxt`N3l7Bg? zzns;}p4FOkR(xK&Cvq&~Y{{v-=j^wYpUl=;;0C9&)A(j*bPN;zIc+r+2A0U3=xHcK zR&8LgzkQ6T0Gc2CiH{}Eio$Kl-(3L93Ia)p-N1iJ)o&l^IJ~D-2@SR{{|HQy6^RbI zixTlc?7Qe^?+7$A{^Y`hCrvaM_a~DoOsDmdg{~TrE_6{SVcbT(XeM3gtenj4PP1XU zC{Rei{J|g%IB9@CQ~ARjhP#;c_=_C!MGje34)I8cRvVIITDioPDT375|A4fCWjvq) ze4~GA5w@0WD6ZC4|HrAm*IJ#(_+sDem<_M1<9;i6?7 zuKM2AjIY}4x1Z~k4Rcj~;getZDa!Mugi6R3)hk{wlw%Adx!v z6WNpf`)dQoy;4L59(=-=IV1!B}gX+ zqtlv)W&e(}gYPjO<@fAgA85Aw`+1u367t0f@t25dmnDq4R=D<|}{Z zr45bC=DpThHGkHbUy&hGOe4Y%XUA*AXmv(bY=0k9yK#2}abUxHVD`Xreo7~t$aUCu z07Y_NyUY|y4O8M-(R_Gr1ho}|1e>%lRD$)vU=zwi&p$IIUaM`<*oV0-BeJG#2 zsbUb|D)B-bG@eZeDW+spM0Z_wO?iR@2FZWy*ryfqoB3wKtipTJfkra*M*qsPW+2F;`G-DMZgN z^57SF@QXb7MaB0;68s_w{&6M2KaTuYR_*r`n<7F?ACqf?BfBD(U)lx#UbAA$lt01N z3H(L=@WlwZEkMk|8L=UYh*uto-bxa;ET}u)8@nOnObxV zM|w8N^I2Q>`pu*7HaFMSxP$4-@pQ@d-)$-iXj9MY-ziWAma4HFECq<+z@Xq44hNyS z7@%t1Zfm>M?P4wcH*{*2Ovt%MSJ||{=&S`e8q}cc4 z`Gui<+;K&Vrp802h;qO+nS?2thUf?7Wxv5T3?0KTc)+1@uq{kwizhcJm;ar&#N@2N zGiIFQfAT_X3pRfY;a1WTL`H9cvK?o6s19w$v(Xsh)T(Z}?`PwArhZaXkT-8$#6uWx=b<6Qa2Xipl*mKHKK-7N*N?QBy-|b{Kok9bEXemOG z1CH98uIn8W8|XIHZY$pTOi>ze%~q#%yqFHg_!s}j@>KZdg)>s0$Oz~9%GTAUV?ZnJ zM>YdlJ<2c ziO`oc z<1K&nieu4jY~Wd?IBYssTxdL0ryfb1fCt6TfEik8+{{kjlsy3+gGSG~kl<>J5yP;P z$jdSO*gh{7r*KL>==}fey?=KbH?lH<&b&S-CFNtSiTmV6}I z9&aXBr$x4;UQ=vschfTCr~BC->IZ-V8clyTB{|9NIJ=XG4WLjc6bgkxRUukA6+1^o z9&J}YDG^CN#|9`PKEnD+ zKsLBq%i3C?icv*9Cvm)-zmU&mitNWhI5pnp8qlu$jOH&vJ*YYqBNN=Qglg2g&YOS1 z=oDg7gdy*S{&wLFu(tr z-VK#Q!hbvX)mx(;Hi3_U2F^34=0l*Ov-ZsguGrvw6j0#xd?>+T&NW)2*Sw^@bMZ?MSUs1S;XCeW zuGHxXyoGT97rsRnUFZBvyD5v#^Xy6~OQ?>55hAH`nwno|c!Y zx98*Z;tv`Eh5FGO3*xwuF}c0G<4I4ABSzMofXq4W3Q_Jf^$YwQ=as`;`%JdYjlP| zR?yGU3jU4;XX!XCQc!=l>BHZrWegvp+itY+be%1vSuUY+BOho5q;1a)8F)=Y-E}tt z+qks0I<*a(blnN+Jd#cnz+1#jw9 zPmds5w_4Hy9|dh&>0rpW`XKTMLH6WYHGiv8(hmcb1Om}5R0Mw^34J=qIDL4lU;Z=z z^blXbvQG9uG`6F?w|j?!liss5SF3_Xr$shR+sW`U0fCs~3FgJ#?wgl;$21v4QLKFG z*Y1L_!MAOkX(BZ0t!(CwF(#K964+>9JyTgB?x4U!VIs0Je_;0>SfK|)_lI({jBX`i&!Sp}~#(0B6LW$Gs1vvba zN6!~_qW5kUUdQ3s`E~im9au;XNe61fmMy+-rNtglu-!2{rp9z}p@08qn|Gpb*IA*;! z#>hFJHK1NwLg-e&xr{Nls^AMb9zE+MOeHr7GYZYTXx7p9y3ych_eeEMm4H#xaRD3V zM(=6w3Ba^n5kTNQ`60l30X%f%vzz9vel|v)~cD_NDOc^ku-sg6Zzc~UHgr2RPDq%|S-V`X?Kbe%t6P627N}bQ%f|TXHR-vm8mf0UfLedf~DjjcO8BWWH;-GQ;I?*q-5?vww>hUa$1hE zn@NA9RY$DUAB~=8IrmGmycEzf&dcfb!+?ANd*wp_dYz85ayN-b4mjK_mH~5r2-x6$ zAWkkbmn!gq#FL}6mGF`y2-%nnH4)liqHH(>mnJ$8!gsa%OmRoP$lz!Vr*>(qfXnqf zxysb3p_v`Qk<5r7^&FBra?hr!t`Meg#!r8BZ4LILEly}uPS4L_`+)n4n=JnTd);V? z9!_zjFaVXO7d=x494MhN4#{!FQw^0Oq5b+>XURF#uZ5n8)MyCL@$csGRsP#Fwx37I42t@Em3XJ1LGK* z`oK_zwkkGu-H_e`4m8G}r{>7>V5=3RO@qY>tT;$W%R{>+Ylb&*!K)n(T!H%{GD8Xn$e*Tf_P%GBbDo*E^7E!*0)oms`CIg}<=#wDRKQuPoW^Il`v>|W=Y z{FZ_6D*LFm-|gr;M*E#+0b1#1y&z|1lJigiG#O5vU9V0{tlN9{4)=c#4);z^Uc4FX z9=$t+d-wb9%rUPwns2?jcteIkexa?}Ta;$EUc4_iJ;8Xw`(bx<;t*f7Q~}j(j(6P5 zYR2x0?K_NpscmrH*d?I4EhRX1yB3_AfRkx{9uLujYLI*?$s0p$N3ECr!`&9;L};D7 z=)Zh<^zQUsf3SC~KRSOuj*t4L_VJs;cm4O^4QCuM`XzfaoGTKIP?Q)i1G9GRGI5+O z#B9u4D%EA+U(~kUy+;e4$W&-qLx@A89dzoa!;shutu_LGh0+elO_Wj(Jc@jv&d=|n z4Z1fL0-c8U5=*Z#thdSViM!Low(}Yu zh4yaA2JK3_PCd{~t+lw(IZw*%yEJp}xvpA-7hySCy9g*Qa8-1W;!HJOc5pM`B5~^)T8>Cw&a4lT}0*r$Hd z%pve36WLEeAW^0HuE?z`T=!ZK!W?ovY#p9fsOq z^6BIzp3Oo3j@Vn|&0ppZ;{x{vt?cBu80TOOOG~_i#t=PpuN!CJ$VgkP+(AL*=mnRe znDcprs)H1l|-T}uoqyoA>qolx<<8`3aZLO}p zPm0ym==ZzlbQZ%kk=h`x^JGmQb?BaOoc>Ayj73s{xK_2VvQc_I(>~{C z$z}X81)89vH4_g6Joc$Co78zQN@6AYFSLI(;mp+N%0m-VHcCb*`UYtyQ?PeXt0DSy zv6DD7X^qwIh_5`M?fE#qXaUB(=p9%y;Q1ObZ|LjRBwB6JTo00~YiQkC)i+H@Kz0=* z)ca~xoF{oQ875=}QIOo$ikQhrqj#OF6*L0;fF46N?TVL&rKXpJuP6^JHBwVz%4L7> zFyUKE9KFO$AL`RpQeI{wzQo6Wy|c~TzDxAgPtlBoN7@NG&X1B$bR$in9c3O5KLA3s zb9)}YrX76;H!{5D(sR!!03|HL!Hu@A0Th@PE^&AaQM*W3`|5PTAM6Hv!Or0Ipm91P z7Pw4c6xjAwXach+nqVJY&XA(#fx6fHcSe_1 zI1m^BquoXQ${`LBRku$k0KtSgY>2r!PA=knTwDRDxjz7uGWLnp4UKe*s}8#so5ifnW>>33^fr)2dBpu7Z+U)lu>`; z0#rkt0<+MJaUXx1wh!8onsl)?o$6ku)A~+aJ}E>uxbpbzwakhm!L9g| zS3DYF+#V#D$|zJx(Fz12bB$6d5{TAGMh@Xt4U5rkp~UwTHzp>d-iGFE4Q2xXc^|c2 z0=xFjF2EZ=UkRh>@UqJxIAVXA02dNdS34o6RH%j(c&*zFmLkx4W-xyT=&qVxK{OAq zC_RE_d(V%K_i(Y%HY~T^81{?nB=@&lM`)#L9mKHxs4hm}6=6o6U87>*tYn3-#S3NH zC;_2>D!Cos;ojT5V`3gA3d!L(YCUdAid1(bS46Z6C7!cf3UCU~8)p~kFuKVmE98Zx zRYQ#(={ic;=_z%GTH1f@Qqh0~s^uE;=4PSn$a6ywNA{?*4C57+FshvU@L1D>vSs4% z0$rE!hx)x67ek(0lNLy#lakaL0oz7C!8O$B1u>8a|7NJrHU>9E1<4038-DtXqVyKj z2Biq1W>i{1OM6RoHK>zDk^>u$qDz*&JmsjW0)G@{#~jZnB+q{toz?r4^y36ZqJ<&| zsvm05%+EMty)ha=j+=8#Ydan*n4_7is!|}Kvl_KVg1|`sB|XsVvBgs)0FeNy1RO+IBBLQcS1Lw&4IVyr z`bjmDq{D>Ea3Oz@P|74l7*%whUQBa3u0-Tb=+c7tLhAaU%^B%!#);%Hx)FzW;t2k0 znhrmV$qbd8pQl3zLb1Gq0%5abL|}u_^Of_-%3Q-?Ei3tE=$NqpEt1o9!0MvFgf&YNUUI!m?%cv~&UkD<~YRrrA~cYjT`T%LH*TpRg0S|D#-g6(w^*dSsmDX5)n5 zUD|c9Zva46(M8>BDHFhe1(eYTyk%rH*i{;t!_b$FxY#Fi#@-6D!1Kt_q~R zcSke@Sw5mOj@4hr=?B;c&*D-wKru3*Ap9KPW(`h6a;@YiQ9oHLq^UzI;xoJ%(9CxU z>K!Z`rG6^r9mnQ$iP#2!A5GXF@)q$9*02EvyV-xu={ z$qKCw>Mi4^&j8@Iu)EPHQF|fCe|;cUqtKVby=V_MVT^}o5y0q!0(llgOm{hp&SQJd zs&aqh!O;Zq9Zd5Hq1r!)jwbGYL+XQCa)zSP7!m?{ejb2&etuuDlQ3XRhO;DiyRkgc z8;gHzxApj>$W#^wK&$PPnBoH%W`X{QVnYZJOlM%knX@Y?HON{;&|(lE4d45^t2p+- z#mpOZP!NVT;!F9ag}g#Um*P1n*9<-~&JTY@FM6*I9Y7^G_lIF)$PcL5jyUkDWYG(klZ|QGl(O+-X3Nd*jbc}?M3Z@ zFtvexRGE0S#^BamiyE~ZhBS4F!{$O9Hs|8t_K#AT?;hv&46Xy|aDLy$z|ocGh3kK; zFzE&%n42NZ>kQ>@xT`sUu~i*d@;#~nib9;FrdZa8S(%Uw^;VtBbb96J^q~L>k3aNi z2-&KvX%sXq(v#t&bCu5!y+_UWIT#%;Dmz4Ovvm zZ1@3>(NLt1V52EU$s2>rr(`(AT?#wg!^>P!TiqiZ;;WCYbokUH4-i>az;n`Q#F=A- z!P;b@p+WFD2s>p5EI&7Bn!YM}ZIYQ77W}lkzzPE%$$6VrcIH z{(Xmr#*|IL6xJ-}P7~Xzv#O@P`?_?d+1RWdSO;WNHu-j{J#aJV1yk?hZC_yJsR0}& zpXTE~0S~(XaF!{w-5^TNNCJ~OXJFWgKQ39n$xvR#3#J11*(w%%u7#@g$Yb030bsE| zfSUe-g9%Cz&AszEj`q#uzovh5;GH|W;f3JP`b1g)-FqV(ZKlOn4^o&Ve3CSqv_blu z9Fdbxu{wDr*1$sL4w{G^Y%EAYKvT{}MIhM8F}G0;8;+8Agl^CZv4uwCJ?Ekb&vbf!~hXzGeAChUKJBiK&KVmuUL zuxfk-S+cbrl;%2bRhp1(0#%|bPT@^MB+J_b&E4q2P4R%fp;YzspAZmOko!zdwgvFU zQ776Gy?J>IY`QXGgJcuG%IN%p=V|oSDapp$C9+|TIaMv5F}dhMAd?q>aN2>)aGs_G z=UTLqK)G`@H!zvv@K}G;#qC=h7ub;im?mg3va8K_@*3kH#KPg4xymMZt%`~#I$^>% z9pk6C0yzTKleg30#AG~<)@fo*x30I7y<^v&#DeJNo)N~BARnPyhPG#DZtI!tov(2r z*|Wm+lJ3`{t+ftZ^2M6f1gf;A%x0>16V48D$q#J;WFIwl9sz$-z1m%Knz+_ju|*4n zfy0I|#}b&UKBe!HfG`Ff9%kUKRUdGmhiRFC!E{KrVIFvj`Qnpt0S7}w6SvgqaF`V5 zQ&@F!puCP~tX=q`u)GL& zkJHlh2DeRWeV--f&WSX@#MfyJMJ=Y+lorpify8LykQ#I{z*E^8XQWZxXLo`x>DH~vH`igKt8^V9kSZ2I4^K~o?$M1h$3(o-Abhad2dB6*{jJ({b4)UnR+CCS9cIKUXS4 zif_G45;2#%#C|YtRpK5dNRUZdftCl#R}Z^ zJgZh$`>%fwqMaB0!~MPJ#lgwo==eQ5aT(u!BPm+%Cj9Z-mJwxU(y8yNJ%R@rW|= zgL}ADflrTVi#V-on3ih!4E?6$kfDS)pCIjU1!8}j>l=SwTi;w;|6z+(>6ULIhq;lz zaVqizlWP`rZ8|-_i;cBS(*<3#uoqf&TKu@S{+G3lr)~@0 z1y)y?Bl)*Yrsz{a$M&4oHE7VOYk_7!_;($72#gr5Tv--*C5N7=4d^+qvMzXm28KWu zAg+IOI=}HeJfzKs(sXvC(^E4#MDFyo8*w!_R!L+6jooX5E*bWZ(VE{S+dls|b$~8L zQkxdrXg-{al3(?%NNqJmY7=|fY=V@j`el(q4{mBl4%`bctJMEvp+~k*Zb3ipfnqcd zM{j{6=yue!E4%#X5<=f#s9;-funz&pliPIU5GCOdZ*a-v{~au$lz5e!|Q{q zH^X_Cj>=2^dp8-E@jv8`_W{%p(&=45&6NWhQTIOGaz##|bqsQEGLk>_WZ@4ETB>61 zfkH1GKQt!B$s4o!7h(Lau&L=}{J+I{HIZ$$K++addn_v3h?; zhlAsTXKw}vM~5d;*L$pvX9zJpL-TbZ7bmM@aSnbVThZ&RNZ74+l*D-EBjwXvaOg`i z((jadD|RTlD+C_^fPkS(ZuJ6Of50ARj9CQTsb$r@pI2A+fD+fU)zx3xmt}cfY<>T| zLTY+d^paTdd@rAVPmY(p>&xr^HQj&S*x2~t&l{aC3<{1f7cq`&2V6|p%bbmPcpv~x z7e}d8uujhcFJyFeXIM2~_GMPR5@iNg66?EZf+uQ`cR zr-sy;GR5f43N0Xmt-Cm{^wBo;j^GO<$u$ulC%Cd%p9pdqXuacus{O{V4bKH6;9a zwMWNu>YOTT5WR@JH9UKk>#={0rYHTzMF<7&jRC><&P%R`{lQUmnW)2Ec8zy>$>btN zw{6DsEG|n(ybCPD*JIGx>9f7lzxSW6L(X{A>nYX$v%To={k5m-KOOg9y|phVx3fnE`Y@oXmtp} zQv*)hQoc11Ud};@CtHw?O8FwhXOl+ak^9funtgQxu1|G<0k0SF45jqa0UPZlWN(5w zQWH5$gr3tgJzJ3(?stW&ih8q;CEZ2Jq)Vo33mz+GKU!?QG5Yl8)KnJdK1sUcKCnox8IsEk*i;bo)jGm z@c=p1vEHAE&_HQOYhdQVwBPJZh`JLR#!d0rEMk8SaF8|~OZ~8}d$euqoUkc`rHeLj zo6sf4Xc9rdKqtm0t6Ew3aGFWZNwgQW5LRU&mhc^it4Q#v ztg@z-L4nP_9&mr_)LTRQlZ1_@uI`KBI~?bxiB0PlUd;qRUPo`)fwrcb=Kzl#J218~ zp#-ZK25J)Z=m;BQ2a`cd+C>-dtxo*WYGWithDY=NVY>DYKq#ouD&EyE4Y?kSvAJC| zPja!1qRQ2n5lT?ROzP*`%BsWLBinQ52x9sKPB;$2# zoLIvlt98l_WJSb~^-vjvt*^9fV$yHI&(o|`v<)wON{x&z#N=JH_)9;8JxhDRAhnK7U{AC_+gb7EHD8Fbf2R5?`7}&yV5t*4ein~Ofro$+5&VCgol7eAlxC=QPLzwR&tIvD16Qr2 z9Cn4nF5s0-#t?Fy4U+<&+o%i#ml=E2N_SW7s-d6NC@vME!PR;DD=cZjjf}&I2$oi+l+*dUa)&~%!w9t;X+&w$) zlF@&i&DfxyILQUN>^fXz=J--13^xO z51y{2g+I-SrFn>~$QTs!s{>=|=qo*5t&bkLkeyG*Hrq&$o6OVNUG18#h@L@!w>YtN z6?3I(G-{>TkXi7<9M9=~a`$w$e$Kz%9BUzK4rn%x4^(WL>E<;yiP%6du~9 z$sqynA}Ya`WYa>?UYz?Vm7eGyh^3d3f%Ag_#OeZvD*%L>%WP~nopE}e2uJdJ+zNkf znOq0$XtN7AbSfuQQKgB~h;!DgJL7emfO1}A2&$Xak6&Ex46B{)hkNOsoblW0Klm!r zMmGxej0wIu3+I83pGbu6)OGUMAd=jU6Td3dR*yL|*c# z%ww{|NH1cWn9$H_>0QRU-e~igI?{hpEU*pmauxQoLVl!ibXpQj*_> z$+|%Od>6f19JM!4V+_$}0*wNgqd*-VZ{mDZG}vR}k6AkMpn>!MgrlTQ*GZg@Q}UV@ z!R@A)r8+V~{lM6#E!D>&`h^iaAfwZXcHOUxkU0SK7Tsqd(uHMts^&=Tx)y(-bGN{l z3BYJoN_BlS8uR8Q`iFjZRzjbnAyW=vaM)luDcSdLWD0k5<2}gh5fyRE8h-?JpU7vy4Q0Qnu@)kkdA>>X^sd3XnHgGfE z>G`*0keh6kxeT!#^dN*JLWec0Krz>yZqyx$k!C`yp;3eX*PH=KFAti_7fI zcj=(IW4pN9l+1NC98v4xF{hc4rQ0s9bDFS}+Loi4Q{AMx7vsgT8i#+vru(!%ot3@0 z&JGaRn%^8A3`8OttvUK^e5O;vEzFv;V2?e^GRz6@IyU1mTg_{iRbyOLeB7^>@;`kB z>~9Nqh3l7&a2vIGcnJB-V4h0Mos*#RY|(DYH;BUXT8em-LI||7P1#+znn^aaw}<atKZOJvaQgIp{Sp{ph#o%F1d&(O{i1tl?RK3d%&&{bRfiT8hXrZ`v@oOjPA)3b-s z23dO%0XFu`Xida!PG&qPn|nVxE>bW(`_EzpO1~&FJReMGjl&JrsOUO|)73ma14QsD2&&IbT*=7!JJRvGRul&Qq8L9 zZ%5G*d%_@|d;ov1iZX+%l63MhM)Ui=lBh;H?qJ7;@EcD=Cbg9}Ck~7iKL@Epb|S3x zQ*oXV;9KX!Y!E)|=w)(Vg4c-u{S_YTR*=amg~y$x`yv;io_Q8XCwV*6qJPgKhqINHuuLz!PRA`G9b&dh(<+H|zB$@dUsts+BXq797* zW>2@vote8|%yAuo0xgMvxF2PgbJaAc;?bnLULd$nUOoc0=v{Fv*=Q?cC7@o`Xkt zfACcXFDidb&Tm+`c!HXnRxUzF(@I-_wqA~ZB*c&Pv270CEGd33wC5>vO#=LuIn{>% zR8Q@pT0yTY4igHUnuY%lo%!!<*sdeS5(nqO(e4pF_bU@xx%!mJItQ}SOI?zAn}YFf zkS?DE8B^(~sPQE~{og6Ig-d;X09A;#MCp-$Joyjo6skn{S8KZ2@>x+z1 zQbT`L%!q1^@AR1xI}xb!IKL(bidi%PZxL`Qxk2ZNQfWilrmofp`nna@89Z-%IW5;l z*^LI9V5;iE=0^i}HheRL^Q0Lc?ZRZ};yfzsU!XvIO!I7VWfL8_=QuoBU?wV^%U344 zirNJ{CC4{hLw=eF4rb`ofsbi)?1_l9c}{=YPr0ywPDZ20zHz}O6Xc%Q&1+^JY_Vy# zG%SEqXfF@)XRy8-J?q%BWgQ#-+;3MqV(LVKS+GNV!sHsCkt;BH9VF?4vj|R+f8*l|+D2{$$8Ue4 zlj+)%AEGtzcRRUEui0tcR7lo5vD7X)JP9bc#=`595~)X!Nqr4csRwhUkr;S$dh$kf z>uN89=NlHIzvr{7;=@l0tu@98@hetb#Rc_^jlWp^gRiT61ugt->uL_YHjR^FrDx$@&qo=Otq9TC^aOJ zLy-`L?@5sP-iMkd4mC*D!@*QbT(tCnIF3LZKPa`TO!sAp7GgC^GfmRDMAOf%Y8#w+ ztpV?9O0uA#=L?wCbcRLY8z{1Fit5&;MkYX;n~|S#rM!_?4IjdyO#_0cbk2VaD4gL- zr_x!`wJPbI(F8Av9P)5`z}AkXEogq`tih$QqyUnbxA7dGNt8J!3$3EM9nY2Ed?2&Y zMqAuF9Oj}9x-sin3hDLE%?Wz3Yzi}vnNM#Xs!={7X20MbMc@MQVfvQ88v?%)4WjgL=hfa)%3<~t2@^`M7-7NN^f;~Tv+`-z45MJ zuhqH9R8OEYfT-!|$b{!6wIGF=$g!Aix=hpPwp)2M`8(L*=FUma2M6Tbd9k>#wX+IQ`#b$3Qb&)X9zNxyk^@t6WN1&cA__r6Tx$UF zy+C;p!1oehBc*ni{f_zZAxjN^@BWgnCo4CY^gZlDx@F!_YDa%vhquO4Mf1r>E%uFd z_@DaM(>mMNofC>JBSDt|T$QdiRtL0%N+gzDI9OX7CyK5gt|NkTuHsP=T+r+jDkShf zIG9myP^>-41wSc&ZUS$6zO&n0Vrg9ayj+_m}` zzUuCdU;a|E9x9~gV&+lGh@bx#d^}4IQZ-c>z`t+Hs;1VXD#`W7_YNNK9X!mV@a&Q+ z=jk}h%75tK$ps`Z0p*_pK;AD4WTxpzmZL!)PYNhFh7`D-@wF?E6@MFU@VJAvfR5~- zF zLqEwl7wl~(kBf)sZ8wQWM>#@U*eBHeQe9G~YQ@PzKdmv^S_Ak%`0Ua7`2(QDkX#A` z`-EfQ19S*r+}_v(>(iieI$b~$xAp^I>H-Yl+-Mc))B&fv{Xrjp(-UC%a!Bc|#O#Dp z@~+BF+|+vS313<3{i3_0Q3lFV|G+*s2ZzSBy0`7LD}%{#=ROHbt%FLlQ=t7Ubx3ZX zgS0Yov;+qM(K$p&nsVF;-3sUDrejw%1kKWYdn$@T8d)Wox4tN-L=N*`ROZmrxnKxU z@V=zG^U&I2I09vVcj!oV|DL2ej0a$WyN&}=eKr^f@rNA=a=Opsbl}AIm)PzXdil`g z$Pn)p(q+OMxX=0NvtH=>*>#!m@zv4M$%15$Jcf5U1XkuGKOXbb*O$)o*f;h?uAcV} zld)^b*FN+cNRZQr6kq z-8j2QhnPyZz)Yt@(0$049iG=-DWeaQ>_!QTS=62-C0K19`DQm^X~W;<(XGlTspY$P z_L@AcwL>s+E%bqM& zw8Sg2=3<mnw;&IIFjRvEOHAc9M@tNZ7==IUT;o$V`-p-RizTzHwKJ+LG=I4!HdcJ#Jr9wO| z7gd?rGpl>&Lb<{s6EWNB&fzpWhFb~G{zcN#j|Cwa>G;TDvKO6FA1~Kz@KDdfbC}4B z3XwYn`~#~HO0<>ou}cw3F`@mQ+_@ph_^}jV zekP>VJe$ZD=nu?wOFxDdEm6L2$^ zM#juh`QfmI3voyN>mti%F!wtBp#Ii-vG?*|=jiod?>N%t>}C!72gk2p_7C?etXAoN z-(PN+HD3X2vEAr3xumr!kgD@Kl!>n_Psxl}O~}QT7fZE92Fd0Y$A&h?WsQo>%!q>{In%p@4RBd2;2q3-4r(NBp44xf&A0iGJ+NZ%dqnz1-6^0+ zH=x*7RY40{`-mU%l^l7Yr0h*?6unV@mK1PS6E}Az4`x?inv=>1zKEPkEWNr)Mvz%{ zJfm~Qx4vdkJsBZZj!u7)SSyZWcI(KyBt0B^roks>-6O*#_-3+B-em_>O5o`#b-KX| zsBbj5sHR51k{g~4a7ew5hfV_xsGnc(YmFUmVCFon=LocBoSw5;bUiIg^0175TE{Kz zs36la^HIiibETA>l-5#p5R@QT@dz2yy~klmxmQr z+$Hp7l`teVpz}92%ON?`Z0*fAe6$WZy_tv&Xdw5 zM9ATrola0)A^fgwG8;8P5aX(UpRYwPIM`0p$rJ)L*h37TPvt;23b?jLlF zuQ9G(cxe-~<2Jh1b%GF<_H+TyB0OcfVS?fitbNEFj}9k5)sm>*O$7RRYr+c$monIp zhNmRnI1Qvpckl`$Vcmu-P!uJMfo`=<0f-QJ!Nk-7hksft!3R$bW&v7%4m!U@0yuwW zQZ)4-yJL4i1PRum(2q9M)5L{(k)CSEPuKrc?NCsXeJ+ReemA)u>27{_YP}4BxPP|4 zWsU|~8A^H&r(+By#|-xnj;&w!(y@&%vl|L6-BLdQ|JA4+ZA5F)dhcoUeYB}$?Kbx6 zw0eNy&J>dH(haibdJn;s$N zDG}bMjhw`T6x}kQDkf5eiG$xLJoNc^X4lK1NvFLqq6EuvLGPn90K!u0k~o6*eqBAV zl2-tSQCRtJp8l5pquxZx_VVTQbTS-I(P!m}2Kxq*U70fg-C$wQ3|WBT;P`V-oJ0iFbQ00+G=ukeG}auK&= zvsP8QILE5(FxYSl%TVN;k0(gQNWpC()JWCkdo_?G^}(JsH$fQp7vl@rpu#-aCC_9T zsAz@zRaW2=6{C-7p+it~Ru)f@$D9`UzJnH_WX#nL=c(GW(6L==W$*xmPLb^AC`T2- z$*h=DKAHl5-ZZs~%=BkO7wE912{e^yCWTt;UK1ruL>Z8&Z;LqDsl|YZKzdp;VdbP2 zL(=2FKt|yo!z{~{h{cpXoIXsa|1nad{t!VoJC)>Q@R4%Sm?06!N}xEzS1-PFtC7-L zet~MEX73Wi%A8uE{d3;4?oGqBD=m#0u;@0&?OpVLIM{5PRt!rkc+zD+Ek90m6}dNi z0ap;u<7kOHIx-@`zaQwnOoLl+8+PZbCwuFpL4ioo-p9dM71|gqBD81$khx)6`p(GQ zr92}qe2sxo7XH}RD>T_qB<6ZfYro|QI91B#@|KFLy!WfjBEQbFe^k1yTzXd8ykFR6 z=C>JtKtDL)uw2@J{u*>%p~&F9uJ|ied=C}h%ZiI`?gsy8Ru?91@V;5ycwB0{W&s}T zW4!HuD;5;@1df+aJs+4EY*v3}CbC)m5Q)Pr7hzKES^uMdFXQOvI(=8uZ}_&;x$4;F z7gopEXqs7-0)z#7@+zph5Mi8|5doi(z}CK}nM z)W|l4M#jBUkCmlp)iwhN{6Fwh$H%zgLFiJgDScY$J?Mf9t}QRq4)6jT?nilceN9&H za|JM;fn_@r=?Zn?g8wj#@jr82YBq;$=`p02j$WuafL|2&iz$P(^unNGL%IwoJMGC}d8?yLp-^e{W*x0O9#o31FU}@4nwhritmNp$wK)H8{FB1H3d!w_JPWoxC z>TPpir#R8fS=9zo?V`TlF00piQ9`nwFR(m@t)Y1Ui2;3Y%O;4}w z)z$vN@#<>F6&Pr}w=ah|ufnQ--NSpD7;aj;l!#}T5IY^mxz>PW7&HT||T>B=~0 zg-!LDW`6OLYTy7N3)iAFhTdX#27{5S>ACZ<%X*diY(LI%ya_~{N^os|w?{A7rGAk( ztxOzB*drQEj?zK;uE|U0M4Hd(_|*%zVLYEpbH-Epyu79zM~42}`a=*&I`|V-urp`!%?IbT2jX0Wf#AE56H-WB z)}--M&6Nf>PDFFwco88><~W;~AC9)|0|!v7Xj-l(mqgn5b!} zVOlnhkowN<;X-D_>JLojJ~Y>}^F;`Dt07Z|E*_5tltDN@$ZgHZ_$mb%HGE$FuJ+~Z2Qd2=Fv1zv!hbDB06gW>jCEZPwg zISB<#)UQJG4<{b7XH%Qzsr(sL<>^remX#P(Z7 zjBmgF_D=GDtVSGF(PAJlao6ac(00&&fV@jBN|Ql4}kr@PQ#}Z~(cL*iLMpEV7S|nA0aI0>)a1 ztII%H+Av~`(|$G?>T^%c-m==Ja|88|<#)a(LNx+jXPXs26FdHv z9L}Hb#NV=QKn`28w!__`mE!H*>f@H-X4%YevkOc7qV=$0*8`_Am}`4(z|$AfWx&@F zgx-s)WIDNq8xdC)|8~N)E}IYTyKb3W1LHyGmLU z7V}{VZxS|micQrQ!ig#ZYD#mE9r%6tQh@SUbKxFMXQF*A9%-xof9zH*GM_HqjrA}f zjgctpT5FlVUDpXPJm2fLx>MG_)ft_CPsd;vu2Q;k?7aUSMKw8$x&C%ghueEhOFBo| z1`xLCOhuELqs={7!@U6JqfMhT$=pg5E<3-Hc;~^qCo;GG37cP<*7y7imgofYL-4~Q z4O=wLF1j2zBY0I~wZGOrDNv6}_|Oa>?k;As?`6U>&G6|f5t^`lq6~qT+iAzWn@p+E_zaEyS@KnY6&iX_4<;@mDJHT z&oiYnt{i5TRZMinxu0UzY_ZiaSwT^KFj!l2YUnFVD>wb4O;rpApogK_y{Q7!-)b7e zGy<)Prz@WV5T(-XWkF}*Bac)z^?+E(talW~oy%R(53-8tZVVbb$Xe~Fi2`&_&HqvD zx-#>#c)hzbJz>X2;}VX4L=Jj(R@&l$^kAfyb%l%IU(P#jtOonR*5P`Qn>MXcl4r2f z9PaAHEz;Mvp1aY2Mb|vR2nk-bUo3Xq>-E@j=Jx#2Ju^f?q5Xy&lpAHA@WHy_^^>5Z zX<*JEsDJHG>zm+w%KZj#c7ek$F<-7aPAj#go0>$Rjh5}BN8ZwZ#f1KPAI({T*EiQ7 z?VH8NEOnPx>)6cMtNf+A;9RksC$n6*F5vnS^k=~k<@!2%lhJ}2oSa^Uj@4Ba*M$vJ z#nc}0Bou^AzBt=HlG?}z14PhQGt}4_#Dn8!dRmzS3RU6~_odp*G}Q%v70OLh54I*lO?4&I#;kc3 zB?x<~m^K|pJty`ju(zQBST6-uR!c2V0&`zb3koMO z-k#XXl?If0)0E;SXAWT&CsQUzV?N5q-v~)4LV@LMuOWagX9GIi);-ejfCB)2)(w7v zyMFllRXjTbe<+8UXMv_6sNZCES%@mBdbhn>Wx-rFukW#`qVK8NK=bs|KKSlo>W<~T*r74uLTE4UBpM~1Z==VRl=8S9F61CoEvgJP`J21i=$Gqmy zV3l@s)vB%ccKy-~Pn@(Sseit6h{Vkm67!ooSO9IK5Ecu;%*SHkAeeN0Ol|4LKQ%fC zTZW9*3`v)SGm~)MtAS{u$Onaav3g;D5n7KcK}`ZjjLGsRk;sI&O-TvxpzH0Q{_U@! zd8;T2SFj67(QJbrk($Dvu5RM2t_R`T$RX!l8Ta8?_pZz0I)-IigJXF8%y>*#(K$@08qg zFNypb!8YEtO*JqXYXoPVSm8P+Yp?Au0mN(F!u@_b72EE#JHiGNs<6nyHr$3*17jFnUs&|5oQH2 z!r#^9% z1rmiSTKhr^QKov}{vkluzvjYjAr@B~Jc{P`scn*wETQ>o*dgX={1#Az07| z7~(;|I!cfQXv9_&-kg)$Me-G4f@QL()WGUWGT;9f?J@VWx z!Ea?U^1q2V-33_@Y9c-TKAf;z*LhxwV;-Qh8fsDN^1yCAvED*byd?Uw>tajP@rssZ zxG}vq@c`ULEV3-8>6c}{D5n95?!ji2)4d|LH;>=Ac@3<3iE-j-vG++)O7S0jr#Cly zGJMBW#g`Pgg7Jla!qEG>ajwOvAi2u=z~koaO0_ywWa08GE~k_bdz4W| z>NJO!)4`P_(qetyy49(|_?Y|CXHD2W1ahLl?<&Z*m>+_F-pvgycDx1F%a(e5HZ5u8 znm2Ee%>a&&3u>)i#vc<$IMm%97fBM?ZNd2lQ*?hZ;EwrA3j}-O{O>IhT&g*nSp(6q zXn}jszvMc1yV{C==LL^@q+!xjFTW_Bmf00N_Mv{xqO+Ne{SG|Ueo*SCw$q7PaT7(d zn=Jp33k@pY^79>xUN)je zk>PDTPDgO-oua`{DH`zDR#Af3V&mU9IXBN{ok6Ms6cRKVnnX9on07HSUwWtA;8}1r$7p~;w(W{9vTVacEy9|<6pBNKtBv8^re;R9leB>y`TL=DwVeY<&FPJFQ z({lFrS1-`Kw&4KN13OG^q!W*AY(0OVvV&C*EdkuJ$2MSFI+LX{$Ak)?DEnb)9|bAU z7EAAkkfxP-&u(`4X+BDjh;K-Mur07p?RxQlh;~Ur1hYqcSDmf{7SGb$=-3)Hq=gGp zZ+Qyox?RsYBR#6w>3A{ww`Gz8oR9>!oKs|&@<)2B)WntsXCS@P%p1Skg@ZsXXJ8+? zL)o3}%pU{f+p0m{LUO}4=JS0e{-h!1703&gOpx?*XecJak3*@OzeRH|16$0U0;xuS zCgO4K1{BPBZF$fpTDkL%gdqG>Xn2K$8H7Mr{l?@K0&U=BdPR0rdU~i-5+R!w_yiE9 zB`|t{ePS)2PEdt=CG-B$C1eIzqQOn;WLcD6UFQlKNt`6dP*O+_tzdEH;DXP0S#*ZZ zl<{bk(sN$$=FvDUu5f4Mpj!8}-`5s@;ugp?#84d)jt6@}gNl{3#j)@!Neqf3a52v? zHUtW!AQxw#c@lPAvXH+!c=<9KDtt;f1zsrL#s2Y78mq>t&nWqn45$43T8>dSvuv72 zJFnkhCg&+A!Vox-{tEZDCmDktj+J<&v+3AOM|_1haSDb%G5!D$57nopxlw_CqxNu` z=V}&Jw{Yz{Q$w0e%Y@&D`iZCO70MM3r)Q0HhqGOV%P?x+G(EK;y z)B9NxiRZugpkpX-ByvwPZtPbMn7qgGV= zVj%gU>&!{V94+tyY&!zpRvVy_Dqw(sk6ro(fWZAnjry^rusGP%cVgTa@KRQ-Fc+f; zciI(X(G@rEm{V=rY4weN`+$>Q)x{^eHCu+Mto;JkAG!Z9twlPxGi<;D&6y zGD@c#J<7}MMyM4Cha*FI z%y7RGTE~?>^~$4vlrm;Up6iw(j!~0Sm$B*88icy+>I9<1KeAv#fQ`*{YkT$kIoWx! zxBKSh-Z2Rqk{Q(=Zo2|f0}5r=a|mS2%X))S8rYb$xfBAJd(rbOzd+8JN6YNmBc+Gg zH9ZxLb|~Z#LBdPaWbKVdYU4}QZXerZTH2mv36=!jzx6#Ds8W?iM-!|m9c$3e;u^E;cad7M zx!VnFCZlX()eNN-v*6Kp8lbYW?v87Qr7-KQAY9a+Xjp{@v8e$V_LY`7Nu-woU>S7n zF*jc%1z0J6lMWGHQ~Ri32sl~Dj8}tSB5o6`{Tzg%m4{#%v%iFGth&#!iHyw>I+0BZ zH>h=(OW3#~@#G}2S<1)fk+&KAT-Ps{$AT2$*BBjxNZ38d80qaFj@2HImQEjd=nm}| zyL$u&pGM0`6YVlFRsG3N|`WuoO?C5_QslrOv5fj+$@t)CRN@ zCE)3Ul0%nS?mg(BZ6#`rOs|}J?P1o5o{zzN!4{;b{VE%!=PA1wB$GTv5P0jNWe_BP z=bUw{OjH=CExTf>gOBsgbl5ClqJ!_PE*K>Sajq8K$p_e0sp6wP1dv=LQG}L+y^l(> zRDV5P@2bsz9M8Z}5DGTbhs~#OYNF2*^YzKP5^T3o7l7?&YMaM@B(Ntwu)io+>prYa zRqF=<>xH6Y71qX650?79@n;_v^!QYNz}ovXOw`VTMi2qZ$Lk4*g%GSC36=tNi*y8v zBFjrN43yu&0&3Y;zj=rGVf)Op>Th1G>XU0MM-MA_KvoHaD33=eML6Suf$a9@DR?`z z|Ndg_siL|1&y$VU{NFzQck}71*KfMfi~jK|-LB}6tCi}Go{hMimeS8lbrk1+g?%Ri z9J`ci7|S7OAF!QEu;i0%#+2{AgzFt z@Gu58_pq0yU>KeEj+RT#w%@?FxAB;u}?YgFz8w!e=Bg3A^?eOyiVTK&{6K>GBeNV)LG$wcF|W8ZIQ%g zAV$#tv4GZe-(M#)deOmzFR|3N(haU{o3H^*6}Xp-)zNT_`>l9}>6Ok%4&IV>(P&>J z??ZhM3Y>6#&SaYn5M&vX4SSy`2<%i7I>J}#@jhC2+;{4~OX6!Z724YT2^=*>>RWnF z=b0P&j3Sy$0s%mkuI*=kuskypFeq3-4M>|xQjgWD7w%E5S1f;D#)_<$=>JTv zlWxx)TADsSsxodVt%#RlwGcO!y7K=1tF^z(Ypu4YxuvgL#;CS)nJ5vKo;%wL?gzwS z`Y>>s@UAZo0#SRTtIh#<)5Bl*(hlUy5H~NNA~eODY~(-zvqXJ=>Q#Cbm?LmLC4H;i zaVH-?hM;hkW?-zIc3riloluhtK2hm5qHxB2#Rj7i3xkn z0}Tt-K!bVs>5Sk~#b)5|VOx2;4a;vjjOaR{kqWd->*sj?DS}p(-e~Husk7$utsrAs^ zRHxiclKya=%$z!&Iar-cudnl@P%00dAvWl!r`o{nO;ng-Iv7EWBtNFdI|}6y6%ef^ z!)?{ydy~L z4xy3zX@?vcHsFPf5<(KQEZnf4d*5LiSy~#wIJ*7tgbjNr*e6ECKloZ1($0%8J8$QR2 z0Wyh@;p6k;y_21z2Isn74fW+T;v>R{(5u3W6Dqhiu@nT`N44+z<$mZ#LL< zG`fV+UObfTNSWgoXouO=6$MIixX8_AHcs^Y3RsacU8jiJ^!qBAOyO80x;AmYlj!-z zI=L;I@p(|tF#IvjXX?j0^F?>rAEtu0^>8{{URBs!|YlSip4^#$dUb>IW1@z1Hbsl}3~6;ucT%VoX6W{& zAYITyjc3`_870wR@C|!I>WRTyhS64# zb%Lz)9A0o%XRxUvj-ujOqL@h2lD!{)qxfT-j?tK|X}D!DIgCH17i2T+^?F-u;&%Qa zcETYr8%=B@y?O=6C|ZqHS6lx(ODAh@=!W-s2FwlaP@lkgL`_(0YjqW8Igx(YqnS)UDxQJ*5e{2eu-PhV7 zNuV)Ke=jU(IVU6?SPaY|b+A5vU7BeT?x3sKC#TBb*vv)*WDdw1NEXOc-h}_17|L#^>-Owbc zy&QO%_%p z;@mV%b#tgiM#9kS|*%aO-I&9vTx<8gE$U1an?77#4TNe#rl62k80|7<1 zPJejXP&%Mq6yQTdXvKOl83}3>HdG*W3#16RzYVgh_%govV0i5iiDlvSamgNuUF zDiJ1HBME`=czTsioQqxbBZ0c$nwSYc3~)`kmSccr#=}N!Lur_T^^W$cY(^{TL_d&y zjL9ez`|nLI;tL9YRA{S)Cic#iIUKAhPDfHApic5UW#z1*|M8}*>cBOsCy?=KbU|Cb zK&`Ysx=t3h&7cmnq-af}%7-Ky-vK|*lX(ymq?o>=*L#j7${yUr*Xd^D5=7~mKu!~_ zxrJKNmy;AY%g%kZe~#zXQHOkBgsV`+IdOG%o@4n`EeYs5SwtmQ0eX8P2%DBM(fXS67K}e8fckZ3222?0&`vFuFRR9D&gKd_L#t zdD25rN+~*jON+6{?E*J+c6D5=Y)ZwxKyiRmN6ug12F4meFSw1MM61!f5D$W*u|eq; zM;B@oljtU%;WHFOBYUnXIc*(Nc|1`Xh#D~NLU8PZbf+50Xf&I|SLu+>*?1n( z&X@uD!v{pfnzxp#3bNO6i9rrm!dsL`g$5abUzGWOl-QbL9hj#V9>>W|5Q2r18%nKX z90m36Y79TU!{p|c;fur7s#jyn_#P5eMv}oY!GHRU_PECodHnj8FRXt0yo0;x0;pcaDk>IGrK*3~a* zHNEEZU=Tj;UjjZJ)(WHH=(FU!US1|jYUIoIVn?u`6VSb%XE`f%Rq#@yVDNCvDBP{E zLUfSJL0m^^p+ub72x^c5i%4zr7@DKeB;1;R&TGqzh)#|R#zbK2Ct?oV(45n9J)4fl zMKY@yNgs~^$%r)XMsq`|CDh@zTH_1o-9`@0<8`IpRXawJh}l&$w8;XHTq}o(RSq4o z9EQ(|PqRJ(I*&;P$rOMW6ec4~&Hw=G<`VV*-l+mpY5ES37`9E`Ls;Uvo13@=aGZ#L zca!0mGMu33CgDM9KMU}L+I*#ysq7YUK_~Js}8(4o)?umuDHZdflwAA)e ziI<5Qf-$*RQIUt1{TRZF6%v>*(%1NZw5N(#_1O`euE~3-B>t50BdLFHG7a$zYqo#t zDm-sJp<8jmHgB8cW-X_nD+k_%ki$Z0c;Ex|E*WF+Br4B|RmI+e@jKV-C$_x_A18*{ zh{>=*&{n$a{LUu6lm`I^7;$T%HrL2r==55YIv~Ox$gOdY?~R;E+nIVy`o#ZYab^++1as*gF=fQre%SCnc2akRU~RbByU zf$y&14_C^hI(C3s<8Vx%;bW6dfG<2u38&?-*R*JxFtw3#4q*MbDZ$y*cARBTE7_{IEk=9 zJO=&q)Zx6!z9AbvHvwP0U2PhCyt_BpBRud82UGCPzwt6b|9~iRBD$!52vROZf=|f} zqpQUrOEco&$d~a@(|*<>*8Pf{4R3tGC**iWCW@XPy?lA}&J?QRgOd|A3iV|Zzue-# zH79LjQVd2+SkaiHpsK$FEHW-rl1C~QSYFzWg51_SdcAje`uyN{PuBPOA=tRW9lbt` z__Y5_QT~)ii%?M!-Bl-lc7*y?{Rgwep$jn+q4@9`-&bVUo?BNE_K?nDafpH@xYBU~ zLM4ow?gN`*NC%*_}eAi_198IKRLF17CNmO%a`UGAz>es zM_)&e_nn_>Yhj6h={432Du0QlLM(a>bts!qtDSN~r)`gLs-9Q%Br#~5> zDng1h%G}tlj-|HI^YOH}6fli$ruwyN8>HVpvH8F}Ads+deSO_dzTdqA)$K++IIF-p z6vumHu;F=oiu)FyQ+O)cqokOKhc)NaB$b6z<2LW%XYs>-=5YR{dB=4CG(L9);PJ6P z9}fVUIo;#^djYO6Nsxn_X%f}YX=FPR$CV1dYB}OB*sAm(+}_1ARB7$&jqQ!Px>`*i zyybhU$~txH?ZNFUT)wu=s$rhW4P?)wjDkSD}=s9*J7PiT5Vt}W}EWefZdvJXt#}yareRLLN`dYA;Eun zGv;d;ZM*y~3L*W^Nvo^j{M?G96^Ijo_ z4J5YK1)Y+Oa+JD96V8004Ls5GTHg!#%{j4$%m<(tGySN3;t4L{v=*+7qu1o{rVoyd zEG52@rUq+D+X8DzQ)e@m0(8j@dXYe{NVz*nb^71D)M+@ANufk|%F6yVK7~aNN9a{F z85*~LdEeL)N02CnoQ(MWDLaG|lv9u9e7(=CNbg`DoQs4Ac&^HHFXgTUtxn`iK7qaEF{m$%it}*>oTIeup z^8CX;!KOh$=HjJc%oSr2h3m66-h|Jmj(g&Nu8s}1l)`SiIs+{Kl2_nuof$1ywmGNl z$(*tusLUyKZ6XZUV=$lA*4WSx1009*;nnDLiL_?*9f#08d6=1!2$dN}K=i;1y zRgKkE8~34oIv1=|$69apX6I%?_m6K!kVCzHj|!QXP>9qxT{L2R%Z8?Vxf1lpjyc{` zWcEQp&!kv|vw8@!3?pJ>Y=eqIUa8wmL2ua>t*#GKKYe5LqdG)^H{M+LC!^L7MtBO-+_CI_=PkNb{<92tHd_#ZiqDXg^{`U&w|Ed9wU0wVq5~Y!L+o>OJ&-V?3Us ziPQw|^lDMXxZ4wsGpNIJtnr+Oi}m9{2RnBLdQlrzrb*|nKF^Y}Q-imOeCR6tV;#Za zN=tG}Mnu5n6k?UgITvzaf&y6TKr?)X0Guv^w+gUWAcXcgM#t7q)g@|pIs$-Br{o!k zW1zbT0!bRGf>y#!0x~XTgC5d<(+p90lSO}mta~4liAmJQQM%+ANJf9wJc*0=QCK95 zDZ=S%E4haQRP;Q+n+?Q958J)Wz#^?5g7+9)j*Mz{7+5MtyGL8mfyu+KdBxDD2bm8c zSIq^PpoOhVX2siaD z2cAb@2^LZdXvo_p88kmtCPBK9>r4^_Of4SdJw+8BnR!6XeJ^bZSh%MCG%|r$U7u$} zALa=)(b-)G<JJR zm2}-;b=|Wl)f$sT$ZH&iwa+O&bP-R5{lIggKe%?j@M-8yij?p2{99^K4|Iz9szQBL zp#mxtyb=dG&{s`rX-&#E8-3NJ{slFum9x6ReL=E@wz zmdX4Nu2q|r9#R+d$2;h1G8x5JZSAYJ_OGt3g_I}%3h(z*o9Z_dTF3Pp2$nZgnoc~) z^H6#dJFG0PI0Y1cuHQp*>OYL8)c>j(EnsB3*cP$0sM$mta+_52Ah-Cp)qtSyfCdDg z8J~IslwKdr>`BWgAtpv$B>%JehRFQ4tVIZA{+Nk`>Mk+6o$+7_@sYIxMMhJ;=7lzY$m_7)vo+wn@ zj1p!MH@|X6@Kj$5!w>@dd6G<`WxWHAlW`IkurQ!Ct|m1@!485Lb))Am`^S4bM=y_# zdx(N+t)W8xQ~~(gmXF8QF-^ zE>}+pbGOeXD!INAEcc-ad)}+!pTQSvbPxvbs5RRB~Fr}@WFHfn_ydblLqts zh8!J#)OHAIl!qCra`RFN0M`gi;JuL-g@Y_j}=ge*Vw{z=$usUe-*$`g(?< z3U^u%ZZ7=rf)MeL&Z3uDnC8U2hTE9%S0Om=zjv|NPVw0sPexLn=U_6+cp}qTM&OBm z0W~1CelWA;qTMW*dcV4@l>`Q3Y)uT4+GDQsWSE$Tl;}i>l`3*~!6wv1j@O2#nG@q$ z{c8gLOGHtmsiXe?W0KDxD*b#q)>C!fOL~x@iCVyXz=6M#L0|6JPHoKJ(xaGok5h>v zeT!h@Eg?9xx~0&VLjxWO3FYh!q&KC1dF(^3`9SW|Dw7#2gW|TMy@iekjrQ}Sy}1BB zK*7J}gFxAw{rqT8?3Tbl)qMZqy&coEHJiAjy?X0F=q}h~jsUk9$Q?ykEFwq=`iUw0Q#6n+MF^clq%j$EgJp?e-->tu64=xy3|S99{K&+&%Tk3NqJVcO z8q>u#S(Y;L)r$w9jYT9#9jmuR878;}+wiPHpygu<>gByoHI4bDs~M&5B=tE;{6QqY zjyS)Y^uMzqS-(>v`3hAH`}7B;-Yjake@+8vxL0u{>NEXbHZ`pG(MzOXtB_ES+BWh^1U)i zpP@N==~qeS1QE0&8yC=}zqQXIfBx`{IiZ}jKF|C0Oy1E79VATb4YTcT3zQ&j7JO`P zbY!|w`1BYuaMtH@TbyH!`o(mH&w=5!za)Lf?%yWECvMVHfe`!&6^P@iaLkw9C&=up z;q^Ybg)^t{SGTaZP7;Xi+I+h17GNrG%wP^_#(VF0slvTPZ}8^$@ZfO&fAsay0p)SE z&Ej$CbT_LMpL&q{<1#I$qXZ#8>mMIgCJmeGSp(WIVK#{{g3N==CC(s2(pV^hHmYpq zmd#{Yh%oB&@?=qsh$w>hy}|}05g4c%9?L)@guavJ!*S9l;)tPn3vWHZq3F(QXR2!m zdP`Lo7#QlRR1}~pw3DQSe*v^BLpp}-psmsCM3qh|X(d}YOf=ex_!TAhHoTmVL;$gi zEhpf-yguzpMxQSs#_+SqMrqUJn+>Ugm*~zVK-UA;ai{@s3MUH^?C-SYIQyx1dV-)Y zN}50Sp_Hci^_%AdtIpjrBnqbWGAXj>H*phWFTHm>>~fRaetmi=e^awIw`uKY;=F7; zoa(BjNLF07hT(V-JDImIxhvdRJ_#)6J1=H-2j2nLMQ}Mr?7rkeuG#G+9t~t2e+bLA zI}?MQ0)Ka6NIV{h7<7g^~;UV9@xNyp|m zcJ~Er!w8RNOcjiTJTOIy9!F?W%u8oo;;t1$D%yTznzR`V4+r^Z(zWk9~AM~-RRjOnODE@9J6hpNE1@M`wJs>E%f!=rTlA~tgKjxW zV=J<0^(FB-f6lU9hOAVzky7p%xcH;1qSq36K<1=a6rS7({n&;*{e!7$FY1Fg7yWt^oM2iA3;42W*O*8U3m!d>|23r)J& z189Qgf2$YhR|e|Eu62Kb(S6&*UU;`n1RVzTCL*w-CEC5mzVdtQ5&Saz+?|5!G}$k_ zs@|(kUsv#QE10SIn{EK2Pv>Ou6b=?otfZ)$`gxK-z+O@wn3MZg)>)c$ew!`ei%x&w ztjWNtTLKAadVZGu=toJR&e1dd>@9gyOq*^Ke@j{$kBs`S{?wXO*M9m9?6KFX>bPxr zu2V%weN#hhV3qapeqdX*+9PZqgDSZOzbm;0cP7{13z5t5Vu|!ii+?;VgL=^T6;%s{ zy&Sd|a|1h~u4v03!E$7tmb{o`IlVt`i7?7YYLxNWIAPPSg;-QgS6=v1z+geot+7z+ zf4bL(@_t(&8{Kk8i?vsrO^`~CPnGegLNl2e9hk`KuE7OA9VZtM*ppK@sfN|?i7=#G zmF+Z4f4-hCbBR)GHL(DIppE!Na*Sx?hU(aY(1KfV>s`HJbXR~w1;rO)DTJ?&0oBiy zC5Bo9y$J2oi-=?&YOZy=X7vEmNc!+Af9iu&d#DP#Q&pS7o1FL;_JrzBM>aQ~1Cwp8 z0ri^GjVkYP1_&n=G($Rzte}}e!F;nsxAelRa7grLNVKoNR4$-)-oiDx?acMxaxjWo zx`lSigT}W}#|4P@?htdG_tcRQqQPBQy?yM`3?hEpNYvEt zoT&fps{akTfD-&`1Af6@gAK`$e`e*_>Dl+EXMgeZ?Az(t_ortcr>7Y+?R&Gsj98tW zeLXw-JUjc&?CkUG>^rlw&$F{Xe|Gi4EOTBuKObh`UQlw;mi|LsOI{xsuHf3t4%D}VZx zKm8ir)^xxB0x*2BX%8_4NH@*{9T(W#!{o)LiwdDo>AJ2$s zWj2BMpD_jEy1mBZ>&sZdzKYAsUaaKzCQNwy|(MH@$_&ZD!o0vf1Oq!Ux;SA&yB;Fe`3-v;eBVMIZO+G zGvGY_+0U++11MTg9_U!R3?g9|a< z$1^AtYYZHQyO8#1cnPuW$*1dFY3<2KWM2P1DtaCw;AN~_k3Uk7=NZH)6E+Nu(%xHWZO`=KSe-j9B(ST%Hq4OB~dmmw6 zU}GcpQT7G)6*jZ3Syfrx4G@y8a|RKjfbOcSth`lLR#ufKj*_wP>W+E!t9j#4A8c=zTl8b#eXuE%EtBBgHnrAu?gUv(1c=oqXkG^79gcaf4*I1Z*p=f5=#E!ZN9u%+~lMg zthJPI^YuIsiPsbZ!#D!yN$6lyL(tvVxkHq``jXFtz3GkjZ1LTa-1bM_A9n>ZxK@f zNW|E=;!2>4Nje`eP?*u_f1N{*9={TizSH$Ne-g)BXI6fD{jGI zD46|4*+oMb_ZxYvrM5(R)*x=VTPT07!4RmgdyGm&AJ!0*f9kpWU! ze<;a)=_#(y)iHr;m^rjZ2kJ-4V(L3sXl##ii5D5E-lyxE8}w-d@rf3VCAR4gs07Pv zk>=+;@>d+`_r#H_*AM6~xf&To(eHe+$xdvyViIp9C|PYuf+~9RHq9Jl;g+Ixa^${s z#LB%iL~&>{qZP8o4&!l=+V`uF^OUzce^kp?WBEIXZnG@}*xW-THW7-1?J!f!hyuy~ zVT!TBkVHr$mzv7a_k}0%Nk!%a%uo6~95J-d;(zQjHxnQ>M2Vrr;6+jxxC?0*2Q6G0 z(*Shd_~U=h#d4Tj+XF}JB3rrbHhwZfe_1o( zT+Q7NF~qe&p)0auBY2LxE{KCC zErz2s{Y^X)7gssxYcOt$FV6obkia+|vPx?qp3*wnh&r|QX$g}|r(UANOdmX+7RSGU zy->b1ebt&yeumjuzb!vXMO-rRf22}tZkTKI_~~>$DMK(;Ux}9g*cYwvul|%baZ+e8 zYsRa|k6Z1BsW{SQqgvl(g;*!qsM6o&i+8x;c;iY(c8u5z#g~g><2pa&K-=l+uBslR z>L-N+qZv^yC66A(o3J5)f3dh6#rv>W&NpJ}#%r;2^oXm)detumL{;TRe~5ThFNVjV zBZ7Li2_4&Wp(U}(cW&jojVk}@Ru+*$zp4rxs^vqD(pK8z<8HIWaN6?&yphV|ul}I7 zq{;f_LDK0*M*_2@mKmF0a|Zq=0*Zd>*O%b}x4v*}+()$mwrn0VBe&aweJgE>KcuV2 z+rw3dx;Q}b+4c}8R)rj>e-o4ZLLs{jq7i~mDF9+ebA<$W51gOohkZn1%%=;v=A_-W zGxYC81ex(aKhRN2&IF1D?%reyH%ZEM)=bVp^OgIZ*$szZCg=8tO7M&z2~&~HaI}zi z966#0ZX6kPF{>SFaDC# z@SHRU%xlx;Pp(vHkaa2^)Geuq(s0`G$l)-B6D*$*e$>|0TuwN>2rk!%oi(`%{t-jO zk#R19zQ{FTUy-3DSfSwBD4B$MbI^GCC)p~$S}gC>Y^oZuZ`?Y*msQ@%nH-F2wSr}1 zEkvm+hdX}ANFs#df1a8R<_wj@PXESUtc>U*mf0642ZeZWBjU!`?R`Ig1Ae|f7dqBd zXt3f;BJOxC260Zd4|zi$-B#b7pN^k<`RXMA>iFyUsrK3NQ_pX&#hcKPudL>dBKi~8 zG~=+UWxL&IoquWi7p)ZUsj&wvNcF>t1 z>|WT_SX|@bY`FXpR%g2#DOqb>jBSEn9pj3=PsEO0NL3Z~)>Q0f1%*WBAHTQ?;A4k)|b`XO#SuG3saASJXT+HM$1p?q z-C{YLBsapsi)#$;3d|Ig_cvyl|tf85iJxx;vE$3)&cEbfBJ^edHiehsef z-D15oJ29j$A#a~V6vTZQ>I@5cRfeuFN4qS!fR=%NiMsw8_m8|?Oee`CyTJ(!{B|0U z{5V-9#TtiV*}qG3Wb>uj8a-XzJvQu09wxvgyf)OLpn8Ye04<0<%F7|eZOM2c&h2vX ze|9Q1vYf+|LZ1&UFXhJb2~llGw!VS8TD;4a^7dWCTkJejf&_@X6i^k-MI>F*WR?N; z*LWY$bbc{gikCDO4>eORFh_n+n_i@BjY4BF*z`#SJ!Zrg(ZMNBsyC z%3joV$@lfe%J%(uj_VI#-@wL*Zz3kTf0$oRuW)yUP(lSkX_dO92;aPgE-lvT8qcUomJEGM{{`5*bN89(Okc`?VTr`L;jwMt)JVsXQcum%4!s&t!D!DY3f5m>2#XMJAKw@ni<+cs|{hn=L1Bb#q#AF%X)sz*t z?4|CWLe@s~j)8MpFx9GtL4wf$ySA=W0F3Vx|_HmjsfW7uUR`)u5 z|I&}uztr!aop7~a%H>bcz`YVq8cLeHa9%l7kLrFz=_hz+E1(9#|ITkiW>(v6o55tO z1(Wa(s&z8q06OtSAFJky()L!_8?6^N*)P+Z^^HSG$0^4L`P*!^f5uMK(e#!<(`#HP z^#z{V4=AA0i75tDyuiC*#Kuy?WHeVulq@N5rjX3?xA{zOyp3GBgR32ORdme}1(e-jH@g&lccQMH&jcyrtaci_;b2YGH%IeUj4{U>BM78b?VAXicnY zc0Y#TcBC+l?+4;iH&M{*c)l7f&jS|zq?{K|#3@Mfco@)3e^eZOLqs`((qS&+6cq_M z`IaOAC3-jaJY&PIS{#vWqjy@9C&pPVYBNB#p`Fp1?I_=L!={9zShEE-reh=o-}_e>lqK^Sk|r50f9Sg@B2pP73JP#kIDR#~Gf=baXe*Zl)J_Xaz)Kxn62!^vrj}xx*K*ia5Ol;^6g5HXfB!eH&4c7UM%kZW(<+N_&n>biHi&AY`%FtVX6D12 z56(4Wf1YIjEVCdJdR?S{@Wa>3DPI13MYd?FV;s*p)Of47+J2M}L)7friMUI9#)wG*%eBYoeR z`|Q2wl(R79cw=gNu{}nCVkTa1g4*Tfj&YM&cOODdV`_Hz9F`{+DE{)6EPdoFE2G3vPdnHL1r;q8w?4fc4yrtqhhX$ z-QQH)3%|IO%js7w#MJu34$v}lzB1B@LNyJ9UgFp>ZgH~N8D%Z}$TG?fP;{oVWh68O zYm$*J+I$2&%lrtn>QibFRGC^PL6y~Oe-<_Qq*svK&MR28d#}pdfIu0Kd_-%q$}CIm z9@V(itI{U!PzzJL(6tj(Y0#qvU3KxAWF?Dd9|PM`4JgS>H~8T#PBbtta((J4gsRnV z*CK>oi1DakcKOsLgY8tmtq&q+*`*5Nic=l@%*xd65bU(;VOW!bo!yhLQ;&vee@z;u zyQg6q(9j+sWp?~nF{Ni+dGAOLFutYPmiUF9ymWG8_P=0q?a7gQPmU?1Zw4_}t^1Lp z*ShFW1hcA=`j|+CVMXzl%3aq8*GH+P9*La@wb65GIjE9*Ui1o~T>hKR&B~NM-1|D4 zPiE%!9kNEDBCC-+g3MkUgbxcae_o5rOL?NO%z<-OK3WoxNC~isnwdT`Stkg%X`N#2 z%v)=;H$}M-M5LATUf*P?1=u+&+@52u7CA+yp<6Z58ZKtIRz>m}T)bo^bYcRE4S5S> zHrr2r#2ytQoi17BznEiv^%9E!Z_gjU&ZkSgK-x;4WOGCYGY!R(*|~1ue|EjR1&nn3 z+T3E~$896wQE51=VK9jATU{)qNx;zUO=c(43i zbqB`t#bTDjVB!jwf0%3mI3+{zfkXj}jV3`lO2cc&86|>OQPFpPyK8Jt!C;x~+41S= z;Wu)OqDv{y^=cQ&&|b3tSO|N=b8KU^pFS)c3MqwiA~spu3l%ZPT2#6_VW>e-8(8!)u5YlF)egL!a}T z6Ox+7!>8zOq~e}p@LMFEiOG}_I}2rsiKtyw(o|NLPUVIRiFG=JRN+JnuN%3TUQQM2 zQ@u6^LZv$;uZ3NamUUCYDB_D5`11sx_3Z@azFSQjL;uKZQ-itTRVFfVpjDHVgGsYF zp%bbZ_9Dx@e_l{6HK>-)-8Rq~gKvZB0;@gF>r($P^5V}SC{D#}g)ha%94+)R} zJ}Tc!Q7|@Y^mWg92`_ zt#Q)fe;25T7BWv68F4FBNQurNsLAArplk@GN>t=Y7==9wLrwoMHo9$S(1cZY8yD1I z8?*^8B1oU}fnNK=4Fr}X+xu}mJa`;W(>Q*9`1Nf7q8qeW|C~<`P9`ZO<*z(un|O@h*5z zqptrcPS5kEbbKhd1WK@n!Yl1Y^x|!LVa8G@b=$1!rx&#%fvjrX^L%SStimF6RpK~MhpG}x&4!)vFW$5} ze`41FRoT2=OK01ptL~ACoWcE8Y%y22gW7Zt%ua#1yp8x6=pxV~0au%5GUm!7Hw)|+ zjF+c}NioG8^O@R#F1NG8STbKt#pX!0?5C4qPH-FIv`8+p0uL&~8@KbfS%HB>y-@6I z5vTj&u`}w(GqJzxc%w01Y{V0J3J*k@f5Oq>*1JJZ?W#kIO6R8{3$Q_$sLUW1ihE=1vXu2t(%+cs9M1 z$H~2uk41V@1WeGlef$Rirv`2XNM4X*=yOH_lFCBh_2WV&!$vsbsnco=@tj(Ze=&7M z6lmwr!LxZVOYM0slSd}#X)JC*SKlp`Z?ffLJ)fwIlPSg&P|_P^ z)I%GxH-6UbbU;az4*cN{e_nc(Q{U6Kmdd)7nN}U_<{$nbdk6oMjB(|Z#r&3PBS1%5 zQSuB^bLHJf?IJV4rOOq?Ta zLD_Spr5uIj@7(ftRpozm%d2CjwbgZxo0iWhIsGA_iN`d3(QBobf8tnZ*)cW{VxZJw zc9j{Y>{eyV8SVx_KPAhh;-~0X+w#qz#Sfm09DxTb$OFakfGn`v37#`wB`?k7jw$6! zNi+GCDHX`Z2Nokd*&>@`DkccQbqfsLJZ<<0w|;E~kABg1?$IL#Sn%l237D+a6wgXV zZ*{R;UBkpj6|$18f41&guPy-eWzu#=@Oq7>$wS$}+o@xn19}|gSIazSp*&1FBiwLf z;79_uw*;V(^TiJ>u%!7m*hMTlb+1Grp6`p zlk@)^O(Y4@_WdYAjXZxW>mjO#b3-UR;SENuv(x0|;j1(4e`dkc?-npf>I_YJL|vkD z{KYXWxjHCZt?ptUC;2S**K2S{C8_@C0oZhZ?mofL4G;_o>xY1xfQj&-pHNR8fP z8>6_b!a6317WGs7ixfpOjw2%wquyq>;R3X6mCc_@Jx;c|@hhV)T=kTz)cY)HmH3#I zerO38;$v3&f1%#wPqE+L6iKdw#D^h`*{6IhMcUxW<7sjH%d}Xzr3ppZr@=*)NkS=7 zYlM|W8vCtVzk0<`8%}S%dpb6Av$k=yHg~r;m&==o(%ZD%n5RD@1v~bw&OM^fT`DVo zoX%J`3^K&Cciic{375%h1ALNZ*H#3e}Snvo1r zgQx@4kYkBE6E%!ifofzLABUiKUlqEqFH=^QnTJ7X*Y+wR?bGFlp}bSTp{l9T^K0aFEBG>vXBdA@-T3n&rP$RoE_UTu|{ke*VD!X zCNNL_cJlli$A=eho~rEw7waXSCySve4e;OX5tW&SlJq04Y!!($j$-NHFT%Z-S zByR_j?G*QFNJ4U_9=1IwlwDj;^S8MmUlVuSFBb*qEKXdeA{;0$k`YIAJ7AJ8AN@6X z5rVg80a)s{5{IC8(1TYA8?+OOdrousFn4^aQ)9A|Bor*w3-=;J6(@=plj-cTjE&(s ze}pVTqDZpn?rOr&-y4%=gQ**BCpvkvWaK7ru9>~(G%f&9e<~ML-F!M1X?>e1T?Ev& z(>nZo_EWoHIA+HVZYVaCR0&-Ed;5k7jU{+;ZVeBgVe`>%jc`5T3G~uI*uf!;Cv<_QUZ;$CA=a$vq z8C;81s%ibIx z*nmpzmnBY7!0^INoc@bzSZ9lOe_}D;y-DautpBgFU#7`rhBqbSkzMno*G}Ht{N=h8 zlzWpu!gIYapAG7~%kBzW=1|YqJrxd4w zSNeWZOboRb2A^Dt`!L>R^A!~gwPF6kw9ypB<|>0@t+twf^`0R^>zM@d0j#W)xu!Z0 z4jEpE8{+U{xy4dLPv&5g$>x81&8c{y`*H=Y-u$r@ka>2S{V-kKWxwWc7v}NT*=l_= zwLkL$q{jUGCZ7Q}f4!c*f3c6&lWdLIsp|LN7UyeRM6(Z;SutNsvgMN;PaO!Kzs!p3 zH;cE$n>+IE?xwJ9{0$nI78c~Q?C#Ct_HUpZ#hdKTw*3r8t!y^G%YRr*EVO5fUndI- z?Rhr4pgnlLc)P%f_w(ePti4-&_L) zzs}Crcu9#x?(BRzgHC*hFyYLn{P>5%qr=Zo!6f4UP1NgoJ_BxB z5xjVkEtf=-o-FhH;@WD_a4HO^P67Hp(>u8aRrZWRDI{p8b_k^ zd00BlUfw_xQn>0C8u&?$X^Y-M^Y8x_f=;-*Xu@K09%W>n=(*sfe*D39 z)4j%z!ATz4=_dL!#r$M{i{2qQaL`-~h-Y=Gd-{a;B_!G@f1?AcR*9w}2kK5qsJ#4X zjT0Glk+?JDD>Mv64z=K4>SxJwEBIz(VYFu^CnYQ)U0l=QrfqcJql?_^Zmz9XD}e?MG{0>)_%2~gXf5t)87Prw=wy;aH5a2BkKKY7x+&l{Zb8z ziDxNFk?y_9Ctm0LNsc_y5zV*SsGuLIpp|+`c<{}ue;41s6zTk5-^u+Hd%9F&_+Ss^ zbU19cLzFO9^*sdI4AH5{)<@)IOSfZ^ew~VbDJpmB^-J@)+-r%{^oC8GfCyCnB_-e#s~Kn9WLd_FvI2SFV)$~f7Jjqj+q@+7=LPm z@sji9oUz;ekqETxU?v}1C$fdkrSLb+IcEhfy5aaQmp3tO(73Srz-u$u?qXO}G-|jw z?wf^M7j+X%!eJh`<5v?QPeE)q@uq;>Oz1!M{FqlN8r;4p*RPiKN z+kmZxSwk>$(7V$MoJ;r*Uze+S1u-9lJu`zgWJ012Y5ck+i(eSHlL>toFJ~;gpJrkiB}{1l=CcdjSOjwj;Z|9R#VCJEOk?$Z;}|-`$!vimoH=_>Gv}EsC5AtEBjS)S zP^E3WDeUYC(<9ZV*bpbV!mbYZ56vbie~}RiG@h>R{`Tx!BlPleZk`vqJd;`~UxW!t zZbczx6iVxtkp0Mc(*#odt zG&rD^yWgI`6D-xg#f~jLWlEz0cHn&RR%_D%F%R}}yzYznH!_NBE6zcl92=EJkd60BV&}OXD-xq7K{#NMDtN5T=#Q0-!iqVS+9=HjH zS_A-<_a(FHCi=8lJkwUP?~k~if4($HVsABn%~7I$W@OrGUkW_}lVqepr6{Vd$(lib z#X9L+9^TIOP0l36JGcjtpUof6*Pcz#qQLrexv!VwS^PNgfq+Kd&@GylsJK};bTn+f-unffJ{k-bl+WAXU%O(O~3O?vcv-Ksj)hVxg?ZGIho{poe_Lv>%{iy9UZ>w{}_m&mjyKe|n z>OR(eTMq| zv>#Cj6q>dpe>Ee6=!gOswRz=00It||IQoIqn0^KF(@9;q(F_ zV9@h12yGH*PRi5ZQyFQIi?@Z59`(}pC=Hak(;M}A{YoPZhJXlVN@@?fW1gbKB8b|w z(hyt|R*{Gq0KC#Pv_75QxMN0Ne_ZI3+TC^98>?p%vVbKJ)pG*V~%R=e$UQWqSGCuIn#Cr+(@?H zyO{1ko82DAXYi8v{(77q4o8DdHL4DbOzW%3DUzxVPh3%gtahK)9;DsCyX=f%@)N5)ptZ*=)xa@}^?T^joNF6s{;{S| z&}Mtg^O+92QQd_ek4EiaE`mKSnSwdI9(B^d8Ap1@rnerqhwUzpe>aYQ-|hb}@T5im ze}}vq^@(&>Z!y^>UeN{Dl9`~w#4E-WIxFt>+uhL3>%s)zsD3x5yHI&F2;EYs-0j9f zC1hFxnmgbSAEtCLkHsyz7zpIr9!JA814=XuM+U)w&S)?i+R$Z0cLmA0wh761>>5Lp zFk9oe-0hBeggrhFhJzu?=guHH5AHhkf5&ZFZP<^_J$nw?-44_2s0-FTHd^7_Bf#md%2w3TuG6RF%Km0`dh`aTSJFe7W6 zqiWj)q{B&8r`@F^OGIfw<3}9UfRM4uQC}G=_u9Va+3Acsc1Vi4%7WOb?=~W_fBBYo zpt68(+V1=7Af3)|6yvYQVW>V1=-hz^k?wjt9-trv)nNoSRwM}f_1x+sMmYN=$oF03 zgEN(a2L5fArM^DaIJF)XX3DqKXQ*b_3BHc1y4w(#m5(+mbpX zisW$E_eYr#_4WK7jwyLYria6BC*DI6R)$L5utgXm<1Kbv`GJ{(ryU?j5n0%UF#@p} z2SPCFb)xk3q_;l`7heF&Xn#HV?RD(L!E_kp$rzJP;I~r=?xkt4L<$prf9pMV%Z$;+ zefM;@l&%9}kWJ49!xAH%x`rf(%nk-=S;r_@$L)|Vqag|Q4}(w!CF^KB3c_Y1@bgM! zqVIRz=L&YclB^%u$m3S~tzwukZy5!#E#d2$(AF?!;nQ)yGaiR|54~=5CEkf^!6Z72 zD-DPx?fB7p8rAA``l-K)e-Cyzj(Fecb}wvt*dGsUL@wQ}J%Tn#f&(G-U>L*LBX+kx z46`PNm|9aSYhv8TkXI1K>{~+-PN#YfuSZ5nyS93-togp%{V)m%-5>FZDxXeyec>sEM2J!km2mgT;ksOmp#iJ28m_I}HAR-GsV8=#Sf1(2x)JAtNtlhH-!~Jng z_n`LJsyi-YjbfGhPLsVf5I{_3iFS8DY7Yi!sB-WHX2P4vYn~Ic^h)0&g#3QGJ=}kHayb?>HDieOIculyNW-F+zEA z1wL4qNYQWie@O09$&^rZJ9WFwOV+2d5#pS*Z4%77X(tWW#lRbFN5@8zh}4C7M99Zc z7niIe;@+jTd&3|~{%F+e3}alNX-VA_El_}YuNSY~r?q>dFjovyn))$SBbjF14!{oE z?sxm0hz~RLNgg!a9dvjbu>(DYHq&+x8p1a1Oukz(f2d>qPBPW+FhIMdjhh}i>r2T$uZa^*X zpwk_NI@d`->4=LE8_fru-Y^{ApmsNnO(1);c6-d#2D=+sE}=Ge&`!rp(jyMcv9<@4 zSiK(Ce{Yb>$c&5*>qu(0?S6+<_+1RGfN{8nDB6q}8JE*gpQrYq(~izzU0%0C+m5_$ z$LFvPx4U*Zrbp_hu_bMn-`M|w)9sWOl24_nJtddYwU^Y(a_!U57BR z1^e83onF5inVrxe;BYjyi?KHIvyB_Fs`EyNe`B!C0pi{9I65Ju5kQ!)Tc>Jl+s1X% zNbP}3v~Xu35N*(ACxg2P*{g1oNRwa~5;lI?G_biKgFt=*@X_u1OGp)ukt4;-ou39|Z}JdkGlI=m;=$IF1-npQQsXf3X`= zd?WPrm>TVNZ1&{|_P=PlQ<{EcyC*Lg*d{CobDwTF>UKMWaXN~O*(6#s29p_*6tlk5 z$Q+8sYQ_WHX&$yc8h4|K8GvxR6Atv<&Jg^gh?(xY-3?_(boZle!h|+cX3I6SIVkb& zfjZqmH!zY&TN_9yS>A&BDM@@LfAxp4@QqLC-hfmCzcTKfxD-yqXLt;?7JqR~TK<)l065^rzy3oULh&5yp-hr+miAS`l;V@zpe9n-_e?y1X@06$* zk|j(#MY42<5u*A`X)_}d)bI7;_4{6LL#E=4>9-AGxfj`NH7?liE(O z^4#W}9{j4*G^ow(K!)1wZgk`12t!@~w>#Vz5wAUTe>VGRbZ|?Z9{1TElPtipWg_5KhoyHfwn5k9cE6jl0@jaB z_dI_0Q)Yv`=znbLT;-Mn%t1R`JLuv{N}bK9NYika0n((?EA=x#__z&^=>s>HWzKow}N93kx@Hr=65g8N)x3)uohH;u1G-fBWD#YR|@PkX6nB z9roJQ2dzNuUWaXRM4g$4VH>wWk2pI%8untxv|p#;6}D z)wmbTzrw?|nTWssot65PY}fiDnGZ+Shlg3VN#okaTcokX1)H3rxh%er{mROp(t60g&xSJkH~#k zC?J)yP&QwOg|tz+2e>vby2D2ToTi^fe5kWq#V6-}fs}7fC(K;;lfi?qV?!#OOs+_Y5gF0*_ z-KylN54P!JbAN8y+q>Ov>*wYCGzT-)V5lxzgQFqXW{A)|c&cHQGSwhagR4&8s@NI= zZUz|bfU*89>>df+*A;B`PV=e5ZMRAtrpFp-Mv=Xbe+alB%O%0Xtd2l1-LI63STg|u zK7y3k6JMf~vPEi23pqt*6N$0aHjx^xv1TdqyO16Www?qLEH{xNk0iAviEEA}$`-S- zN?N2F(;~eGEz-?tk(SXS-2h}jo4<*6;ZYkQ!SHF74v9lLYN-vDtfeU~$fl3Tz4QSg zRj7byzLwOR9)DE}x(l@FVX^}j$^##Q4$H?COjL(O)ZwBmT8E83&}M+leHbZ0m2*;I zP=}SITa~=@!8Uzt?$1r{90I@h-s^Ji`ez!{;Idbx2DhV)G^5DggZm!HQYr8;t04_s z_p78L+Dt%z9ZHF_bv_`H^R_}p+)em(M8v64M?xY^G=F1=-bX$hh~?tpqE$ya!k$+O zN2C$I?>iI?v12-*i+EZg3UaS?9T5;E>#*Jf+4K>)532>Fau&#)}LsFI}~(58pU z{aL8C&iWlzkIR{;x82s@qAFU0jRDYRfXqD@sX&!-Qe#kqm9$$`ybQoLeQb8XO|rc* z=sI~8j2jAooyoV4_c%?$%AXGyLg8o(s0pCEA#c&5gVD^gz6ftiq1iD`&e!r4G~GURJW40e{|PFxw)|mqHB6qDqnO#ezv3 z@OyQyM4II;ppXjkd6guqJw&zSSeC6N#Xi_(h|s+xSi-81Uxh?1=_P%umR%okGr(vE zjP*9I1T^s8S1{V&22+ROUY$COk2cecBYYo0@L-k;gpXVu!Qg06DIn2?LMrS|VBFmo zK7Xa$x8=g(?c=B+DsGJ$f)Z<@8AJ45IdCq_QX%mWt05vx->U>9)`;KxtwzCm-H(il zJZ5DhV@da8qhqWQzWcjaU{MMCK4H}3a&6Gb{x#G<6|Euv0nlcE%stGa0#z#L8iN`V zPPS+?XEsIH%^>6PD7K@GYz2AL8H|Q~vz5c{#TbuYWja)(@LWG~fvqKVuN|#VWonGO(-?^!_hNTjzV1-*8e>11_QwPM zL@2CJr8$nk&`i-3v``!8@-ym@KkLWqNJKd4?NL<%Qnt$q!;=5r} zr&`+PY1JJ>ce1(m2ULM!_JG`(QgT=Y8WWJJjv)&H&@I_c%67P73tH7aT!7gl`kE5x z{ZXA;*M;b$TxZ74iQ#4B#LH3~>r{`3?IAk`2?NCLP4%f+)^oGb@HaS+;Mv8KY`M(l z+YHU=xYzH8n%o1S9ooxvqJKj(82*yez4#rN8Q+w=o8XuaWRD{grHlt2_XvSM3m_e?G2UWzlOb6*y_d!u2}cUL1ca zWupO4bKExFVy^M@25C@@F)OA0UN_qRzOPyg{zA2w`nwq9QZa&as@3j91kKZE0YFoY z9K1T{*bVh12d{#5;zhNLtPa|3 zx!Nu1URn!)J=mtqpMQYa4EXtN=4{!ny#lt3zR~(=ZyfDGzb*+l1iXQuT!6dX2zK^j zy^`aO06+?(08bv{DU=+ag6GM$+3vyMRExeNy!MJwfG(aV7ek<5mjspc?N!hhMn87I zCs(eP9LxaS8*{!t4*7P*S8~KF-Ub8!So#inQ4D&{z%m$e27jX4v!}3Hy zA|jv2m3y+x^NZ_k9Jyf+uSg7}4zC~?nR{2GhaE!o&Ui>F_3bIX(G$_3Qm@Um3G*6z zUUBr;HmE-64oRhS7^_5hhxcoanb}l-j9RNuiQZrU)kh#B;q;?FCI?e{)rYkDnDn?u zst@gT=+T2%9)Ar7?Jh{9 zbVsq9c7F(4N8(nmK=GjZU>G^$mNIn&#a*=s!O!Gm zJAVh;$hmg1%k#lHpG*P{V6gN!O3bt2;MFYQL4^<6c1d}N1|pf z3+C_m9Vy>;A{%z{S4#U)xzEG{*z~Zdm9-M@MkJfgZ8fdmjacTK53uQBu~9d0o1$7E zqy5s)Dz+)=rL_uK)8*=HQw%^hLu9s+IDZYLe4CxwvaapAb(;H z+*V`Ofayle7We?09u}KQnTTm57}TIWGm(ypBLhu9q(a_I!8%6p?^@dr}L|1 zl`XGwoK}hX9X@Gf`}#f1(r)tA^TMLsVUOMjTdzs;tz?0lxCzuzw2jGc^OVzylJjdy{D%te#6McHOSiX@>xG!ZL z-EOW3`geu0e0(RQ^!9dkm)roornj>Y0@bPS0Xb~NN|7V)%lvk+Tz@%an650fE$d`0?YbW^(l1$@9NGwkRaYLq>E?Ko*WfI}#+w z(c|REZCJi()qdeuMfC^;E<#Y*^aD&ZKwgp-!kj2izcNg7Oa-0Da3Y;2ft`}zb z63{adz5D+8i5t6fhB%)@q6At6RHcRdDNrPAIPyp$?1WVF$$#|n5-ihvWpogL#Q32s z)A29a%`L_PGcaklKpOZzYl?6G+d7-TOlKv3O{UVy%U`&oNFsHbbvA7p6-73sPJdX0%PKUh8nX{xqtEu(3!Depf^X_VEPL5O}=tu00%dXMiCVetCEMVXbCjqf(1tXJU8$| zYr6ra9~w>@C-X`EOR}#KOOoe{RW2oOB|I|;Uk(07l`Pm zVFk2Q#fdYQ{zSA4npNy@-U|F1Zm$>&erh53z*p?;Cx^nyWRuBM?h;I%PG?|Eg_%KD z`O#`U&s|faTm{~NJ9=`O{J2=oCXNG_&F>tGImMwP`R>K3kQ`-$_uh&DAL@XWhN;Oy zd*MRFR)5#P@vCdyz2~kkk5sWu#(;eVKB3tyqE;8V0s8E-f{Om3Hp zbICnGCBvzm7{U$P&CuQ|uY_@g36A1o&2V;modI92W;l;oy+g_~nlEHR%blIFS|fKz zL|K`%>**EBPzH0B)p~+W0YwVYS7^yjxPPYtl?=TKXk6iinlPW(o*aouJV0eSgb?>G zW{dUYPE56nJ?%WA)QTqvTg>&Ac>!7_$UU27S4y%mQX?Fd6VZC1Y6WvsT#j7>+#3ud zpqnJrtN01y-2k#q??py`1N{bm3$mLN!3~r5>rO-V_u!RhRjdj8PEm5`XQ( z8qF{>Iw^A~d@-p(2t;I0pb7|Jj-HsDH=|}XB};J1E4}Wunp;|@Fc_?bMq-W0V*V#n z1O7_ER(kRR^~nIwe*u)HxrjYj+Y8jb&fbEzi2enZTWaFSNw$RvKFS)5k5wpV&=aFx ztCPT#M=3~PzbTc-O!^kbiR9r!S%3WDL)Tzfe;LiKwCaw|OjR%d>}LU^_M05~JQV|> zT-i6}p>-g@>`s~FS+S563z5TsBf|x_)p*PR!FYz54_UHY%qSxdjTK;3Uuk$-NLNRj zp(|kFyh)N0ZWfb#Cgx}JJX>PuZJFIJaLzSxz%D>k#L#o&86RtH<-DYc$$ty1FhcLz ztwGWlw7=L#(imLwShG_c1x3SsI_A9-GJs?jf28^B0BVnu@!*SQlFF|seC>*_qY-@V z$*(>5+LvG3@O2=*4zb!$e#L4d@pXXJ#_}sx6WxdJSg#}7>tn@^Y_N|tJF>+dR_(|p zd!nvvv)BHDkj;AT#8LxgLVw~l#ULfdCO58h4xV=gVk90ve2AU{NH1|6j5$*Z^T#lL zRP6?Xf2lR5_xHv?M_wGbqs-&%@m4744X-t-wl$3^I zjwhL9IA;|Z^4M)1rxBDB^B6@wYR#r(+A?)wC1%~6YjvCVPjylB0*K-lP*qBuTE0$~@AynHw6~yezI-8mpG>MYZgAqRi)a zq<;+&#A4)=66nP%+VOH#7t+^A-ctZghi)G}f(g*t-*vQK4#A&B`}2XUA%A|6yD6`c<}j~3rV$zxsYV`%!Q?W%v{J4X{_4s$z1S4 zQ`=l<0x5H$Ivz6@L~6CUPym_^-9C=F0Do&S2($fAy`N&vUyiWh@3Rcgtrk->QBkLV z9D_Aj$uO2Icm}RfF_q4}G8wo;D)zmpJSJCfecLr)vLFTMf(rD*1uI%n@`x{g?z`L6DC8-PW$Kx;v#Iz4JWLE&VMUFA1lajB7c>W ztZ~v(66xoD%kU6?ywu%vKVoDtbLx~y;*A;PAgi|usFQ=M zCnsD6u0;Ms$G(+uaPlZ*ZUxN0acy3Ngq+uWG8x&#iP>p6fCcr`gBwYo`RYA9f1uOJ zKIm5>Ve$Dfyq1sE>JeCVj7;jt_BjhZ_V<^^JLe|_x=ViQY@aAcn%9t%gsl>n06dXvpDs>lc_I_Nl$K-{M2O=D)9 zjNZ8t^u*ntfsKonR<&FDhku;eh)lJzmRwmSgkcFn`e9cYHqSBKZvkt48={PbW~j`( zb9m*TgyHiXTg9G!B_>ZwKE=drnHL~2I*Mwtd*h<%c#cLQoBk}PPA?SzoPa4EfyvT* z0uz#zPCc^bPKvP--}^N81R)CA%CSUl?|7HZMPPh!uFSd<01mS<|9>IZMz%*o&N^{> zi_-HcW^no%2Zs3R4AlUPo!GAxjjOw}-)_yi`iEp5L=7{@klf(+6#LW_@-5v5F`A0M&N8DO-jql=<-^yKsmoxukt>&>(# z9fvcsAfc9d&|%en5{kMG{(5WbUR%C#)6)4u_Z8;X|rB$i8`R~5m=oK?leXhUrFH5E!utg3Nl73jZ=wI?Eu{vfpP zx)rfYpK?q1mZ+VP_fq*HAY6M9Jd4*lfho)KEp+N2>9+l1kNqvUjRZs@m&6K+s7wC#*kdz^baPS#AcLGRmYrMKwZ& z7hfbs@>MHVMFhV^9r>-II86VrEc_RNuFZuu*01DmDIy~43k>^=5U~}~`52YuW6)Uu z{`~1oY4x?(!WAQyY)eS@hNO~kMa|EcQoEAK!^;wh+@8n+?B`D7KUhMY-pwzrmy5Ys zN`C~BO|P!*Nv917WYIY+J_&i!!0oS`R9AEZGW=?aWmr~=XSd?8yn9ysNwjm=Nq5gbZks& zVmv-;N^fO8Hl)_(WZKGvY>1UMB3rU=XJ+J~3Z&rIC3fv7b_Tg|hD8m86Iu3d^oyVX%|)KS_0Woe>1 zTtbxNM)KZy{bApn7w+u5krwkH0pd{rc1hD)NcCT<90;fJ@8@dm{_b!#^AZ>2j;L38 zk*^9abA+T#i1ZT;m~Y)I4w`e)ZGTC$E^c(`)m%}a zzy3@GL)j$q<#X0CTl~jm21CfQC5ZpP%doKN74qA}iwoCMRm*mvloIMYAXut{xhSJH+Lb)OcZ-;Ly(qI7eGeDru)9OG%)AiCkp)^zf7@})RI z0za&}_??Bm9qJU&1Cc4@mW|ictvY&P@Vs*Mq8iK(`9+rnBkn+L?0?sSfIEoaC%*)6 z5P0jCW^%{B`8D}aN+F?o4uVu4@kJSa1b~kph3(?!yfOH(jp)?hgQSu8(k1~XkJ$E- z{r^alKf5p!9>g6F;%);Fzw#h{MG!uK{p3HuCFm!A!IRP2T;HwP53O{QkJ@5<`k(zb0!K`6n`{TUf5#~ZjtJ!oA_(^-YTR0a4P5wit=L|A^j=C<{#=LvamHY zm$nRAO8d)qPbnl*-=qW#HJZWknrr9 zg99BKV7j>hVOiobkH}2VCpy`oA0@gigG9H#e4;7KIRUalYw&C|CuLW;rmsWu@e>(t(H(dzlhMUf2_ifsm8Fzxv!BYt7!R!F z_)F3&uRP}qtvq$2Y(-&|2qble;!xbI!gS}w3tV> zjvm*S`uk{dK%Y;m+TLlfO!qj?1#4lP5d9YY-S=92$h_yG;!N)~ln()4=y_0P08#%6lLoGbHC;YdwnZN5|6RJ9Rk^<^>(8v|@XL5Ey6$>-Y;UT+ zHYxXT(q+%W22DwTg`_0lzaT^5#JO!)pZ%fB$Yny=ioIx7KHq5dP^Y5MwG>;ZVjpvS zy?-v;oJ)WCfyrPs$0#mEh*>991h7}+-kSmn)J{oYHXl1bH4*+G#O7)0FCm1BScWPN zl5w)=JAdy@6ai?bBvD(f$q#R-wv-;S!a;~#oEGZx;(L<<;p~(Wa>Ka&z6l*f`GWwn z5{VEuFyCJuetflCX~13e=Ich10L2e{tbgMD*Y`)?n*wBwcSkf(#{!Tao6td4I0&&L zR^FFstC-uip^M!3;YoeZ#Bgu3V4W{`G=h@AI5EBucMfL>9x*-3gV#IdfjX<(SwF4d z0g4RFJ-tfki|>i##68CUb{1&q%BxxPa;I@jo%c?ACIY}rBkHjIm3OrN$6-5NA^n}O z-8rJ&S;4K#c6Zsgob9wMUHdI#yMJ5fz0=+{Y-f!%jqtH^vftvORmI<}E?d=ge+PH1 zDpY>!t5y{npVM`#n_C!eQ5Vy4LsIX`BMTdBuvpad%IdDYZ=&oQDDkJhchzdC(c|Cl z?p1{h!RmK=2dj_j@8~X8h0*WeKGw~x2fvj3QSArYWFvw}z3IRv=>VJ9k$;*tF`H>>af}JW{Rv6#?p6I@ks4#1`Gr~imQ0+NyU0RB#x&X3-xB7YJNpacR}E7=HrNfN zp9>o-#&sC+iK%QdcS^`?uKuyd!|#C7)#T!K3|kVFcUR=g2!BBwTz{|cWx2|h=XmMN z1s;2ChF`Dm--u)P1L1P+1(^f=HWw+i7u8L+iI2d{VeB!gksXIxy8L96KN!m%rm4uk zFuaggG_V!fdB-Mv5COB?65V`(_X?}71`;K_D{7*S;Tj|00kr!s4c<{b>%~zp0YnnWs6#K6|+xsfNS-eFT z3-9cmiko6w;skd-{Z6FXT_=_epWx+D0v&OU6!PtMp^j{EF70wJgfyZ#MdassJ}Jzt zzDS*Nk6%*Im4Cbtso<>qO$6gniC_dxk!(N4bbaHj-is^Iv}wTJd?+?xL=Al{YL20z zEcG_X)H4Ja`VZbUY0w=R8HwMsQ@?vk&;%5iz-h?<^qp0he^-Y^6p{*w@6d3CUH zpVl||a(ZE|SKCh4gnW7A+0s~h#A+l&O-c+kqX1IqjekSs4#>neh}gsWqdyh3;5tGZ z?~sx4WuAR97K8)5?xeaGrv?-7kev;9P|82Q9>7+9q(8m}T+>BM7^jNVH0JdLu>PM+vFL;(~zs4tEFimy$q~SgSB!y4Kob+d{Qnl`* zk7u-Ee1A>dv2Ly zft-h>CP;N3dYnyXYTe%SA2E@anQk%A)$f(W;Ygv-aQjlH6W;_$gkneQ7Q-`hfK)A$ zM-CZ_jnPx*Dk?X>&%KYz5qIyc*YjuDo7~)HEPsniR~Z*fz;QN+>ktZNAuOjbi84N{ zU1<>uE%$ZUDkDVEO$u%@IuU~7MzVLjTrQSP#Mx_3ED*I?tvx%^Fplc!#dR*OsT5wj zhF~vqo2v6Jx0^G}vb!@(R^^T>Mc@BjCI=zlEJ`{PWmBL)6M#4-+w)9Tfk!qgmO#ec zqkj%mZ0i~Yc&@7D6Sequ4}NLVHEc(lKBA7w1`KRz5SCI078p~`gMQ#O7n%H^Few0- zpnv-0+vB6}zdL>J>{-J z6)#HfK~+`dyItN+>MUge<2g1`pDF{rM5_h8u1<%Af`hV11 z(XL}(I@e1~ZUX%c#;;Hl!l9t1D3p&VQXs{*H2|-VgrPa;GZY>K)b%cYphl0Hg*|(dC9+ zToN-uO;@Gd{KUwa7dmv`YFZc@ls2CiN`1V?#4wzp+$$!-RP&=ij#Ez#AjR6MfE&DF z7BQVpqO-B*zDGW5`8WelCQ{@HSh1_c^3Ey3>p6Db3pk;K*-LCURc`~3*?;nV*6>R? z7KrPDr<=N-xLTJKp$37WgKHHi5PW3;SRh`Hq+#)wPZ?SVxVKu#UeegZ{{xip-9Dz) z?pdh*;Rzf+zn{OEFW${{wyp;tdW-;XzhY~{jI611sW0&uED-*^FIwp(8ChK8wlBM$ z7nm!JBhj0iif-O{igZq^2dyiP2PT404UOp%XYzIY`Ul!4aZZ-C#~GVOGt_MX7D)e^)6ipKTaBZj&~4=l&JO z{yl}Ujk35$W`@Bhu5zgw1}zXU>IB%RrS!d{SfIVMk@DbsS${8?pvPwv=Z@L$&Ewos zVQWMJeDeD{;_hkDW6jhKgvS67Y|q@d1``w~QtGPn*)^K9@A9|#>@fYti&dLjJls-^ z{lo`TJJPo&MESuNMe<0sod~eW>o1C*lExS5WZ#Jb25LZpfU1#Ye%k=R|L_0)e?U#I zKl`M(`$|7|w|^`D&O(Fnp=$%#-*IRV!%ZcjK`o|Xfbg@64~B;MH;xj9al*q_$A@g3 zP`oIO6RN5kBnDx~rDk%_6~>z|j*JIN$Z7JjAjbsCvMsmy2Oe^sl(@eR)!AZ~>M#b+ zH!S2%vajCb!1keNs^sbq>R6kLTwF=5UeiM$3=^tLI}b~6%!J9{tGl^dSOnz z2uZYAR+QvcRRmU)3L>zUE0HKH%lL3R3zifl{j!t~f87kU%VB;KpbEe^oBvFbsUkIU zpv3^=b^$7Q;=4ZErUZ^TY~jf@k`dHkaRJ`Sd{yYPdz{+h0Er7xwD2Lxh>?UtMO28K zdMdLnaepk}x|lRh#HlFjTOE{K7x|LyhPZuwF$WI}=VFV(G_miarH{viN+8sVJmx3~ z9(s&OPnXjiuN}{e=XtjL`VL*QDej=u)WW^I@bA);quN#0L@-B4u!X!>Jw1FPFrLew z3H}ymwTYuUH6SXNfgRj-;XzjajgkS?sb{~HQGc$t5aLpZaYjUe$cjXh>&I`mIKs}+ zJrf%~^xg6QiVG;tu5%hW{px+`Xz?NL+tyO8?Z=3Uo#M5}h%iEw|vn{t;Kz-->56C-D{&=04VsP(0AW?GgDLb@!8^3NWypDHIi;qp5j&3 z;nIi+Hkds4Vv;;cYKKh(2}taNLH?r!Qh#d4N@^`Oe4SmqLB|CqWO(Ku_((>z!2d-B z7l*w?olkTW&+ig-^U+2s9y+A5FM8D^s^hYl?sX#>jIv&&AbYL-^DJ8{Fh-U=5!66N)(fjbrnaKW5}@oLl^qPHj`r+-ii zd}A};hPe>@0P&hlT0bmiFfgFE-8y~w?eVK)Blton3Q`Lmp(=UE>fpSzAy#dc*<>p2 zFCDboZS(kpr#1Sw`tMON4{(JNY4ucibx%&obzz|llZ~mAH9>iIPNQ_!*=4FIiEBQR z+LroAcO`%g2d!pI^r^l04P^d)a({nc+236LOPA8Tk;U#A_R$Hs>Vr6*=Qnpx7THXm zTEh#tdEY2urjP=W$V;G6(Rh3nf#lU!;V{1{zsP55i5^C(s$JJ{D1r*4ZH@>P?Vrpe z+|Uoi+Ns2Z!+JmYPw&<|GHU1Q)XVqD9?`)<7`MkTggdlrGOTF2U2J-MZ-18b;{&h3 zFbXpqhdy``h4IxABE>T2tE|2~5>$SaXW?B`8p&2yg)I!vO_Ae+B(FZ1P#rlBtzrUBD;qnI<)aY&QA*9&GHeM+ zr5Spb{SwU0RGu6z+gZIAnzHli~^G_10*Dl&1jfW-9CzP2V7 z#L}zwSEiq?;o#`NPh$MT7(D?E0eOe;32Jf81<#^!473(P_Gq~KH-8XEVi>}SkROg; zS51j*D8E{4N!XtZeoCaLbF#^^)7BqE*PiK!2|D`Dn$Qx)@}C3N#kABs8Wj;iQe7KwIgNe$$A7 z3^3F2d(vA2{!2$}{2nKsQ1O>UJ3^^irzISxqU3m2#$Gs}?T@I1&>n$U{)!A-YZ@Wv zRQuyw4ZZA`B?T}$mpa$Rs7oBp zOA6JCXyLk&pMB~tVev~**2OJf>k+F;zfoVq)v?OHvI}Z;`^xb3SAA69C*`ZWu5Kf~ z>ZQDU@b#1RHC(COjIZ_js(UI{Jb$B}hO4cWJ!KbY>vC1D%*q@Pojl_7`crs}?sf~A z3G-wzDcQPKlYg4fI|&V2bB%qIw9^y`A*lQ!(Ue)=6c%5n`cUdhpTw+dI_2e4I_xmu z3cr?q48EVgZ;rlyZPEWJ0>UY>0XVx%OM6hOmZ9N+LsEZMB*IU!E@m_tcUV+vZuG*<=MVM5ZTG*uX1sRkQGW+~Pz9Q6f4bI8QpI*kzgwmC zq^YLbnxjWom<&Bpd6qh7S7%82`8c0SkKHT|dFPx$X{GY9%VsRHtVrx;rz`%(l4#WK zjNK0wQok%>rxEvefWfrb0umP*A0GO*89zIF{ovWzpF2N^^kdBh+%zx3CE~;#i-DFl zJS9e6w|}RStJN7gVk%{j|D_ot>RG?N# zZVR`{K{UpVrx7d+HF3dePj=BqN0RD$L9Fi;xqtjt#d%3!#0b&oaIZhHh*3-dvBOo& z5W*w*&?xq0NxAR#VlltK#3DqynShDkQztuZqIv(~PE1p3F=sk|tIh_4(vuv~VJ2EF{^4Lyt{k-5RBDf9pywQOULVwXuhVr#+` z&ghnl_0_d|A7KG_$W2-o*owPWjlkV_Wiz?SRu>Y6IU~j`B7jZx<%jFJ`_^8p@hTU) zDDb+bxwxb$pEQ%=b~atf?G*)m)3V+hm46|6dK&&N+sPkpkktRP$x;DWce7-ua>Up> zp!ogw-1z~b_{ot!YXFwRWznNY0RtXem zjnI)H9qw@3AU=S1uBO^SqC`O57RrhlZWxqTg6H-I%$8i}&*sTOEK>^^Qq^(us<`^? zVj*wjG_A<@o-`5Rlvl+CvS~)|LVxY=(jTII>VPR>js3$sDrJPi)Tdpqmu1JwFwb1m$HGw9vvrXxVb($@y-<~XNk8ZC{*C+4nN*fdI2|YC`Qlyj zF8|Zp+&VXtz}}N_q6vzIHH5lOp@hzSbTUL@LLeHXrqtYIN}2$Dy?=v24p5SF;mO*p z#ZaVh?-Mb|-T=Xn%$S7e`MHKY`)jb9GY8 zCfGHF6_WuXR~oftd6VBd&g!Y>kIT=-{)P|2pR1T|Jzwy>Lj5i8nbKvaqS0!;8{y=F zJ0397{nB-9j6^pBne~Tzha$e@dVh^2T4-M~y{i#n|zFWQ+ z;T%=WPwAjbe19Bopk+K9fO`)=xzri*n6tgJ>~Ny&%5)XM3M-X6rMRrdpT+2>0h1F> ze$2HGFmap!FH)()NFr4E*wju001*bo6RBADfX1c7c~)Lv%Jm5H3KzgMy3D1sk<$!2 zbAB%j=H85>P?y|mpMl0mA3ZX_rH5-A^4Vf>>(B*28Gn?xD6Vvqa+%?XKE`Y@zj6q6 zhR>Bnmi`Ai{v>d0I)U&=7{=>RYb&5kR5)~DHb3Ll%GA9g0j^PI!o$&UoD^m01p*ly zHc8iay(-|DIGSUA7n5&dSU$>$vrH%%xplTMlT#H-cXkchI=hYLyRdg1)%REzMD~ec zJ8O_DOn+ri|AAPJbgV6B7dMUME2MBRkpo8Iik3Av;h-TD@}3^wy`njE+^sl7SAQbn zT943|Zi-;?Xj3ZDnou2Q)9!;#%yxK~Rw-IpcdsBl_X5s1>C zeYam~(zWTDNfKy59hs>W{HiSk=JI-ndv4h*;1kw`S{p6Z2$_5)c7S&$avyvTCNk@REi{&x+(?X-EBUEyP5XFW#73SEK zxLw#JPUH>gxwxUGWI)HnqQ}nL@<0&B2xj}0VFdGz8=9TQMV)V!V0jvK!caSv3kDrg zrw%%?F?PR9ikUr5Jn`haxD-pWNss9C*?)__9Y1;T-HTVPAFpAK*-W}^kqdL(T7t!1 zz&P7(j@xwWEWx*us2-ZkyAm`-={TAmMrAlLo2Cp-%)1htO2Y^YjYyi8`KrcSv850u zH?#cF%UO1pFCRaA_<10{5+h^;D0xO zhOZ~d*V!zaU*HT%EO5doJIgL|90zZ(iR4x^k=#s+0!&LGu!7Nh{%CP=vA)G=*)=wk z6tjg|2>>Ki8dDR&ES3`)mA_ps-r~#+@7S7OWmni{F}*>%j(%P~e>+_+=9tUhl&$1= z!lGUkRtqlI*>Ws%5>FKkd!SnoJ!JLT`eaoRFHw!Qqi=_-k0DUt|7tCk;IU%Zj79s(+q?>o;Ajmk6@B&EAQ_)u(g64>5Ubxpad85i3XBRZ9vN*r zrWWG}9h%tLNq#9$l1DLCYj2{(6_`!6{&|zHt``&4$wRz*4!S>=`jDM3)+<#7D@!n8 zWgRYZR?dJlr_X>Si^+PHn>{iDut(9Vep2?DK8p@cZ*FI~-g=WMOn>o0CV?o-JHYdU zHxf}Wb*mc$wr8|)Hx^yr zSy@?`S^2SZK1=dG;D1aqy4l$1sJUR!SqR|JjrKnvr5I#g^_`03^hvsyvx2V5@Lg#1Zb{~M?u(g0BZJmSF!!m8KX9J+~?!3yX|r%l6bfHp(HH~WQnqwt`V-H zi8e`r-|7(N$+>E;U{D$}8QCEPuI%Vx%)5ogis&EN91j9waYCLbd;k?KnHRHclKv~< z10rYvPLW8Y`mXj5*$96~27X1GmpBmFqc<7{CSrmE>OO_pk&cs}bYveC$YXIAxvpo- zP}2M?Rs_7bN`|Ch>|EQuX|;*^l%?fb`@Gm zoKzL_>*(22#bl|II^xGxuE_hPraH^hiwk;T$n?c3-q&1QsYHKH>ZFhiNf}kq;PT!O z)}2HArJ(O!arfR4mq!%T_PL932sm|js{7_cJVqMk@+TG<-xVD5jjlD1#2+Z+!W}%| zzJTBZSHwvxlQ;);2(bgeCV?5a9yXn7L?%NbFcgSWdY77|W8QF|ajLw_{^+hT=YT(| zjhYaHNSv}4$g+RLiV5pRKM{UO7BKpsjB?ikqi^PC2iWUsbYkBIHkY)|&}U`*1!=!1W&s;*8TtHe!j;Q*}S(%V-v zh>onrdeZBf0r5Z9L=UNpS$u)a)Ks53z0DS}aHpBZ^4R8FTi%za$3i9)vy_MA=3`nU z^&Q6!y-A4q^E;OK=AL>_kW-@X@jak09Sv#=rlgj@i+mh14f)ga))XM&gbLtHTmQk))!D2E=eZoGD?z$p0SASMq|AWkAAIQ_+8y)FHpkllPU5w8wd6;#sde^wU3Y zNd(#2*eKE4PPES?wi~@6`ns;*h;Qkl$1*y+8@xU`=_5w%9vu!&4qm(i{ZLlYuV%Rw{gmbJ>7u(xNX`a${)gfh3cTeh)t~hZ!ko6kkk*l1Kt}F~ z#>Hg}JP;seIsy|A)$Yt_fA9#Y!PGI={%&}>%uFE6DQIs@!XLB`C;fpW&hWB>!4NG= zbGY;J9W3v4jDc3v^)N;TXrjc-!iq~5^J{-3HfT{Bg9?w0`wC!~!1ojk-+1Y-b`QSW z-8qhmVVZPWtxl<%R9j_j4tU$k`GG0j5oFY!}rLH@U0h&6v)u$W$$lRm&qG^Hp@_iRnp5}vLo>_aM zS@sn5iQj8R3?C4gjzG|T?1cZW@8W-lr=pJ=O1wy0Me2j1Qhz|eVuxIZOGQUpTUfh+ zO1f2_|Ea%c+TbQ;PZmyfNV2-DQ?L-jf)Xs~oQ$C0HX%P8u&7qVyaUV>adt8DSL;1F z!+OlYOOaVZ`evq?F-y)6eiibHeIk649TYc?$nvhhXMF>mu9<^*8hqlX3DbYc!4{?l zV>IR=blw9pGk|9ibf-D!RcL(0_=BUpBdfwvz;Sh8#PvX3e3?^=>Cen-ZU6mnLCcLC6fU)U5aP zly|BVJ%c4dffFi#AGh$eC}GCb2%0Pp)X19Z(u=^lvCMme^l8tcrdM?Ij`=lblZ}1p zj8Kn!ZJWWY7Y%R^zk+qf1jA^k3)vFMhEz{Sgf*mm;Ki93H|l0LAIYTS{t z6+zO|h)ESVBW^AI)0COJh<<29sltb_aOjJMm4acD{RpR)WO-E$bE#w%GLAaX0Tw`S z1`mG81cs(#_n0mrCyj2X6Uw`m;`2w67hOmGBME~=c4w&zR_I)%T!V+_cA#mfKo;5&i;(poUZ8s|E{1{2F2hwF?6YwkF>OXqZ$>V<}1hHTeTDyS#;q zP_0u%k8SzcR{ce~7aA+fmnoZFI=m?BwNN2|8x7|LpIq?3(1`y70TTK~N6j6+_1>N7 z9eao&cHH-hGYEfiq18k2kafbD9HVC!wZ$WK+!Z6z-w)>B8n-Qb+Yq~*T@(`U7WYK_ zs?7>Do)?$iH|^%-9+^<1FAP|Qt*?cZ2OsYU50c3h+NNw9$brc-f7KRw>3NXe62WyW zzo_${;%iJiL6PCM0Zh(lJ@tqBFQ)k`+@^hX5e+YG2a|sZsDJG2_={Uji2+@A&@KrE z)U$320u43qRE$$<@-``o_#zo(V74kGDL8F-ha)5)$dkiT5e_c0$COb$$1)a{U zZ7zlu^mo=I101|K0UQ>XiGO?Vt^QO?({oP*@F(1{zl;-lCFqn6u0}bcP5jRyB2`CL ztP;f170GpN4K~3%-cu3asZ)19$DShJ4yi)WL^OY-ULbRC`m3Yw!xDjXIM&E2E2Nn> zt1~=|dETM#Jxk-)t?_G^r=frby2oDF3(jM2m{#(#d_!_;9V^%%pgMYwZ3CCIde-U{ zf{k`0B45oAwGJo$V<71Z(_e6jPon>oJ-eTvV~PmlMk2>l2cO`5b3#g0u<1Mcp zJ@kKDdQ})BZGl2#87W0cN7n_J*n*}7avr;KOMQQ83C$-jVybWmE%UrpX} zszWrH?ztOoXf-O652jj>`4s(K3D!nGrVgI@U#K}YlYxd#cJ>b5^@-MhYf8Tj(D-ik zMtK(dA!i9iEbQ2|^-uS920N%tW`aOV?zn%o7WjdjP_Xb~<~oAV|16zws&(xQ&GNe* zz^mdZ5HWP>cHQ2^NixHrPfR)7p#GmaKTa3I8&kkC$bbbWxTbLSUse~sa5y&$Ed&M@ zNFhtAM)t)&Z`M#p$rnF1KvNzPo>o zJoIaeZ)%%@psFV9^gs4wnfJOk7q2Ul1${te3<&2cVi#z= zPJWkLbj5nm!a%#kN-&S;PK08D>mYnR54D9Cc3GidI{78AVenqzyeves_&vD?fXUHt zhyoQ}KdybAuRRY?;?q;o&$y0Cj(UH+>uA*1yLB62H>_rbp<}F_e%B6Zg<5@rq0MVx zzFvtrp?DzGd54`RFy|==xqQPxhnr8!Nq%)oM*YQ&6C}*Nw9e!gSTj~RV}oSz{Uo?Piu3%+pOk~!PO9(c|Hcu zo{w=pBB0YDkDl*EsA`eyD?@+(Ur5Gjg0Ze&TqV|82+fdC`yVTepUp=sVPKtcI!U!h z!@DaCO^~NRk2tKM9=WS6bxRW>6GhGYR?JX9kftKXZ3n*ZOW*^m#OvWWu|^>2be`O% z?;pIr_vXeqgcjpIn~-BZKge*B59xkJ?)_f$A~5+R*GdP8+D-H??nHl|_|Yh7N*uq8 z#innf0k{f`k}((4cXCclQWWuolT>-0U3HFeNHB@hG56K9h+ZTGxgtRS@b++io=gii ztQjTx56_yp-Kw8G_4el-+mN5nA;GH}CkE20ZVVD}tR-9y(VeVOJ$P=Y>Lxlb*_;na zhe$fOlfesNaE-z&3{rpDbyu75gST4}KFy^!YHjsz1X|U0bSudC*M;!>lJw0ufALO6 z)~2s?53q07R@t~q_DF9`zMcMNY2?<1rr9t=StB^u;DF^1v6*=0(-mL!D!!z-mzk zi5HL=B9ZGoOV@uiNh?G|8;7Xr>&}qG$a6aaBMOB>udI*BP_Tb4qLj%DKI#Z=5lBwo zbR~;=HNCtF^zy2@7d!tyuA9;N$P%GKS93~d;DH?Hsm;C@SY7?%uvceQ%oq^|78;>z zTu!Hu50L8DtzPx4r))-w>()>+f(6a@>(N_uKxgX$9ln1`Q=MvvSkAU-E6sAN8#ui} zZ+j+2HuhSc{O>%?6EuRc#&)~M@6=KM2tHTDd0k>8NDyCQNIWqGOMc^gA6+H6TKOb4 zE{aD}brE09XJV!qhiHh;ifpWCfvPY~VD52#Z5?#ZVLr)pcua z1k5szrVf9u$XWFqS%JAv+((~2EEwYaylRZ|^Ri`7U?4J3)Nu+q8{ZfLpkk}TqX8^E zQTL1EqT(Wi1)vtU5_UANpT6}&G}f8xT^rf5Y>3HTmDgw%I+-gR(FIz3VV^`H7~-)o zb7cW3UdyMCT1$1#+7DE(l4W`mT`Sf1mZX%1#ISr(ngc@%$yH+2({ zLGxjkiVGnw$nwo3<2qHLm&^3x5+h6N>2Tz9Jr$e@sNC|A=FUDUP8Q@G*nL!i7e3AV z!J&V7YY?wOZ5UjiYuZi61)FXElPgY$rd`g4z!YpmpfM^!ay$#0)mpb)TO+ zW@G?+0Q4uz+ZmOwl0y8n2-e+L;1iVZy=Nq<`kSodq?|ytfK3W zuWL7F<==b<|K^iK$LPSV+rm(3|I}(0JVt8rsZEK5)wPrWlRvff@rgCy&HU{9t!Ouc zhl3da6|%Yly(sH~G^ypc1{g-i=+M|UPTwaknu+u97d6^e1YRh>`cNk*xE+5VxG3ad z(8?wH-dv)8K2{rc-4Gf-Z2tK<0RwCnw39F%P4`6X0^GDv;a6FQsTjPl=9FfCr z$D|e#PREYIwCs26-^HGvu)JX(%Xc^b_NB)+_o2)x6MM36&$as#S(ck`Fpi>z=t`!)8IKa{9>k-FNi-gBH%Vdf z&WOO~FLOxJ^6-Ff(<%K8BPkq7Cv*LLX5=(?i`3}vC*NH*?LYoi^dJ9vyZ$SDj(64O zj28H!OAJJ(bcx*+e(9nLxTfD!dU=;00%#`IY*p3>n%6HMs{`&;_lkeji^p3!RtJV$ zDpoJ-|BhH)12W8HPot<$_!kUfz=Tw|vj*l}M{C>LYdf#rfQR&pofG=A|7L9+f;FQj z6k-TgrE!I8eHbz4U}=s&QS*pS>p^U%Gzgrrji(Dyxsi9)h7nVrMK=QL>_7P;O z=TDzL#Vha!_@RCDSe$?V5a%iKw5&1aHa~Eazo>sLa7E{2D9RS1_WQTdx~NI!O4|{V z6sImdEV#8fmbwcKYGnuL;&QsU7W{7)=eX{gt=D!CIH=w5Y3h17-}P{`>tU1irn^IE z1W8p*Pz2yneEz_uO-55IXLIZyUb#!3wnM=1TKE)ZI}9tYl_7r<+a3)4diuC;d$84O zQ!5AG!*g&xbfFDC8gy2TQJs!(=MAD;Yz#p_$DhgJ|_ie0CziO%uGOVZm3G}LO zo3kt%C+adF8vt$Vn}H}bOvYkMdUm(=F_+eocMCgR?N)zzw=~pWkn+6(?9@NNuLaDh zOK7x$_!7?t#t>TSMp0Cv)jqksJ|ckkucyPyJey|f8s%;|hUG2c-3-vIk|~xw`sr!g zjn+YR_L6aY&9AcX%L$!Rc{$_#jrX#;)ZvFUD|7DXX7oq(aSH;ZTCFhqp(#+itzr_i z+NnFxpf-OHZwTsERH)7PK!#dwc0)SUnB}*Gs4drTTdt7xVHy8h?HTsF+OrDwzvA8& z*EB?7gg5-s?TxSTo=QmWAHJZ1BEz>?Xm{=7;H|HHjzqEA>xaDd+in$hx!S2aT>I@H z-VoF+uKjl7pPshf?7N8{tQ!O+DE!)ZII2a1-bQ~~QO#DT;lag~cAKT;mDOI|MPO+I z^QORVC$hAe0m>{bH+%`yi{4run0nEmt*I9^Tb+o9rCzN!3;SH{)m^ApOPDtWb_?p& zat5cT?KgaB)QfTkJUIPo^+YHs*y@q+^U$zX8z!jOYQOGA$6CO>Ilx=fvKF(@)U4gc zFM)raoeW<1X-OVR4W@n2+bF>XFIETRfmL9wwuuyAt6jRa`l|)B8-clv>Z`@rr>8A9 zdD*XBhG4aQ>@p2kDEhE(UANh>eJ8g5YR8(qbaBG5A}O1v+~H;yCau8%_y0#Ib?U2P zEF<%yLC&?~bezrhl6WNhhyM@dl@j41!A5_#Gs#9^qgv%p$k?CMJj%v@jn#$aFDpx$v1N@l;HN=JWF z<5uFPtSp1jT@(PLYGEc82lSz12E}6tQgChkN3=tU4l(f_yrz*bQIw!$N&!H=c_?6= zpErUQX9Ep@i-Xmf;B_Vt#=T9R-m*e7uk;}*Pk%@C{vn=uQPLLSu+AuIcv@jAPLB)o9gu(mr>yWpxd z)Lk9sqN(sfb{4P-Nx0mQgl;rKKbYP_MJ8-{x3S2ofTDAM9Vht^f~|jF&9*)n5NVk1cuS_Y=Hac+T#U}i&3tsbk1 z!>~L$lenRdjYpZgHS_FKvc9^tF^n%&Q;q*3Nb7~Yt{p) z3g6#>hEOaFjc>Q69c6lPrl&l*`vKJfJAzZLI)GFyd<4BNwu65?FFH09Oi0Jh;@gFC z#@gICKYrqr7^gNi%#FP|3r-C>12%jq;_mP0>8lE9vT1DGZadDJO1Zd$!UF3!(4_;T z#9&NSN+FTSkY$*Ug7g@%d|99In|g{Vw=zXNVYIVk7z3vrQ=&Kp(hHMr76Uh8tk?oV z&O!)N2y~zDU5S6su0X9YwVjwF>99H&<;Fy8)mk_H2MF%9D@_L??s!TgQ->aK5n!EY z|L^;UgMQCE#Bp-EqpA5K02*qEPkp8p{q2N9O};6nXoTyaN5GM+R@bJNcI$I4$Xb|<;B(-v*GF} z+?W>QtRApjHefBGqXi9Q*7@Mx_o*XN}Y zb|=I#{?E4lFOULtnvP2Yg>(aMyHZw9FOjRjN6de*YRkN|t&t1EJ50Lel${BC(=`*+ zb@&I9hQ{v_v@W9!g#Jr8XPvIVzTsJqi82owqDXCH1+#2JN|_m?v!p=yqy!m5V0qx9 zfY~6HQn4JDGWFFMI0V#Xu{Wk~Ld!pl9T77jn zqN8o%@O|Fh`q>!%6+dtM=^Ng`9!Ia097@dZLc-W@;Yn+peN>xqJ<*y_*h!iQQV3kP zq7G;pFUSKiFAnkH7G>_yrna{2pHKpa2Qp_`!eW zyy(JyeN^Df=8*XTKrfeSx|3_-T)T6~#H(|GbjwpZhh*PoFsZBxq!!T`%Tkl;lCy2d z>AGq&$O9bZ^C_li0=|*w7}_S2bhsklBBxt4Bc=RiJkbE5gsFgE0j(R;&41ZW4AU7n zK+~Nxr_Atde=K+mBWxYI8xQ(-%nyH6zB!${!R0V_2@-!#hMNkr%TrM|4wP9g^U0IJ z(cY0xrV8(U#z4$aGPoXJogBS8?sBRXR29A=7O3-Vy%(H=Ffm|+Gdh13IQ29ke|H_Rk;e0jD(_7gwT|iASzPiz1oBt1V};4Gu1j(L~XJ9!K@ir_54lZ1Kzy)w-5S^BIWm9cj7y9$_}eH- z9-NXxJxcSeA|G|6qb=EJPcod{vY!Q+@L;!oY30_)vBjTWw`s>c&LQOkIA=6biByeAYqh6MF#4Wl=6BE(|FwVD}izcQgNBf z60lRqZ!vuiifu+5DPMn(KgHKLO-`Sd{>!|UlBiZf!#&8Zt_1Jl`);Jp{CgbERnFK+ zvOq>MIbQ9Y8^~J4xbiEU!C3-NUvYko$xiTl%>w#HpHnJxRXRi)BpnGu;-z`0k=xsi zo^}0%NWM~P&85GGM?Xbs$%Fl9@OqyZn{O5kzE4KG9T;s>=nj9Izhx|XZ{df(z=Mjg zZC}Q4v5g28w7Uz5ZT2l^fi^_ax|6{R`&MF+l?vIKW)WwImOSf5p2SvvZD4y7k}88^ z(K~}e*azS~-ZuQ(!N|gAHaF#Q+>G#j?=f10?d=S9=)G?*p2gKOW$UtztsB+QU_otx)|fUuwO@O? zeYEl-!;=ca!WW}wMNoB(vI9tDZ|TRi_0*`(%D3X_DKGI^*IngpFSTVrU7P29UcjH8 z;ygm2d2omr+sMxi5;2iFu%HK_qK4z-$?-V8PV#@Pjg5aK#l}YTUG#RYuDSB(odfa5 z>-i*}s=p62{1Zb`s6W-sr2c-w*}6A2j`Qq8s&4AtaaJfon&we|eg(#|=xmm!YA)kz zG?9h3&OFPmiXP>3#}2;v`9(Yh|Fr|)I)CZ(djC8I4}$3oJ&=ljs(skq>Hl*tji2@Y z?sm!4@+)rr+y!!Bv*S_SoLE~ zFW?%02}#k{Z&nz$JJ|oT@Z1c>GFM~|(*svTkTweHkhDb1 zpQDivthVG+Fj2@fBEy6PXNKM4X3aU)M~TrW1O>V7QcKv-*cZ#V)=s4{x)R(WFAY% zltetSkSCsrHG={V))}XhR6CO5G~-+y8^!iAf29ZSve8v}PUHonCQs&kVo)(nVV^}) zk>d`Gle*_cI9LA?FPMNMTFC|HG6sLI{bZ`0kF29;7pm8xvWctU#m>5rqN zP zU9!z2X#MrBfcdjGwzLLC5RI~gOp4x5L4wlwCASa|4cwX0R1^sy+ZquJ-zUjcGz0}A zUT5NwH4?XX;~D?Or)GF6$pcZ%6BAEIA%xx;qHkVWTyAWjpYQMzU3$?sjqO_r(-b|< z&Wa@ekYe`ce0IHM$Ad6SarA$3XIK0Mj=m!rNPJbd?mWuSx5N@8=svmxmPphbYSpCq zmH`k8dLg;Rwmy4WLl}Tg#ReD%EkzY}`tD6C?aaije9QUxJlDZYAcwFYM%?K#sr5G% z=_HU+h!#RuJ;DhlFD9q@<||b;y`a|a?W(@GcWa%SxliA_+S-Qf+k$`OaSG=`u>yvH zWs^NiVpQAFErGgefU0@dp5k65ivd2EvwS|BWx1(l9Kuxu3If99GS~Y~@3jYFa zRm%YN_6sqr1W?UzieZS+oLRU!Q)7-%)MwvFnj9R{3SP~#BMrmV>zm2<9>u789A{lKtluykK;#eto!Nsm9Rt^CTuhh~DVo=ZU0Sv5f&f}rj z<0?E7{Wy5>Zg6mP*caf2(~pvRdNxPW>jUKH|C)ctzy8v>oXxI^t?#~5oZEa- z^pf~uob>YfcasF-1N5#gum10R`!7$wfBtlRV`B@t941#v65APlNQX)EeO+>xo+6^51W5}{(XzSU(LajYphU-1}OKx_2;L5e(DbLr|+LW{{aVqE)^CbQhi7Z zdQ=rK+nImQF0;G@t(W8U;&R5S4`;G_7(z)+R$Y1*c|4iu36nx-l8xqL(g4JT^&)bv zYuI(Fjc6pw=&NF86n3_CE@SnOtFEzETTk}whVeRmvD4pY2SyXfORIux{4f@+r%Hs^ z47H9^$DM24nUYhg)L#GoZxR&S(I-$t?gB*E#dCj6l&^LF{oih6_})dVi`3mp-Y2Zs z^#C9|&u$BV-$5AQuRZK__5Kq>5sYL90w%E=cr{+a$(%0U1NbX)9%3nMgoZmF|IXUU z&fdY%+V=L^&Z{>N<^09Y3H{lBv!<@@JqnYED2G*PToDk&Lv#5mp^NAh+oE_kgpU;= zmAil5*80)Ay8?7wZ@tzR?|`)3tQ-f3Ljk>aqZ)D>KW=e7dPM8`FDv)D3~%aWSn}AR z&-#(GC$ME(tlyOHNZDK1WCY(&h4;DWSGqNNvGG6oNv*YXvKHOwzY_O*y95A6YSjDJ zG_wy4-Q!IUCwkDG+uT0XN;j$`;IjBWJ!5~JX*BVT4K=9rjty0=^p7Qskq5p_iVoen zVZNWXn}=Pl_O5#G9%=A5$^X+1`nLePjsiwa=^E{j?lK;)<09B0Sp9lsLz_|TF_0jw zk|Ps(4(m-*aUt&MkdQR8;YP&Zz*mE2)?SVo1R_(=p_fz$mc*CvBw=f`s_XZl2d00S zy`Pf!Dx2>AdIgR}G_v!_8J(T8l~|m8j3)EpW%Lp2inj|CPDk)ch2fgx*(@Hu?}1mg zjf%;-s1f?_jO@(6b3K3Q&FM0ON1B9Y1F|dxaQQfe2%1B6+Vv5x+Q1#0z<X7bViW8k(^49F7IR~r?ptw3epiofTtScFN0sS7tp5M40RBG`Ydk%jc{7!!s zuGp~K)J~Won2~zX(Ye~4d0yx+1UY-rgRvW`@d$?HrqO850Z)@W2al^^^-LJEwdURq zFK?tqdCA(*#M9F|P{rEpWGgbdBBLuZx+0@r3K=DRSfgx-S_IS@z(q_wcBvaR)~NmF z0~Os?1k*d!AWLG~ox(0xJJo+UJEBpBbfYy=cP~*zvT#I*OFCN8fKsk+qLsn1+bU(S z(Mqll$@LGUk7>DW*yC!~mZObn1^cEzZzcP*n!)L5`;A{h3i&-^26b}QhtV^=u|sv_uodBs zM2(rBbGOED&P%xK)C?rt)tkrhN%;79*Aw9*I=3{3^KV{79^B@ZDb)BF?n3MwG84`X zsM#O$lCBDaB(IX$G1lt9pX)t$L^AkXI4<`U${XgSC$pK!Oq+ko^zX0`bdiB)QvEs) zJ^4Acv-9)ba#F#VEr3XrgzB~FQT$+c5CY$6$dT8`qSiS(&C{sZX3YJODgSOWj=CoY1 zcVUD0ejM`?O$L9!x1OmuAZZf0m z)*^KlCrm46xCz%gs4{K$S}sb@B}3amfUQsbe!B%>C!KY8$=9L)WPX-^{o)2p)c^Ch z#81};->4L$3wj%b9d_larNxv}V62avug37jt!q3PFkF9WV4cn!kLSfD#Vob!=*lFN zB4k+UkHi5<10XVk=5%9{C7Ezk`d~e=VN*ja-HJfDRqxs2(2jKf_J$Q>{kE(PtF~@s zYxwZChE)i+vNUXDX;{Z5HR4{ol5BM&|0)aJkEN^8-=q-d#ZGTBtFxq++Ya?q==8^@ zPrZsFkg$LEK~>*;TB`35rtQE={Qk}9-}aa2)wCXlh-Wrvp~?FAB#UKBR><4Kf(AU6%kbrv)Lj6#6*2SB(2H+WZU*^i}EhZ zVxIEm&*t_avXCYF}tBblj@6eD-Ymn!bZQPr21V-{(@#_8LF;VsaH*lBzG# z30ivAUMpzZN{yekSqZEBWE%nFpC5}qn3rg($8)A74YmJ}7df6W0am?AX`Ruts;2UJU8|m%h%>FKzYyv_QSvCe4Je zf?><(#X6Wk778cJ1X8@2lRmM%Bl)CgFP5lEYZ9?< zs!)4xw;#anE#p?OC}RxUh^6=d)}QBb%*+QI5|C(SITOc9u^eW?wQ% zlY*qw7(>-_HjAO+&p`&vF;FYyZSYI%%NWCaL6Ga?+Jqv|dBJgv`NkcPU}WSDh=Q1?>D(9aU*YT6gT{w%D8Jdcw2}H+VA}n8JSn(9y69 zoelvGf=R^TM#>j)sx9ucn5Cb#;U{`}rc(&x>NwZ#Y=b8mD&YKE!>W`Q;oSq|bospL z1R8u=rG-e+!`MY zgzydMSu+g-1PTu7?b{#izM+D(a@(M^e%E|@D|b!v?{?qrcejG@im$@(!l{+PWp+%< zMXTHvtK1g9iQE>t_n#D!T_t%{*KV)R|GfE^^{wD6_mc4}{>xt)!r zSNDAU@cAWvBdIYo39WisFy1xxxgWTkP)>}8&vJ2mpII)B8)vz25c78#>Oh{=&z|AJ zZrp+l7pr}{hYS}Q`i;S^r?A9`@_G(+5#=j4zhP{iB}Z_!NV|U^f^!9L_M3k~zb%Q; zJGH;82+nq=_Uj`!|6X^hiXm8bJY{qH-%C7Y|N5^C(7L)s9_aROxo!3Flvlg9{CLV5 z_DzA_>gI1XgPM5CcfI%TFP`!(rJ07|Sbcg{6O?szHXewgwcff#@sw9PcOUVTHT;_c zz7=I_Ig8qO%6EUIZjE@irNmRdCADi5n$@Rul>u5;=jDMYUCV8&kEguawdKcC*0661 z^i~wE)eLIlDc=+Cc0ciyZ%g1B2VX5wx!Mq{t3&fpr0y0Q*dU(rYX9yjp0c5QZNT4- z{I#CTqIk-8A%wS$rwrETWP??>af+oW*jaXwg1yu&Z3=(d2Sq;a-RhuJVDx zRhAZgkMSxEsjd%)XN`D!?)H?!;n}LU4u{9V|7d^1(P5`9?43a2Nj$sk(e#~IUY^CV zDDkc0*Fz&{%qw^A8z~2HGxFE{sd3Y;H-npURH}!M9O#36>0tv2247>?KxenAVg@!j ztYsi9BLv_vMEdxUsMD293lWs?OHcM;!|a6T;c|cF@5uVyERk{4f{&*^vlZ@^ctnO6 z{E2@Y(6MH$^ADs|AqQn2OAI+^J9Tq&!21IK&s%#*V^vlRSDOHV7({6dZ6{QojlNn-e0nnuRQ{!k^3GybaFd-8WR214m0f^ zuIT4{iW1-}T+bOMm&hXV3hN7m_*C4HX>otJfK_sSL2XP#xsi8@Aj&_PDfhYa;JY@YNUg##}sR+M~1a?=T5~sl)fp!h(OE<(Wa$^ z+QmWy45LZ*2Rnm<-QYC)xax&dqiVq!QBjLI8Q2}~?EdG@tNkvt6STy zzqT;M1MAT+4=iJ-$d3-}WfVX^>IMfoA_5Q9d^IJ+d2?|1>dn#4UJ!S10XMYYY6wdN z;96RMP`iT2gnI}g+pe8=16(U_3-Va|q2*9`Gc}Ao*D?U76RuWO2*j#=Sxg>-dFAoR z(e8e~UlFC&wl&5`IDaobEv$b_m&XoC)(GGu*Muj|~~;+BOp&!8ra9r*t8s zxu=FWJtII!IMqXXp6HNn1{;fX0?!^g%#T@@UK9;p;FUUwhpLgnfmGl3w8&9TiFG2}oq3VL(j_ zzsj=@>8Mnf<|fg=4f6<-ps0mgwvLQe2{+k`EY3$apj_Rhdl7=@MfkN4xQlTK`R42e zJ9?w8BflT`CVNp5g6IeE{6dX0hQN{v!l0|uVc^1JyqntTYQ@VY48#RuC>G82lquRQ z9?cnB5XKRN{L=h|vs8bS4^deu2dbKrS-CeQfA!v!g1tfD?i-hj(@_o0DH^FdlJM%- zoSI-J5P-Fe7MlVsDD=oe zix8**w@?gkNvu3Z2t> zrp-us=Ml0MJ(z5?9Mb$7Ip%dXk3Pmz)J}0w!QiHNS}Wu>AUrO~d(zxVq)|JYlbjEu zd^@JtVF)cydY)gy=Nz;4;$1DGf4if7BXL;cz7j_7Y0rNsz(kWhQ^ORM0~Kbu`cjQG zoIb?kd7=Y>D<~2?M2J!&f~!L`%+o7-PcajwDC}cL_7ZXRN-T+}P~xRDJ5(0JppDCE zArOfD$#UWs0`f4ncF39z6zXvt5ystx^_{`W&(swO&;nZA_OMLb=_sLd`=XP zZw(`NOOStkFG0rquZ3C}Xik;paJ$g_vopKj00fb(;Q)_-vzT!j)*4>2}$z98pY4A}B z0R291I5W#BU~qcHZ{s~3V%T=&-q>qe=Z)ZvmP~(+yws|EM%0GT5ptDR+Ejw2Mm;c9vfSgO-CRSnd8|wNYumt)YcD)rQ6GFt{@IM z>&btUBr_a4Dv^M9;yb*oHiA~F&mW%ZAfk$ON~a?UGBgEoqFDV_w(j(_)#fenlzshp zfU5zBYozdn_*#rWmAt3!kVDnzJ6NjHlF~G@SjD`FjV4RI!(iqaX}MII0PtC_yWb_; zPfwde{wuj0WYKVPsHD2z$r%Qv z9mKw1c-=d+^lLyHc-b~zAjIx>a#E^o2{qa33xwN!Pmf_o{&D2OqzI>`j9Q}yI<|jF z{GMntdY)MG0aWjm{SWjVrc)?c3X2o(8#jqjM&9YPAR8P>WOc#s6x2jp2jF$%83^b) zl$^zwr(rj2l}74U{W>65LR?^!sB2|^dXY{OOj5T0zw`8Jf=?5DB|Cov`Ixksp`kGM zN_Z%#|L36w`g2OG$b6dq?_B*gQp10N5Fhlo9yX_XPCAsSYg7U+dNJix%yLd&$_DGP&@zPPa+t*V z5@!P0Ih|p5p0L>?H8X#F+|e_b zrsFPJ=%||AfRzsLLGlS9o?at2#n#`gXDwx?(zHRTf4!6`0Bk^$zgsJ<$_bmuTzzdp z$mX!Bi}P`OVO~^H$zbaTW!om;0#o>Y7n-6 zYcWK?6wVpMTqc}HDwIh|6+;>+e5?e2s)<(A{DCXl4@{KgCwUY~-!-zy0vDDXWr53F zOmNH0cDkP>)MUwxxs09brYGjhNyaAeRrFU)>bvF~9kW&j&(ZNw{{Yh6;W_%7I6O{) zzEw-7zpRb07bC2o;8ebR+Bh#eT+dN_rV3*?nKzoQVa+7 z&PkITMnuw4W19XFoG)8V<;J?9XybMaZN#oRYg$}e6LGWr5SFG6q}Bo!()^&bNhHYVZ{crQBHg9FyFpn&MelD1%sMW3ct3?lb+D&#wPCVF7g8TEHx@9({PvwuP-ma3Bc@w0*DFC2VfQ4qvIGP%MyC2aqL_DAu5$EnVKZB{Jp ztF1M!XSA`91~;K{eHgzJ2_sHHb!3P$TEQ-)m&U*?a^nmWe5uXBDJkO{%{JX$KWrve zAqSUNbWi{WRxL>pQd@B2l5Fkn3{e<#oh|(loQllXP6@B2SM(-Ob~7dfP|_XC*j?tM zcAplfXy!-n(w<7zowP20tQY^|%EhNA`^P8y{r$rM-7R4$*{|iw%#Woo9d5FK1#~N~ zISf=qhmvgxqLNp?nLv%C~wV2$z+m@Y9f&1Jh0Da+HK$xailI&g1;*x! z2`^TwqPJF&ivWawKfg-qN?TAqa`0WyQVNQ5kCe2Mir;2rkHi{0yDOd5ERUI3_#8pp z3yT!8A=Nbax`2F>fxMNa=;gHywmRMK9tnpwW6*NS;b_yys*g*t&TDs?CAO^0!?qiPf#OocVc zc|xjs2Z=9#Qia^?J2YpHg20?uU7X2D%D)tFD_hCsI>KAkLdDEa5|YbdTIfY#Lg47& zT9TH{ya+@N!*G(Yh#lHm4mSE6!lqSkS&gnlR|xQIR1o5v9B9-tUR7N*VZPi@Key+> zf?y5tVb3h~nV3zItUe16Qa9YMbGMMg+*f|Lj9m+Vg%)=UumDne@T01D%9G}|k8|(% zcZ*b>+H=LH&ggcWIXmai)0embxZ2`Gg_gy^679@{fceUh1I^*?7dh;~J(%vKdFa?< zL1#V02HJKu%fr#sIxWhurspm=H%>l92i zGwMcvR3ZbxQJ!6us})I1;hs~nFZ5SqIVTgN&)_C-S0TJC1tZtIFR^1^7Ij+8?b56l zpxhTgkX&Zh-ejn`Sn;^a<8fVpI?Qb&hO?GSw3sR6<^VJH|9YI5Q}_?k>)$JvC>blk z96oKp3py86Q6QR67P5K$cPKGSqfxEH`dm z1wH%h+*XD9ih;d|u+`k9&YpRMO_t%vq<0CDINIE5dKM&ni@2^TGFK(h--*!uZfV?N zIld98tI*KjO-FxsEq%C$AT?c}u)mwQ*hXn@LU@)0zps}NjioLBJrW{uwHV2)hAudN zk{YwShS@F8spjpQ8#_{5xdYx>SA>(>{CWB2yWKOD>~rPjmIN}(KDYzxOr#`faUepg zp<{L?Egbz`>=br@SHKu^qip&uWZ(PXv@VQvRKq5*D5zuPrZ!Y}>458az;@pqtCi0o zq1OxDIHNo5-crTs35ILYY^;ump(W{mfXTZfSbu49wLu=DE)|qvfwc@r3$1*ZHRbK! zX1VF2=UJ{+@j|ORS^P+=Yc_nR)sO93=`Xov&&r3Bi-dHztDQMM z$CK_Y2lrof1(GiHVYft~>Ti^~7u?0Pxe9+D=pL>$)Nu=NEt-W7RX|Ch4*mFldN`Uk zxggl0r|XW}MaIVdC8U$)cZufU?sSY;*j*|X%)W+`Bg8vfdH$kRz7$7F9-;(WaM8Xtq6rf zpO@>z+zM*dO8qRh9Iq0SC9QpE({XbpCtviX-1G)r zC?enS4y{vvG}d>RIaCfBR(&KjT{T^fzxPd^v+sHD7qVHTt*+y5|?z{UICi+rt^9-f$dJ`<` z9=LuUMk)#iHDbe{9aeir{*uC8|BEIU?ZItII~ZFFplSQ>rlQeHR8T%+o7_^BnxBw zvn~y+$I|Z_l?I{0zk$D$A4L}0g9fmzfhFjdj%)4hL6?qcx5qQgj(}~3`YqYFA7NGZ zx%T)z*yE*vM06FWdC~E(PsvZ}A2d52Wg347&#LSKU1#8b+gXHv!HDvZcHwj_X^rtV zzd|!xa{r|0Zg}F@y7f4Hu0|1lg(6H!i;jpi>G*irwVQ`_-`U)Brt6k;$BRgsuQsl+ zr+3ZadBocXQ>kU$>iv-)iYDGYq{<+jpnl<5vmhv6%PnhOu5o=q4|w>WUOoN$2X-=B zcnvRdufJn|q=hdV@qQce7O16t@ssrX!O;A!cYQ4t57BWELf4_>kpl@q&BZ}5%q4ei zTij!G3i@x*oXg{Lo0#={*$3-^VpY_JV7Pq%hE}kB$Q!az7=~Lxvp$Hz*K}cOga}@f z@+vIfZb7vksqZuvb^6EaQRglE=VkxxdbBefCRa0mN@{|OyUMd6Byj|Kcr}i%70z{L zaq)gViVMhs0?B1$cI@r#ZTrXL=*LfwcXkJ-JN^E_tHT=zJ|E3=9BK^p-;K1E@W)SH zKAaoT(c{Q=!-?i63XJJA*|N2~tO8y=$cHkCr|1|Mn|0~@v5V{eq z(|qUkV-|F5>y(zj(IxOhzH>@V2@KW7#wb01KTq)5OVy4iCW{i}u!}#W**xFah~`)7 z1D+;IS*Xy=@4qMUz(ItK4N8N)vEg7rQY2BFi%ju4ZT*OB2=%6jMp3k(dc)^9)mcOj zbClFVAv-KT#*=}(41=vFTJGRsz^3%mw3W3^@JwMX`e|z|TqK8Wch~Ba;F{wPaY}K2 zr8}c*g<7fNrPP+I|0u4CleFMY%CoV~`EAr1vq?6BlJmeu6om9h;h8=>{D0gKFDr?GTX^dn=rq46=*RRQN4k=Qi8hk=t z!9^rm^}8t{uU$Bke1IMBt8@s6uPkA=ZX0zr)&uQp+tP8UY`bvmpW+nK(3qxwOx*#n zAE`)pcdmq#vv}yVeyPrkH#TBtSIBj33D`J->#}YJiVZC2U`Rq(gE4yQ6(0 zQJGIUh(hHsXXf zY9Wgk(LfrJ4Mg7|N})E3N{!KrEgtC6f`Xe>ya@2fEL+$w8?&%958#&?>Kd~bu>)lDB0(I5 zQtz4l60aHMtZkabhMa3CqQVd6eb*e%C%>(()Q!|!uoq~5YWi!~c8&OS`)g6t4X6t2 zwfoRz3kIlQP-JzhB@p%_!wbQIhg+oXQGOgcg&g^5#$vN)Gc1%WZm zzZg36*rk5u-3a>4W5?k(eDh(_{yXS zdow^M(+|CLdY-8}A)Q8`4q3a)9uTj1zcwLlTZ?)z#S*J@Zd#P60xdoJEx=(LsQut)B8Nf{Pn}AHttMHIH@~#VivC&%U2bhY38P^sE5d znuT!WRz#<_FuUytfhIg~WN%6?AC!3s#6k68O{h;CP6)duYY}WN2RYq6di!?ga4$F* zTbKm+7dk|ybMcc19epTGf;0i`CZS*7+f0Jm$Vqrd5P_|6wk1_tgz6Rw9S@ajIsVUo zFiQf{;#qRNidp`S$ytz1Z$rH!Z1W(ul`63fhEDjPnTtUynmSV>T;J7He$kckoV zj@Hfl?3uc^CX}GuZau;dK9KE}8Hc8?S5-fA=X;l6=kS4lWS*92Az zk?XUx>CyI8@I7E2m163)sV}1&cautT1$aM;TF|KUZX}i#p znCATpW+n4P5CxE}k;uj1t9YV@kvn*!IpQBbflzyc%3k#cJQLnn8%wUl_g4Yo!>^P8 zl%YrYES;!N6Vf6VLioxu@F$fN(D2;E;qgwr0|^ChHN~_rfI-ylf^42m6$daOu}1Zx ztHGuqhJy$t&YQi-rzTr}hTa8j>?t9**yh*Pt})jXT{bh*#C`Uko3R)7g_-V!@S`$U zU+|8s{^9UAQPMtUs*6 zJly4g;*W3V3Y$!~)X~6U(S2K95&-KBFFkui$BYs9R>$U&`Mw5!#*ZZX*)*URnSP zx2q)LnfvJgO@-C1xhOXV1=&Z zP8Ne*aZM4NCS0}r?GK{49mv@YT;sxy#9{?-E0?Pu-m%b-pYSS5RLYsH{=i3DjHtF; zO~IF;g>H_;WTT(c4}S{+_Bb}d;&sk&+9me;6obVCm~bknW5spT<_x)Gfoa%zf5=6= zGgT8Nm#w&eXP%4kC~`Px=ORHq+Qw%tAxC< zI;A_uH#d-g(v^C35Hpp&63_u5iv+`*%v^+6!cns@EFC(n#3?%(%024KoE=dqx)U)E zn`l*H%5G$7HNJbxe|5{{GveLT`a>z}#G<_w-gsQETU@c{gst?>aR&q_(@XkAUfV@F z;sQzz@mj1 zhRc0HU!m`u?d}~N(vF}6`G7B~Q0^Sg+WFTC@LJD*AYT zPY9J#&!UF}T2cik)?5mKN+g6prKJfT7L)k;#oo8gOuksI_*Mc10&#&n!2mE)!WR#T z64L=zm8YDjsOFPod%Y$ zboT9G88=*ZL%yw~L_V0(u3G@Dx8=2eq-qIF1RC>RWkze^-0qzTt2lID91q5uem52j zU@8-Q!ryR!$}AU@IE2#l3GdrD*l4?y|^W;J9zzaH)F_O+Qm8Ze_x+Y~=@w#le}1D?>u z`r=U8ySqp44hOgOPT}S&|CHaoe~QM0uf#(ICrJUc?RL_7;gUyG!iP6@gkT3X^e9lr z5kd=O`GT?aXF{wRW4p*M<0q8`>K#pSkEUMVZDl$BS1%Gc?YQu0IL zqkX!!zq8k`md1Ka@*LT%?2_X2W`m4FOGVvAC|DLOSr$UHEY_ofXdpxhf3FY^=@Jk@ zlqZD)iY59NO0JorlS)6C-9UY;OFWT2+6t`0_X;p5T!=A73AuZS$mJf>2jVkY>5@z` zLvtadsb@(d`x;+UZ`rkfCbTGcIfKRQsK~M<`rt?|U2Z?lUlU7k;!!t%AT`9kRJMHcF2Pa4Wa3XYj z8bNc{R`{3*5V#>;===S-nACff)VY<^hVx$N4*?ZhHSk?g7PmloY>lFJ0j}1z&~;RG zkT%T}8@3@CRSc%!($*(rVPtXB^HO{{WYBP}WpCot!OGTU=*dH;;)_#~>m%6CyF&o} zwYm|<36^%E{lD*j9}fDxJ#}}mr9M5kU@?648lRp58oFK~_Fy)H`a-zh3G%hDuY-PT z_~Q!&6O88X6SsCO2zmk3nEm@1X#U*Q?#vs8xx z{4)@{k4Sad&q@aO(zkJdwIMdiixXXDkgJ#+-TZc}j?XK9lm}nT=upK5>`s-LO>=gK z=Tt_m#Vf9kk{dz{8c4t6iSD{KYO;w;cgHZ!k|UFGoc;av0_eJ6cK>!N24d*StNDww zM)Af}h;otT*MM}d|7#6hPjIb%(`ZBuq9M0%m)X$D$K8J$u9~H=avWoK8*J5HlO1uY z+s!e_trt#z@N_THGMg7>*`fZry~k{ zY9`fFlut)&4O(3R_AN@`A-G;Qg~w$|xf;>4E?PdW<2~ggc&GKzSg$=4=2$F=Vg4FV zMoZ=y&qY9z3h&T{h6ZF=uI-;l95x2WP*Wchk>|hMzq2b7rT-|4F`9fWS2&=QGwJpA8zN~ceqI0{DmsHk#B_j#DsdKTP6kOcK$k-}@oslrDB^v)d-Ue$q=z%P3D5FX4o0>uXL*5* zmOV|HO2s^na*j#WW}e(=*%Re%D`tA4Zr|4X4vm&Q%@Y}B)t%!{m#N}ITF#LgEqj*K z>y2kj_q(|nva4oK;y%?f*&Shj*q=zzkf}wEy37I^_OWnoVurBKO9$BV5PMxINmX#% z!EW0bT2;q1qUx2AjV$w8KmUa+exQ+)JOMRiC5U0K%uPSC-sz#OV%h* z=*KJn`UI2EcHQ1|Z7~it+{Lg_o9?JpTqr3@Nz*kU;5M;WE7jw$KJM~=s(JJli#HZs zq?0ZQ2_cIvtlDcdW3RS|OSHn}R=6CNHfiDNNf{tEBq5$$yQy~89b4qZ{hvSvMAB#~VXYf=Af9Wq zQD@e&9(@=6QT_L?+gNvh=A{bg@4nvOd-rDlgy=NVT3Ie;weEh$37LsjBw-K(nVKvd zd%|fqdiMR(s5FL?vw%M+ZuzF`!QyE*`r)bL2^7iBL^tNA*igyvArBWZf4{qbIM_eY zroY+8DLtVo`ZY_h#^zDXL|1%!bkyJ1%4e^W3y7+ws+Jme4FN@ey*6hIGtp0aj)HfW zUOjgabIEL%Ol5#_pX?tWofx&QVKM15eQ?ydFXAG3Ou9#Is`xvAgu~oX5pZ!gdN8_D z_(q{}o+ckh?v4mU;FzYfaY6-}?CMFHvr8YCe}MYh25BL$fZv!)peBfH9H2!l@y7f1 z^JDSHAD;aJ6ctB*WL=#@NSOyfJQ4sU&`_Jw3>a#_PGnqTx3 zRyK?B$0Peh(a8)`Ko7I&FxFvYsTU>p{&PyccmhCxl{mafB}=kB9oZduB{^>IzzeY?NW7F6mHMkS^lQ>v9dEzV?(9Rdbm@GTeY2-iqO*|^?xK|>)Ve= zsvaZgG5R9SXP3-cJXwH8RL9}oYi(a8>qI+JGCD2WtrC^6{yy8D{jc74Y@{C+NbL<4 z?46Potot?~Mx+8bNDLj)6n123nQh{C3MFmXI6`HA<-6^|Npjc?Y_?Rs`$OoR?9WvL z^nR++bvgqY1kmk_m%B6XuDmF?YFF-533Q>k?Ip7~9T%NHJysO z4*SYaRcN-%!d=M&!qx^8NM6Iio_Rq5tBR{Q&5MrzXjLGyKgt3?B+vsD%O z%}N=_T&|yGN7MwzIxra?63~(i$0-~B#;C*~{@giBq#L2o$gojpq@tl4YpWG}!evuH zvw!Cz6d0QQf#7g~_A{eQYeZgBsIwx85;nPi>4L%qM&0Hr6gHa;adH4UXl8n*l;R^` z1%YxLiHv4;j@q3xxAB{yQ;0C5^Cj#0nfgy2j$ekRyJ#sU*7QFa7&rH7Zv>jvK7bQ< z{xjYUZBFLh&*fcC<^7z=Tb;-S=h2)v{;{r~NFqW8GWay7Z=A#*P)AveGm7tfk*U9b zQ76Urgfq|?7E!O~WQ7yErj113O?7Y(t8@g&_T;&rp$)1++i4bIYxKU#=hLY+Q5>L3 z(;OF^4@TU?T)mv*ZvlG0fn`;oHzu!GVYNUd1gwvMflQ6RQVCZeVxGiu7#xu=f4yf(mGpzgHbJG zAqe}L0^n;aDeUICp3+kYSBy znTsHp;D>~W+bDm9=H8{zCW%jf1@2i5885=f9#$Qz>L_5CbcBuJY?YRG2eA^MHI%ES z?5Oo<&Ng6C6rJTs{NAuJ^v%N_n~mI~5Gbeb?)sv-efH3Mx_fIPz;QGN85l}MI_8oa z;jhFOgj;o^&8J=e0IzcdfZ9df0t5BC0gt@^Ic;~f>Lv6 z_!6#N9VvTCtdNh%TTwc9`%=NF_%UF!0ddI<9f<5G9a1m_g|raX;u<%X9sOn)W*WIb z#h+Y_lUWlsu9EL^H1G7MP)}(e3$MWLo-qUvCI;)D?G~Zg(9`~ZTJ(hC2D!?$BgN%? zNc97wb3HUXc035Z=Xay$3S~-g^O*P$ijdlZkON5>?xCnV;>Q-O%!}wEwrCZI`3R&3 zaL#B$xa}SBLMtNW`iRi{RqHYbO z9+91?u6Pc?DL!U@`FqhM0l($lHwS8GZ;Gz^4LUCR)gSD<+P^8HWH#hzH@2#iohnZ* zI3T@D^77*+p8t)B9OU?6|M*G4dKaI7ZhF`^4!x|NVY6>slP)1wYby;6V&7WEpoX=A z(dg2nITz6>|B#hX5eQah4rc0o(r@^IA_%hPe13Ggqc-D1 zLU)t?_)6WKFZDKQ6B<1`Itl~Mk+2;-6=lbHb^(D>!liss!G*}8N~!Rrg41Q76yleP z@Tjs^$$1ce82j&b!NC{eox-0lcMjg{@2S5w;V(tS_IJRr?>R0XmTDI-O!{Q{B|Or4 zLr00iLO#V5@1Pf82RM?-BrY(_OkPO{&XU>3BvJIAx0h(M`bS{_M8Y!1THgSA9#yIx zM}^?s7oN3bKtiB7B37nl1u>gwa$+Vd(M&qB{E&)&H^G=v96QL~mG}azgQl>0h96LS zH8VNR(=1PC*E(PrhdUzfim%YME3UNWDTI2_c`s3%yXpjZBXo|&h&}KJY|7e8hzDf9 zuPIpsjr7m+1fwoZVtOfohuzG1xYluBD2zy!733{o^V#K!mO73QfPDmd)gjrG>r{+iDJ`e_G`pGz> zXZ|XXbI=_H+aj4_>G(RT2)?7Fv#k<^BFGm`}a&g5!l&}UyA z>0e`D%w5(k+7(Us^?2=YXK(~4q-2g`_*cvjSDH4aQhaBP|Nw?JXzpQ${F^5 zn6D6X^sE#OVcquU?o~8IX}iq+I2GkZ?0*tglJd5#d7y*Y>Lq4_#G3PqvIwlLM!TIPhjj< z-w9%KsgFfyS$2{WmLKR#f_xqDT3E{L%dutuJS|f%)VoyuOWQVKG3OX*R`=^YXz{Ag zn*pg8K%#~Qc>Ep4f}>Y3NsZN1)kOJo5Uj(Zm@WVESKkPpVrDT zxOuOsU6vi&16pG)v>+D<5go{xbR5TotqQh+GU&^ zeHtp}T5$sG3|gH5Zswc8C`Z1`nto-7CjzPs-@BbJO zmjKoPNZ?(qWPk#Mn?i+W24ZFk%K5Y9`g1R zm&p#~*6Jcq+jW&H z9~<45^JMIqEvln61&IWg&VEUin_1ee3fn`wAE)=U6$p7Vx-i8GShwDNnZC*3gRfLh zpuAX5WOg1_8q$&mPc-4f>M00(BI9WaLx!`)U8B+7lwXw$yzonZ*tl#u$50FdZKpd^ zf3;J2tLf^AQk+q1r%;aptW{jn=DXPM%xK*+NDSI}FA>HK0Y_u&2Vn^}nr{%l!-%bQ zLAZt5ZqGcGwBz?`tP16I6?%!URx61&dXKydgEUWB*L1C*Q(?#fXxxxFGrR8?Xrx#eEji;>b6n zjI4nsQbCct#imq%y)0a_E15LJ&UUVlXN-1v*aVsL90dEOqHmbEYvT$HB$>^i9ushf zW6;$FH9BN}QDrXxm-Gm#qY=T4hXNR2DoXzrZ`-wZi~m{CiBAzc06vXwYFiURyJmU* zGl<}O1r}1v;Jv#-xjUj9QWcot5Hu^ATQnv;A&j$}$1RYq3hnNSbg&!K0zAZ$4@rJa z?gN--`g0rPz8U8o-Mo%dsA^C(w=5x$X7N$ zKX*5|fE(Ky7Qy4K?+U=;1ho zUs@D@Qv$;_F{3i`RyBNR+|sbrfQknh8$Wi|1INLGl)}=CjC%-7vqfNHQ=oQ0Iy!uH z1eUk;@UasqjaxBl7&dmaM^B#I0WeppPIJH@#GMI)dT?D)j0U$2%`DKpoT5%5 z9kAl2Cy7UoKzO9qpfjdlzWT-(x043v(W6K7!KN+`TblKH(YMjsw#{za4cToHxXhU2m#zD8(SLe5&-4YvSR= zjN}`|^;a6g+fFJ}KPI@^9^{*yU|8JJX)O00y}-m z<|RXFj*;YVnvB92X}wmpldAA-b&V|VjHM!Y-a;fb`}l>SDRuaJM^u5JA-CLrMhM!> zJKq?U*DfYn308$+g+1ziBu!gNbzp##~ zixxhj>f*Xv7s>2RdJfj$e~X^>o{F~6*j*N^OICQ~ubPd43g)604^8#;X~I6~_73*% z{E6K5cXt^GS#I%z{}rtGBXJ^s#A#(jcgpb%8(c|wHe1e98u#MDAy!g~G| zV>$$04S;6rqL`m`)Yt!Kt-H3izDUI4Xw}jBmN~u7!xNdOU1T!#D8^-f(C*-2^Yc_hCi6WQVX_HiM~9n0O$fba z?BfA0%*=yd10qZVC}yrkNQ=v)2z%j!_r5d5$-K}>tD?;a7Xw#Jv1RXkI)yr6`!Jir zY&hTfJkNm+>RX;o>~y|=7G10@JE>IRErc0Ws4>1~AkIxlqUbnQd_gutm-LuD$v&Y< z`_ZFM(b~Iy)ZcllYeoO9V3V$CyH;>quc~(eaRnOHef7bui1qNl_{;3(UUD_guDR79 z&$6omK-=*l2X`H{r4gTSgfSiWdKl*y8Cv(M`)h=qf&s81I~qZM3X+$4)E;jlXoD9K z{P0MUe#kz>LQR~YDck}Sq(o8C7uQHZZ5KWKWlaWImY|Nx@M7K7yZoHU;>tK}s%l#l3j*ZvQ(TXpd^IIgl`` zIY~yL#lTR7IuGM9rqsPIX32!cQxX{vSCG+0BZ_dN1fHG2ku$X(cp#R+u%&R}J)Xbh zn{U3+vpv~=->aW&;0OWh57cniIBTk9a3_U1&LcHMb9i?l8FwTtOf<=k?QIcT<9~e& z;o=n7j64JXA%5GR=9m7#|Kjgb&?36}OwzcX^vV7f0gsO5D8JAx@ohKy_UJ01;2-fA z{F*Xaaq7hfGxrm zL+C7?DS%z$^_^#94uvX7D|YGN2uutX@GbAQ-e=xg%|mpmZbE1lvQBXuJM2j4@xzNm zbhg{+n3j6!|@%&lkGP5#=;-j~{K`9Sn zz34@#25}WNicl-$+{fQ5gFGZOpk5wo-r>Q2u{e(i=Z8kydG&nf@$>EHCxabYIi}0( z@uy3Qp!5r1ovoN4Tg~{i|KepZ<*$F`Q;rw;$*+9IiADb1-_w*+64OrH9wjP*nB*b)oc368Pg<=hW--Mf^&e&bDe!*g2_ljIa!em&YXaM zjk=M!uT@K6$wOt+4dE$TZpZTExt>^QkJ$RQ;L3k<$ofbIJ&rK57vY&n}2!|_+}pDf2Q+~Q$rQdqeOh$1+D34j2j zl^j!W7s)F4%zKayV9E1_$HD0v*j$2MiU+aH=_no*RtcN0%A=39EAbUZsW0K56aHOO%teMmnow^&3tY`S|zl6=my#;UE9*caPL_YWjPBmB%qXQ#%=& zGPpLge~jwfHsykJ^FFf#AVNaY+TGhKhSJE*%>pBSNDM19++2X|&1GV}lvU#G&2kq@ ztOqK)ub1U4v^hq|m4Vqcf@#SrbN=FJ20lO0FT2nnI$k8Z7x8LU8T9zbB3(>}Zz-() zvY1VvsIM7=ZZEmygOXo=9+`~2c0+Ji^e7pEi~|gc*{aCCMpp!|Fai660i#{doX-wQ zNs0+K0F0#NkjJdU6QWQTE>%ktf&dRQ559~>K%oo8g@Qfcxu(34l~&*|td0@Rz8n9+xj6>kmtPnW;*wLnkYWc~q+P!~*@li(IH}d~& zj(B;1=ETA!HDZ#PH%7QqI!{3i1a&;AXYl;qIYTdUlnD z@laI1K-??Ij4hni1i-w$#3=Y0e0}|KkfzW zlp=o1pK4wLQ{}WOI3kN^BH+pgcHZ?I^DxOS`0W%A&{9^D_}IYE3&kKs5urJ+$LYR1 z*~XWDkS*J5iRM;NJ1A2-O3w`s1rLIeC1otf^N6n=MAfy?BCUVBt73Z+Icwec4%tel zLF&Of4@RLi=QeSzho6JVceAn}4d!BrwlHi&u3>6ry`jCzY1urxcQ&skGT2Dc!3Kw~ zB`9HoNnxXs7B&cF_el+W4@L7w$QO3FV6%FE;7Zr|F310(JHS2{JxDdJ+r2zy#Q;V? zxxa74M|RGu=6xuuY`A;wT+J{sc$|yYP!&Ge_13hTJ^6hEqw?&xIeojvdWPzE_p8?4 z@P}s&2)MYNj#P7eNNOJz+LhH0Csv`yDTUvK($yCd@x;87k%uJxyrN$w=UG=$8$ufb z>jRavf1kk~2CQ$s0kJ7y&%^-2gmt1UE_2E<2~hMZ*3F$CL+~^Pgi`#A!7I^xJBnv& z_o?~uqep?J8zDC>1sL%dL;xrOs)HDU1%OVt?q6e^))P@a&*%qw~q=hikN;eK4do1e?WA zd2o&)GmE?h}mI?2x zw15^sX4Gy9sLSECO|0^6IC~3X@Cd;#a~!zr(u9RZ(3}fK%K35@VT0+$vKLLovZk0J z{Xk4#VCp;sntgY6LeLR^>I%Ij5gAEef1nSS*ZzS2ONZ1IZfu~y^QwAj5Q7Hx>Wp{0 z;y2dgNxQecfDUzD^qq4j*=v0m(2e{TiM_+u%kXZ+PPH54XsnHT;&W4qTV>B~_H%aA z%^r0jtdTy{@EQA@?qERav{l9rvui@Rn{|*^T*>YHT6krZEpjLYwM9)`XC#6Ke{7I6 z3McicmR3_=(K!-0n8l0idvom~S`vYMUhR=3*94|;h69{q9_AsAf=Y>g9-c1E^I;T5 znw|A`{_wC%qgMP2;!JCsy73n@zKykrBbvaC!xm700`E_h6DU!s!=2a`FCg@qAfSnI zxM@VSUbQ2^RU#xK9g3?u#1t#ef6CWdo&4U}v1|H>&$|9S-b{ZWft@qF`3-w-|46hn zNv~+0kdoNr7!KFu1Nk9Glj&r^=%sox)_V*42E?|_#=h|zazdG}Ge`!8L^Pd5&l-a3 zA3fw_wG-+V6H(SRcfm?U=ft8a;<2g%gEAQ9zaB0;k+3X5o4~T6JdCiNe-|+HnZxdq z!zlDUR7>#&jpp6 z&u7lKf6lm=@Wsa$3DhE)#I%LcpLfC|-asLT_;k_0D2lwZBMDftAI8{0zQP(Q<(%=l zj+0?p@>wvzbr8U+IHOf!e~heAmVbK1hQ+3sY~$4&&LVyFp0R*m?|1=knY)l~qvAdW zmMjA5@l=aUSNS>cAR9G^CbR9$H8D_7>$zSa`=e!aFm(~X{!km4_6Mgvhld(Lx>nyl zDos3}jNvNf{PN>)hc)drvq$1IcQ}JUV+hZI4CO}ZE|ke+R9s#ze`i=B7@)yq;zmO? z1oHIpqc8~BPqxA~SKg^O(A*D8XM6V>dy^l{N>Iy}d}SpV5o|53RZh^n9-OoFfzCj~ zNypD#eGBvDt&&VE53^Kb#xj?A(4F7~XE%`rGh3DGnJ^v1BD9-5N`6+>E&T37dU31o z;t<{J@ssqYfqs)Sf5sQL`ZA2bGuCDjz$7fPO@m{7A|Qt9mU>s>hTWyZR9JGw3la{J z1TlX9PpT)2Mu8O#FC2Vh-o7XyLyzYP?QDGc=n-6#?YKcC^7*Um50BtNta%p-T6(o5 zNjYXqP>-hhg@kShX688r_V*ql=j+5F3{F>l73s`gMTReWf2lYgR3q-uZ|bul`U}y~ z;oaRR47}^2yy>1r?PnvT?dj1|y_r?RN8VVj3BB@bWR=PX9~8@aJ8YoDRPigDs5nvn zDm1BG7GQbvvvgpNW~23U1_{!GA8(oS!zSIq!FSy(|8bE6ioW^z+hLh+LQQZ?dy(y} z@Q()Xnc)X`f6GUIBAbBa{t_(~Xud=H9#y}>hz8$w$0w;5CnslzJu#l$&p+N+a)$G2 zUc5I#1`GZbOUS?n*U1^$MX~G9m>KPZC)uMSF$2moansq_r3VtOfvITl=3&+`!%-zw z$?E5gGakR37mzsu2N~17I(bCnEQXS4P4}c@GPlgJf4jBO%$Ktnof_>(c*t^Z(qo=+ zLbDgcky(xWhEka$NKebl7T|=v86xh^Fr)`~J}krDhqM1&PUbci6EO=(aRi>qRe|^i z7$8fm3M4=1Wu0T?#aKOX>}XM^O(Tl`PTNp^AfHe*2Ns?3PO5e%Z*31wik~7NsYKa9GAF4#GNE z=S_ZH@PXDo2b(*t1@H(^ks%x=;XCKYK7H0Y7wwW{LlUvG$>ox)1Q!LA2p1wINXvjt zx3@X(==Sz*cKGAf9C?JaPI>{C8(mN%;N=4Rf6SAgNFEiAXC&PsQlbeU`w@d`{+|Uie<&C;^O-dEkdVm@wG!_&@l;5>5G?QVP;&Z$MYL!4$L8i{)?b?WLW;eo)5+U+i+whK zfNh>M6T^5IkvP1(L?vH3f5X~6f6Px`RCWr!y4Xv}Sax#3kj4VJ1dPc#y=VFA>e1g%8GPn8k?8W)Z(-XY0ZT=qQh7UOl z+!{bi)0)zoe&sysEXN`bD#9)zp6@&F1O7R)@CLlt=t|ijcQY70Q23mcfAD?QCGnE9 zB%0&X8(k#_1aV`lc_Lv7quIOsoWvQtQKVx+1ml$)2CRo!)LtGGli~FgNVU)mM`mA} z%BVIWkrx=@ARxfv57{KJf{im`aCcRl3~c7giN&p#V+8i#7v;j2*4M6^tHw6i&S8>U8@k`n#Nw0b_vallg z9V3g!ykBIE81R~OV7I}`K|Z`&q`-jpi;?lCKPCb<5!w3X8?Y!Gcpv(5ivwkrg<)l>i zs)lz@8%_aKk6!K=fI(*B=12!g$p_+ntmat5R%En4(BX!Y3Ic{HRl5BEA1Zw_Y+c=G zy_>qjQQQG9ibDrfD54HhYDJ;Ut|UAoSD(G|=1;9_LF&41e|gndU__%pgP&;kXlAfF zS&)~qo5>N0Dzs46-o*LI7WO13!<9WDeLs?G?ze_dZbM5?35N&UExQP8D|7*^wyFKk z7FlZk{)}!Qe^)iiBObbmgb)Ni3C7s5-40FDV-{I1SzuHi+dn?r$A;qhfqkTcEyYT~ zZ^GlM+`NhF340Z6)dAUOVxPr;g*zTIyLXgERZq;lgOa@p5?#p`Wx$rpkssf}U~dT7 zA;A=Ha17Nzg3{oULkO%V|6VNd_St0iezM?N2r>p2e?+nUY^9XmhKCa0KoC8Gt7Fc% zic?5_Gh_MWa*&CmJr`ImENl)0cpa#6-wr3RHcANr-i{&8&e!#W5q}A29$!@ z7NTO`sXv6StEcl+dPcUyRivCf_iIgG?Wqm3KMQ8BS5C8>zsMf&en^Hl=>NZK*UOz#GzcuUkkU=>6;(48k` zq95kDh`Qa*Dj;QA<{7^A?E^4F8%CF~3&`k*yiL-TsH?LA(^DSzs%c!&UZ6;CKAiEC zFDjAJ3#sT|%+Q$iKuxsk0p+9|=GayE*sJWbf9L&m@u*ZwKp)hz*2DZWjSqE0)YILg@`%z!o}i_+<4@m;4!J<-*J06ZF;O4YV@a9s z(P4B=S8C&q+7-l0#os$he#vuZtA3p70F1->%CS+v`D*WQfj4_K6vTjJD<2h9AMPBysGHv*@KtK4_}Pu? zieA?6y#`SfIu+ZaQRhZl7tMgzeB6jxe}&u1y2pV;@V6$it!dCX9_UxQkqlwVBJq`t z;nw0FahaxsMiE~t$JrCKo+M8+^uBCc{|d!EBhKFCsuBH`y5SKfVOT~!3LLm%3!qDh z>RVKm>engu!pOqA6@9X7&9YBV7Fs71B-gE*@tLNKTW~=(jSIxaW<|Tbz3g#-f0)c0 zQ-W4IKIv$y2;`2~(*1ERe9^Rd_g0eMcJ>S)=|x#-yAXG=-Z1p2%6rKhhH|x7FWl!X zr1chp=B|6#Lc~A*c?YQ+q9C2YZ&6ZcspO*ko3tkPK zSk@y<_T7z+Z+-?u++Ya(H$T%wpcS@5Z9KEA5c~3?laIc5)KN^NcCIJtdB}xsi?K~O zkTKCkI=EMkP3$wDNytf{&GShPNx^N#mVs&;`%@4B;epR=4=ur=fc1e7f4cZHQPZ7< zpsUkPv|Ahcp6j=>6RR`2kBXGqvjc>3lRB^{=wuVoQqXY9C@9eGK*c)t-7i&|g3OD)=j&`l`51;Y-4E0itq=`Let z9UjBSqyMPqV$AW4ZF+vrZ#sUvP~!+Nidm*Exr#ZTYLqC0KdoLQx<@ry{HaA6zEn#O z&c5<^&;EAf#{{|1e+CgrO5d{;r}kTgHc0AktB`cnuJ3khPdP)kcaEi06I-_&E!^*F z^Hw_-_1oVM@M6)@)tXMFTBW_`Y7t?f@V*gbsJNfygzrc|#h?%cN-MUa6y7Pa!=rL^ z+Eq#$sw&W2)D?$DMzU~+3J&RFT8mhB@sM04g6Y9(h}0tVe>w!DVEyryPcX#)xM;aRXkk*;SvEv;8L z?zJ(1aZr*Y?i%XN(EfsIqFx#=g>>x^g)OPgYXMhRsfHtIVBYmKxs=vuI;c6dVCrwh zGrV1$bhCfUadd!YgiOAQ~wac0q7GR_N08z zjc-v)5Jkdtrv9k0j#Yn|oF6xy5*Cx#8HfL&@tD$_m5I$;Dr>(bqrT*h=hbb!iQD93 znx?FJX^G|ObXmT0hK)NXwL1!e2`n~xd0=t8J^a+=f0(@=RJT(2-EJy~zZ?9vwSc7* zNY0}VNV&kg=W1VQNl`Vb0Vj-EO@zpDGo4g?qH9kC61HpgWE+aQlF6?cJ93g#q!Uoi z-R#5ug$w|8kpD>jOZ@c0{MBoJxNO3o{eY7Qhm}^{6^~^6i0@sivzdBPt43f%x%&i$ zns3ETf8+y)$FIl$&*|p;;}o>)dN2h;MZ=u)HU4NgL0Z=Xf%DqIub%N7iZP&0LNn3) ztxt_=hib5b_~*D9S$6kcf+{j=#ay2RbveIaasd46CQXX%31kDUft0*+!y`IHB+!PM ztVSX0P%PbU!6&uwV6#=gsUy|&h8ym;wBy{Le`Bj*7p~d3RwqV2^5}aleBiki-DbKI z!w!RnsV~uG8Ofgx%Bz=%yVPAf`?g1zN*T9Y8-;15{vN8^VYJxhG3L+O@D0FqZpF&! zI8M3ex$NxR4oGC5kAZLc&{pI&mgA~suo>kCZPrT8v ze?rzU!|$Wz9IpIMueCYSC8HD{6#7qo$gkx_&~ejDf^-tD*E)BlxVyNcI(j^RVS|=( zH_T7R^ICQPKCn9k0|d*O4NeYDcC(W=<*0xnW&g~#vmsf8O2tE&BWcYo=GZmV9%*ve zj<8lOOaTykQL*EVAb0{H&rRTj!@mY*e?N`1oGR7HINwL6cey0C=M6?J_zA2r0@sOj zdv}HF>BM}(^GLx7Fv27J*&3HkUbgB>Yv5mOuXWWpQG+mzp9424eAgoQd$w_6M=J8l zX{Y+L@zH@qKUpJz-*=B-4)j)h=HGE7sg%HXadFT1UXAbTZaA)n(4>mww@fKae=&-x zxcfe^)TA^uqi1F8B&iC^>SR((lWS`#!_~m39M8Do<~}uotby*vw9Dep8CT6NGb=u1 zyfj~?gEqe_)T4CcwfP9k5F{|Zqcz~qc52#gRR+;<3P+WJw1vJQ z)c}W6NkYDPwtsSZbo|VvB9EDqe{-2f3W!pqRhu-tcse_pjq@M7oK|B(X}K@o7)sHH z6l}Rb%YprKWqrzi@t{JbLQp!%GiQVOgAW|iM4vWP5G4*o_t|x(fpm1mab2E}M%5vu zIM!qW=&4C0!Fsk1SXB#9q1Cq3X#E(JWO9_YSc2ZENRWzL3Oz*)u6qhgf2)9%Q2SGG zd6X9-D1yWy=;&a5^u&z92_xubfEe>&&vr{8r9CJKlq8uJl%fsWQ?W`G>`|!DIK02cQnIJTeNt2U18hX)H-1^i_~V zeOlqj%ayY#idKY;F{Gi3e-NbLX@{SelJk8P1X<@>3k><;kE^0+Rq#UZU=m3hq;^FQTOlXHi+z*CUtX;nn63}NK5Pom0$svWML7tWYHNw2Qc8z2pmmtS$Ha<8|U6xMJ~y@0^9Bo>NrlEn;T zXRU<6u3Z|rYuX1MHS&2`f#aL~qgY^Sow~z^m;E5V>P-%r`PGs8x>(LBCZne_uxXFX z?)f{NtmsPQ;Rmk2i zpo#_X=wpHIZgF0P+RC-4renE6-wNkC8)AC$ag4cJk^N#gm4tw837tOp1)Wr(zztp> zX35(N7r$|#B#mk(`+XoWKaDjRLIQFDvDK1<`{V7Fg-4SOf1q-OBFuXsbe|w%an^ju zXopyJ=(ulEzras@(T*5>-^Wn&kRrRW2ZhR9Eo}YR$ zH;(G-RSrJ|RN2_#c6Dy7(ZK8Ng zjH3y@K)+IWe^m07j&$OYch6ldbZpb{jJg#A)qNvzq2m)IDC8zN?1mJz6$DSZyI*_R zBf9G1A{=|Q7r;W-=ch4yDl%xnPB0YCAD+XU-MK#q6ZJ!C6T#g~1<45Pagm|t?TXUB zSeFnNzA-A35q$G&*ouoN(~D{N-K136TakU?G603Ef3&wX#=bTzq)i|9T?1U_crQCW ze0g#@@bk=Sh{BBD>OyXX-@uM>V060qB|5|3g$1;Oq3~3u#Rs0*>|g{fq)GNR2q))u zc!Y=eoPPIR7(4{9d*sux>om?6xVN-iC<(lif^;vrd{8o*%x#zP0AVZto|jR8DvB#g zXp0U#Z&%qMcrR&>7b4=_)~~#p$zS&~tM1g? z%yclwl@*mG#X(S!_a(llR+Z9er{=Pny?I91wMeYJ{gSxhR-H@7byMr`dQ<)J+sEJA z5y=0Qz5x?|7~{zIvc>StH1Cay*=T4}0O82pe+;+S9B?d5MwcJ3v`Z+Qbo7$-{*5fotL08NU2%M*apd~3XVwhi!U4Yi`NF~@l|l@+N;bd_O6cbG1ZRqCBy>aB`S0M~hivx2^*jwg*>f4aq8;&r)YDcSdI?amf6w;L=DWsuNgzN0Zz zarmoZdVN{Uuii~Y$;AT#hC0}g3L>AsH?*VbVe_8pmY>N@j$|S5WjpB2)FK4*W$|SX-=z9M$w>odTmo;LPD4E&bwkH@^7LlO?NG(RzUnFee}5aU>DaC zyToHVnsStn)tk(z=MRsB1$x*gf1Wt%xJu zyo9Q$;%pi1VGbUD7sIe)^>Z(*jEtp_CvUKve7T#wKm(pRTVbf^#rboCe>(%>s2f#I zHosxorhmu=h5^Mn=!z}JwU(cn17iN6nExPEm;WKFFS;D!#~CE-#4}znr_JtEZLmGo zcBxi#)JWb^;o_RY%6#n^b2m!Ov8Vlgy?dLNJ%2nE&1q+)mK!uqH(mTp@RC%#OzLAx z1!DOg3M^k6v=E4Zu(~b0e~6L9MU{iH%27M2v7D?BoT(|er#E_S08xr#ER1YDh@m1= zf^L*v8o$j-=$5WxtEdLTS}&p|WV9Awx(X52MnLIvsc0KVtjl7wDDQ^PRB5+{87iXA zv>h~2wo1L#%jcm-kFgu6Wu5G6n}7SL2r*_MIxkkX$AL}!nH@YF`Vr%u6yjoqa-N$**)M~5OQBguR&+(SktM-oemd{ zQJ5f8LE9-GnY~?bf3nCgOEG19^Ru^W+^j>kAAe8rPw$;@Q+W{KFcgrZcR4CjqM|r; z`W9?^kb9H^e`%5dy=XWI=s5gdwU{+sa!_#+(pC~$DtTXI@4!|(<8JvFkBfI92^e)9 zH{EVh?#7ECLO_Oa_}X)Wwb@kDVqSgX>2M?;0aO0j{`13we^=kJGD2ztfub8q`hu>} z?E?K8>v>iH-%W2OTM_Ff=~3MaR4F=)b?IRAhGLLat@&K=ra6>DcC}ZM#p4Ipou&tM}4`uw0Y>!R3Cye=_M# zX7BCGD|E8Zf2Tr+ifgj+d|X$g4Ea<25llMep;jJ+;1K9ezvPeDNx!T=2C6ZB?FYg1 zIU$9yrZb^!x#1u6YFDAn@y|8Cmjht`^ss-{X19*vNJtFZXuVTl#i_0FNVv)&ln#xl ziN!mS-TEGP)xoY_#P3_X6i2kMOCV6kuxyeN>}x4vf3p5~J6OaH99IVVgYbZ?W#jD4 zb;}*)cFKdJ1Ecv0U`H@<^oe?~R;$6B2WNope*i}EKEoio32dNn!_Wl^&|A=xJOi)PN0+d)>alS17iTe>ZI6 zyRy4_kq9paIsoA#oV4mhe5De@mUGI6rPM%IMkhS0zAXX}R@)ksP=UX0FHxjY+No1< zfOxNqr8zgvB8wLJIVZDa-ZRTeDN|9w7-2U4f1vraD6XnbHh8?)sV2aD$(;ejtYCf+w)gQ!u z5Qsxa*hI$j=ZH+X>j!<;Yz)w;r*;R<)9U7F)JrknCNO0F(3c%D+dN9Dzu~-TmW>y~ zfB9SUY0_$U|6_(g`!Yh!-be~)vgLA&CQLnDMoku5dD5yq$e8VI2s<*MZEt7fDT7$S z^Zt`(=Lbh;FZ%;?%sq9EFoU8?JieAv-&~qwrj0UtGXxdc5OpxqN*C-V#Sd*JKAUj5 zRykRfpRZ;l?fXQbdOaInPDW^u8!jM&e+j8lIFsd-nFXIZ%)`J}I+ke0G3r=`jBcP6 zoJcyWzjyEVam~b@&>*G;)G6Jq$EQelfG?Ee?}~- zcin10z|i*gz7g**h3)NKxGX9X9Zj8-;0Ap5`7FP@#tF^@!EBqDLrjb4w+4@xB!!G@ z-1lNWG3c0Hb3e9c0LsmtW*B23MM4RnZ~I604AAjOR={5&WXjr73EkOY|6pD4-YwGL zVd0+%WVRm8%@WRsy z(#Z}OgkF{q^=bA>Pz{C6d_B{1F#wqk%&YU_mH44XYxvNL2U9$j`3?Xc@YGx zP=uv2C2y?XICb{$?)Qb}G#R})tO$ChRw->}OSF{W3zv1*#a?(YwvWoFrJib2U8?yn z5UWuKhIiK_Xhfzmdx6fwUH4PA;8k$1=NGC8BI-8u}uFFP?~3S93XUObiZYnVxvjZEpxnEwd5K%feU^z z<@(t?%UNM3N?vu-z1K*(hwq2eC1eIM$m@78`{#D>O6j*fK9k;Lk+o4jj(*T@8=Y6AQYDmF$ z35g#8BU`E}nri${d@EGmB8Z@AWFPW}5TsjxGE^3z*tw~8Wcf5V=*&oHfp*66$YRpH11HqbnT|*e~}e9beZ1VP9Csyr7MzG58zjIWA@o;lD>F>Z8Am$glD%5j0c?j)L!KFg$f+-J_(tE)~L{=qy&N} z4)Bw!Ot)lxe=|kjYE;enXj591^TiO=g7dMdaQ;2>{xdvY0(Ddf0N23Uz}Rp`2j59v z9-A}gx7U({+*#3%%CJq0VJlZcKtQ;_nIirS7ubp<4^;1!x=Fv36u5r|UxCFd89b#o zfXU=n&f;c@J^^-xx~^?fks z!x`ye#b~sgTVdg^@TAQ4`JQ6`_`s$D@RABZBb#7jJ+ac#2H`ihVuN$@-rMnJeK|#D zKIo^;({yn>bu_WeM7hKLI-2k-E`bOv=AAQSx%|yF<9{}L6GA2#tl+Y4X7hWeYT^mQ z7_eO7e<3|Pd}W))7ydm`mi4lF|y=Opp}5LtE7uOFSnM_220Ce%Y*^6#}xAf^43b3v)KL z*Nc7Pa6U}vpuOwBBHFu-+}Ge)M_;t(S@$<5e{U@R)ZfCl4yLbOD14ff5ivfjI)1dV zHd`!wx$!gP+aNX4U`=lo8$MEdVH^XfBd8gG=X+65BwT2r(fPe}M^#cb#y@Lvd#Ue- zJ=BhwS?3+6Rdpl6n?TmFOoT+_<|P$F;19cuWUd?Gt8N#N#_65F#%$R!Q^8=NTI78- ze`NuqR3lX9YnVyL$wlInKsGlR1kF6|)3_Ds?}i~{j`<;XEr=QzB`_*`k=xxEtKHxu zTlx#QOF_{CYyx!J@hRDBomkEOc%0g{G^KU!EKt56=H| zxc@bgTKI|HkKOp$b>i7iS(~qZfA?KrDYUTc4hmYt@4o9~Uw7#M=ncsSt=F6U3(*9r z(miv`xr8`WBoQ>;Y<@X5zkXR_q$uDzqh)ygW%+&9`SQtl%P8&s-L)0|1xKn|kE1ZO z{$PxYd4AQ&vW@@y-~Y$VP*dR6`a`Q>+K;gng-7wf9nLJccxMFJ+jlCSf2+(ShL8X0 zLo??|^w`L~N%2tX7PBOzuvUJdKCAzT)ye}f4E^O2HO1h%f}?M z%&~9dfA{bf1yJSVji$?w0=Grj$-f%;UsyW`7(i?ee7JJo#Tt#{9I6U<2&G`Cd~9z? z55Yq3A2dhcHPP*rR%tl2qA47e>flH5Rh-A9162)R`iTXF6C{vf3}Yw;?FA};r)Kk zrf7e+Djt}XYDAp@HNGH#QEF9LLv7H1oEntl`6aN@no1MF{MP(k9m5}H=RWn=7j3T1s1_pp9bvkrd zu`THpH`i&VhjQI((;B{AY`Rq8i-&NvT&CAhx)kfMcmPO!x}dMC!@#*R(bmjqCfKdX z7ftdXCnd*TW7m8*pI%dVd$C-=S1*^diD4^RkE_B=3*vyte{k)r{?Gr|M6Gbts32FL zq|;=px?8%{!FP4^>fe(29JYQZVe6;)`w5KqKA*F{^3DU+6)Il;*&uB2u9$!C1*G@K z?Rvpn-gh@v<*QF>K5ae$!z;EG>CaXxHUzT z!Qh+$h+Y~s0x!u(E-Ly1%ZQ^OvW|OBTMu%<(D}p&7uqD=4d3V4#&C{_c{j2lc@p#4 zTcea=Mqa#x6ab3_@8L+j`InR9Lkcu^b`u@d`x9#3e@6KKV6<-=U2r65uw8Imh=IW! z=qevg3_*^&de4vxZ)Q;zu8zWpLa`aK@HxKGJe*E>e<8~ghGCSY5uanSLPajth_E7; zXkcz~mL^&0z{1oGYA&}3Y{%*jZ9XI8lKCxSN8xQ7;uL0C3hjUaxBDj%YurM}y~78` z3ze6uf5rn0!7gKWQlX!wA&e&J^0Q3UU4_#F9CMSz_fze#gJXWd@SGd451TC>G78#P3$1oZKrYHJD9RfvE)O4>W zf5i%FuN$z5!Pc8)nVe>gd=U(9U{X1wHx$=~bf#0ag0?PUiy>r@qAKm_z9JT>N-&B= z_^v8ZS=EzE`*Ef7sN%WI*#+f*rtHjNqRYU^`sL}#{^8k~-E~vhOl)2pq)-zF7TD z>>YeGPAJ5^<1HcXS!FN#WoWXI9#3X{pvTnAK1qU11SUITh~;_y9a6KBb~-wsrC8Nn zYDB7z&QOz&{qPvxO1BUy)5fWIike^wE3obH&sKJ`qTKUi;KT;BL{^F70qpNx_bUwb zZ=gD~=d zD3VxN()yS5t)I=AGAr$ru{r4()=&i72RLoDSmI@pGezX-Hmoy!y}ep*+-M zYr{^aaVmi+5YfG9V`ZLX38fINg6=QQOft_gsnv+@D}X$Ha*$m?GnJXpe+|d4CMA;G z!Z`vjhgX%U(xvk$x)uiBfb>{F@6p7}{UxfvYL37i-Z zz*s$=grC!WPpSBNNEl&Cf6ta&QNP^E=$vMskH;!Ojf;=S8%b%{aG1O$F@KVz%qx(f z%5Wb`HzC5UOFi3GiU$rI-yDFXfxj?0CP}W1T)vekBsJRqJKW(vX$nC@W;21nGSrEy z6dshYx;If3I{j`(6b&*UJZAap^Y_2BwXBbmUvvAZ*j0ivTXL6ie=Eoy~-SrN+e`=`sOYj3rVO~c84H0Er*&)$YB?|py$n2T4*j){#F?qTVA zNH)|n5k7~po)z(Pf7Io!1L*LJH4$|1euWS^ygVJ4M%;Hk2hn{FqPz1TI*+n^0hGAy z-xx->Qt;e*aeA0yV8hQ9E8Oje&{bF1%~UxCC0D-fA0@;J+`+7nD%jz!g$jJHMy8n8 zD~J@hSDTV3@R82~<+DJ!bAh4>%at#>eYpbh>go3 zT*#V@rpqxz#Y}U1`}xJmiCOCMx`aGgbR7-i9SrubAdm*ZE~VlWP)SDi(cC;dq5*qP zCy>~FE zJuAln?|tP#&k(CvwMP-$^A zHmEYZ4VRRm`+AXq^q_zlY_#%Q7fx3-aq=Pe{}xsa0=UP5)NwdatPE} z{eFL#e_RB4nG}iga{ji^z>*y-ZcWd>0{a+USW1qmTA_uksgYbDX=-M^SS}Qps69O& zLRx2|AXTB4^|-IRY!In$NkiT->zcsNy7!g>xXe(~SV1wV#NH6LW;EDa+{JwCG?#I1 zbo4o1y)HTPfMEwn-U`}GFFU|Q(u-ng)KE;pe@^&3z;0yi#q`pyVu2PrTI$L2hYsF2d}<649jMjuP0vZCE5qjKst>1 zX51_V8mcg3-apwe-eoObYF$l$s%DsTo9~yCdgD@EoFDWDebjFEGWpd3CEN3+yrR{G zP5)s-Bc(?9$?W9fqQm)hw?NT)-3XFuAlE@*>Phkgx|8k z0G>-h>jDkt#^xEW_GmiLtb7TP+#1P^0*-$g3lOc~ccyhI4mB@fPn-wcAy83=4Wgkw zEPY4Q`IVtIkaLYXg4g|<5v+F_)d?LVHRrRzNBy^C*Sn*1nRBc>aF? z2PIFUpaZS3Icj@i*zEbHmyWD~9M*{2FB{w`|%rZCN=vS!^u z8m~8t)vQhS66Y->G@ozsm)+pr!E%3*rgZGrR{(9z8x6#@KB20lDy--^-5&d8#&B}0 zL+`=HO5h`@pp0DdO4wN&X|iy%nIx8-C|FT+TKyN%7rMyBlsu(2CfT<2&Wak4gB8iS zS48j&HFZXr)KRw|_jKC#6c%2;pVCAbe{g}$=%#DJe>d-K>n(c#pTjVKPa$7DF;v);PiO9KQH00008009610B;!WKt7WzFC>3UZ`(!?zWY}Y z>LmqgfHTWyfgTu1sg3wmkxo$5g+SPBEkv~qNlqIS{qLo0kvmIrX6PJpke9o&^Ze$U zy<6SxR*w(m=9~AEcl7U(SO5EMGOK_6SbbYwKdg2ah|$zS8ez$(?HuYIvl$H zp_UJL`7l-GgJ4jX?3O{o?AV?erRtg39(hv!UAxKWY$2HhhzfsEP&FiuVoV}z8u-@C zU#o{dtIf9dJU@+j%NXV6fU4P=0tdXzE6_E03!I^?t)7ZHT@M#x(F2tiUfLH3%T-PRoPU~H>nbu!}s<2YWn)WWUqtjuU6~Fof0_E zq?*P~{ZxNK(vqIJz|G;NABe<<9hjHTvzb@iSG!U&UJwo$Pw*|>CM1&hIsrZxNkl!y zxjPEkR_zZ`E-bA;YQa^51~KWIU=SjaOLDumNI>mukdGx&-x^E5DO6%Ng-V{Ey0{R8 z;0IKdov3LPQnyB7KfT-CG}sGzD0Yqmsd_>iR8D`r8`hxh>qmev*TKYCWmRYo)F23p zlr4(kA}@QPZi?!k-LgSe@3VaVW3ef3t8Be~=j}GnmG}M4k^T9s`dY2uf%DsRU2eDU zyqlM=ZzkUGAB!yixZH;uyW;#b&rdHehW~yunazv&w5S&>v&B5iZzjsB*=c^271NXX zf?0ohwYZ$l^ZX!`xMXp8UCfm=vw2=DE`NPlb$r}km)D}quFnqQNS%#8l<*aZjcQM& z1{i27Fj7~rT@yj)Zh-8$j%@vO->-orkGRMRB|Yc@VAFrTDbJ$c5rSugk`cWO4nTqT zLr9?6RBf6eQb8q4(pgnU7Aj`N&*#EvT+x3c(50b07SG%5s`Nf=R@KAZ`kUg)zDjUl zLO^Ap;XMl_Us%Pn3xPuIK*N5)BL2`RZam?EY}v4prP(g!e5wUlI%N{0V_OY_$Em9f zz8M6z)w37W`x!UB{JuVIYXT!$6BWHeXz9!GZ4nsDskW_&rpm}_{`A8qDbu++M*)AA zLLBwX$wL3voE z-dVXFn6Z)5qksmg1_{uiVEjO!rBr{m5m}s0b(waRBSbvgI}mcF>&L=&vCdg>-e5a4 zS7G8<7MdYtjp7%lmxYuy-NqKhDO8igB#&sWG=e#WaFCxFz8x9&y!Ktp_S?tJZdEN+ z;z7;nI^sic$Hqp^Gp)m)2UWg>48=@Fz{#|8O{gt6L!rmADXFb62O z7dw2j&ofj6%&s7MAWp}(mqy2Fow*|L3XRpx8jUM+mry67R28xLjF9&(cmbhHK;P8@ zO3XY&1lCK9FN?s4dl7zJJQII`rX*=)432cl;|aADM0A_iK*G_8qLjqz%*cX`;Nk$o z&kYg{H?c1?Y^BlX*Kj0<6=gjo@~^MtEz)s)IgfBLO?&Wf`*Li`E7s zP*5E998`Le<*Va`Smnmm75XbYNQcd-6=R)jRg-F_Lh{y#vH(1nnEE za0z_%VFk-CTdbihVBdcd6ZymBt$@K*h{{eGHObF2s_XDg!c+Tz<8}T*BsF%x}$z0P0Z0<8whjXcmyhTACEV1 zy^k3vi^Nj90&LP-(t*aIdB5?WcYN&C5#l}m;oYs8SMf*x1Cx<9zXi)2^YS{ARW~dv zDsS7CSGKFSx6TQL%4XS$mL;z&S5M8v{`bR+C_*GiB0)$5?ZnPGJL?jYtT!TmzeN9c z^4H{FruXacx3Aeh?*1VD5sCis({%E%biHL%9M2Orit7^G2^KuK26uOd;O-LK2MLk*yUz4hRXi4A6vvEAH*Q>`h0UqB1Uw;}@ufJizN9UNXYC&0W zsu=2#N77WU*=@>T+=A-Eh#(+yh^*-X9-hX_+OuA zB(m``0X>e7{TH@f`pWy4j0hbv-~ltz$JFP=uk@f)tv*u&hK+|XI#xfRuwMh+VKP7lH=hbFxya2J-|stG+zEJm z5ik&dtKb_=L~rRge#yG7Ii&gdYL@-h`O6gvn82&q4E5#r-T?wb!iE6a;db1CmtvE3vqs1Dx8ANk>1+{^WVJvF5+ zkb#WqH6^|+`sn2sqy3hfR;ZrhkY-N!!yOH8_WauT%Ixu{u%S`N;jxQq*VXaHBejmA z_yGAI>yV;!E0h>biT+n^6Q2Ev?&loIeD?NhfsSqHJA zhb*+^x7(MSr~Mk`6McH8(L?m}5@GQJpr(`h>F#j<#Qn~nJEsaeD{c9aOaSiN;*HUo zOObz@-4nId*y+mA^NN7Zs;fCS&lmc?=nadvoS_)MG#;Po$d`80Siq#Vj++L|ss+8Y zDQKaIzy~_O$XQ8Ta$!tnrhFu)n`A@R~$K z@l8dCB^BL?zkH;}%h@xa?eWR`DdEj_V3>S49H8P(kf*!#X_jX^T4L^~e$#iya0z;zf5*2n}K)Ako^9oR?LNV^~Iw3X6vz*&UhMo0$Au5P(R(DlNhb5 zGj6_w(i8KmAT$fo6E16{Y@y*HWyq;oKffORh>lE3>W+7IrBi^nDPEfQp^`aAj{_FM z4v;aGW0aHT_wj8d+8&RJQg_S6GI_b=q^^Y*nJjqlwd+eNZHvf})a2{-SNodK)C>GP zEH~~9MD08mw(2(GC}9>r3BY2$JiWP{RKuAeQ~1q9^t&e}Bz&}n=S?Wjf-|$%`qL7d zi&iHpyKa@7CdK{xH><~8i|RihHFMW+ZBwzgA(tm-w>JH-bP2=Ih#b7VJkq3>`it|t zuuIks<@b4IUvQsQPy_@&?rHsS zIMS~eS0CZ4o+Q_cW!dOoyzawy-n33*^6@Kgj^R3BPsSZj+MF{a_lM05J|TK$W6OIc zoOtf?-@`gBf1St@XA+u>anzoD&b{diXYW|CKi^R#tyo`LI^I}Xxj5mHe@(4;-{4(Q za#=O|lv;7_bp2=tTzte>exHLS{Z(B5n~tQT|MN|3bo?pyhqsW<^zJO4@F~+L*Q@$W zTuSmNy4@=Ny#bita}~H8R4nB85N+ghYljZQzS7ATSMQ%?;(_&>%*tspQ&Fd_#*|e9 zfqnvJ?@Rl072f+%(UBz4kuvFh*IJ)!=79Mva~J3O!UaVnpltW(3wM>|=oYz!JcY%= z2X`XI?2-y$yZCxu8D2G+rCXV-p6cShPCVN1ki?H6iAJ$A<6~|qJ$~N^+DN_&5Pt9U z`HnC;j^zvD~*a_am69WhOr4E$+7uFgmi-Xs4}rK8vpT} zA|!1uGSCsXb|~pJ+j6T$SnJ0B+MV9r35On8AZ{vuDrV3r2!JKLlF}l!YSZmCJvjA- z;qdn=twB}1LdZ&oDDP@mhI!DmnM2B>jt^DD9XQ1#UDMlvbopN99i3#M{V1&Z zu$pKS*Y{3no8`kz3y|GIass0H;w#wtQwrr_K=b_vlF~1DCYzXs(dzBN1^DptsX?d1 zgzq?~%KIFva3obOZy>32DF`@J+Kl*7f3gJ&D7A1lmQ< z2g6MS#93$qVKQfFPC40@=;(bgdjWoWza?Jnf(<3U=wWYEnsu5Q6$7u{Zf}cj@U1lY z0RF<^)~!X`XXPI4wjUXJKNN5$ZfgCynG}%b+)9vMzGaRlW;Q9m z=JeS<>D)z4zIEmuSGTRi?ALdoddEMU&_$%BLj1T|A_e@CiX~;j*^fyR#!swz0%FWo zWwThu44fFfyb|)_m-2%(J3*;$SyEg3Kq02_K?1dEUrEeP``iMOc9xps~ zHZT3RG|%wwXnyHqoLii*(gnIFO@Ii76K7nH_M8y>rO99oRZ_%T7Ug9FXjUc>2AZYd z$AfqE=@cgiri5tnLo)o#6~!TWKqf2CnZnBy&$=~vfE|vos2{;4tOKCgl5^|;gqBdr z#Go7dKI?Y3%TJzW&VAYX)W&gX#_uhzbkyBFeqLgIg}gQ3qrdFjmex&AZJ%xTWXPh* zd2LfMkHJtlah6>Yie0AvDtaV^>)a~041aaTy1pfmtZtaV5sy@<)XG)>SNO|^=5OoZ z*RTF^C`}{=Ops&|?HT~c#RvIQqNW%ops_<@N_=>zl_0KyX6>uQH^=Hrj12ebHh-Fq zN`ewph&*6F;zqq~fi1E$B)Ks_jE#E*08-G?sh6^6h% zfyu$?0Y%@cKd)b@Nl1h@h#(ti5+)_UDsFZ@JFMQ4k(bAAkO#X&SU&KBt+Ry)p#43i zf=Eb^LC4NI-11CF#L$4-9+wp){g8Jd>-;oDNQ4y-=u(@O7eq(|rNI}UhdJ4)U30i# z0DH$&dbg0K6pJyy>X4(;jOs` z-*z89uy>FA)y*`34Alkv=$|?WT&xxRoix5AFitBW7g8A1)pd~n8y-bDL1hG__q=F? zW$RM*DH2jC9S}MTdYVnf*i3RM66=cVDdG%ljKy=8p}IoXtaE^M?-B^s?F|Ex@L|5XbohG?XRl~ zp=#u!wO{7ARVL#XoyrTEnZ(aaPt&heH}nTf9dv8i4m@$1-j|b0)ZaM4Sbq%87GK4| zV*Ontk6Fnuu!_rO4^~|v;}6Eom7pRsh586<{gE3X9_ao#K`>Bc)7{pOwXL#6&NKa}3E1t_5m>e1kU3r!0FTnu=hnQF| zAq99<;I1bPYZZ#%Msws9-f2~8hQcpLhq$lcjfTLH_cfAupBB?V$rc8>e}k6clBZCi zB881J?;Fkqt3oB%EJrc)^A5&G7RN}X!o(?{FW(A13T-Dkl+o8j&QpfcOgry#f;YpY zseUOyS>&yTt+_+7+t&y> zy10TCc_x%BbSMz4HMYO9c^r{9)Qm-$3bV=;n0$XPWs_8p8{L*g|6=T~=bgpYA#Gr* zp7S;U<-$TmHDgeYmvcBh$e{-lf3BM6|8m z-?_Xw5H7_S(wlZk$fFA0(4~n5CKYPAPpMw^G^63qOD1V|boY*V(jyde)eHimB*`y+ zI=XPD=9=}^UW(_EU`+#o-;P{A4T1nV6p6E;>xJ;>B3Q{f`x8l=l~QlJt?etjXyu390>Oa24xa;q&c;cVFvU$ zk(krI(eSh4qq$DA;-4PCYJq5AoL1BHdm&yijH1DnL#K-Ta=`d$w_Iu`*w!Uzo`Y%R zO9cL3VN(<#4P%$Sj)Wm@!%~0KCqR5X2<=l|F+k zN|9S|kQxS65dNnzMvaLai5}s9cmo3Pg4n8ah zgrUzGC+gwG5(n)SpM(Z7?-hYWsE~_q%Fm++bBPa36{Zj_MKHI*bf+BkbS_m6n*-Gg zZxrsrSdeGMfjFV=gqRG;cIn(G<3!0_P+C85LPY-_Ym7VNMF_ zEy(DGA(EQ@Pf|08q$d9){r*qV@6c)W`rNGFp$7=+gG@l7EOn&f^yKg!4MZ7jpWng@ zwP>bTzf%xeL!J14{_oXF5QI#@l7_LFle2vnT?AOe@Y2yopWbCWqg8AZ{MO3ojLWv+ ztHzw?N17k>=lbbLIs&D~|HxH=YL0OjGjB>Rt{5BZB!s|z!W6e@ChQz!WOm{I4Z4wc zUjcl*`w`yqjw#Q#jK^B+X@qv_!v7|9A-?nZ4p(DD-p2HI!cTI;D`A&HO_=tjZA z(vRTLAD3IFB@~UJ$Pw04D8D0-Bhmutpi!|gD!%l?Nw~-9YDY=1kgxZ`NhpZ?9+q~K zMOoTANcb&swnFVK3mOx6a`AqAUMvW(E*C%~heb6=BJO~lGg43frZ$0N?}(rUbkJ34 zUCZ9MtM+Tj2P7ZqC#74(#ib8DNFB}YPfOCHLX=-F`n2RPs8Le0QW$*d%ln=DuM%Hr zrluqb(cM)4n-XrSDKO&r)NuH2s-rPyZFJP1F|QHY>82%rM8_WFW&3USn*StLIDy}gFj)-q}J4osAKZT|As0J7xB!zDTD|s(X3zi7j zw7Sj)8G7H0T5W&u*@ai@o&Go(yxO7tH;v@BXXKe|?NaQa_RLEK+eh=vix;)F?N)m` z24D8aqzJvoh@RlAGZUD)+v1^{T<^2<#K3}5!1z(-H6?u7irYkn`-C@Y*7ZSVO*UNC zb#2gux7!3N5D83pBd9R&NXQO$Di>N5&+s~#siH%!{EqzBK)zRO-;Yb_AX6KW@ZY_N z`N0#9TR(aC;iJfMU9;gKjk2A7o1JcV46(WLiNv0K3Dt6lklSLFVk(!SP{gyO|>{+y|NX zY1sezOAQR2c(l!&;;nhd&bL3!i!K45bv+dCI)Td71Q{6uR@OBi|GsFogJ%4-3!bs- zam_F<*p?zA=F@(V9c_;#*Mzs;eLc5Fmg~Ccz5w5T1uf*uKG&5r>$=}4-!&V$F;QNb z1R6EbK?y$*Q04}xlKEC#zgLCp(x;Smf8cIke$RlOtbGe!{(Hh~j|aXH5X z4c49%u%xE;0D|rBUt>%_rVp$ppm)9LD!9Ys$2|6D9F_0PoIAM(2&^Xy@z@+zoYBUx ze_Qf;0R*sar#p+4G~fF$WqGXGz6TMx^M)~pu^AJH-%_QSArYsUJ(%RUv5lxG#^hBM z^2ctDS}|w|9&+EEZzNVfC>*Q@DMu@}L&THChlqDL3K8$nG=@UxaG_u(l6K9*6%_g+ zcaS$JVj7b-G2tC3TgE)1G67@n_&UEsAO_@n3){y~*#8>?<#5#`w6K9-hi28^6~<9&&6$f&jp<~Vla zgd%!~O8U=}7)G;U17oma6G7z$wMg@#Wc}p9`P9pn9t0ox7$)5w&IuKTK=+#`$%T^J zlUysDvz5}}id`j2oJeMpJA&P=W9~ZA>40bk%HW^PLAG&@T&F;5v}EhXL>DKz!<)IN z1Gh&6{nBQzH&`jWn$%cHG*QEYHcs?Xu)nzCy%lq zF_MqVLEc)@h`zr40w1}&wKn)Q$NfLQH>6kF_QWUZ{xk$@lzAuQAi&lA3F7PF(;_iH zY*F#^K|ZF<(s(Y0kc~3yFaOR~)P`iFe`T;i)s1%7ueuWVsim4?Enpxly*`InS5}DQ z1X-3OtEftVRDX(`2^U1AEliL5+#XLFyU+E!`bEa0m_F3TqvSGFlqZynkPhsv_owSK zNT;qlXf%REP}?-b_)fq1C`h^) z?wK`O75I|Oc}hSMFO>0dY7^U1o|_OK6iBSYYX37nl;@HRy-kGP=Zli-8P>D;iWNxC zAc2pl!FfMW>_5v1P#R5sNL0`u2S7Tj!&Y*GpK$U{X+_lC6&Mg~w6>F~w+9&G~1{xs(SWPjGFXj!f`@QzU#4P%L=3$C?WOy!yPab(RNwlgNJr0s)D!-H{ zh-Skv-PLY*}tjPnc=os^2aw?Agz<%j{O z75EyeYO5d}cZ3(gHcgM#p%dOLUwp>`+aD1&32LHa%~{V=*xrFc9%8VM7E|b#(HuN! z=aPP|Pw3@@q`$)%9Ie9_wIAS@>vve3;-QPV3Q=1Fqvoi|t8{sevl!~%%2Q=c-IM`cT(laB z@TeM5dYqx~WU)9LI@FuH^z`q=4T$tOHcV+jY5+%ivqjq=mp!F7N7V2N2B>vfmqwI5 zPmZQ)xDPz1dN(M__vfo7=TsO8!wf>GG7g#6!?M@5cAWR-r%_l3I-A>QqdxHun9&^+ z;V7M=KmPUeORFA}X+Y5x84x8kkfOQA@DwMPwB~Fhl!SFcl3KTB3=R0m8#!ouBC$3c z^lc7poLocBq=Mp8qt)`eOQtb&78wTZ5F<_m>0AsyJ{6jeBZlVn$>b!n4g~RXYk~sY z31>xg3(1wMN|J16Qdac#)R1gB#I#l?6gV-BM4s5$3^Au^DJBFdvH)CcivC%*HO{*s zI+84l`s7Nn|!q!RM(Kk(3t>-+^L9R@Y z>R~Xnwgxq|r-LxJ^Rq!*Yvuoqq$FU3lK6+4d1!)tlg3n;8hw%hu#}s>$#bK!$)jT? zM?j`Ux8ikN(mW4b*bHz+_>?d>+}enTszeQgr^JWA#^dy=emXzwsmH-iCmOekw@RAE z>MUp$YMhMWrqCo+q4gQKk>i3IRK<<+sqb*b=rK}f8=Z*UE_cRc7H<@7^iy3Eu$Cr{Z!rhIj=K5Im=^#;5Sp z#Av-lFHxbKqaR*b!v#*){(H0AB~^ zqFjAaB~^fhx{4iR%Q~VLj#hw=2$97Jyx;KbbMk50(jwl%T>u+nEMs@&>F}UsmjOdZ zNoh(^H&F5Yww!&^`{AmT*`}Y&W0Dj*N>|Spmbx_oY_#5Ih~RBf$rS)?{-@$%>S>Ng zByx{eMmWkYOANOj6@S;%I2}h=L*%S)1_r;=-H!C->O{J@727K>y`AV|4MW69IDFb6 zyTs~)@0n4H0B$as$Cw~|UL2#*LWY$6s!?(V{L07f(b3Xo1~|Ts2sd4SL!r*`l9j|) zT}nVXW6Hgr*kRwdM=ivYao%t2X3KuzO;eP3F?IfmTBph?NCI2n$3(X9fEfpGy-6C4 zQO~;aem{-c<@MoK-LHLN;()jIciglbHWhab3j+yId z)VBC1cQ=lu7DbR##^@tNh3)IV_+6PeUDY9M2fLK(OP=3{OSr*3(LcpBOO{qpVt!cU z86zgMtg9zq2rw)YMd*ca-u~8m&+$-9J0Ulcjm^8BP-azz?594uZKprOAJlaC`mAE_)F_EqQr?R}gxNJLb)gO5( zzRkURY7_Q}t=RQwN{okS6Ed8)4GDe5bebN-RN|CD#U*P9&R%}llQ+=_%MR8p^8qm) zdI(6T%}=~~bt@`18~w7$2uFm1i@Afvb1f$x$q11hALen9E{mGpYz?3jT9W~hS#@oU zN+FC%M#DgIqWbpW=yDiqAq`v=&A{=mki*>a-q6Hy6q+WVP3lu3<~qR^Ma@1J{ZyGb z8|6UzSJZA%NSHwHj>aJTQ_vluZ;S5o_tvdyKEBJ+{kkBA%_FjehxA zQET>j+y=EuVNNFc>CQ&znXocIamE)?7|Sm>Q<*=xIP9$zeeW6yaO;)KIHPVw!izmF zk!T9mRC27wsvWu6JxTM?CcpWCWgyW=pmDW3cFefMCAiqcxaB?QX@El6V`A=fUAlh6 zXFyXsdUCVq$Lk+R|6XNfpE}d5&eN9yAa|k(U6*z0lWL4`5czEGB$T79ow0-D)p`Fq zdozOG7I!<+BKhQp$K-o%;S24O$u7AZLoB0dT{3QD6-%F3Yfw&gnP)OjOvkKqE9GN6 zUXQMSi;zfvkJ&n9I!`=SS4umL#{gZQ7+^@Pt5i;nF?;gJOhNj^e->(yPUVN7tiOCQ zu>}m3eB>W>uL88l3fbJlGR^~S7wB0!lk2>RKt5P|nmIxdSa$^GOvdP$v(UvREB=^$ zf(?1Cyon70IX48kpUsIchFU<`#74T-WNn0n4xanQilN0JyjXgw9f~``2r9iCLMw_& z^2CLqoB&FSVT6^3iWLS_5MRi{ekq4}po&##4z@eOs~du;T8R~5x6N~mQ}ZLY$z#Cv z0x;KduYtE5z(P&>K5$|qe}Wte#vS2uQq}4wRNjP&FzpeD=m?}$$^rb@d~?DMC)$95 zD&hSeNhqx)L-RlcEA29;>p1slj4Q7WzJqkV;41?%vJuvpnio-_P>L zX*ZyeJH@dU2h-)ih`ZrKyK(i!@^3b}7(6U^vkV(iLyd*sY`8EODo^Nq3pO1JXC2_u zwN4#&AcY|ARF z)k*Q@B)a@{VG}DtuV8Hcow9h^H4<^R#qG0!fG}edGtoE!E=u-druw@>t~J#_{a`O2 z9U)Q9bhc4wp>-NChrpHW>?|?{A3!+y4mIby6bGJe${@2$pt)cET-=}dnM=yc8^o?t zF)7Jk^Bb~nX$BHcU{gbP*+3^b>Mfz4avdk~r5(e4Ssfwv_3$B=62 zsy*%8Z6C*Lf5|*{8T4{yooZskkm$*eBL#&MnrURbLZb8V*R`Ix#G~0TkJqKM9Gjn| zCj=1qvECiYB6;sVpiH>O&H&kqY1%4AFuaxclaR_-%9(zX`hW+WPqZd{Ejs!-Y5vkJ z;<&DCx0Pe}RDiwLDfDq2P=M&(@^vwLbD)bF8ML=>Eg0#_JVIYO8~TLUeZz>-?1+tV za%s+!+7aixp&DSUIU05JlS{jZgs_0lGeMlIY~YL@e6N5jRWG_33-EP(4|b$?`BSiZ zFYa4a2aGlb$*Q(Jzw=2+94J@m#y=|_URRoJq0-mqRSLo_9a#72rriya4`h9ZpU4et ze3}R$20LOKA5ELyqsbB?D5887;Ly51}?gP7;Gaxant&f)Vm6M zpD?@fi%efd2U8eL?!zDh)$BuVw7*~U7ajjPOXm3`dFwQ-x}$Z}4HM?51gPH!A;$?R z5(Dr56uOkz<2J)#5P#V&POLSv1Uw1O#kAd%4R-1Ze7Kaz+6(wpx=Il`y0)koN-hyR zG)7{Mmb~t}nvq+-wqEty%pIsMUX^0rs=zKvE~;EB!$9kU$yMQXZN`O>;cC~WUhZ&g z|5k%Fu2UJ5Qp^6v8wn8`k0EISe-4t4VpAdDfJ=A903IpMr*2XHiU53B;4JOl;5Dud zW27W?1j9)A;nKF<%U`#8VfI{e{(bsp+W*VNcbZ)J8sn1&x)p0-mzJWcZ`fHHhhE530?E|kcUhoa2Y7;~jDD`_|61Pzw8=oRm$-9;P{B|WK^AQVCAY;$F;1Ua1`-fj%?ckqO_3JZi+4G;l7hH9#NulBXlj|7Y-ow zsC=}GhHk9K5?M)KET&)k-5`$M){J&WAMclQ`Rn@M!l|kgjKcn4)&ov|2&~wFP9>UX z4tt+gP&Sp;EGuyMPDm5}7CZ@i7E5+8Tt=MbvOTEYBKI6zamCeuTGLW)RCx*CLR8|0 z>lA!T&V=}knf=An7R)#GuJN|EfcV^A$X|on>9E(+z;AI}ivPU*^bSsJv6FR=?Wcdo zo>M?a;+jPvz(0Ntm-PWxO-4kucr-)`MxMqB2!wxH&8k zQT#uuLOUxkUnjPBNJPQZ8_V6VeGcWPi{PTI{&p}?_EMun&O%n8821vp`!*IiV@~HV znq%V`9FAtH>QonX=eW2xFaP=7J0bq<>VRFb*JkaRW<`F(*) zhd9TIt5LPsNf>wh^QK1Ln6G<{N(S#k| z)|`Vrf+$GPsM zXa|N%8Xb@8nE7R%jyi#vPQ7Xy5qTVs$|XfNE3S1_%ZHq>HQ5_SceA!`JGwZ50f4Pf zv5Rm1bhzm~eAi^zKE2oFZ1-lgV$~jk(f0cy{}y6X!f`>bV)|>#pmE3aHk|i4ciblV z(VwlJi!q0wIRV z*1J&^R^()L&f7hV^QfkF{u~ukFanH88qwIz?W*v2dTYkAR8S0Abdfldez{sa#`E$` z)#uBCBX>-bACh!PTRFbRqytzD=B?G+J|g@r7!5nbgKi3}NiO4HyVV^my|swRuaJ7d z8@sA8V)&oekc!sK1iL^~&F#~T`jN=i?Iq>A-)QJYuyWfBY>vw%eG7%! zLG-zfCvnh-#?5d8`807&1rMplkX}5G;mnDhl0pMT1i^F zykR~5h&Hq}C|Q@gz0-s>3xwx<^|1jbT*@TB6b2cL1faQ&Sk~uUw$`~?^I7jd!twm^Q~LA6dSgB!{7IDUV!drTR$(Ij|lsZ2{CSn{{V`r=?C;X zQfVKO%lG)I{G6}Y2uR5?;r3oi(Uk~5U=W_rC(Z4z=t||iTOrs&pP*F^2Sd~au7lFQ z2fC}z%L%dBl!EwT&Uv$sgP)0yO0Rfolje1PX|@YDRW^5g4`d>oyALW*e-c`U5Fl=O z4G}W%bRbn@(KA5_le^Y*m03;&9YB)dyG8`d1ve$Gv19)pbN>+(yz$p>SF7}xLtVXMl6)sp=p<%HhTQLJDYlB`Tgih=no=;7|1FlTe zo7~D>%ro7@i!NxwfHnh!90l9K8Z(vYiP@V4>*%hxlXG)eekDgRrS*3q?YJY&drmC%!zG^REAfgv;NwpAtVSWz!%;Bb#%9g<5pBh~vo#|w979nPZ> zZdEwi#bV*;V@t2w{gPL3k_QH?GyJHYGV0@uRqt*Q~=o|lI0BNhnPtI;9(Jy`X zOo!7&_f=s>QhXQwsNk0RI&3EXR}O;Lhq}6DbbRIt@&t~m#VxPqwGS6FX)EkZUmpss z_??kbcQiprm1b!1j^EK3klw6=#IMW#DP4(ly72 zTNDx}NO+6`67SIV8lc?zheob*Y!Zo`KLRV&xhjW&C)YHVMi#ZWwH&uMivptrs}@7o~tRX|X060Vhj4 zfiDir!XHX*yu8>6Nc6yDtJ^|^?IUxIYaAl>6Z61>&P@G`7U#MOC5hwi5lH2c%%L%+X#z6yj;<>RgTi34A}L~0$< zY}8FlcH8EM2)yq2BiW`{m@zfs{`i$r=0-tfAZx-+iQqyIGEK~g{rd=;$4#{374A=? zh88;cCUPwgYl*%t{JTF#L0b_O^N{PNAtD8370@Xpo&?&4?fBqtom30hZUn)%%1uB&1ZtuwMoL zr@Ke)eN)U@0p_N%*`t-Or+>y@K{Rao-wGW@al<%N@Dn_<{=@7PXzf}ogH;00r0LS3 zW=b@)1-y^~6u^76w(ts7A)CTyA&(@V&qQR0g8vQ-xR>JISXDgF-;5A>9E!MuD~tto z-~qIRi$l9+0j58;ZMqYwR@Y10*mN8G?|C;rIzt1!efIchF_EHSwDpo?3MzbZTDL)1 zV0O@A%x@79XT9x%*5UXs^F?Hpv7etszc>Y{?fBVMmhKK|)lrBJc2+3B~w{j?0s zBxvmRkrsL@VUsh2&Dr80VR!UW`}bFd*@eK3eBICjS#FRN5s^(TB2r_%OB^D{ON&x_Iw#52eQBH z`R-i?>b#aB26>+NcKebrSf(6fL zvnpri!7Lr>yuHhLCpKL-;BT7YyF$Bm1pXoxW;GFsnMEta+bkwUa=+H?zk2_g=X;c; z<^P!MNMFkelq@9NmQD9lW#IAA)iw7?=X6LE#a)KoEu$^xR}* z-m-0fbW7bBR_vLi^&w;*7ni>`GU?MVn@aV{W~I&f-xl)(>G?m8PEmjuLmWl}1TEZ^ z*}Sk-YYFYi7pAy2KFIz7htx1%?)T*(CGs1^8TFX@pc{mO86S}Lx1e=AcYB`b4j65{ zi70e$-FP$SRxj@xyvud@hlS^FH$oY2d_=DxE$f{~|7a;vB5&)RdF8e0_?FZijUt3~ z>sx;WLC0@X8pI8s2|a=9Tl?3$S~lq3Y6YRG)I*>5mE4|SFCQ`zuvA?;%J0UkM6{WK zZW80spv?i$9vc3jCcQorQ;M|=0#EU_*+T}x%0AJrX9s*n*T39u$OrB;zYa;B`)K-P zcKs7X=a*I*U0Hg^b8I)BJILyJb%utZsvskp z#6;kJTa@&b0W*ZJtOJH|NK-E4RVfMK)u$qV28yVZGj|4xxRetLkpWHr(@K6)*h<0R zt)eMG%!;L}=7?uy)eQMc+!`n2M|GkkAEHdpd`>99_7%$RC3hxJPW*JgloNt-+dxq6 zWQ5lwgl`Gi78x-PX`7NO#U*_n-}mHTZ{Mefat7vU(4(F4s}eyCT?9&AsMv@U<=wez z5V4Dtazf^wTu6Xi$SINF5z!_f01iZa$DlzZRy0)WlK~~=jQFh*T?%i8d`8U|UoLru zTn%V&z)#PX3o&l`qeX0z26IHp-M`^i?EV3?#7!^&tRh~wA!M;uj@8+J1R*-(oi_F( zDN8PTDjc3K5lfJB(I$rRCVX{j2;Pcex1eaVmA8qV9Yz9f=l<$Nl~;_u(5t6L5!mf#$5ACGzd4MVBn|9gw<#1Fw(wY=pTQk z`uvJTjZ6JM662$w>@FdLnj#2ECsPiIab|Av>*yC&K@}iS5e}C1+g}**-Sr6#xEI|8 z=L6nu&yz>1zjeSaTKLE-$1HF2q|&$wvhx$Epabaz?PjbK8>^c$d+y&~OBa6xiWcg) zoGZ7nV>3yg@$!lRIl89LD4@{fQl;>OUX_fy$)EIsq}d>Zk%fE2`&Q+!Gd3=V``;cQ zc4u5T3|KiQ$LX)h8w%2%UNEJM&5{jWBG=fyQ}=>nydHpom6yHh>?fhR38mPsILSPP zEQ}HNWn|89Tou{y{taKdp1%9Q&I_jWQ}}c;%N!`!aWl>?&wy+%EP+cfud*L>iiEdA-v$?xelP*hSivpP-N0}@wp#RMOBa3f=He;USfXSgnXgc7FW@s)JjeV5$@ zbBp@(8MYi~(5AzspPhBQL&@U`q9QlY6k!=U!4*3DMRSyMi2g^uz_?!6LpNxj0!bu7s?EoHC3Q{YRAZ0`-hF2BAKkQzMA<^onX= z8DHlm1j@nK(`aCcz$U{nXI6TKE;T5Ou$Ec*NC6p&?BbwOS_4!UVx9w{?0JrayxInC z_qqm;HIXc6_ptW=FRk@-2x_*vv7aq2w!_>CF~Y+=0x{Q1k_*j(IhN6qN|h z6!s1O;#yFj_=VXMH)0CLR6eif+vXEFDC^S^TEsv4p!x9Hr#?l-5ttKkYy>$t<_L0DoJFkD8x`=yWhd(Z1!MjBYH&dnw;+29YwyV9@F_FkTpp@0btx4Q$p2~3#m zxW+ZqKD)*Nh}~kpagLDHl`R(T2owl;|NkZe0wL?UlvM=Gr9y~`@C#YTye(pig>Sts z(p<~H+YOeYs6{mFTi2Xqzu%AmRw+G{khCH+7yKNKd2c7NB1JWlh{Mr+X-oE1iN#u$X@P=85vCB4xI*kYdf${;5`pP*h}Q7ESw)Guf$Bg$;` zc4SI2qcVWa%C{x|)TY&>Y{!KrfRnDi{+;VXZ3~&RUUPdnaq@D@QPwrSUBgkz)d9A%9F!{>Y~5*9T8y z|8ntrzMuT;n4;WVU3@Kmy?QiUq_6s*-ruvl2Ei07=AT?2Wxp>?*u}fGUH^bv7w!pY zn0PW8Vh_;C4n8>E>`k~u{FxTxN={FcvEVferkQ%4`Dc>7y=)UoX+1Vrm5imCIXUW3 zk-vG;{4GQpLbwHeX}4PQeb85ukio>X-`Jn;s=*TPc(xB_I%Gxmzsl$6=0^v*agQd7 zh#?e=7Ho+VjO5 zw|XLyU1;2yNb1G7dhW8jJFmBBcU;-!g3S@7%}WWpoks5P_18BIL`2M-x6;y`X)%{Y ztJgCl{$3(&Ewr&PoyfUkGU}h4*?|-?SDlAqi=zA012RY3V~5^L0?V_#s_9Nykxmra zO!W-!n!1Gtn%e0hS&%H3y{9EU%;Og!W%{%8 ztAAo?#rZ-+5)$L+%};9r=S|c2qAU`V-tk8-#2F386%ibv51XprjU-s_Svvfxkevy4 zT3?XhUtj)A2=OE>*`lDlnf5wlp_&sj_bR})Itw*;CXLGVEv^CdOiBi36$W3{EfEvw z2&(xP2xq40F-Z*fwNT_(;}^n|^waVDq*}Ja?xcL49=Q4au@AbqvJ)uD6to_%5b4;v zyJ{yVw#AZSEf2lxFeSK_L)xI$y}c)V<)$FoceJF+U1MFClsLGzvbE#rKXS`(^BFj{ z@Q0opx2Z_I3OfgotYTH~$v9IIxN{owXKo@=uDDc8bV2xS#qms{@p|SkH03o0Gp+4z zXj>-HXYE66&Si>HKEA8>xop_{K#km&`qTS<#4LX>-ObTSkk6OhIiqgFjkqC-zU)pK z)NRJBwr)skw&l^2D$XKQEoR~5sw{hptM@?&T`JYkS5Bbe(yn!t@5Un^y}owJ%OCJO zjAz!$Qc)O{{3F@F;rP8aK5D9p;l^0&dy*aRvJ(H}Oq*eqJv%u|*`WuaJMxd^3#}WH z$Opc%oji(Z#qiPcLQ`V2ve?%k**>UFiwZqdOxr>-N`P-Mtny&~VwH;actNj_Y|%Uo*Wpr*C*F zj{5Yd*_Fwpf4SeDiZquU()2zopo=qg!cYO?vCZT*w*x*m!#PwNPSE;?!;O)XL6ly4 zl4uAqN3%VQWHHnE)i2Pt=+xBZP!RKKaxzu`_^iP3SD&5!&jv3SgD7T_KXTj)$K3~3 zb~{lM?ecugO$)R{;>a?3aH$TUm2!SER233^+gLeMz+fTh!E@KC$Eg6eE*hPENt-TpW9}&%!#vKF>Vz#Y<=^a|Kv?QsU;uSguoNT(_WYa-y z?IzZyJ9?NOi@&^cndzC@zL^DC-}P{$NIn(c{n+aLw8lg7nw~$v;HlxFcpkV9QTzL+X%V%-q9APhCxEDOkDG7U2 z7^!r7AT0mHX9Bwqnhc{{jdJ9#S66vR_GBp1DY5E0x(bCJw3cOfse@0GKb#X;WbRe` zz`2Rqv>*TE?s-Q>yKZ)2S!8EnMhVGGtv!v(SuTP#AvZ*lcsGrW&k;aGI8FuN! z|AGR82bTZAAIHTqk2QW(q0j;sr&D|({(9q17KB9g?>-btUsRzs@sKH(hO*%P$c+;n}3sC ze9!cvh4~mziB{1BzsV}KS+i{TcQb!P_JEiZm zwIEw_u*LmxaAYC!uV)f2le60hK)b#!$=_>Yp14XrMAu#goFY)Riz#7 z4<1APGa~3*g1?%4zSrr_qh2LxuXm9v4ih>L7M*;qS-R&I^kav7SW|OLCcx^(%yoz9 zcOKU|Q=j^PTf!E9AyqS}Oq!5P zAE`D*7q>xUz>atYRPntI5X~Q2J}2&1yREVEj`h_*e3zEt(XVkC4(v!oM~A;`%F=N}WPhXv@fC1RdYrb>0Rj>v0Dp(qAIu@lM{3!F(nLsl@tYC;baSE zs|cI=sI118?!yUdfk!+TNwQW>FfyTFj<4j*QG-PO<<;@#gb9V0{cXh+E`Xd=4GpW( z6eWIovYPRdLh3%UdsV|;53KmyksU|%(hu_d=90dIxg@}I(&}GZ`=?C!Hw6&JUd7cF zP4xiO1G$EIFOt2fApTx7n(2jPUI7dkBByM1_#K!jP5#X~hwg^UeL2yiW8(XN?T8#z z36aEFJWNebR6AYKpXid1+NE}PP!)0E@g;W|%LwUbst;QibXHkr2HFi))Z&R6t>QZT zzL+x-7z;xB)E%3;RwmZzaSggD2kw#PS|Wj{w#d1JOxXsKxk85;Fo7_q?Itj*+;psG zGu~P(AmB~NNNu$VZKd^O66IjV+ZQdB->WDRNF| zEPNGZduXS@mFI~2$rGJ{+JvQ0k(O|KI4`6#Q)fuv@3$TU0)c!YALMKaOBP1o3s8h) zlN`kj{{76o4xeLBo;Y4#viEr-(=vXU{^1>m<*!vR=-bZFDtYh9lIaOP`3HH_MNX;o z+|GuJ0`b-M_QG4g1az+?O^X^-Q3VKF+J7;6AwNy*0nZthf%e?5#`S)u#D#*l?)_Qs z3$}ps&hDD1ivA*>4PW-; zvvg!-zZ^|rS7%CJXvG(NQCby%HjYRD_nEOEVEshYSsvTFcrr?+lyMk~}U z>42tkZTx2{&g+@-jN{5CU(F17T@fUR)5eyeF$ z?ORzTvtk{R-gi%~_g9NIsv4HPC0~V2e`8IyTGa7~-@F&=(SwcH8`OW4h(X4?nQid! zTuL{CiNEO6FNXb`dgmfm7q0|9%e@(vn#U9!7;Q+_Awd8uvO=OBTI0Q%HR5Lhj z>h-WGYGx$-6*`PqMi4`6v!z4p^r8BiuNCvA;%CI;rjNM%-Q$5~K*FT(q5 zO7q-L^RCk{V*R!J|6ptK4*yC&G$M!FLAlmZlg51o8v#`+*l^Tc+VW$l&z&zVHot5s z1;|rm9s7PborwlpJb^eKE}Lr=+E0Z_eIqw%<#!KJ=ww`baZpWZqcX$7o`a!k42?`7 zUbG`x(I!NeFE^sw=*XU21a>^yvc21Q>PKj=UiCUS@yi=m^nHU_zprQLbA#uL5A()2 zfD1%>^y9QWhEU@5qTI}I*x@halD+JOTk&eMKIjJDvZQY@&M366J$=MZpmXNd|2P?= zykZo6VdXba{X?ZW6p}C;##kTX<4w;a5Lo(%){m0tGUaR6#Aml0Q+zpebnef;zJUL+ z)OZ=k?MUZ1!WLgnu!ql?xTl0H*+l~i&#LoIThYDQ57!Q|&~j<~p^cwp;ZQ z!vh)(w&$mmWYdT*IB5dul2!QFf$}Zv+%06w{>H1Vi=kYu?WV=1#&>Kne{IArY1`y= zl*A6y49`@wRFmxPt!Ut3h~IqJKF$l*mwUI?55=o3OnLX_04PRZ7x>kKw%c@q}raf;t$qS&rXOQgciM0>GhAgpBz) zZY@psBzjNE47Y4Ou({>uM5O?VIz%doVsL}x^%11>t1J3}d?A!SOb=?1-UMWnMU!_5+xfx_X?+x@wiZVchR5?m68WHD;kcUrCvogCgc|m% zGVfI3a%)MN9`?3@$y2=xdEOO$TyHi#7T2xrVbSiNB2=s>Qg>k7K3r&tL&b5C?f^9w zRat)!Z5D=JCGY$C*gG>vS3r?bS4-gjM_iFy^_95guBbQLPv;bvI!1?t1@?&(7ikLP zd~+*Mv;*bl9D>9$WV`D+34~T<&fu$RS@mp_0}HFEE#a=-$1$gLrbjYy^(04pDkc%F zzLs}y2U;^)o&)_+kx)B53ul!goie#=IW9|OjEiilWePK<-Mu-0U7-Y4&Iu_chaVOp zg5Y|d4yjwsrdpFrp19umPuWm!*^WvTI$}O3a902NBw`LhHGy>Z9Y}ZSB0hbkY-jE~ zQ18;|#!i>v7+vTulx-M-Q4r`5ij=7HprY6W&jX|Z3vI_T<2@0x_94YOPKBbwoWK_5 zG9moKBKRGKYl$~xgcW%FFS>h;%ufd?26nO~c= z7chhz+5b@7#8OD}Sqb|!{rESwBDQLn-Ii%VAi>rc{^Ltg=DJQdTWU_$ZJ(M^1fKC( zSO8$C5%jFrzHBBuqbVCn&R(qPlB9y5tqh8@QyqFzlR3Zl#9TNcz9dO#_;K05^OdT~vPKFFS$U&{Cig>UNrLnFQJkGC!SsH<^n--2-uzYy;vkWQ@g zk2F>Nk{VtM2}7~UXfuO!dL;^SJ(?-R4Wy8qRkJ7A;LLm$tI-s={5vnYy<-x_llItw zt=959$d_q?IX#?n+rd0uS+5W(6mb}?Cx}9<`34Ez{8jyRrTIWX@LZ-_NSP+^J16iH zamS%4EZsm;6qUv&tGc_$%NJ*@djEa7=*!X3%#6!kEdvwlfvuXin8{_IhN%a6ZGhR0 zSjJ$LDRQ%{5}5{CL!^rC_^9trLY!xK2cq>fJ3AJ|f8m1HHbCfO(@{U`>0C{buN zZ5FF@m38N2k-9gKQU@_>Mf z=GCv*R+1Jx>P{22HxuX6Yl}W!6x6As;)@X=K%xEnF{ATY9@ac&-5&ZzNQ(A4HKlw3 z!wrjG0wZ^IVrRN|NOXpjGaBLb8nd(P1rjXf*oBs#yogB7G4|oAV6$D=aG>hd!KI3DudWwO*D13i%EJLqS_I@7j(maG2gPw1UlbKxE?5@l=rQ} z(=Sx-Lxu1Tzl1N;Xl)$Dm)$I%Kh=tZQn>kvxuG4-sy7Wsei?M3YIARk*XX7(lNx~R z2K=O^r*8TL_;J_K43V6;Yq+B>^&MLDObq9t$@cGwiw~4!!*E^PI4-vhuPeV0PoZ&{ zmONbJCc)&71EtV@Q~np^7(COTEVff+Y&2d+$5-~Vos1jln-(AVt_-=^KVft-T~uVC zl28gf@Rt!l#jkI+r!1-YhnB=f*}S*o-St^GuEDkf1bIu`Sy+rrIvUJEO|8J{=ARb_9F<#1)B9182Rc%`ppQdT1fZ5y%~y z^#PDPAon`!o_s|pQD_6%%J2&}X~R)yhZ5RMHXOq=u6ZyWeR|>UB|fJ8rJ|Fy(M+V^ zeqX<3oO`R`zTh+CE>k3DI!3b?I+vxhSV}hXsW4^h$KSg!yX7c*m}KLXn$fWC7js1fI%yi%#uh@U%O1qrKv>fYGY%Ds%f&_z0=ra zP$P1ZrOWcIztW6IP$d4;*%cqtTK1aCuP1R_1NDlvcy8bs{u0>8b$5wNqD?4%A4Ysu*) zGvXpGr`Y3jWq1toeYY9U`(z?oWYEmM0)x8CTD4<}&L`a_guA0<&`#JR!F(b})08Pi zr+06hd)}e7;OO|!{4@PU;DH0$ZY_>p?^;MhS1q6r^f#t;7eb~E3; z82r;R#`nJh-XgSdy`V1l>~+NntGBV92&g9#_Bu;}C`55RBNHB}Wyz+r$x;=hjSS*` zoMxjjOLyw%KgZk~5{v9vJ6z4pmgRm%J&+`ffGKl(c0=Ox0OpuV#V zpvXSP`MI?q66%jhbxtpYr`-}>CoefHuK3L=bdHW&4D{ab;6KBqX;Ard^Gm#|2-`}f zGG0-6*d_5ek6oeRhsGH)%r>$EdA?AIgQ){mtvuvESeElK>V}#j+N>+s;vYuJgr9<< z*2(6Q0&tO8iE$^NgX(!0-I`KZY|C;R#Q( z=8_YZ?h?L$^IrLLv1Zt4LvNLMImTC~l<$h*E=3!JbVw>N{;>>CX)*Cho>;K|iC3HV z;V3M_j8dSvS?ky3x(e`Elm)#`C2a zD=piLv^B?$k~wZpdkJNExGw&Y)dBEVqxb4Y5h}it@1%_TaZ_wQ*&l&jYPsy!7sN^L zjJAc_@Q*@MCTcJqnN{K{g?XY~w+=U$fRhV4;aiVevf2-68$|c+@K)QQyl}8YcBvA9 zd@08N-*yEL3rhiP!}4$Y*Nz1X>sb~~;zYha<9`h+2(YjoKf%JH`R~mCy8=v{NK`CP z#F;6=Nvss7P5fLy&iLOd+%{NPz54`M;7$K+|9f6cF;3!OfiTV{A#Nh}7h$$qa$GE! fjhDkK4_|KwZ+$I1{C^h{gS!F_7M48~E_n2R7)>j3 delta 26588 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 ao2$92x)KKFzvlSJmn3rk9`xLQ@BSBHxXJkd