From 844bdf4dc074c75b9555ccc64d72ae48b2a629c3 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Sun, 7 Nov 2021 14:59:07 +0000 Subject: [PATCH] feat(missed): Electronic missed approach coordination (#346) * Missed approach storage (#344) * Implement missed approach model * Store the missed approaches * Remove old missed approaches * Missed approach push events (#345) * Implement missed approach model * Store the missed approaches * Remove old missed approaches * Implement push events for missed approaches * Missed approach tag functions (#347) * Implement missed approach model * Store the missed approaches * Remove old missed approaches * Implement push events for missed approaches * Missed approach api calls * Start trigger implementation * Start test * Style * build(actions): Stop actions hanging on clang (#350) * Check for clang clash * Remove explicit clang install * Clang check in format * Missed approach indicators (#352) * Implement missed approach model * Store the missed approaches * Remove old missed approaches * Implement push events for missed approaches * Missed approach api calls * Start trigger implementation * Start test * Style * build(actions): Stop actions hanging on clang (#350) * Check for clang clash * Remove explicit clang install * Clang check in format * Expand ownership roles * Change how user callsign is stored * Get everything working with new roles * Tests galore * Add rationale * Tests * Format * Enough for now * Working renderer * Format * Split out options for renderer * Fix tests and style * Finish tests * Global options saving * Use service provisions to trigger missed approach * Additional trigger checks * Add audio alerting service * Play audio at the right times * Style * Start button implementation * Button settings * Start toggle * Button toggles * Bootstrap dialog * Style * Indicators * Make the dialog work * Style * Style * Docs --- docs/TAG_FUNCTIONS.md | 1 + docs/UserGuide/Features/Features.md | 1 + docs/UserGuide/Features/MissedApproaches.md | 84 +++ resource/UKControllerPlugin.rc | 40 ++ resource/UKControllerPluginResource.vcxitems | 1 + resource/resource.h | 28 +- resource/sound/missedapproach.wav | Bin 0 -> 457128 bytes src/plugin/CMakeLists.txt | 37 +- src/plugin/airfield/AirfieldCollection.cpp | 7 + src/plugin/airfield/AirfieldCollection.h | 1 + src/plugin/bootstrap/CollectionBootstrap.cpp | 2 - src/plugin/bootstrap/HelperBootstrap.cpp | 3 +- src/plugin/bootstrap/InitialisePlugin.cpp | 3 +- src/plugin/bootstrap/PersistenceContainer.cpp | 2 +- src/plugin/bootstrap/PersistenceContainer.h | 6 +- src/plugin/controller/ActiveCallsign.cpp | 132 ++-- src/plugin/controller/ActiveCallsign.h | 75 ++- .../controller/ActiveCallsignCollection.cpp | 331 +++++----- .../controller/ActiveCallsignCollection.h | 77 +-- .../ActiveCallsignEventHandlerInterface.h | 6 +- .../controller/ActiveCallsignMonitor.cpp | 4 +- src/plugin/controller/ControllerPosition.cpp | 45 ++ src/plugin/controller/ControllerPosition.h | 9 + .../euroscope/EuroScopeCFlightPlanInterface.h | 88 ++- .../euroscope/EuroScopeCFlightPlanWrapper.cpp | 379 ++++++------ .../euroscope/EuroScopeCFlightPlanWrapper.h | 9 +- .../EuroscopeRadarLoopbackInterface.h | 1 + src/plugin/handoff/HandoffEventHandler.cpp | 4 +- src/plugin/handoff/HandoffEventHandler.h | 4 +- src/plugin/headings/Heading.cpp | 28 + src/plugin/headings/Heading.h | 29 + .../InitialAltitudeEventHandler.cpp | 14 +- .../InitialAltitudeEventHandler.h | 10 +- .../initialaltitude/InitialAltitudeModule.cpp | 1 - .../InitialHeadingEventHandler.cpp | 14 +- .../InitialHeadingEventHandler.h | 10 +- .../CompareMissedApproaches.cpp | 20 + .../missedapproach/CompareMissedApproaches.h | 18 + .../ConfigureMissedApproaches.cpp | 33 + .../ConfigureMissedApproaches.h | 35 ++ src/plugin/missedapproach/MissedApproach.cpp | 41 ++ src/plugin/missedapproach/MissedApproach.h | 34 ++ .../MissedApproachAudioAlert.cpp | 62 ++ .../missedapproach/MissedApproachAudioAlert.h | 48 ++ .../missedapproach/MissedApproachButton.cpp | 149 +++++ .../missedapproach/MissedApproachButton.h | 110 ++++ .../MissedApproachCollection.cpp | 78 +++ .../missedapproach/MissedApproachCollection.h | 34 ++ .../MissedApproachConfigurationDialog.cpp | 228 +++++++ .../MissedApproachConfigurationDialog.h | 45 ++ .../missedapproach/MissedApproachModule.cpp | 133 ++++ .../missedapproach/MissedApproachModule.h | 24 + .../missedapproach/MissedApproachOptions.cpp | 53 ++ .../missedapproach/MissedApproachOptions.h | 35 ++ .../missedapproach/MissedApproachRenderMode.h | 28 + .../MissedApproachRenderOptions.cpp | 54 ++ .../MissedApproachRenderOptions.h | 37 ++ .../missedapproach/MissedApproachRenderer.cpp | 136 +++++ .../missedapproach/MissedApproachRenderer.h | 70 +++ .../MissedApproachUserSettingHandler.cpp | 72 +++ .../MissedApproachUserSettingHandler.h | 50 ++ .../NewMissedApproachPushEventHandler.cpp | 57 ++ .../NewMissedApproachPushEventHandler.h | 31 + .../RemoveExpiredMissedApproaches.cpp | 17 + .../RemoveExpiredMissedApproaches.h | 20 + .../ToggleMissedApproachButton.cpp | 32 + .../ToggleMissedApproachButton.h | 25 + .../missedapproach/TriggerMissedApproach.cpp | 126 ++++ .../missedapproach/TriggerMissedApproach.h | 76 +++ .../NotificationsEventHandler.cpp | 4 +- .../notifications/NotificationsEventHandler.h | 4 +- .../ownership/AirfieldOwnershipHandler.cpp | 15 +- .../ownership/AirfieldOwnershipHandler.h | 6 +- .../ownership/AirfieldOwnershipManager.cpp | 252 +++++--- .../ownership/AirfieldOwnershipManager.h | 39 +- .../ownership/AirfieldOwnershipModule.cpp | 11 +- .../AirfieldServiceProviderCollection.cpp | 150 +++++ .../AirfieldServiceProviderCollection.h | 53 ++ src/plugin/ownership/ServiceProvision.cpp | 9 + src/plugin/ownership/ServiceProvision.h | 22 + src/plugin/ownership/ServiceType.h | 23 + src/plugin/pch/pch.h | 2 +- src/plugin/prenote/ComparePrenoteMessages.h | 2 +- src/plugin/prenote/PrenoteEventHandler.cpp | 2 - src/plugin/prenote/PrenoteService.cpp | 8 +- src/plugin/prenote/PrenoteService.h | 6 +- src/plugin/prenote/PrenoteServiceFactory.cpp | 6 +- src/plugin/prenote/PrenoteServiceFactory.h | 41 +- .../radarscreen/RadarRenderableInterface.cpp | 8 + .../radarscreen/RadarRenderableInterface.h | 2 +- src/plugin/radarscreen/RadarScreenFactory.cpp | 2 + src/plugin/radarscreen/UKRadarScreen.cpp | 5 + src/plugin/radarscreen/UKRadarScreen.h | 1 + src/plugin/squawk/SquawkAssignment.cpp | 13 +- src/plugin/squawk/SquawkAssignment.h | 127 ++-- src/plugin/squawk/SquawkEventHandler.cpp | 6 +- src/plugin/squawk/SquawkEventHandler.h | 9 +- src/plugin/task/TaskRunner.h | 47 -- src/utils/CMakeLists.txt | 8 + src/utils/api/ApiHelper.cpp | 7 +- src/utils/api/ApiHelper.h | 1 + src/utils/api/ApiInterface.h | 1 + src/utils/api/ApiRequestBuilder.cpp | 21 +- src/utils/api/ApiRequestBuilder.h | 4 +- src/utils/pch/pch.h | 1 + src/utils/task/RunAsyncTask.cpp | 24 + src/utils/task/RunAsyncTask.h | 8 + src/{plugin => utils}/task/TaskRunner.cpp | 17 +- src/utils/task/TaskRunner.h | 47 ++ .../task/TaskRunnerInterface.h | 0 test/plugin/CMakeLists.txt | 29 +- .../airfield/AirfieldCollectionTest.cpp | 14 + .../ActiveCallsignCollectionTest.cpp | 569 +++++++++--------- .../controller/ActiveCallsignMonitorTest.cpp | 45 +- test/plugin/controller/ActiveCallsignTest.cpp | 244 ++++---- .../controller/ControllerPositionTest.cpp | 203 ++++++- .../handoff/HandoffEventHandlerTest.cpp | 16 +- test/plugin/headings/HeadingTest.cpp | 139 +++++ .../InitialAltitudeEventHandlerTest.cpp | 72 ++- .../InitialHeadingEventHandlerTest.cpp | 71 +-- test/plugin/metar/PressureMonitorTest.cpp | 2 +- .../CompareMissedApproachesTest.cpp | 53 ++ .../ConfigureMissedApproachesTest.cpp | 45 ++ .../MissedApproachAudioAlertTest.cpp | 119 ++++ .../MissedApproachButtonTest.cpp | 263 ++++++++ .../MissedApproachCollectionTest.cpp | 143 +++++ .../MissedApproachModuleTest.cpp | 112 ++++ .../MissedApproachOptionsTest.cpp | 57 ++ .../MissedApproachRenderOptionsTest.cpp | 112 ++++ .../MissedApproachRendererTest.cpp | 53 ++ .../missedapproach/MissedApproachTest.cpp | 61 ++ .../MissedApproachUserSettingHandlerTest.cpp | 203 +++++++ .../NewMissedApproachPushEventHandlerTest.cpp | 153 +++++ .../RemoveExpiredMissedApproachesTest.cpp | 42 ++ .../ToggleMissedApproachButtonTest.cpp | 128 ++++ .../TriggerMissedApproachTest.cpp | 355 +++++++++++ .../mock/MockActiveCallsignEventHandler.h | 33 +- .../mock/MockEuroScopeCFlightplanInterface.h | 9 +- ...ockEuroscopeRadarScreenLoopbackInterface.h | 37 +- .../NotificationsEventHandlerTest.cpp | 9 +- .../ActualOffBlockTimeEventHandlerTest.cpp | 1 - .../AirfieldOwnershipHandlerTest.cpp | 75 ++- .../AirfieldOwnershipManagerTest.cpp | 377 +++++++++--- .../ownership/AirfieldOwnershipModuleTest.cpp | 2 +- .../AirfieldServiceProviderCollectionTest.cpp | 293 +++++++++ test/plugin/pch/pch.h | 2 +- .../prenote/AcknowledgePrenoteMessageTest.cpp | 2 +- .../prenote/CancelPrenoteMessageMenuTest.cpp | 4 +- .../NewPrenotePushEventHandlerTest.cpp | 4 +- .../plugin/prenote/PendingPrenoteListTest.cpp | 2 +- .../prenote/PrenoteEventHandlerTest.cpp | 18 +- .../prenote/PrenoteServiceFactoryTest.cpp | 11 +- test/plugin/prenote/PrenoteServiceTest.cpp | 33 +- .../plugin/prenote/PrenoteUserMessageTest.cpp | 2 +- test/plugin/prenote/SendPrenoteMenuTest.cpp | 14 +- .../DepartureReleaseDecisionListTest.cpp | 4 +- .../DepartureReleaseEventHandlerTest.cpp | 4 +- test/plugin/squawk/SquawkAssignmentTest.cpp | 31 +- test/plugin/squawk/SquawkEventHandlerTest.cpp | 36 +- test/plugin/squawk/SquawkGeneratorTest.cpp | 32 +- test/testingutils/CMakeLists.txt | 1 + test/testingutils/helper/TestEnvironment.h | 41 +- test/testingutils/mock/MockApiInterface.h | 1 + .../mock/MockTaskRunnerInterface.h | 0 test/utils/api/ApiHelperTest.cpp | 16 + test/utils/api/ApiRequestBuilderTest.cpp | 23 +- 166 files changed, 7214 insertions(+), 1717 deletions(-) create mode 100644 docs/UserGuide/Features/MissedApproaches.md create mode 100644 resource/sound/missedapproach.wav create mode 100644 src/plugin/headings/Heading.cpp create mode 100644 src/plugin/headings/Heading.h create mode 100644 src/plugin/missedapproach/CompareMissedApproaches.cpp create mode 100644 src/plugin/missedapproach/CompareMissedApproaches.h create mode 100644 src/plugin/missedapproach/ConfigureMissedApproaches.cpp create mode 100644 src/plugin/missedapproach/ConfigureMissedApproaches.h create mode 100644 src/plugin/missedapproach/MissedApproach.cpp create mode 100644 src/plugin/missedapproach/MissedApproach.h create mode 100644 src/plugin/missedapproach/MissedApproachAudioAlert.cpp create mode 100644 src/plugin/missedapproach/MissedApproachAudioAlert.h create mode 100644 src/plugin/missedapproach/MissedApproachButton.cpp create mode 100644 src/plugin/missedapproach/MissedApproachButton.h create mode 100644 src/plugin/missedapproach/MissedApproachCollection.cpp create mode 100644 src/plugin/missedapproach/MissedApproachCollection.h create mode 100644 src/plugin/missedapproach/MissedApproachConfigurationDialog.cpp create mode 100644 src/plugin/missedapproach/MissedApproachConfigurationDialog.h create mode 100644 src/plugin/missedapproach/MissedApproachModule.cpp create mode 100644 src/plugin/missedapproach/MissedApproachModule.h create mode 100644 src/plugin/missedapproach/MissedApproachOptions.cpp create mode 100644 src/plugin/missedapproach/MissedApproachOptions.h create mode 100644 src/plugin/missedapproach/MissedApproachRenderMode.h create mode 100644 src/plugin/missedapproach/MissedApproachRenderOptions.cpp create mode 100644 src/plugin/missedapproach/MissedApproachRenderOptions.h create mode 100644 src/plugin/missedapproach/MissedApproachRenderer.cpp create mode 100644 src/plugin/missedapproach/MissedApproachRenderer.h create mode 100644 src/plugin/missedapproach/MissedApproachUserSettingHandler.cpp create mode 100644 src/plugin/missedapproach/MissedApproachUserSettingHandler.h create mode 100644 src/plugin/missedapproach/NewMissedApproachPushEventHandler.cpp create mode 100644 src/plugin/missedapproach/NewMissedApproachPushEventHandler.h create mode 100644 src/plugin/missedapproach/RemoveExpiredMissedApproaches.cpp create mode 100644 src/plugin/missedapproach/RemoveExpiredMissedApproaches.h create mode 100644 src/plugin/missedapproach/ToggleMissedApproachButton.cpp create mode 100644 src/plugin/missedapproach/ToggleMissedApproachButton.h create mode 100644 src/plugin/missedapproach/TriggerMissedApproach.cpp create mode 100644 src/plugin/missedapproach/TriggerMissedApproach.h create mode 100644 src/plugin/ownership/AirfieldServiceProviderCollection.cpp create mode 100644 src/plugin/ownership/AirfieldServiceProviderCollection.h create mode 100644 src/plugin/ownership/ServiceProvision.cpp create mode 100644 src/plugin/ownership/ServiceProvision.h create mode 100644 src/plugin/ownership/ServiceType.h delete mode 100644 src/plugin/task/TaskRunner.h create mode 100644 src/utils/task/RunAsyncTask.cpp create mode 100644 src/utils/task/RunAsyncTask.h rename src/{plugin => utils}/task/TaskRunner.cpp (85%) create mode 100644 src/utils/task/TaskRunner.h rename src/{plugin => utils}/task/TaskRunnerInterface.h (100%) create mode 100644 test/plugin/headings/HeadingTest.cpp create mode 100644 test/plugin/missedapproach/CompareMissedApproachesTest.cpp create mode 100644 test/plugin/missedapproach/ConfigureMissedApproachesTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachButtonTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachCollectionTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachModuleTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachOptionsTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachRenderOptionsTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachRendererTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachTest.cpp create mode 100644 test/plugin/missedapproach/MissedApproachUserSettingHandlerTest.cpp create mode 100644 test/plugin/missedapproach/NewMissedApproachPushEventHandlerTest.cpp create mode 100644 test/plugin/missedapproach/RemoveExpiredMissedApproachesTest.cpp create mode 100644 test/plugin/missedapproach/ToggleMissedApproachButtonTest.cpp create mode 100644 test/plugin/missedapproach/TriggerMissedApproachTest.cpp create mode 100644 test/plugin/ownership/AirfieldServiceProviderCollectionTest.cpp rename test/{plugin => testingutils}/mock/MockTaskRunnerInterface.h (100%) diff --git a/docs/TAG_FUNCTIONS.md b/docs/TAG_FUNCTIONS.md index ce1862221..d5d14e8b5 100644 --- a/docs/TAG_FUNCTIONS.md +++ b/docs/TAG_FUNCTIONS.md @@ -20,3 +20,4 @@ 9017 - Open Prenote Message Sending Menu 9018 - Trigger Prenote Message Status View 9019 - Acknowledge Prenote Message +9020 - Trigger Missed Approach diff --git a/docs/UserGuide/Features/Features.md b/docs/UserGuide/Features/Features.md index a2a6650ed..b7c003967 100644 --- a/docs/UserGuide/Features/Features.md +++ b/docs/UserGuide/Features/Features.md @@ -24,6 +24,7 @@ dynamically to the plugin. - Enroute releases - Stand assignment - RECAT-EU Wake Categories +- [Electronic Missed Approach Coordination](MissedApproaches.md) - [VATSIM Nattrak Integration](Nattrak.md) - [Electronic Departure Releases](DepartureReleases.md) - [Electronic Prenotes](PrenoteMessages.md) diff --git a/docs/UserGuide/Features/MissedApproaches.md b/docs/UserGuide/Features/MissedApproaches.md new file mode 100644 index 000000000..d82d256b7 --- /dev/null +++ b/docs/UserGuide/Features/MissedApproaches.md @@ -0,0 +1,84 @@ +# Missed Approaches + +The UK Controller Plugin provides functionality to be able to signal to adjacent controllers that an aircraft +has executed a missed approach, without having to through other coordination methods such as TeamSpeak. + +## Who can trigger a missed approach? + +Only controllers that are logged into a recognised, active, Tower (TWR) position may trigger a missed approach. + +## When can a missed approach be triggered? + +A missed approach can only be triggered when the aircraft is within a certain distance from its arrival +airfield. Once a missed approach is triggered, another may not be triggered for the next three minutes. + +## How do I trigger a missed approach? + +There are two methods by which to trigger a missed approach. + +- The "Missed Approach Button" on the screen +- The "Trigger Missed Approach" TAG function + +When either of these methods are used, the plugin will ask for confirmation and, if confirmed, will trigger +the missed approach. + +## The missed approach button + +### How do I trigger the button? + +The button may be toggled on and off using the "Toggle Missed Approach Button" option in the `OP` menu. + +### How does the button work? + +To trigger a missed approach using the button, select an aircraft and then click the button. + +If the button is `greyed out`, a missed approach cannot be triggered at this time. This may be due +to the aircrafts distance from the airfield or the position you are controlling. + +If the button is `green`, a missed approach may be triggered. + +If the button is `red`, then a missed approach is active for the selected aircraft. + +## How do I know if a missed approach has happened? + +There are a number of configurable options for missed approaches in the plugin. These are all configurable +via the "Configure Missed Approaches" option of the `OP` menu. + +## General settings + +These settings are global, and not specific to a particular radar screen. + +### Play missed approach alert sound + +When a missed approach occurs that is relevant to you, selecting this option will cause the plugin to play +a short alarm sound, to alert you to the fact that a missed approach has occurred. + +### Trigger missed approach alert when I initiate + +This option controls whether or not you wish to receive the missed approach alert when it is +you that has initiated the missed approach. + +### Service provisions + +You may only be interested in missed approaches when you are providing a particular type of +service, e.g. Final Approach. You may select which services you wish to be alerted for, + +### Airfields + +Only airfields selected from the list will have their missed approaches alerted to you. + +## ASR-specific settings + +These settings are configurable on a per-ASR basis. + +### Draw circle + +When this option is selected, a red circle will be drawn around the aircraft performing a missed approach. + +### Draw line + +When this option is selected, a red line will be drawn from the centre of the screen to the aircraft performing a missed approach. + +### Draw duration + +This option controls how long any drawings will remain on screen following a missed approach. diff --git a/resource/UKControllerPlugin.rc b/resource/UKControllerPlugin.rc index e37135622..2ce7dcce0 100644 --- a/resource/UKControllerPlugin.rc +++ b/resource/UKControllerPlugin.rc @@ -58,6 +58,8 @@ WAVE_DEP_RLS_REQ WAVE ".\\sound\\departure_release_req WAVE_NEW_PRENOTE WAVE "sound\\new_prenote.wav" +WAVE_MISSED_APPROACH WAVE "sound\\missedapproach.wav" + ///////////////////////////////////////////////////////////////////////////// // @@ -291,6 +293,31 @@ BEGIN LTEXT "Released At (Z)",IDC_DEPARTURE_RELEASE_APPROVE_RELEASED_AT_STATIC,7,64,51,8 END +IDD_MISSED_APPROACH DIALOGEX 0, 0, 257, 311 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Missed Approach Configuration" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,147,289,50,14 + PUSHBUTTON "Cancel",IDCANCEL,201,289,50,14 + LTEXT "Global Settings",MISSED_APPROACH_GLOSAL_STATIc,7,7,132,8 + CONTROL "Play missed approach alert sound",MISSED_APPROACH_PLAY_SOUND, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,23,123,10 + CONTROL "Trigger missed approach alert when I initiate",MISSED_APPROACH_SOUND_USER, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,38,158,10 + CONTROL "",MISSED_APPROACH_SERVICE_LIST,"SysListView32",LVS_LIST | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,67,84,60 + LTEXT "Alert me to missed approaches when I am providing the following services:",MISSED_APPROACH_ALERT_STATIC,7,52,243,15 + LTEXT "ASR Specific Settings",MISSED_APPROACH_ASR_STATIC,7,212,68,8 + CONTROL "Draw circle around missed approach aircraft",MISSED_APPROACH_CIRCLE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,229,156,10 + CONTROL "Draw line to missed approach aircraft",MISSED_APPROACH_LINE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,243,135,10 + LTEXT "Alert me to missed approaches at these airfields:",MISSED_APPROACH_AIRFIELDS,7,133,157,8 + CONTROL "",MISSED_APPROACH_AIRFIELD_LIST,"SysListView32",LVS_LIST | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,146,226,62 + EDITTEXT MISSED_APPROACH_DRAW_DURATION,7,259,55,14,ES_AUTOHSCROLL | ES_NUMBER + LTEXT "Draw duration in seconds (1-9)",MISSED_APPROACH_DRAW_DURATION_TEXT,69,262,100,8 +END + ///////////////////////////////////////////////////////////////////////////// // @@ -397,6 +424,14 @@ BEGIN TOPMARGIN, 7 BOTTOMMARGIN, 130 END + + IDD_MISSED_APPROACH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 250 + TOPMARGIN, 7 + BOTTOMMARGIN, 303 + END END #endif // APSTUDIO_INVOKED @@ -466,6 +501,11 @@ BEGIN 0 END +IDD_MISSED_APPROACH AFX_DIALOG_LAYOUT +BEGIN + 0 +END + ///////////////////////////////////////////////////////////////////////////// // diff --git a/resource/UKControllerPluginResource.vcxitems b/resource/UKControllerPluginResource.vcxitems index 57307e915..6ea61c52b 100644 --- a/resource/UKControllerPluginResource.vcxitems +++ b/resource/UKControllerPluginResource.vcxitems @@ -24,6 +24,7 @@ + diff --git a/resource/resource.h b/resource/resource.h index 5e9a2d5ae..1ce323922 100644 --- a/resource/resource.h +++ b/resource/resource.h @@ -24,6 +24,9 @@ #define WAVE_DEP_RLS_REQ 133 #define IDR_NEW_PRENOTE 135 #define WAVE_NEW_PRENOTE 135 +#define IDR_WAVE2 136 +#define WAVE_MISSED_APPROACH 136 +#define IDD_MISSED_APPROACH 137 #define IDC_CHECK_DEGRADING 1001 #define IDC_CHECK_FADING 1002 #define IDC_CHECK_AA 1003 @@ -38,6 +41,7 @@ #define GS_DIALOG_PRENOTE_CHECK 1013 #define TIMER1_ENABLED_CHECK 1013 #define IDC_MINSTACK_DISPLAY_CHECK 1013 +#define MISSED_APPROACH_PLAY_SOUND 1013 #define TIMER2_ENABLED_CHECK 1014 #define IDC_MFCBUTTON1 1015 #define TIMER3_ENABLED_CHECK 1015 @@ -70,6 +74,8 @@ #define IDC_LIST1 1034 #define IDC_MINSTACK_LIST 1034 #define IDC_NOTIFICATIONS_LIST 1034 +#define MISSED_APPROACH_SOUND_SERVICE_LIST 1034 +#define MISSED_APPROACH_SERVICE_LIST 1034 #define IDC_HOLD_LIST 1036 #define IDC_BUTTON2 1046 #define IDC_ROUTE_COPY 1046 @@ -120,6 +126,7 @@ #define IDC_READ_NOTIFICATION 1087 #define IDC_DEPARTURE_RELEASE_REQUEST_CALLSIGN 1087 #define IDC_DEPARTURE_RELEASE_APPROVE_CALLSIGN 1087 +#define MISSED_APPROACH_DRAW_DURATION 1087 #define IDC_NOTES_STATIC 1088 #define IDC_HOLD_PARAMS_MIN_STATIC 1089 #define IDC_NOTIFICATION_BODY 1089 @@ -163,16 +170,31 @@ #define IDC_CHECK_FILL 1118 #define IDC_CHECK2 1119 #define IDC_TRAIL_ROTATE 1119 +#define MISSED_APPROACH_SOUND_USER 1119 #define IDC_SYSLINK2 1121 #define IDC_SYSLINK3 1122 +#define MISSED_APPROACH_GLOSAL_STATIc 1123 +#define MISSED_APPROACH_SOUND_STATIC 1126 +#define MISSED_APPROACH_ALERT_STATIC 1126 +#define MISSED_APPROACH_ASR_STATIC 1127 +#define MISSED_APPROACH_CIRCLE 1128 +#define MISSED_APPROACH_LINE 1129 +#define MISSED_APPROACH_DRAW_AIRFIELDS 1130 +#define MISSED_APPROACH_AIRFIELDS 1130 +#define MISSED_APPROACH_DRAW_AIRFIELD_LIST 1132 +#define MISSED_APPROACH_AIRFIELD_LIST 1132 +#define MISSED_APPROACH_DRAW_PROVISIONS 1133 +#define IDC_LIST4 1134 +#define MISSED_APPROACH_DRAW_SERVICE_LIST 1134 +#define MISSED_APPROACH_DRAW_DURATION_TEXT 1135 // Next default values for new objects -// +// #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 136 +#define _APS_NEXT_RESOURCE_VALUE 139 #define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1123 +#define _APS_NEXT_CONTROL_VALUE 1136 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/resource/sound/missedapproach.wav b/resource/sound/missedapproach.wav new file mode 100644 index 0000000000000000000000000000000000000000..fda46d597691df864a91464834700bf85ed1edc5 GIT binary patch literal 457128 zcmY(Lb(|B&`^R&dsevV2w?|4y(a z+lm4nTS;44TRB?=TNP3z+rzepY?Vn**dDW0rR`~I&)Z(Gy=JRrdzaL}R?qgP?QPpL zw(7R>{Ij&Jge}SDwOQP?(8{x7xp#q8VC7r6R?rGrnUqbKYq~X&G=tV( zc;8LD=^wo7Hn3)&b)3E&rcc75^VUDsZSH#?9E`Qa@!WV@8Z%JURtAiDno)ffcpYqb zgYr#qye9eUwz{O+;LqD8)#UgMj%$Nebx5_%jMQTtyk&dG_AGN!m0r5RlQ@GX@yvJ< zGvOq6fCq7`4I8};(bo`t&SHK-yjK9cxx-A|p(Trv%;g>9`Invl7hw#wGK(^+6~L57 zz}uIA7kTFEyu&--MFU%Nu%?@>gRMKz8R%*2P2Szs4ov#M)`<6f7pP4?-lAu(@NdCO ztOC7rWoD%`PbkJ)+Rduf`&|PLGLAbK(@l)-a%MoVn%ZVYdL1~i4qRGF+pl2MFRbOo z*3YCLtofvc%+!z8H=J3@3~ppK9|XrvSf`kaf0&WWteNx7^d-v6fDg)aA8hr5T>-}8 zz8Q;r#v{kz#vR(O0vCZZ+~Fv*yMx)@ZSZp!_20}}{=`$i13xBPQ>>xZ0IQGH*Xm() zwc1(jtu|H@tA*9d>O|^n^|A(Xt}ppOYmhaZYlm3FtZ~*T(pO;B6z=jJz50$GFJ&F9 z2K)E%zW=bg?=pV@Xh6P|$XvMq2QyY^#k1l?Qxh3UVL}l@LrO5VS`80DU#qZID^se) z&J{7FTzZ(nnEMSLgs6q-y;24vau3kx+ysMff-5)7h-u`m@Fv$t_xNYn&|Hf*(36XR zHK{zin4x3kS$hxj)aMP})CF5UHq@>Y7&5>%$Tr3{*4EE99K7jdYh`O|Yft-Ul#QXG z??83x8R}FIO8Gu3;9W52WoS-yl5nRS&nn95FW?=5yv;3U^9)qw0Bi6t)cYiJb(GmZ z#Oxn4^L7wQasV!Hz~rJnd%>yS42~^loR=|^dl_x5-mCQTU&cF=(YVd{++hrE1DTA? zJu^C@ap@fSXbUnYxva8y#x<95jL>@9;Egcu9O*c3v6r{o#{Kv5{H;*IRbcWDfM$Jy z^@YKM?pA-R3z*W@;7VI+?ZB9>wDtl+hFGJ?N11DfkdFbzTH~!r;N5s@9z5-5>nHB~ z2UKW3-0P^p`P+<*Xk8u<55{-^(Flv<5cE%+#R2v>nLo{83g^?9#|NP5B@7iPWpFAV z?jqdym)RFq_zYHD=jdOsLKO5G`M;dIYOv%eZ*Z30pC?_SU3lc<4tYG!W~f#4i77nO z4(`S zr-h+QAHu;t25%ZcsYE$-ugAIPqh>W!fE$!B)G^Gs{LA>LKUbmuSHSXr4Sl%EyQ?1; z>9LYT>4 z$s~<+QPyc`)@CB-MF+M1Zt=`p2K#Pubd#rCHhgdo&;A|EUB+C0ZOsIC#(@Q*AETf> zLk)fD4{aIB@kHp!7|whNwoCy>W>TL)>ttHLfuns-E*$&?in*1waEuq8M>RvEQs=Eag;%;Ai$Vv-32%kqDrV+Ul;9~Ni#)|~Re0*N zaGKYc|8`(US18PI+n3Oo3AXXJ$+k(huWVCoGi}p>Nzk5QwvnV^P@)mGp_Bt{gQ*Rt z)*U|9l6$-l2YU;s1qM9_NYZ(bzKZTf7@OO?t+>cJ@Z>UBlg=B9dP)Y$2Fvfziwoe@ zX=u?QXvz_A=MYKj;x9(}0vHvbZvn1WFK;mFqPTY{1K?bAok-r(uK+nmBdygTDN%IX z4zoGro7EOZMi~w^6fI9=gr9r@8t{Xx7O!(7cIPe9x;+kMb^UuP=_R2HWJBQvlfI6 z8H`j8cMNgopt*B^)=WnHE-g36uQMap$VG=VBjV~|K+le`1+607RdP)^#;*8(T*}4M zBq2TrmwE$9r6F{qsqIr{zde+t7uYZsY?*1BVw(+i42Dy6hR%EfrDHnG-MF?YXNE8!eFp#x{>u|`!CLUPp+9C7{P4bAcL#&s0&x`@)mmbdH#O^yuQdgoML9TfH6y~Y1S<8Wx6#IO4Aw1=5wpH z)zYMvR#WR!peZ=i56Uu)t7aL_G~CdlVPMoCFlrFzd(tw5YsOh~fcaqLkKoWs`nLzX zImxpn<7ftNQd1u@=tC~=7b4|DM}$S>#lk0tGxelaOxuOAC=8Sn~oS7 zp!vMcii@BF+PPmbMo>CoB3fY~ZHb1exmg=tR!C9CvNU*GiJq1>oKpH!C2Ey*%LGLqq>TLr+1h`|cc#Z9+(3NzaFPnzs-~ct*8!XOBhAPX*>7I`>hfhG;#G776f_;Nj3#gAw(h+Jh$~FYb zGR3w4SPd+-t+ah_TV(s%_Kj^8_%jY{8UmK|2Z#EAD?La9Ov_mQ`=#MtosoaqK$Sj% zdc9|!P=kKH!qclUi)Bg0=vgr^D3!b@bNMi1mBPFg!Z~v2VK%*xrg{~cA0!?U9AxPvo3U$mh$UeoN8q7LXQ0GnUY@5}LSy zv+Kb#l`Dbo%+ZG4Opd3jP>S4X0$9gRTSFbCKWRvZ9J-RJ8OU$c zG& z2z{##mcK*~o}(`>vl40>O;&cE=UFvR(Ow2f1w?C{Q5QkH4WkiZ{rDQi->C%#3GrTp#86uYyBwphZhcX>IVP7xdx_aA6koq$oP zMMI$`SshEjMSRC;h_FD*hqMkB)?E!mHIB zZ-ELO1`{RY%T5xek6z}gIPXyr`cek0d5E59B|J=Bw5Js9Q46?wC%f$HjB7(cbf&q{ zY~O{ttFOhlTB9h-&;RsB$t5MYM_E8P6$^+nNpF=Lb(LInUAn%ke8R01-bhb z5^t}}TWLMYE?fsn*Mw*H0Q$jw`XS#81ioPYCqYTRwJnCu{0er=<;+Ormm%131|uE! zBXtBjx`HJGpjW++id#WnJ~4Jc&0~~`q=3!hK@Qe%KI0_oc7*xKLsJgXB8!$Z2HCp9 z^d^%edFP6elx0o0POoklnjYX9=|SmSFC8N)--;$lN0aPV!2R5eoamYOndpbsP)ReQ z9^N7uZs;;HRyx=J3s;vEn#B`k|B*fCU-GL+9)H14wnHD*!E06^zy3g)1%;V_>^p`u z0l8%gGRq`#NjBetNApOs>wRP7oOw{DuaSV~!O^~k{`>-Nt%U0Qf!y>bay(ljwu6DwbZvFyBS!MLB~}(x7BHc@zwL%(SI3&v9UjEGyELZn4%R z5sOYs!NIw>ifCP`(HOQ@!#YJ&`)K#Ib_!7G3N3d{|(R^ zS-^4_1C4{ESU=@`Qa-pO3uE+2HYk>Ctf{;~A}Q6(g1CPQHHS_7FB2>g)`^e=yLLc2qekO20I2K4-Z5Z9%-cE(a6Qa(6t930go`4vKYGZ1N>{Y!Ko=o z#b1J7BPpjM2hHbxE1*N`!Nu+L?I1lm&zt*M0lDxj*`pGeBlRvsA1%Yj;&}HW^gvuk za!E1pr;@>#XThAOp$FACR}9*0fgu6#K$fvf;6OU}5e1b7a-UI`{W!n~-!nAk0+=MN z`wTdAgCyFM&4}bMelDK)0P`j(q6kOwGRQtH-uWyv;SHcE(8}mYos3pA5>7PTHUkPX z2l-_hxYG|92BjGQHuV9k1nt1Hwv=tavo7QF%f|@?bGs>AclT4q>I=D@* zP9ZNy-#ZIjq6eap(l@o{FLHF6v&Yb3{$@q)WZtBo?q?2VSGml2;aLIJR44tAE-KuR zu!&QxRcczkacD_*f9~@m_RP-GXec-4Az>V*lqgouVI`U z4;7jVzgh^d+63?02|YVxD5+?uAT40)JvwXiAXW53Y(6>z1lIXZ-%NcqooVvs{XP|iU&&ob8|C?;FvSaBw zQF+mS*}r0WD|tlI7{vz|&2sS6>PU>WkzGH6el&rSNZJwo7yz~m0y;oX+JGUl>gfCj z20PvdI~wx8M*LGgi6&r7eX!*XLp!Aj$Yx__UCUxF`XWEF{6qKXmu&jNg2UuT4IZ82 zU9ZCxPBE5$(T@|HJIZ=I!pzCvAbV_R+M;zWgQklJ#&td}<@BVh=O*7xe9< zS?l60S^?tM;=#G}Puiu{gmTfMI7U=_R@5N679L<8i?beF;7$rUU<|T+7QMJ`dMbHM zHbT*O`5k2K6PJ(LJafPktryLS=&j~Q=L3dj%fc={MiEw@ybDT_KOX{XA3?g5rusS> zM?H9t{FS}&EA%s#nqgqg5a^5Km~POc_J*>2O06}LSW9S)^spA>ZGlgyiG$UILp{ZP zs{#-5j0kJzIS35^tPfil3Zlzm4qU$p(`$fDF>OoXavO}Mp(AM8(bqD zK=!RD-atO91Aavw`5vw_$JqNuf+bzSk#1m({FXg{4wSvI7WV`Cf+r&k^yR9KP@`U0{B-3I zux1L9(R66me54@x*t8mCqml$7zq_p4TGz6)$*Q7tAR0 zb4GoCvcE@tQ+is8(QBm9y>2W5^1#7#<0t94{8#%JgFVdp32@*X{Q4HFE-E)= zGdtOgi=uX}LDeoWVkb$WYX|wyF5XRA@*3oi_0X9GM$R1uCiDdpx-kE;2ft_4wcfVs zS#MaitQV}8tyioUX?fjhW;Mos+=6R5Sf5xQar7B&Ev$z4Je%Sx|CH1P+j2i}ZaiE| zvX3l?E0C18@VrBCV#!tV@E)Xpe=`Qsx74p=%&u&MXL)<^amnnG5T#8>=M;^R)^M2q zN5}6V<9(DP`DTt#KSKZZaZcV0#c~KsWQE_sh^{g{-puIAJ|OBJjVm~5o-J$sW#9t$ z7Y);VYTdfwCGy)mNLdv=Dg8q{^Vm#gfU!Df4Kuey+PEZ5=tQI^@ zI`+%t((@mLddiNgh^GwJj5x?~o_d7Q*v42WZbEi5Va*ouKRA*XL2c_;g-aMmSwvyl{Kd#zWsIZ#Uah!`yu%f!lsp;7 z`JepnvYs5~xso!r8*N!umNh(m74Qqsp9)8jJo_cN?84olEG?{$tq-i%tmmx9tw*f# zRynJbRnw|rH3C{2jA;ptX$6LSX1xi#MS73xKeXy|zBM#zC^$0?oSAN(AseAqi{f+i zHm4Y)Xncumh8tNSvN9?*OqN1%^y9396JY*zRzM~cKvY(?_Uq=q>i-|~V-Nj0Lj5@Z z-NjwD@|>;Q@hDH)1*~FKE(Bv0Qy@#h22*a~%AH`&F)&K<&N=QWiAUqC6?hkH6+M^D zITkus(&*;TfC;aGHSa)i#IKY@b3Oornt(%!B~$#6tRb>D$^P*+#~*Ordw{Hn)xbM( zTTvSM*rlDwZ-128+{Fm2H{+y;i2bx3H=`o|w${LQ-g*Z~a>Z7zTnKOYhI}67LNG}Y zH?o`j!4;yb|A33q7~}_3Ox+&tCY#84){&;OAEb0k}pF(chQ1 zXHr|PYXPRTga*lrC9La-WHcVBcouzHL|>K}`leVCNvV>73!xj?thWDgOv$f$U-d#U zWa3-$4+!LEj7FgqHQJ)Ck<9oPebQJTpkJDO$%?8SX8z9^x+N|w$^RclL6ldLvpAC0 zmh=LRS_V&&1SF1`2$s0uh|)hrVZ2~WdBdTim?8{$AHLKQDW)S5OGlssa*ZUH_TbLP zh8rn9s}6Y546KqLp&jzG?1r_#A=&F?n@WTGDP}}=$otHo{A6c%Z~5%x%hoCqby4J; zXy^(0sy=F+tfhT1>wY#^GmrEw^kzQCvbFB#+6&x8u~k|Fn){<9QIVsZ`IEZ|7fu5j z5k2=NxFY@(g7<3%E^@ubR&+q4z7sV8mY;dJAnV2Zj~h(0A7&KlOs zZr<@Ed{nWnl5m7I_ra9_z4_P77z%kghafR4PNJK!^NqqcJ|5|LBzjkW zu%`p}`V<=Uwz2G2LE0`4W(rSbK~dDDWOdm`gwyg?>i9lyAA?0ldU`xEpmb+>I%R!Q zRL(hic8w#&S%@mfa*cYTm^RtQV>p%uqDToz|0!6VA3zdT%%_s#3}h>R$XKIguad^< z3!A>-&j0R@}oFc-A1WKw9?*a7l4|vfN1D{uWB| zvyqCv!TLANv`s)4pTp6woY`dP%Q~=g4fwsERelOgxyL-^qgzWyksYrpZ~P!5ps|n~ zEU%SidFW?J-nx|G3{}CFYFsJ5bTy-or_f7Td8B2#!nx zcjn?XmFWK#B}Gub12)!%GS!7HJ%hxf`IR0h ztuh3@D1Jn7iP8}H5hWKY9pmhEdY_LjBwMNjeN%Kq zvE6RQw6vk1B{-)wC_h74##@l%4iDr9YyU^L3n ziwg9o6zfS5dQqQVWk#qn@U>k?PSU5PB}q=2Ww?bRX%r(P55NLud<8SRfvbKqSFeOqtpt|ypLOuk zwWKZJknE50d}M)VqI$A9$`)RUH(Q#SdEG^kl#%7KvdyLR?y3g{A7D67kN{o&CQHtP`A3--VjC-i0rINIzfsEIkUU57bZR$+ zN>Pf3=>Ln1xOP{_1N=DSs$=m#`RKI5N+3&lc@OD?Dex@maS8a*71^D|y1oq0IR!54 zf*!0eI+DWy0Bt% za=_MP@K?N0epbQxR;^Sim1lB%j@_8G+EYBNs8LFP@DOEm9tMV79|-= z#jvJuS5c2>RFL{68-EPcL1Qd^RW>Sl1GHC1JW9TA$*{V%1aBlSvG~eEjF)toYK*vI z1D*qa#SI!_<<%^AWwnnov7Hm34KqlJgqR0KP~6oNLnnHI7afe9P8Q%!U`StDdXa`8 zr^x%%jkQqUP{AkBuM{gI&$X~Zyi@l0+ejyp2jpwivFx$Y_@AifL-L9^ko4bEzzOKG zVm)QG&SowZ_v)dSiQF@d6hmDRv(hGHkBFll_3SGmU$TfsPc$hS$1mT%Y*31Ml+R99 zamjUxQI7g#q;3C&#JP*vUT0(u#aYSzKN9XT7^$@fa%+Eb+2nd4!S+LT9c*-DMR6)V zc$m?^2ctRn0eW-Am&hbkr9%Nrk~_dNfV-iiX+mRk#|s9 zW~}LX5oY!QX0|l5ti2xcEyUQg%G4j(Vx%9*zN;8&MYBnJl+R3770F`4->9anv6XdC z_Fj2+@9+p>e7ui1)R5qLepiatmkJpuWS zB+E*UsRMq?fAk3$ClA4gU`ieSE$iJ&tOMEY9%EG~a!4~L@3o>_^v?S>3?}% z7Ar_AN&AX~RW@VijmAz(CbD9ocWVc{sX=#dxmn^(xtP@Ca-7+X=j=>=~2>~3MnPkMJcnW zbZIEUBZkh^X6`=%2bvh{XbkSOgMuj1L2(hip(Wy@jk&5GbV`&;d$j5sIi$&dHeyLz z&ypn13M-{e3sbdTtV&G<-byu>@7=gc{SH@_S0c0K)4yufy& zFUxwZeFGX{@scdYBafQ&Dp@FGSJLW?15ZT5qz{O;h!=?7X$CZY0@*cWPnC^$C%JU{ zeH`y+6lK3y3a9yzSysHJJd9nL|Ie|&v_yk$f;Qa>9l9IXB)iH$(idQgWS+iYPkXLz z1wM5|mu|z}mOjuM`KG4B=VWbDiSlYoTDZeN_DP;vINPBet>b5zdgq*eVuN{w2kj zcnn3=c!=7_t0a6AO%*4M?sJk2^@gDn zLG9rw(lM$4Ps3B>2WSMbrWq8+*o;7|1o=F~TG zZ+%)611#F}0&@7n+*@(KY4od*rz<8!`}edjWFH`Zf%e9UH_00)Yo2yA$tJOftE7=A z%2v9|uSk^AV>dF|@{;I!#VKUcU-=T{t&h?y?H@SK2t;>#DH=vJTk=qlx>5!p+7yj7 zIuF(!GCWG27OleV$h5ndPrZv)rsC?pWv<5oBf*>AP?qMv$HrUK9P8buaH+b`pU#bQ&Czd-RD(fEa%9BbcTG)i21 zo)lv(j1mo#FJEdPV3GVR+Vv=}$9Zt7cfQppt@6& z%sh~M68JX@Z`m^NMtiDxwJv14m6WTU3Q?I@K6vfBki-@b zZHmDjDx2+X`hN(%v&G=PMqB^W$`Y4Te26%oZ0qvMNPgVG>d>xv(fL!f%Z4s*t!U3J z{;Oz7MU+IqAZaYJSjbW%>0CO7>|M_x9ZS}24&`VGz3GT$rX6J~;4?#Mn!=Sn0$1ul zo9a+&M60Ca#vHxL|3zmWMy4srT6hrd8wbvW7!k?B;t7&?k5Jbh+$i6^#QVvf|3AG{ zvZ-wT@)EA5KeDtc7FX5}MbIctPy6B&hb>#Cq+(fuwcAB&Mm9i6ys`?4;!Dqzm0UZT zrPu3w0g6eN%%^$LtQ`Xr<$aNdLh(8BQ^_N~4wwa>Qslx2s7+5WpdA>|4xdp|=u0E8 z<~=-=@?Q_(3i+Y5Crk zJGpj&{mZC{Z{OpIqG8&ttKEir1IZJLNs}#1-wV+GM$s63U86jpsAbuuW&hB=4n;U9 z>aH_1U>e zoV2ME25aTnl80*s`6On41e&;Hoxb430OXcIVAVJ%k}QDIueBp#HvgH5^!pVcKYVlt z)C^?oMNnGpVNuNC33{sUNJvwWzAcSU(u4Mr$i|{rPWd^c1$%jW?I*5aEGVy{!PG<= z)t-_USgo?C$givjO#Q^MWb^QHA4L{NeIk;CWFu4DuxvJp;Zj7PzT6_}m0n_p zrTyKC^3?YMw3}`Uo&;HT#!~B#1xMeQ_>{XU-v14rQ3Woj82DuRB^$JQ6pjCiTDjzl zm32V-XEjd3^+M=_xU6tr^Q>Ki+P5qFL^7ZqInRTa+F2~mxyD-78O1&dN93WXaPW{%^sI z=>}7#Qkw}zeP{TNzK5{}>{tqKQ#_1f#}y;I2B__UjerUA3n| zyV0Hl6RPmGiapX-Arup*x{j*TmzSXH^%>od%u4?ddGb>@#JilS!Ckbs=|RS%BxOnF zzZ5MMDP=R%9t7W8VFbPEtxmL5$!3f0ad95rbyG4z2!s0 zgGNGCz5-t+VcGi{Sc-2zJ62>N-el?<@E5GGZNzrGj&cF}TD14+2XJaJw&O*{g8UPB zwiqm$1N9keEPn4;ehP5(Qm70 zuc>xn#L**t)8%30vBw#^r;U9`veeV`P2Vq4OulmMZcpydtegY*yWjBWybm(5JFg2p%+&!jIUMJ>F-v&W3=sxjBDurhEOVUhNs zNbW4j3XWQZ#rGaGd{3NDzKF`a(^KYcq%Uc=toF=F7Ll)^3v=5Od3Pd|W*YDVwB;N8 zM|1Hbd=D(3x=#2JDhoExeUC|vZ zvZ>0ZvkvZ}n4KlaS&Cqo1$+%xQrxHf+$(@}P#MWBYq@e6S1y3c%!3a7j7GSDHra=c z7)eSt14UwFfIH&1Ze~&Y;`H4a?Yh;M6QVwCMLEcuB%N1rO)r8!vYE+7@&Onl8t@vk ztJS9-ilQiXK>SGSA!36%L?0Cb~`NrM`nW`6YyORH)$OC z3~*%;SIdtz510g<>5u-^0g%nGE-OSDm%eAF-DnRmQ~K_QJOk1F*y6j|hat`*j-l_U z=?hm;3#GnbAel&dh2p>CI4k-p`Al?6<0%TRolWv0xS$g9$o!8>32(H!w7dafPBk+N z8XK)L;jMf$!WsEvat&`(Y_(#u0?0Zypb@8x&Y|y)%m)+ZgDr|~(08$9yPJxfBPm8- z$`Tb?0sZ;aP$R{O$?q-M=4H5x?a7y2hl9Zt@OX&+!uR}}4g-dgVJoOp}#dbIuC&lh6`dd4}6-%L5E{(5N zi=rzOA+1>AILfG|l#Q08h$_WW7dQB$5z}4=@pj>nXqxDuVx$@|zpdab+7}u9s?j)n zQnT<|ehpRm7Ai6oj2O>ZMM3B*Sv`!5BT1(pttwUP2-lNrtp2>rD$rh0(L_bD>kCVY zt5@u}G)%>o7LaRwYjxivmlZ)#vC^5Mad*+^IYr4T9!nCm=+Hg>BkQ+xBH5#)jmk!* zs4~%heHTb_idK_6DKXR~DM<&@H!KvVBL9S9E@Tsx^-<52{aW6Q-8_FQR7RG$UzqV} z;K>L;`wn_yk@*b$x`9=Pn9Wbnx)sma3)y%WUI;~JO`$dxZE84rR39|<4y4W;OM0FR zcFlnzE$8k#=#k>(uJIPKJ(x329 z`^2MO8byuEf+Op)_5@v`XR`lg7im$LV1H@tQnM0eVpH`0Nuw)$S$9{MGV zzpzG@AL(J@UebRw|M~Ppz60S;bjL@b>8sADO}dk2Lb`)&9MP{j>0H!LCVPf_G1~p3 z@zWRCWJgj2w|oqW+mk-EP9I1n6B6pd`bUmA^FesWQv zFHPHIO6|E6ztT73B>AZS;!}zxl2=;$2$R7GMQO|5s;@H14o8bpK@3U#hXwPCAWebSIhkRdipK zxSnKD$vV>8UO@5?pZo}D1zyNo+23eMqo6L*rL?dAXXs7zn-Qa+IwJw?nA5kjdK)@3 z-2A66lCXT}iu7#6{iKIWUR4Z$xRbu(7Ucz!ARGpB^O;r6Z}i)Xd4>mxcIv$PE`Qz? zgHiI8$PVsT1*~ahwK5i+(HK zg}*Uk(KV&74BTN|i!aKSBJ0vlFjtc88l)BNtrcF30xt%GBl3CcdsxC3?Tk}AMO44i zmuO}itz6$knu9F#EBK-v--_AZhQzE0U3uNbd$lK7mJ(5p==V~ho@UKj88Ag&n&`KM zq#sse=4wL8UxiO8D&-B1pQnH27zyogk-aAjrYN!?k1^4@vGW9RC+!f_-lu%1iv0fq zeN{;o8`%NmQ;@|`yMiS1w*=k9ojXg?~U{&??W!%cx(Rs33|7*7Wu+o1#6xKKNb5@lwM2jQ(Ura zEZRFO-Xy!Xjx}#u8?s}ZgKJ3RkQaJ4W2-$ZirZCuuWT*C8%1#H`xqKs?Y9-Z(H&*q z6W7wHDN0@|Sdxxn^kg5Cl}!4sxR_R#_LIpzCQpp`r#PH^F^V3z21nch#Z!FNa&#qq z>2nHFiuU6U09$&&t)l*d-bgnSp(Jy_ldr*#1xAk1x6`!4Ti*hmY~-Q2NY9J7-x~U* zZ^@nnGjGwO9AjS-o=YFp*Hg9kK8iWf=%EzGuo$Tv^QJvU+AZ`tD^uTakZdDq^n}e`tR}IU~&|23X&# zdf4cykC`_RZP1?V&R~S1eTS0kduocq8;3ljh~Swf0%r!=)+jKfi=ip}l-LMvR9L0`#JOpJWt*}SKGar)|9)Jm;Ay9LZ*HZ!QNziQ`# z_?o;i7r~@E94o3oQE{?!Xbmbdt|)jR%^$=ZD+d!xP?u>cIwH$2CI4HK~0=zFm< zz^6H;{yli|Em$-QjF}4PtB?BX(L8YP7qszBW+j}3BZ{I)`WD_wHr1CBL|wG+D}FehbNbeZzRmF{+(bghH3|KE9JSHlj=sY;3A~!Z@eE_{n{M>)ZeUOwI9@Yw@k8tj zS`UgP)%S5^S1HN+C~8+4a#{MM8J4w98kF8&oKds7p!j1IZkp;%$r!OF_LdM?C z-TtN*ild1}!bnDwY%4qw50&OCnj$N$#!I$4aa*-W6O=tid`a?!zCm3doKeh*xK&hJ z*4J)|^FOT|Mde5%5YHCBla)eRXBwlTt5lQiRoJFo4boAiu_<~~_OT}nFVqam@~3D* z*}tTJcY-&GFHHak6yvF{8~uo_X0>gZZ5#2R+ikmSyMZNW;`6|tnaDifQYu1tw!s%2 z&!_ec^l2JcGu-ej?bFp4RJ3PP);R5-(|!caxa2*pi0HQoWUq`;jp*7^e7IJDpcw#Dhh7qWg@WV_YTc>sytw6xZM_)bn?FBds0j ztnzy(`cjq>MQTUm5T&n1BPX?QUj9MFFv+{I1wY4+P>pX5JrM_*1(i`+2%aoKL*EE) z$P=QkU~M36C;uG|ryYQs4bMFb?NqFt_5v%uP|--zzh$Y>m%YV<>hPwr#k@2SbHFb!mB@=YEbh-4(p~iU{(B=1W*> z$k$@C`Q5e$+we~FO*P@o_uz-(iN(E!kw(GG^gSDWM{PVfBcApJ_MC1|FGX0$Zlf=$ zRpecz1E^1m+K_f2+7R6lA<7`RBkChiM5pXF@QJb=^F3&qf?SHa`MI#aP{WAGKqzTKmc#)Ys z38#{#tbGXj&Xsl<%!j7ThVRJdGzW^Z63kEp;x_1!a7B0`ekNE2#t8KFqb)`j7Uo>} zkLTuDq6LZ-*2=2NYzlXzB|S-AntsXJSDrKao|m*4eP2X7TI)b#M9!Yzec3l|rzDEzi?Vd2ceuL~y>PAeQ=IJs~vX-eVL!Y>P_lO`9A{U3Ruzlg@K*z%Vh5ZT#az^bF3MX-oIfXwI&M(|ixU29j-btMG9Ao@m%=(V^aOtPh#JWeJl2z*t)Ur#=ZyC0p6j#cI^8=1L|+b)}yU9wc5bD zT>WDJDFX&a!y`Wn`r-IJp z9ShnQbO72Gv@ZC(pk+Y|>g`SK!-B>Ibqn4rc&Fg)f_DpQ6}(zdqu_;tX9}tVl?$pC zlrAV=;3+6d@)X1s#24Jp&(6P_@6W%Se=Yy-{8Rad^Z(4>oxhK?BY#`|uKX?ezvXYv z-yBd{TVHLxy!P5z4fRUEI$-vn$0)^ctScW6-1uwZTBn!;a^37(66 zHZ}}YiK`m-P+a-A3UQU<9;g0v+;ed?<7&jcLV7Lk)wp-!-iZ4ku5Mh@xKHC+#kGj* z0JM&4L%nreA2#dRU|i0f(c9&tnC`o|548%ExjwqBII<3`1e zjT=vz95*FyDrrvKcX4w`i{pNdTN1Z4ZcW_kxOH(GfemrnfJ4CPxPRiV#$AfL8|RA) z#%0CD#K*)Z#>dAe$GhS^@!t5N@g?Ka;!DK4$vyF@w4}v95dUC&>G)EVrQ<7-%EVWo zr4&${vk%4>ZQlI6>3K8rCg)Ad8wZTe8&5ula!lSx^0AboC`aTC&l{3AAg^EEz`P!L zUGuu-^~~!Cw9o61*E+9FUOQ4}phMp0d96r|^P1#+1k}%~msdOQy}UQ`Y5_0jy#&0F z_jKNqdDZi(9XF0W`_TAn@6otFUQ<`(4U=0e$y)&(3&aFs zTEw@AFP~5$;kksTf$9l06JDYIOu}mkZza4-s*~_RLZgHR37;f1NobbPG@%X94(OK9 zmDC03X6juMx+nBY=mYdi7?v=EG(2HU!WRi+fiDv#CQJsVB}`A4kuW=94)A@#4<;>1 z_$A?|gkKZZ0c#W1B>eFo>`B<0u#a>&;Yh*>;7r2#gewVG6K(?O3I2p&LPkP3At%8~ z$WMq(OiZ*V#w9uv-HFMGu0(HQ8p%UmJh5bAiNpsIOC>&(Ska_M6Q4+YG_e|abpy{N zKAre{;tPo{CBB&WZ^G?_7V*vFb8UIH>IF{}Ov#;^`)$rQIdgJm=X?*$0;T~oa%SdC z%$bxkE@w0_K4(bIfSiHky>t5H^vvm&)0xyIr$bJ=oKB>6Ic;)U=d{RamGfy%lbnxp z8s{_u>gUwUsY`kXcq`}4oY!(*_~QqGIOQ^4cEV>yrHRLrTIQz55pPH9px14VO+ zmZqf@Wt+(7krt8WmK`l`#xI!H06HN`ltOMZ~*vUe#o>OvLCh|0S?*^*^kWy*`>mq^#h zFOsXgYQIj)75fd6+EnU_o3!iPzqI^o;3oNX`z`w&yWgHpANSdJChkmJ68CG|?!vu= zqjN{*YTV-^wn)*)0}*c|HR6mUk$WQUh>Kd0ND4IvtuE>=YKf-R8A&iTSH#Z0?X)GD zmS~<3iH*dWGqy+!XW|fRW6V>mY_*`AWfx=@Qn#}60F?Xeoa|g$3MnJh)EdbSn=+hz z-<(w$&d$ybWd}(ijDUk42mILq+OufSpcde)kG6Z+w*X)Eo$RaGm$GkC zznXnM`*QX_*(c5041}gy`7s3##a4(-ODvY?xBKkxIo@-;?0A**qT@Bk+Z?^l(YuZh z9Pg0Vb<`s@bTr_&p`)>*k>g`W6UQginmL*}J|(qwv~;v^v~sj_v~jd`bZ~T{*4feB z(Un>cM{l5qqpzbs(9ba#7(^NYjC2fleBl`D7)L(dG0E{IwW+`)$7IKB$4tjG(mZOj z$mcu0b$sLa&an_!L|W|l(eW#=1Xu>F`VZDS);ZQV);l(twArzl+BU}y#~+R@j$ObW z$6nGd$05gGj)RUr9s3;z9G4uIdFoe=W{E8mZ^!%_GbVprzUX$t?1tHC*+sKUW9;n4ORvn;lQG!iA{edE^D*NH|O_JAB_jMmQ^+ z5%z`eg#)D9;hUse;eW$d!`Do@6uuBXPdXDm7d{(48U8!`Pxw^$Solczs7Z&ye}?ym z4~6%J_n5RTyfeHjyfwUuv@N_jygvLJ_4VO3;dSBF;g!JZ@DgBIcxm{@@Xx^F@Iule z@*l$UNk4?Y3x5-y6P_3TIy^f(BRnfSJv=Qu75FMVIXosjp5w2=!^5M()v~K*`*YHB z9<{1lx8twHk7lNZI|n-_I;S{iJEuG6IKLsyaDMMx;QWsI0_Q^KBH%~oV&~7!Uz|&w zOGwL|t4J%IYn*GTt#fX4Zg8%5ZU%lQZFO#U?r?5%?sD!naKO3WxsPI9pwPfjzfg}*?@+H$&rp|8r%(s-4xu)omZVmp=AovcW}(KRPk@g@AA}kL z^+NBG>X6q8y#c%#dL{Hy=tc4uL(hh)hn@>P6M8&UmGl^SrO?Bnisa=(WkU~!N`^{> zN{5Pt(n7^UB|@noH;@`i3OPavCdGyd*ho@vKkt6zelCP5eE;_STlXIhRSDe$zq{vk z&wDVoWb97+cKf2F#YwNYUUgM>JwbZf^|Gsm>qS>hS1s2ot~Xroy56By$Mu2heOGN) z1J}o{#;%5>&s@z(&B)uh+LAtZbppD%x{`Xh2DtjV`j8KI4R#H2jc|>1jdG1~jdx9S zed+qjHN`c}HQhDGHOn>EHP7|E>pRzc*AK2AT|c{iasA|4=33!e?OI7%PB^`48Mf%%y!oYc#;H>M4>xS#5>niyjSGw!A%jXJE`dkrL*cEhzT$ZZ< zD0C$jNh}guB(8|JNNSO!BJLs{(!)g_De|Xlr_1gv;(QGa@}Io(d7Z-@!jIj5{QmCX z?%=NAPGEQNkKpgY4Z%&o`rxwQN?=9sXW%E`dtg3kMsRNMo8YYASHbDQspMY>BJA>;$v{S_eN5HVZZlHUT~a8UnQq)D6B7d^Pxb z@U38t;PXJu;4{G|f{zEE3O*LB5_~9FEm$E~E?AcIVDN!p(O~hQH<%hs3c5*&!PuZZ z7!%CT%E57*mvuiYNbP=BMwX9sFY8X$zgai4u4Y}%x|DS->vY!NS!c4&XZ@9RBr+jC&$5#XZeE%RSva*ZqzATlYNoeD^~4V)CEe%iSy8zqnVpH@JUy zuW_$;|KZ;4-s;{)e$c&_bkzN~`-uCb`ycl?(na?b_jS@O_g(ir_Z@eJJJTI@-*@M@ zbKM2*LU%%PY;s((Gue~uNG_6GG`U1_8mUxrIiON<#pJ5MW583%PbWX0{9JO)6Q zO@1Z$?c{ezZzb1Dt_ysS{Bd%_B0P&6GPS zH&U*V-ve%w0>HhLP)a1lpW;i&p)5$TOx>C4O#O#DR&rNxrzZpwQu34XcLx6qj?EaE z;SXd5?gj1yZUnAVrUxzsE(fj!t^_WUE(Oj7&IB$5&IV2gP6hr691r{*I0+mH9HsVG z;1K1Jz@Na8!2ZCVz^=evlWz}f4{Qx=3;Yq-68J5!nY1ymjTK@d>fcUZ65ioz+BR_z|_F(!1Tbxz?Xq3)F%YKpf)iu zE-*4Mn)*m!L|{l@IOQ-?4hakj3?2LCa2R z2U8EzvM2Sg)Z?j#%+WDw$2mThdNTEN>Z#O!OnD*oa_X7XbE#KTuccl{y^{Jba4q#_ z>MaAe$#19LO}+D9ekb)_s*mHlbtE&)_&f>GR(;$M^hqI1c!4oARFj7VSQcRNf&M-0|P^-!SD3|268j z$glbTrR}Q!I&h8hs{e{fmnkpvu2;#=11it>&(V6(lozR=@n7JI3)C)B3NCQv1(WNl z)6{g`Isf1OqvlB}SNqrauLiCL&fPzIKe5nWIKwf+aV_~ua#7ENo;N+OdY<>ZZS{={()asDl_tXR4_k2K7-T1eY5EAnani5yR(K8d7!B1te2m|$R{|4aW^|2R`0 z?;lP5OTS9h#*izG_K)&^Ve%1_qx{4CBRLx8A4)lbe1w0ve=zwd(g@l{Qq!4H|Fw_d z+Cjij{yoA!fZAZvC_wM?XhyY+R^c|`6SiZvi%FM~R;8{=UFKQl8Ri-3`PMVn^Nr_w z&os|hl;3&2_RJ&AHmyH!Jd^q&&wS4u+JE5cd9;1YmC^JA|5VEYbM6PvLazLdv=CTe zV3B7r|6fR2osR+29xUuo*g zJu5uxJZm|xa-C-lt*ba*=UHv8((wvlHD^}ypEaKK=9<-9^Sfu0x%=i#EQ!U~9oM_n*06CQvr;kpCh7YyMaLZ~EWzKkt9mU(^2z@G|um$ZJr(1U&D5 z(c~{si^2<>*OBt)Ozm0z{T%68|1`K135|D&`& z?yqXfNBvd&)j0Pkc@^5KnX(FXL1lj>QdQoyy#Ha&RP>jjtin+_+A8?V(o%`MEJqJ> zT#l=CwgShM$SY9Sk*=>uDh-t7ewF9)mnO3){Cwq%|)3}q9((5hmEy?rqJypDqd$Z|rv*gc{ zPbC~rSf9Hw_gLm%nce-}{a1b0eK&m9eCK?Ze7Aj9eW!gF$uIcM`z}*E?>pl=>$}3a z6TW{a&-+ese4cd9%oBBTA9s|2+-Rs-w+eOPh z&g|oweYEI5x^gE;|J~}_X0F#&n|!~St91W&{crpCX8xJ^LEig$s}ffwzRvp6$baB% z;CcfIw!A9@>mKlV1{Oham)aNNM#$lHWkBkC$Y^nOI%`2QV$YR-H@ z`={Qf=Im$Q=G2>T)ZE()XliOL%~1=U*23G8yd`C8ZyQoe@8{mOq*mnZX!)GHJxNFH zy&Vjw?Bwlij=OrhaMan`mDI)Cjl2`}F68Y2wRhy7D!Y&ibXAZ4uJ20O1L*7R=N;%7 z;BmW)xNF6|8CNz^I?^QL(~J(j4!(}0IlixbLwtjL!+7n z!n6HKCu zSNu-7f!5!6hH9I=+i3fZ^am|}7}#p^9o`+LZKrp)cRS~Hc=vjDa`i5bcL96;EBAW; z#eKzm z9-yc%jiXdw3Mq}`CV9w{DU*Fkz9Qtw9J_o@l1e+ZBAl_Cv#P7rWvQ$ zv{ax7?XEN@EneD_IPw4(P-sb|mP+a7$V)9+dd(F&S1hflsTVV~q9!kv_F!5Gt}d2V zI;|w-18HSwDNbF-CCJNAmP{)Pluj$fQF)*g^-6$_9|Fp8wqn}Dqzbe?Y|cHLR@t1Z zO!+XihdEc?loh$YqB&cEyed~$B(IWIE$vbBl&8|3Nqai&$+XMfYu>)8y;9$HzUQo0 zP^TaRTi9sdSl{yWrRgVugXxFUccpJn-%q(eeQ)|sj`jn)fIYx&%H5PZxMq(zzr!4> zeWwAH+tas_?@r&AzJ>Y@jqr~P z*P3#5`f77#1?BqmRp~1^vnG8x<=XTW>B}fraqzPXbWZC;Qmt!RH&b@w$}ZGP zyQlS`?8{MK+WV0NDhH$u;>-ZjAYe$^U}^&?houcCADA{YZ4~vvq>;3YAlI4Ev<{~} zChZGqqsUboX=)?PRm1t`h_tb^4x=1RISv>?>%_G2q_4~yFHc*>oAyboo$_YNUE(pW z+SROcIlncJEV6{@0Q+~)G@t%dKaKQZJo&5(b55EpWX&gy%l8_%9fn# zmfkkK1?M``){>;FKIe+ow72;`vi<_T$?N^&#)lNwl1iX$q)j!Iq%H35?(XixVb~Oz zFa|?57%*WBXABtIaBG{iO)YKONU5i|`~Q7T`F;P-^LqHauFv(kJ4yK% zFd2+0A5CUFJcjPEc%ep@kE9|n0th{#d?*lVSor|dXng2GaBXLEse4p*&AgM+3;5S z@)|kGSsOXcJdW{&PD|whk1&C7%zI#JkvO-D(Zz+ z38@nJO#3X4XFfd381VYs$Bg~_` z4)v-)wd)>qAE%M#{Jg)hNE~*?j?AG zf98IHf9`&Q7tvGf8Ij<}?nkI6@FQ*u5x!7 zV;;&`^WDYnRCkhl5s$^DJ{t3A^xG+Kht}#->o@*qYEVQdq7?>(572^Og-+q8P%FX| zfeMWxgh)d!6dxqLP$CX~&q4HvC`9@&ML1C?ETG526p_p?qG+Heic-XYD3n2Alv?;W zsp1v!_&7y#MJ%~yil)R(C0dYcinYX=D_SU8p;}6ArD%t5u4t?1fN!Q~t!P7~DXNu> zMWv05JJKP>wgKH0-B>9W?xYy07^7J2xyo}DucC?1ZMemA#NXW6awpksb{}#db{}^i zbN>mBfj`Kcbe|^st5hf4zoU-Q^Sk>usz+qUAJj$XVeq^AAo?)TuXG%e{ulH?@QeFr za)O2Jb05GT!0(s%$^8?4pZf>*9{f-EA5nWG|KQ%`-i;D|Cu%3y1-^H0m+^M@R+Nah zO10g+jabxnx_6Kj-8;ZGsymqFd-u2Q&F(F%v&X%`y%F^-x8<<=3!?US+TB_6Wc8E0 zO3q4i>dvg&s!^LpA1PKT<|*bYHYh$rtyQd6tWbQ6UV~asyk7As@mj??{O3f%e@14# z;uH82Ak?Q)eTM&3@dfyV-i>$>ZI*Ly!fynd6`R19K=4-*U&CK3z5#-_NNkn7O|gy4 zw~Fof@35_?UD#H|4#iIV_ln)vcEt~hAEn<-Rm5VPsDDeJ=ocNk7`08YSMd|MZP*Wr z{fzpNcnH|ZGgQ-S&Z>EmYibWZIQYQo&T+@M_3jz&x$Z&kzV7bs-tIB(QSM&uKJJ0; z{_X*AKk|Lye#CvL_mQ#4^#T3p=|{g9(T{2`X+7cI?jG*0SZ{YvcNb>vMbwG72i#fK zI=VZO6WR9e4&=HLw?%g%YUgevRXcY}aw6Zt-5PG=ZY9wgZUF>02QA#qKueixNwzhe zt!3Owj%neJr>iZwIM9?{F|sMv#O=$D+Ust0uf;u{Tz?+zVelAIe^7mK7I>+6rN~v- z6sd|dMUmpR!me;(Hbt?bOi`f7Cs(9!EAA2%DefrlEAF8M-%~tNJVo7CJW;&Bp2Dw~ z?TzBK;;rJHqB3vLtK?PHs~T2AqNZ0puR30};YMB!yc)t@UJ5TCFJCVu?B^BWrSkHJ z)m}QUKrgLVkXM9P2wLxD@Crr8dKuAiUQO}wUTwUZp;~*j$J%(c_3Gr+1#9co!K)jp zy;o;^H?OWbqP*$VVh{PMC7!Mw8hWuiW#Y*yJ!cxu^{vWeK_vTs{8XtbJL#vd-u>Wi85D6E!P~FKY^7 zK~z~ZrY{RF3o8qURb@dyUDmkFudE5|13b$bVV-4m%W9X^Dyvgg9aJr=QucwwyV7T+ z&r4r|2c-{7AD2FYAC%t5cW<#KlEOL zE%5%xd$sop@8#YfgSFr@?=QSR_x{9tllPb2UxDwuw_@A8cY`0jfAHSxz2Ezw_s`zH zz{k9gcpvpX?)|6tDep7jAMhWz>U{;f?rri;^iJ|Nd#8I_yt6>IcaFCc-1g4*F7__< zF30YBKa_ao{lfdD_Z#oZyz$-x)bOe0)4->WPd)BveSI4GG=dxZ_+b7%8XuKUpihvG z-Y3E*48;31^J(GJ)W?A8>C?@pjZa%?>$yGu@O<$}k7FJao=tjoo&OJ&mXwuzQSy1o zcO~0OR)V!9Yf4sQ^GoKHECw@6W|hn+nO-unWD+)^WEdD;GNNQ)$$*l6CH+f!gKi~V zN;+U|OInt+C}~|1TM`3eOTtP*OG1FEL|qb4(xk+zL|LLJX;4xZt5H&|!~=U%EbyxM zVeu2}Ua`CQPH~|`ezCPUuh>?cRh&|shM9_Q6yGYo3N94?SA4GceDRs$Q^kjij~5?> z_ZJ^1{u%sOyt{Z8ysLOe@z&z+ioYrTx_D#p=HgF^*B7rXURS)TcxCZ|;)TVFizo^uIgcZWpeE?^UaJEt4Wi@tf~&zWKhleXsdmg$sOLzE0wN z@x+1eecuPZkFi(2?|dsM-(ppi^_6v%wJ>j`r_xKQRQh8orB)fN3{mQpVagb#QQ2JC zRM}G5O4&i#N!d-=S=m$BM>#|}Ksiu3OgTY028>tEQchFORL)f{QqET{QLa>eq+F)_ zMESY$Gv%ksZ@?C?3+w^=l?RlEm49G|m1mS^!5QUciK|MpGEr$#rYf_Q8Oj`GuF?Tq zSfR2+S*k2j-d8?UK2|J$%Be>uAI&YDQ+llMWZ{>EUlg7w{Ht(R;r7CB3%5yZBDcQq)54Dn*A^}- zTv_;G;ljcNh4Txi7EZ$^7mg_$UpTsOWZ{s)p@sbm`xW*A9Sb`bb|`FH*tD=kVO*iH zP+u5X7y>k)G4Lt$F05Nvzp!>;jlwF09)+(9UKhLo4+`!Vlo#A7C@Lr@uovVP4HBCP86Ih_#OOG@N>bB1$zs26l^cpTJT-L#)1t6UleRA z__$yV_@rQU!P0^y1*-}c6wED{Sund`TEWzUkzh>0=z>87g9|zqbSM~7Fsz_uL9>Et z1+@xvMVg{t?jF4BeB*d?q~76rU;A$H&2Ey_q_$sOzsh`>LN&jdeqMeJ{e1iy`!(@X z`h|iZzhJ*OKcinP+y-<2-Tk`x_3-QEH_)%Y-w?l{extzzFvD-U-yFZWev7~=zmaOU*F%;-wO-ySNp5r2!B0@@HhAy{p0;( z{hRx@^l#(e+P@3v?cWRQ>p#|il>Z3-G5(|BgR+ozxtK?V9ubf{g|CQ^N>!s_3>%Qxu>j5t` zm%7SaMXo|uKFD$9x@<11E5()Ovbd66NiH)ie1gk_Pmn&*b<>pyZb{sL6I|C_S7mfn z`s=Pst}Ct^GJ74LME6Y~W=o*&2D&15Lv~tN@q$#BT-h8&CRksvzF-U2XKdj8ZJC~_ zo*VtP_)QNO9}pAJEMP>y;DF%)qXW7HbU_a%8XC|qpbu&!dQiX+Fcb`vwSm$P3>YZ$ zqBa-|lO4n97=j+fNYOunS;t5{lC{QA9TPB?>X?8D_>tHo^5gL1P!sVJ1E$E#^nj^E z69Q%gOd~f5oKNCOSHODmrKO3Hjp6Qw<;~B0gs9CP*($AnW zo7yZn@BfXP$$Vn8$j?Dfag9eUl z**}?2s6PYFq5cdw!>oV7f6Mq>zxiXy2B z=@2#X)fn@Km98eR@H|Ao`?|bbK6r0eBTOm1P+r8Ippi=fH*ht? zH*nRJzOJhlzLBert2UW>KxFE`H9|(@@Ub!1c!Y z#_7j#)u#543wy##^d*7M1D`Lfy`Xkr1nduZ8t^=zlImT6zbXKJ7f?@CU!?#w zRMk}tL48#NF2}43dxHAthSJwYdy=n7#*18Sv=~<(?@6YS9N~>>fc8~+)8nmbOh+TQ zF?j{tM5R>mz!mS0YOL}Dg8i`o&_tzDY4Cn3HC`>DQ3XmLq|&LhWVNbbRfx<5;#G2F z1*;j~gpMGYRne)Iy}lezD7|7GF~dgxP5#Zjn|sgqnCG$m;r54%3zruD>pbuL)%lyV z0GxB4bzXE{cBVMZ&VQW$Ij=jfIIlTxk^2XA1G@k&gG-%iMoisLRRnvqD$}X zU%VLmFSUP}`(G;m;QwZB;m^Y7oo8g`jPz%ne@UEno+du+JOzY4={)ZI6Mx)!M8<#M z|04R`d5ruK=OO1&Svw3Lr~4$?Kjlm(oG0+7;XkPyMxABM5pMBs9Mx6lkIo;Q^8pt$ z=8I<=%U+kizEuCe`eU0+YBEbTMYTrtscNKZh-$iOmTC@|s+z2tiJuEkSIx%Hfkkbu z)WVC*OqrXlnt`A9zn=M0&sEJ=Eue26{Gn6}REworh+l+Sq*{s+(Q>JlsFvZE%V?Qu zC0I^omFgqa3j9Z^wKB6-wT|2>j(H84weV^(AA@zW=VNlq82J$p^R1%3N{(Ev`h>1! z%qB8ls=nq3=cv}JK2sf3{j7TDSH-`#XLnEWiEk}=-FM;b1-JJ*4>%_{$2%uFCpi~8 z7fCoXpV+O%u?!nG}*ceoQ#`JOa=4dB5J2|@$_v1D%c6N96a6Zp}mfz!Ek9&i8m;F%1 zAjMVx8~(RcH&jPdM^z~xL3K-YUiB}^th%ncsk)}Rf=WhTrvD#JEIYVzgOCVotNl0+R6Qsti>sK2?>4P9`gS8k_~EtE?acExc8gE!n2B;d4}Z zARFYVoT^;BU6qgJ65CY{e2yv;%VB(m9GQ-`qp~p3?I5n0@wV!=szg;Pcl?AV6Pu{& z2h^X-JG7(AjAaVW*cpy2M}#xnS;tw=`2qit@N)V(YdSrgRh-qGp3eHtO6WT1>Toq@ zWoos^RD)|u)IilnRRcAo7uD*rN6b6;9mgV&h2>acq;WXywH!Of8u!Pc;pbdhg`h==zn?9|C*UzP0 z5$b4loZ3h|Qr#3pfmn6C%*DXX&>|MKc%o+bX6hE|=5R}OE28G=)}RHE@U6+UU|e&_ zVuq%S7hQ4heOo&9tld%F4zyP{RyR>ERL@sGQ9V=n@T|hWHUFu3@X4Vkw~B5Qo#Nd6 zz2kewZ{V!soa30|h~sz1A;(E@$nl5cFm^=denTSf|sK3XH+%{~xdWZTu{P*gesBJP9YA4u6R@B5i-%74f z-;)=&Pjv0Z#PRG0V!n^K6?X&N0eyY?`9yd`dJKCo_`%iN*KbdEOm%d0bau>g%yhJO zbZ`uC^m7b%jBs=V10928+|$up`d*Gcpf~9Azjz=@Wcxb$GqR#q(bv<_L&iPuJssWQ z9&k6TyQ3>Bc9p)9R9!g+p*nyrsJ0wS7pyI@;C7fmJ4YKwYtRO?bhIGT%FzsK12@ID zmf7}TO%D!54PB61Nqfdef(|7noEVh$Ih zE9T;9P+2Kem#YiuD8`qd%G9OITgF99rNm`q1>BPFsPA!ks~fv7)qV8?sUD~wsUMPk zsD7$`Oy+_5nff{W5Pk+fQNJL%OV<--ETiKt^Yi8tY@=l zQ_Yh{m}ECmDX>4|c3SP3_5z+u5-%s|_%s_2@9N$m{I4QN6Qqd*-8J1b@tUTZW}q`@ zhHk5AuW6@gqiKz``ybkquZUY}T9R$6X@#|;tD?VxJ8C-0xC_3Wrjtf^F|SZv@m*L? z`0kpXC=vD4^w#u1b=UMmbwl^l^d;L<(+4GTz2L#*dT9n|2I2>52EqL`!=xWbZ6HxU zMvBfp%-@}QAFQt&E#?`_*lyGZ(K#51YB4+bTb_}8R_}Se9WS@PJX~_PWQ1dsW4L{& z{hIxT{Y#0B_FwD=rP^-)&c4IG+rEWptNnYh)&3Q>oy^y=_ASv)84JIS?6*W)?cdPx zo$T5|=4(cMEu+oM^bOG_yHJ~?Z9s3fe_`K1yutn%wvp&F@CEq9{weBHyx?{A_4d#4 zYwRDvpW9d4SK2>D3BL|}WM50ider9 z75q*t_z&XW;KTSsnjUPahTr8JqVfmvQKFwU zr!{BMzj0i9G!9Lk##^J*q$yLBzw!?E(T|5co?p1Aur+7;Ms_cI6zF5`WjEU6>`g&? zds}q8y_LNg+(L3ZDjwCGo>+WKl;~|iS5tB=h{c%Zm{C@Y_*iLic7r`qRt0t>~$>>*gVJs5IsW^>K$T`M4Zx8z&^;@j@P*b2O z);#2m53e-uG^M;q@>=tO_NnHv#7nZzHBYgZ*fVOciJlU@CVHfKqTyqEV3Bzt`(B}* z$+}RY|Fz}~c!lxdbMRZ{|3LdrawSwHZ58l=wzAd(Us+pCTa{cjZ7p? zYDa2&XuD}g;D>65X!~mWX-A{`YX_4X0)~TO+Cka@QV+%tlxiq`5Iw^gH*ex~xsXPuEUGPt(qjYPxo& zb{1+HJcryws-kzC96grtGZ;UbXd3nDtUXrx1==OrUfQnO4cafYe{0Wb9|k-MDE2J& z40{*$t|!;L>~-#QuF6}HwVUZVpnanIBT1UKs zu61Nqpx2UHj$Q*VkzGrrUzN8QtRVZL^vm)V<}G5y#WG%$w}5ytnfZBh;YILVD)aN^ zVDs~46U~EXOP)n`cHZ>7nfRIbS@1L<^i*s*`MI)3%sQVjV)R_bEMU$lV1bMm=8eu9 z$}z2#vx(!0bH%%!@UGHFRroj_pT|B2)W51f)~?eY(jL}c(*CFYQTvnjjP^7?#lN-3 zwMXIe+JCkGkUL9VbX?G0(_V%zYj0?;YAOl=01rL}2u zuv{%CMXgKg#BOVguwreQwgh!Ydk-svA88+J@54{E&#;$TK88&DiVrV+r+ouF0;>jA z3ak=XE3kT?M_|psdRXni`hg7s>vEyG7pi`sBG8juUAQ6n2KYvSO#+p$cc5ROZ=g0X zgw6(VL|{;$nw%!^7wul{VgEn<`|!B~d!PUId{)tvqQCO~&igj^o7^mLF!%S|Be{R% zp3Oav`W5>l_b8bI;1}Xwa(~XxyRqo2%<+&?zm*p{dLJ2-bp?l7VuxdU?h1C z#3ECX>yz6fw^MHS+}^p}q;HuUFUK^)H_Y|S?U~mzZ(_lOg42&rKORwgMD06G?lp-I zGzK;b@(ywa<^>i676m>Id=hw{H+Gc6Z+H>n9M8|&AWR^H!V0B?nOcCTItx1q?kWWyPAT{R4gK%w-Dkv~0I7kPFfzY7vpy;58 zAbpS_$cTy!ij!y_)I6vqXbswe4ngg)PC?y*x&(EFdj|Cg>J9n?^$i*j)F19EbzgG5 zQ2oG=ph57Upix1?f=0rF$V?BK95g0q0v)}Az6;tKs;$y zo?MbV-8RLxz_!S?%C^F`5I@&8hj_kiwr!qmrfsTix@{I60+VbL;EA@ew(;;d{5Ue> zY@=<%Y@^`ew!yZc*kIcr+dx|%TR+gp*3;J8*2C6KqNA;|t&6Rbt&Od%tsQ7(YiVn4 zYk@Vl#o1zQMl1$I*&=Nb(n4)Pwh&vWEtn@lHE4~^&lZ3+u{8!hHeZ{UO~Iwo3Y({` zzO4a~FHu9AH#G&`(^k(`*H#CvkG|k z(CY)Q&7PT_wKP>U_k$ku!j8XAqtoh|0xdA=Vs&wFv@S;195mH6)3wC6(6!NZ)V0R9 z({;ky=-TSq>pJKiX#V>jvlsVFPtTbt82{zzE$~-DrvN zx`|+%Zjx@QZklegZn|zZ5Ik2mA1u(#(=F01)h&US>6YtOf>qdB-8!&Fw_f*|?hD-~ zk~ir#>Auu$)NR&n!EYeiOl<>Ns4sQjkpE8it!^v41MJrA(rwdyulq^&qwZJT@4C;K zCqzvm)?{)U;?4~)*a{A`<$qCNUp<3lM&oSnN=0xYj=IC?6 za$<7Aa{_Zhaw4(791W(+3CdBUf^xJxQO$qxa{_XjfW|rgIX*d!a=cNBoccKpB-hQU zog=JPPK}(JIn{Ei+EOQ&*8_}k5Esu?`GdaJ;*N2E=Rkw zi%_MoEBkhK0cOv3fV}J+EIT_RJ2Tq`r(~yPX8{ps;BC~>v(4ESV&To%H?nWila_rY z`%?D#?EkV0v-7jJW^c>hmAy0jrTw+t!8=QntEN}|RJm4}&-vx!;Pt_CgXaW~3LYIi z3(Uc0!t=2YgO^|n;l;trgI8cngFgyh6TA|vMy-<3YGzm;ye|0T;0?i_!e0b`iP{8z z4Ypw425$pfgLee)4Bmz9277~l0(*n^2OkLj1sn|i6+0As7#s~g5_}w-3_cb77xriH z+2DVI&w;;#F9cu2{t3Pud<|T|t_LTAo544NO~J|VHFN_0I{qd(Gg)(R60im5p-sV= zz#V*t(HDZtg70!WRt9h4`9IJ4UiHQ2qSeZ;l|LY7V2(Chlg&prXBSzEt#z_%XWzBn zv)<(`xzDXHtmW1+P)_Eawa8jXM+sbrF0kgq#jrzayEV`1wA!rM)*LtkWLYzz~#$*1xPLr8*5CM~nEV z^#su|aDv=XAkgk!j5{nh##>zuP*;28eS zK9~Ko^Jiz($5kKquG6PZxGGFl7+f6uUC5S@4k4XGR)wq#nFq##MPL?~9x@|j0l8Tr zbHMD7`Cv9`E<97_riVV?D~hAfuRl8~k758*}d zijd{h7KePqQ7j5sLbO6+8N8a@GI%w-GGskRx*}v<$XfIoqBZ2#6R#$-7G5E{movwQ z^skbBB{?zAQr1{W@27N5kiSCy(w){_<2tdqHRspN;IoFl=Na~&tb44@ ztu3r;t?R5at<$Zet>dg8TIX3OSf_wVWM;q<(Ub90@e{1$;TbYBh5B^sG%%TX8uiK8 zRM|5fKUVfmB%X*GgP+9wW5|ojI93};G#)=pt~^qD5e=g<(mKLA#5xofelV&6gV2Mm zgRBE3_m{rEbpXB}S$XPL8-fC+yi z>v~pVRzlXrtSebp;j>x)W}VOaE9+F&nXD68N3srQ9nbnB>o@QR(V?v0v7f;~uoL{4 z^+VQ!2=B$m1fCtZ!KFus&c=*bp!% zY-HHzuyJ9d;0a-q!^Vb<2pb54#;uGS@WqVF8UJPcoAFP^`Ha6Z&Sad<_%q{p#)*t0;E#;M8Astm8OO*R z%J?1sTgJ~BzhwNHu@CG9KW6-p@qNb5jLjJvGxlWc%~%7bW=zf)l07^-qbR*-{o8eK zbG&WdJejCl7QQ6>_wd8vyTf_@(fR;g{iS;n%}& zfSdU1;fdkq@LS-qHa=_}Hgr+=3IMf%G0W$BC2E7F&w&rkm_eIYzI zeO~%>Fe`m_`sDOU=~Liw=@a4c>0|H{)5oTdNFN18rVmaZ0tTh`OCOltH@z3=o!&LQ z8|a+gKD`UpD!mQdJiRH_B0V-eF5QqGlO7Gi)59=*dQf^`x-LC9Js{mbor_D-mFbOv zZ@Oo?ce+=4gY>$n`suaPYo^yqua;gTy?T1p^ml2M(goh6y-ItFu8Mw}_5r?1`ir!e zX|KsYOM9O7IPFo|{j>*ZPf$f^#c2=Gd{he6*JqqH_=neX2`r-OP`d<3JSWomY{Q%-&`l0$U`q9KA^%M2u^kelC^pjB&uu0OU=%?$2 z&C<`2Hdnt;{~?&KU!q^3U#?%QU&$J)^{e!2_3QK>gU|Gz>Oa?S&~Mg%q2HwcM!!Y> zCH9?uJGKq}0qoT8((l*r1^e~C=nr7O>3`QB)c*mFf)o1V*eU%P{b_JU|F8bM{;d9Q z_#E+HWJKkZ{vZ8COw=#wZ|bk3|IsIaG<~}MBr`k&CHgY`E$+L{`dsk&>CLV;eg&F> zZ!$OY<+qj7YVon#HPh;(RZFYFSEy7;tDN@A^3qZ%?TzJ`<%#8ma5LzHGT@xdi^P{0Gik&R~CAPFqf*PFqe`j-ifQj#z%T9JU;?{ASr_`Pp*7 z@{?tkWsl`Y%lDRTmLKrjiT7dK$o^#6V)@3h)$$#Fi{*3h1^CADm1VAF0hzBYYb@)S z>W5QWNu_$WG0*s@*)c&izAC73nR;c8+#P_Ao5}4 zW4=P*8F;~0hrf$_1FA>Wh^i7*DXMN%{ivEz)uTM46j2SM8btX3Wt4AJlPG^Kz6prZ zLH<2VJ4Ur9>Lhi~sBTex zqWVSk1_Pr8M2(6XP1G-HeAF1!#HguJ6QYhp9*=w${4}^WpRKWqPjFstUur+aZ^M`x zle!~ycj|=H(W%2z$D}SzU6?uyKS|oO)R9D^Q^%!_g$Jh&B|9>82)U791S5t?9+EnU z+Mv|_sRQZiliEAAAG#;#g>?sAQoE&gP3@H0A+>XA$JBP9V``h!mZ_~$+hWa9o1$C5 zhSXR%E;W+licO79jllG&;UFwE1P)CNO4X(Yr|Mu;ss;q4Hc3^(zIYAQ0P;Gz15trw zRFWG}4@&h<^~UO?)|9i=Pi>T%laiUz-!j;8+IGq&K0m-x&r)wm(9$6BTkIL#C%S%g z-RPPiFj^Pw%iFcRqy3_Tq61(}v|8#AsY0SdqJ!zt!8|PoS45%Fq0tf1VL%@pg~dc0 z@X^stqmAhJ=$2S)bc^V=SaYIws5V$f&_23Dbhqd((VaoJ=pNC%Fv0zz`*LJ`q6dJ1 zU{Lhn=;6^rC67T3j~*R8E_xI^A$n5u*y!=_c$pa)Ju-R>HY9ok`H9$Y`ld!t0h8$; z6Fm>ij-D0WGrD*5&(Zs$6Zo6R)#PZF)?8LIpj=hn!rIi@lHb|*lrbq&Ql_PRn6e;c zPRhKLNhuRkW`QX{=!q$lu?cjIfhVMl03seuJPwQnA~!l^RLW3^;VDB>2Br*184UMJ z>6_9&r4Q(x(krD0=#F(w>5|eJ?wry-rCmy=lnyCvQ(C38k!YFHJf&Gm3#?g6TuLm6 zOEG|$l&F-5lxPr<5|R=QhouCi1fzpeG$}e*l@gc|N+lSJq#6Q8rl_fgkPD`!O!1~C zm~Krv#hx47I+pAu$YWmjj{ULl;xG6e0x@Js`n77gIqO~zvR5jk( z?-S!6ql~E+(rZLX&cizrd>>@n65evxmV1W5nn~zZsKY(nePO9d;87p{b~Et^vU|iXEobn?jL zQOP4Dh9wV99*PMJOzx9BAbC)7-{c;se#yO)yP|p~cT4V)+&#H-atG<#Cbvd)f?Hy3 zlUw1NB{v66lZ{w2I4U_d*#L8A0iw7i5oBYNV?Z-{1oY&al8d4en5<%!Hp%K_|74%! z#`0K-%_Zg(bFx`HUOrTEs3gTB)ngOa*xlsbr?J7`P~TA7@G|CYOjW*kyRM;up_;+N zPzTgU*TXk7G%|P+c^Y^dEbM9UHYi{Pc|Nql;A`M*+Hhm4N`s#vz|e$QRT6;)Etf%Q z4IzeLgANNbgklkfNI249Fhm>laIC=y$AM;s<{%#5+R)C>0^P>Y$t(gVi&*rDx)>|4 zPWF6^UjsySHTam>*RZY_{Sm8vLS`lTPw88QUn?PwY7I(^Tuq-C^%?luyxF_~-fI35 ze1qPA{?@$Fyop=&xp}vFmps0$=I^-0+s&qAbMi! zhBU)a!!X0IhF=WdgH_;1!ydzC!$$C>VY}g5{8wO;VT<8Ang5b#gW(IqXNJ$=&%p-l z6L>SZkMWz{=S5|s)EiJ=$Vl{lL5J|0Mt(d+sFj{0L9rnj_4i{M0`&&o)mrk1~%p&oj?4PeYGqAI&w-G*4hh zO(&iKCYdLjXOWu@=FmBhXbyX9KBFt*xp;xO)aH_%jh`>ah*ozeh&4S#IxlHvC<5=t~kDFauwl4T^vuv471tkOR+_A9|^sJzk^lmsrlvwu=pGJ zh+8z79kdCx+8hQBrW{P+5>C6}x#4-NPppqJTp4~R=1xq4;g;dB;TS)|uMCF_N3mZG zJJ~r$3F6fZK+A!-nY1(dSgBRY$9wta7igtngUc z>9qakAJ_{s%zeyV&ArWo*(<}%JhgUAeJ7xgpuG7ls_1nWnBFclFE#YTWp=F#vl z^GNd;Y#4epxna!mfBBJU(K(#@2*!yq!=$gs4w1dXm{s%*2V!o~Jxs16<`Oxvsu(eZ zBN!^H17)A641M1_lvT%Y8z!*JCz~g-J7%HAp+zM2rdUJlTA>F3vD<$(?>GNtIcFKb zJDC6FyIT|dZu%{cS{dawl*ym&arRBJA(cIG!0?aZKf^`C1u{ns|Dw+r&Kgb|P8t5D zat3u8{?l-h`UxPqj#E9sK0Ag#jz3BLC^#kKW8_ZCj^lI*eHwpMc8M8Ip(;=@>nY|x zN&Qd!Dds#W*E>dhmhNLjXPD)LJgVa|E9Mhxo@VVo*x!e^9Y4$S=K;CT56IjB_S(-t z9QQ8HGyBP2;x-N9T>ELn`iOds>o-n%WqNgv_pj|p-IdA2*b9*wa$aTV65p_1V#dlygbtURZXD@RX+1Z2cE@Zo5T|hU`887l3Q9Z~D zFY;o9P$JU_79%=KEvg;mI^D^&XC*OXNBsYFbY_GYRe=up&QwLcE8I)&^zL#;4P;;R zGxwFdN~j+EWsGBAbw`OK6ZgCI%niA%Mssn7JEKckhq5^}7Sx!{HA_#TUPM(gzA@w& z^4Jq+3<(Akdneg&&2WWtw@KoL;X2;T87Wbo&u|3$PIcNx11 zUxu&A4w1Xcm@DkWYtoAm0%H7iAVyr5@wNZ?8*+Y;zmB?2hgk6je4RN&_YLVq{TfG? zz$};Wm)K2bSxM~lKf&MZ`@h*!Vz-H7_=7X+S;Kil7Wd6xaYo7p;*paV&seYYT;cim zqjQhG<+WdHa(c2jdqned3otikM|9+ck|1+190x+#N8xa^Io7Nv7lDdKMX{%1u{eI> zqOXF5YKnFu;X*tTRAey zmdeV{J%^4|EX$C=?|>&Y4Vh^1SSf?inX;BGe?Ju!(U(D9n5d^w%f^dQqGFX%hRj-J zSGMdET@|rSu9<1Dk;x*L1w>Aa5xF#w%zsD|f!Jfhi#>J|Z)VqDV&A07zkwun&N*ht zF}yXrHf)amDtd6xz@X3g?jmb>R(b8LI$2Tt2GUF!Ch_;*$n0gV&tG2z;KK*mMM|^U ztTFqTz0FO`{=9L}ALT_;3#+~j>mC67<6DvlsR?y3m6^CIO==Z678IEtq1 zn<#RP8QqheB>HcfZknt-ceB&}qx}lssQHu60iP}5--IvZe-w3%p2mBIQqDLo9`-yn zxY;x1+;`nGJiy9{@8a)*JF<2UF2u^<+lFHHSOLBWRR{#%CRa+fKvs+1dr>Ww-9>Qm z{|6#hL`NB3%u@!$SU1NY#=1cf5cyIdW)Mdq))HeypTpqfRyf!VxpK#eohWh^_L7sw zFivobeRRk0h>i^I&5j#_jT+;m=xNd7J?m#HpRc@`&x4tgHa%@2k3(y*S6-W*n;Nn! z8klRFKQLD{d$NOSQmJOHPOculCK(aerB(}w?uO(%*=-6`L%G*HVNbLIUk@dE>Y=@u zrM~Ro1HJy|#VF7B6|tg->l0VxL?lLuxIXKO73xV}k>%@fWPfc~VXgx_&AzPESndk3 zBmLQ5f#iJQK;i(Z-kd>0Z5DKBU(ue&^MUwT;z`9~kc&o*1eds~BG!-f=E^Zm4XmWPHir<{P4C_~(Yl zhKKC4=ll*HkrDA@_$mBU@)Ozlhc%eDvr0JCDrRjx9!B4c%RAA!E=Rqa&Bhy3ETksk@ z2k)r7Fg-K9d7rCnu41lCXBBo*RWn~@Ypx;Vszjnzg*mF2J){?19%lOqI!0 zGQVY`CVrb>9xBb4ysXhnxO25}TM(}Rew4u{3>K=^9pE7Mhz=mVk} zaz+n1PVCQWDA8M$is-2f*OYVBqEazZbk&kwHKiA`h#5uBgCnUUeI2}5PsC!CYV7RF zJX(A8e#bpEd_d-z+|_RlwWyV{XFQB=4GoOmJWRN0h&Q%2ibvWJ32M%^KE z*K{9qo65=Dh3{Y$Suy4wRpIY~QXuMO#AUKiR4V2yWj&!pT*``~v&2*=bA`krD}2Q< ziDMDFUHD?Sm|OCg>v-bBDq1bndc*x z-(PZHeACy{kS8JDJWJQc*q%L7*Vx?H#u#GMf)HbzF}Z@CGdZR| zSFalDMAuo!_Z~!Lg=gt`G`YsKTK+8mGhH-Y#>8WtE2taX=bKF?sT1(GOxHnzDUnJd z6*HVdcQUmUQz||g&r@e;@h_DqN!CP%NmdgXBl5T8ShK7r!D5{R>64^4QBNe7C?mll zPQq>hG2*5?3Nh+Bng8?RC{j%p{K*+7;l)0B-uf?^-dk1An)z$aN&w67ED-8}jXBhw$yCb%e!U?S!?V))_BG zi!}t=%bKXRqo)fQ;XBJ(XOvh$%wC~8%i|DSF++P}Q)4r%DSJQ8DE>7kHX37PHkN!V zR5Wo*wD3lFZS=b6Cfdf@B9Bs!(Bh!t2Wd~!#QA!=X{X#T2TZ@1j+u^{j+;)Hj+hQ( zM^VR2hw!Il<|NT!>c@%y1gA`AO=s|Dz-jC+GN(=FP~yJtEI0{7EJg@_g7`0bDn<)F z4Jxvy@FEst{`?<{npo+$^e5i0eT*X!vmC{rl2uVZDUauG(|OY+_WworGWgH*FMI|5 zN5<6qO??7T#3=S_P|Kbf|tZcBaOeCRa3F}^v~_(bC(t{2#C+-_WET*Ysut#Oud zrg5}!jB$u@sBwyMf^oQUEYWb|IP^&4D56o+$HT*oBghN|LyUusgWzG9@FU0#z>mZR zk{wQ6WQV=4j*$H$sf+HB)WvFIz6v!`_70_A^oSKIG9$=~p5Zbt<`sEyR3bMTU(qX$ zqPMXx=q>l$08}UTe_v^X*o7VW>*&qx8p~NT)Hu&L-}n{S8M!lZx!*Fs?eBKJ^UwFm zZ)$04Im6F=v*}yYetvp;O#4jVo4zw`H|^plxtr|wi#)=_Ag8 z;ykztwZgQ_v>dg}wAi%Bv=slLX`a;cO><52!93F}(@gX{^a9fox|YzlSn?uSSxG#_ zG#x!%&NaJqzh%6RN-h!Ejj_qbbdZX+8o4~q zn1# zEJ+zjIb4)!CQ6o3a#A8bIq4>rk(5G|iccjHJ>ssz%(z?R6Pf8+(hc;Dq$^2Rldcn8 zlDM37A?ZJG5j&rBF6m!zCh2t2KS_Tl{h4$s>1@(pNhk5Ala7Ov;Be9(s1r$tk`AJe zCHE&2{I>aZO9+xvJAJ%7;gGh??WealQI6%F?Ofy*%kW(YBwyf_dWK-?u*?IevSPZ z^-JvG*x!hLi#-~9DE1(BH1-I&BeBQ83G8_6pRuP=r{KS0&%~Z3J`?*-?0M9A{J-yU zndARA_G0Yy*vsTEpwH3&H(jT}nb`lxoW+aUO?pnCO{nX!2^`7(*r%WlkL}uWjqL7P zdux4Fwy|s}*YQ10dX!W*sUE1Cq)75k@=sDGRZps&)F`Q;M2)0c__|3olIoDHi5I9P zb!~DYE23J|s#B?+R4vH^tD00fsS>J6(%Zy0iIvdr5?_KhiSH6$BtDhuY2u^AXGD(@ zA0|Eq4^a;i?|?^%_Y=z!OB3%U-i1rhC7>7-f&%Qe_~=mBk!Z&|6SEWZ@VUTAXOTo9 z{pE~z$j*E@M@C|%99v9uGx0{E`~7M~=2EjqUgf-RUT%Gv!YBH+j%pp%D9$UcXI$5~ zrg5#}CdG|I^@!^q*C%dx+^D#IalPY)#*K&@7B?hrFgAkhNH9#QA+k0kZXj_*R#Znb zZgkw3xUtx{xbbll<0ilp;-)bB)3hR>m#E*2b*{A7Sg`J|$WMe;&6k?&G-6$gKgZ7_kB@LwzLG zI(kKZHGQAcC%RS={}{JB?i1pXT#+*~U{1g<@Akb*<`Z25Qv6b?C)P-84wfb^Nol-`Yz2gFn%II>nkUAgT4F}2tQ$3;_O#%ETsU|3o!4(G?29Fm<9vzM()zwo#&2VhnTpB>E;+1O2#txe3mM zq=bZo{M7u^kBin9J@I(v(LJzh;2K^HR>phBHv-k-YsA-#ug;fch`3>Vz4-d^jpLQ5 zMtGliuXx}1Cd9s&fDe6qlsNrfvd@Q#9~}aGFmQYT&;V7uI$jqa7_W^FiVv0+5+C{= zk@5QYu=t4h7`#57%NXJ#u~=!%$Qt8Y#kU}88s9p;WqdOtf!6VDR$k!b_s z7!@t&(8q_!xucjVMy}j~nWC7_k0b64yf}V8j{8j9zj5D3ZizhL^_$nh$G<+-S_7@e zIa4Pmq$ZdWZYAta*q3lK;Yz}}gntqaC;X9c37k&&E8z_MPr`o*XHnF`F21h2yvdU*)$c zj_4Zxmh8JpEIP$%A}8iDk-Ll*{uVnREg>brnvjEKCFCaLCD`E11WSS=p@3`#$VTPE z*$H;`jE&B!oF^-1yiMPH=c_wi>vgTSF?3_-V*aYP#cz+V#g6FndJFiblBoZ? zxEJ^0(nyo0O;T(Z7Fl#z+;wqV+?|ER-QC^Y7g)SW+9Zt?Def-Civ7Q5!amRQzWsfU z&dg0ZGnqU0-nnP2^;JvOL90yrl&96IS*xp7t4N#tLaUWWN*h{e1B+^(Xsh){TS<#Y zp$j+vkda6WEoo5Fq}nZ-c8r9gen_#z79Isfphr_edQ>zeBA`gfpmG?{#3z>Iku(E{ zpkHcKAC;afeWQ@-#nS5ws6H&e4@oIWKh6rIpZB!(whlHAG>_Jd)y!nK^A0aMya@0T z+&TL>`v%*3>h6ZkYjdi0lX|z?;%Ns59zjq`Ky#4hgWTliRXO0N_LEf01dvI10vs(# zwcAydBJCq7S4g`->-}e6r_^=wCE>fKLk}q zUYd7V-ZT**5fv;yTk2cuGA5MZ_gGT3L22}GMuGpvhSH>zrZq}a*TRdFuOz$xtx}X$ z$OmM6kve>6Q|M#2}{H~PpWYA>DrChhvb_LTa4 zX1fa>qe~mywLPMipV%Jae+qwr^$h-=ntu%!nQZ%jly(uBth~ZM(njy#AC&Ktk{@g- zN`A6^R;ejA@qhfmKmU&v)KbXx35ZM~SBmW`)g%kNNZ1RPiN0$ z&u-6b&t}hs%w!L>=Rsz%XGP0t&jAnR%$$*PTQPfx{Tn?q$u=i`cKp($rAfELFzOU$ii362x*_vv5s;tE`7oX0GxGeOPBbFnU z*Ff69OFg~;kEr)smK$s_^n%*HL;XJh_f(7ABIPD@op!lGi(Iz^XoKstMUh~=o|67mvxc3QStoE#^K?@`23%%V5yjHe5qF8nIx zWs05nKyq(iM)_#!w-Phf?DQ6?ZK-$pEy}#K4DBE@QZ1GaSq7g8OAEJ>*T`JfYB#GE zvM3o1kAf_u{3pd1gya!R+EF|~Zsiz~+p6ZvCj8~mqezWGN*=4d95PDHs^!QrKvIQU zm6Z8*IcD6yFc+7Zc`Y?#mw9|W=Ib?p%;Os}lb88 zv{+i?oaHi7+C|#yGNsRhi|QyMFGJ^4n&c3OCEl|@@}0+XUZwn}{jZd^ml9`Hj-Wh` zC4J$7k{3vkK5`j;jJ|Qwa+v>RFW70>V>yns#j@S95Bn5z-xXXvox~eU-;t}hY>clv zEW0f8bhCA`xAphe-(Fwf{n)U$B5_7~gOwVWcB;ZPkX+G-{SzduUJ;b1UCO})6~NC5 zRcMJSpeFKXO4j1)sfuc|YIx*2s+vms1%C~7eN_!!L-}i?$*0vsR>xNZUD~>)YW3R6 zu8A#P$txx08m&4c{+gZ%mEwyQ;dXlid_Z_L$TYp9jIg}phR=Jr4A?aBAM zy$znO_MYlWZisyZ-Zu7)+|L=}ALP&VI`?bY{rzwK6#YZ@|`pgf2?ta{~t zGMMWyExC`hBc$zBVGdSvCFbU^(E;~yU}-% zM_NbnY^Md*L0eR7Y{j>Rw%Dq?(r%lu)~gmq`0tthMlsswLNw zDkWDVH$rkuDZdT~7d_|;_KKiyHGM<+!9vRtu+TEwGS@N#%(u+3%tW8g-I^uzlBvjP z@XdVxLEI~vMVgDT^S0@p>3FeY#a^a*o$CJW+qdty?z=A8FY;>^)U^+^_oW^i(FV<^ z{XdX`=KLNz(jG0KcIeWp+R+*vX@{<~K^J&e)lNO&ooFACUEn>))rH@6cX&6|T0QN( z&^wdTQ+c|RCXl?{kdn6>JScmRJE-+S>qefSEl2rJ??sQ0JU#7wKu>!={)F-uZT`6_H;(d#M zGJP_wvaICZ*a+1t*3ouzsPXNz>@wPIg=H}WmAZtKWwg>FyvxbG z6fTfovE*2;axbS%*C@{#ARbe z+vw%n(6-W(Wu7AbZOXGr9bZ02&PVe9t>M$8PsumhNSQ5^pUZbX^y9Ztz*4}H*_hGz zxzM*lXHx!2*^;y^X&28YA9MB8j@FiQM;T^}wV4sbF!IQqxXPS+Wac3A5II-L>?3%# zDu<^WB`T3xkz6vXs0f#th@8npN;#Q(L^1D?BSunQxY!0|8JLwCI48;+N~>n5I_}WP z%qm>ntqW&XRR+jBw=DPYN}!d7m*AeB%)R8kUl{Y?VtB%sDT^!xg7aPD4=FP;x%XF! z^fG*wR?TPRKBC)&CN6`)AQ1AN_XKYWj$*Hu3NKNUvL$Y z6HV^>$+c1z?)b_5oJ`o+(6fN7POL$(vPOXJsNsR=kofo=|d$CmS-CI(`nW zm2;y7ubkz&`M)b^k#fB**V=`+mm+r&3vzufSMy@a-IXxz5rlD-Urb%|6G~s*Gbn*o zlDh&Wk)^n6P=Xu<70H!(K0Z08DxV9j0FZl+axYWv9mpM|H(XEEvevZ9?E9?#U;R*a z9bfon{+sv27?I~fyKTF3qjij%Tbv@AIl7f?6zg>B zH0xCKsn(fb3c04@AIGuf$RqgdF+h$yP91R!N1sTpi5zE$bui^;TK8M`T8Eg2nD4O( z;lOMIv*me~`&mEl0B>6ET$HwHZ9{E?Y^`jqZB4)*prx&atvS3cz7|TC)aJHk%4%V2 zgVh?|PN5B2d#EKokCpEJ*1kG%XZS|ohw%?JB@YDwl zuzmv#pxRJFTOC_XWPNy0*05E_{>@eu)B_@e%Fax2#@k zqSa${TCWnK`HIyCu2_@sd6g#-`x=_pnqc*#Cjg()eE395R91jISF8ankyqeS>YBoJ zj+~^lo7UUt*Z6e#OJct=q~&b{DJj}^`YWoKULOa|dW52|P*pl&n#QFr1Nq?`jcgpvb z9G_#q#(qPlpnXIADbo@UVvA)How&k#8 zRZ_lhFyhJZ)_qZ8dn$;Uw{jAAP<3^;(geMdn7$jWV;KDJ}S7{c1JD=wmX* zWQqxm$sSWICM+g>Oy-#UF?q0ZC}bxkM@+7mtoSp>WQj>fj*OH|^+TpcrjJRDoeqDR zm~YlJ$aFE^tsyEUDAUAzwWf~AKrWF|Iz&+jB~s)2PAN$f8N&DaPA-w(ILdddZ&uM$ zar6}B6Z_(Yw;~sWv7X z4qjJjQlvqVf?tb%ZOr}}Kk**YsMz7L&e()lBY{mG#XeH4pVby>drvEbP~#b>>6BP$ zhwrh{3SVQt$7aHt9%Qy}2;;cgVwU55hBqXXswK zXX)9|Gov%o{@z(?1AddVB#wBK(czjJClZlT zQbwvI>viY``W2P?3geoTO=MJ)@ymNplF*3D^kHss`xA_)kI2^&8kX7+axWT zlqk}zwiwbP;pLDfq?L3jWyTZKtw@0dDo2hOrIlCd;t@@#BB%yxa$c(qmvdHKTP<4+ z^d_9Menr**Z8?WlvHikXvnglUwzf%JXZ`8z<<0pb_lru|D`j7!U84=*nL_rMY%z8J zl=TGmS#ljioWqcrcy*Zsj{A&X!8r36b1F?b&D79|p*No0 zeP;Jw^2+R0&Mn<-ovH0wwx6hdsq5<0ZFTCkE>?Bs4AKg<)$F1sXSKSJ%oBcRrqGo3 zkQsxtKqFd4Y?)C=D+T8=O_e6|BAL~+R4FZ$Y{ERFCAnlSBqUsN$m~OAMKYU`S(4;w zP5Vl!%%dcS%$U2VN=f7<%n`r7&^^s^1Y>SF6|8>}!;9d(** zGT#3DHVWI0vP$gv)hAc?zd!uGc7YlNN*GERZgXc?W+R808P4Td_)+9G>U4v3Eqo7k zy@eK#`rpPca})Mv>w0LDb+vW5YLg|_W#nB=zguoyWnH1L4Bsl%Mk1HfR?D!KA(#Ij zz7&0_!eW(UF`vH_O{^u>Mc{wsLhC~1TYz^ly2wSy`AQQxA8R4A`WeiLgR|!`Y9>6E zS@Kk7zeAY?Pq5Ci&a-y5cHncCk!u>y^DSze-NL%D(?oaQlXhR){_KxE$2HI8;`#Ov z+c0K_9c(>q-E0HVJM;VNscO9^HQk#w=%MPjJF*|>OPdTJ=TJ%xhL5DoU}z+|Si|AN zZ6m;Nyu)mxY@?NL1X5B*f)RMfAf?nel|B-C3|`4S`hWCswz1?GW19etM4tdjj`2zx zP1y4ReOx!>PXsU7*{7lxoVPY6uG7vj3(DygGrHVtpVzqYbbJnN)>xB*I)fr z&i-7J4F>(xwVCAZN6H{|b=Q~U3}l5vUv-o|NJ;IhAo=@p1t`?p+TGgC+Ma&V-rCOE z%G%o67PPdsu>N7~WNm6~Vr_%JGgcj*`?n)a%BA7SY$$P|&V-!{Yw@MYm&fezRF}7_ z<@_^>7TL$uskHVy>U^3RgKDL9%D)!Aigal$k?X-4eCxn^Xf1pLat(ZwN?XsB^#=SKv89ywHzGIT z->58+8?iRqwsIXVSL=JZ%9d+=x#mBBewr~=uK4$JZGMsa1^dwUkm~_Y_oY|MCoLj| zTRQeC{ypRG8JiJ9K7w~y)?3%9)~#i&ZS6|kc7(T}AN_&sKrOeRZaY)M&5<2xhnDCq zX@REjCalqEXl+LB`e=>8?`Vzbhca7e#7Zdf$~v9~Si$u?4bXqX^E=$fZnp3QGJznvVI6xhqET@R~gf! zTA+()veN7_wAK0ux|34_AcvjysQ#>4g#zX^0Pif)?mmQu_vqq$;$KmPhh2Y zmACNA0xx57RLGH6ms?lPQqIznnv=VEP3RfrsZUvHSDhA!23Bgj0;{rQ&6OFwJnOS$ zwVai92xnzfB)MLVExn`R>$3k9s;TNC#=VLLaAr0&3OWs@?wh=>lre* zM&~JaWM8X%vPw$U^~kEJ7eH21y&&&v*5$lX{$%v$}B%=jaR=vjamb5pl)p<+a zx2)9>`A$h$!}Hx1!YYqctoI0I?aen^Mzz`|t^KPl4Qn~Xm(l*7{IbeaRs=0$6XkOqE@4L+4X-x%shTH>A1LO{E4(_RDrhapyXH$26LsdHj?-R?N z;w=15WyMo^)@C>&0~-Ro+|CRdaf_4f|7W~e~hPavKFxsZ|MvxF{H$rG#k?V!TZi%T&dlY z?c?n!)U>Rz3Z;HC@?`TXzr`%*>FsHF&X-25v&pDd;bdmDQwFu-CKN4~T49r&6*02n zCZ}3AlO0)5*|HKRFL`85O&*Ysd;0lQURj}&2X7&)ylVZ7q{+G)S(PK}aSCF|x*G8n zWMxeul`gUnPZ5L{Q8G-W73N7pQS2h>=|L%;8bt66L7pARiVG8(9!=I^nAGzId0KDe zc|!@FSTyAsoUE{z`Dim) zo09+%mF0!Y8Xu2ZQ{!P(hG=5DSfe4SvQEQ|mVhU?szy>|y^V|293ta*7Ab3OWF?24 zr}ig#YJWyOrI%;*CwYQ?l(iM-c?!D^|G#MGcs8|>{ITkjA}x`YcEk|#;E+dE3r##!1)l}tqa%6(*dEO(^Obr`=tbj4AwKnq9NmjU&<9YedJe8`* zQ}fDjS#4e!`)6g#^Q@ooRE7TpucmB~veu$1DYC9YR$Ba`Ry{~wSq)JcudJ7lRT(u& zuS#lgZH274kTn*vazb=jNgkZJw$JpUle>aam6zPtaT2J3#WZy%o<ryqgFGV_YbO-syVWzto$C4dcJv)Qb=;|Z+tB6t z<94tWh_!_$jUqRLZTPmR6nXk6x;)$5q&(8bKo}(^<&*jo!U^<*kdW^xwX`Gx0+AQWuuHK<@i*p1cW9ES=aSu4$aC zOZkIm+OqnfC(oxFs`Upg)N}7<)O9;*Se|;fhRfQNw(9A4d+J}FYRl7Xc{(jmx_cwL zVg+SywOT=*Ov}^lK57+151v4a-3u=H{)Bp?3uK*xSbcel{U>(cANW&w#OiM!KuTYD zf6z~9gOqOo&$i|H{UG~ro_mji#@i>^|K@JYB>P18IQtm;9Q$0>hYVLKD|iYn&%jsN zS3s-ndsvHq&3DaL<5l%n*>mR1xm~wSw~Y6hW>U99EJM|A@h{#BnGfXI^>i?gx?ZT( z9mqkl1l+j9#RByM8QSYscQ}3;e#WR%D@mNE6ZaJHtbmonluDoZ{1Bm_yybpePN2iN=Bc~fZrl+M;v^M%MJIXxE@;uA;r>UM@ z^q%$hruJnm-bMQbYJDK9ZbsXeQIF%O;i>jntT33(>VwJD`An=iYK_WV`#dn$zL4MO z68mENQXqUew1mDpwiYM*Q0H)Z-j5P@3il>Z@2GbJ;ZVQVf20W6KIF*2bF%t{*PKK zvBkd0?zQ_Uv0lCRGKlDwd9UZVp78$4`)~Q)=X+4W}ZuLXhu!8;XSBcyt&kl zH=U%0<-Mjq)H_d|sr!zkcT}=9vOW1*kk*3tq?*$P%`Hv-KkrR7u{7dss#c^n;i$n> z;qp06fxLOu90b#wkz3xoYNB#9M{f*T;AyDJH$gV%I8AtWt0}T6`Q;eG#VfhwxD9!; zs{wC%)w29*sbQ&Nsg4w02VT)qo75VXs+J186DG%w0rDsPZ3Geh(y()8opjaHrM;i| ze)fdS2{Dd}4r$AIwDdmed^PpInjWypzD2c)wAd!qLee(tXqycRn`xo#p@QNIpZ>Yl_^=CrerB9otBg zGF#Etql+cqcN4aJn?3fu^o@h|qx74@_9Iw(?8m@GySYHvJ0u5fRq=A)}542@NV#6S?P2fFms zwv1K%EPCcP5^Z}|(H%uEg*ZY=-Ys&s6j8;`+Rrd@52NQSRb%^fRy0jxjG3ZFnd$Z! zXp{COKv(Uob70Q^rC0Uu5i)^UpH0 zm5Q%re47t#qOUFG2n$q?l5?2!Rf(ql^v=^ex{!#FpNdo{5@RfHjOD%7Nt|s4gJD44 zRUAfbiX4m_gEkOtINqVygA@%^{i?U>VZBvL_kj0SEhjA*Y*Bd&Q+h+Ne@LH^w=<=$ z1pAZpxo$xEs(3_7Pwb$2V@ISw#^Ez=r|ICashSgJAV$XloNEagEX%kRAHQ|y=pYt0pF{B%}4k}A7T7I z&hPjHI0;0a;yfU~=Tq?0^vTm`XTT}@zxMOWI?p*#QZ8bjfi6L3u`WaBpjbQ?uwo(M zv39%C9rk!7S&+1+S%c!J$YD3b)njsWlL-caxO-cfJ;-sjEU`%1fOe+uq^ z$KWLpzhIbSxZ|_yqbvKZytmw+ZJ)ITjRjL1LJTj=&&@wktD%;xmR~?AP})+4U!alN zEM*C|lvOB=UP7gmpq_;ag2KEFoY#`ylFyRMk_RofB_~=gWG+i~OE#rvwPeAY1D+XA zR*=P#31r1iZ^@u|I!i{77Ni4dEom&NpwvJ3SMxV>D)cYrujcR27wj+QPvEQhv-u-P z0Uyn8&B^9B;F0;E`GNVd`HuMp^uT<}e9e5*eBXQ*o@l#*8*>%US^ST5f214zaLmJ}o~NE+j-ie_yjiVxXn3-jmp8sMJHi~rcqcq3Z-p0h z6n5lC7Q$B$DvDjmQ4$mfnjg?P1bT-7(!rw~5oiPwL`FKyNTVa#!Q}`%8aoDvRqhAM zJIZsmtNMXTwk0sj^GtD_dE34U|bbJTY< zaI|nVceDcS9UUDVhz-yh+1=5}(ah1((F1gL{NZSdHQX_R^tR;d&2a_=GkboiAE5_3}O^a$2%2Btb)m4 zs(FfeB2xHx^WSKHna80`G*9Ab;~3-w^9b`;yrYyHgB)cZX&$clP*z)wFb^{KH+MJp z0^Q6V%$?1h%gClMgTw;S3D--)%8ND@1-cVh4Qfjvs!O^Uo}ybJ39kQ|4I zKCuh^po$eC);?v4M`BeRB38u#=qM`|j}zzOoQipInP?XdB4C_X@h>i*yV1`OHN!?c zjYN)e(BUU;MuxZyaZZ=Z6?!Ynty160ej6J$DokP>4lxffmo|r+tC@eHjystvm@As= zgU04Y<|^i@=HJY96{?b>uDK31{wrRws+udAe}XDPmCWUlKfz;^&x(|k7_-$}4sUs+ z_+qfiNvI3ujW$Q(Estd~ThL7AC}b2eTJb1kq}izaB8|!tX#hd4H|v$9H|xw=sI!7Q%qbv1&*zwiznfM`RsmBDzZO2_lJh4Zvfvb?8m?hU7R~*;bn*9p(eTy77 z!F|UA=q~(`{@fe8q#PJO60rI)yDISr};UZt)d5U}qp5b{(9~R#$q@=z@ zdk!UIzeK(TuR*f%ym7o!GTHGSf3o9)<0D!~Tu9t^N2<6iaT)0inV|G>S+O#~Q79b5^3j zq@!LlpogM`m^0!_Z%zX;pr zDe-WgMLz)&7v}*{b8Zt&=NeIUu0TF|#8t?zV)Wc4j*f#s4hh8Ikx$$ny+eK403syW zbavhEc^c+1TX#2JufdSuw6LkjP3+h=4uDKf+(~W%$cg zp-V$&gwGE5aL-R#Zdvr2=!evIEHREwMjs=pk(bCw=b>}db8Pf!q9a{`pQFzIjXnXL zC)cs)f6xy`A4MKj@))s|4ud1$ppplXqVH4IUgROLAG!Yr_C||$KYWkU_C)VS3-aB> zY}$o)CoL?#ZOGktwt*eWD{@ElmguddY>VCyy*_$X^txz?6SgpVDOeOeA8iS83IF9R z)}}273$Xu=9v{7$xKMWDKTR{uHpP~?P^MM3Hrd`jfAhR-KogLNSDE=OPK=ur*CehH zHC>loLGT-_9U$MuQpPPD4taev13#M>M6 z0lh#UqFnXG?nT-Fs24G>1|oZ74OFQE(Sk9t1`-Er0J#Sd=W5^&3|3LE29Y9~#K;;* zl&ld@FtXM#e8UxuP;s|L6CZ1;`fsMG7+zDbX2;Enn?n?@NnjC}4Mu})aa-_Aqqmvj z?m8bktx1tdB|m6BY|6Jb-(cM^o$NUv?Kp{A97#mBnbC8ir=gFgPG?3>hmR+1a`a!w ziSTiX#zc>a9veN3{3D`=Mh}JrL(oJY5xJwO^^E=#Z!cx{CO({adSLZdvL~`Tk>bSe0(#-?9Nmo^U5T&OLB(d17;jCY z>qj??ZceyR2eGN-WBFB4v57E~{9U30M-jl2@6D4jE5; zzjJXC@AMIoM-$yXH(}6k8^4>rJHs4dCD~VO0zDu#ab9Z!iGnBbJL`km#CEh0AF&P* z?<6+k&(yZWZ>$U|;IBaRM2VPa;bFNcIub516b*R9HbBwJZ^RoJZNwv*k%*(B8Hk;y zhes%>N7t)pdclaK5-C(X5UVGcXVDN)TSp!ZZ3LCp6HK6Sk8wou})XB*GC2Ref+6#mBQ=3toq`) z<-euj-i`eJ*Z^0>V2vdL=Urm9J|u?g6(Vbjc9*ELw}>?>zPl<_;=W28ScxV3oLYaa z>i!kcU0>sQL&VwFaIunsXzz*i`Ud?S_yE0yB+hX%DI!0>B_gcwk9?EQ%Kr^1mPAMX zLj2?uq7Z*4PYN-KL*hRZC0Qa9e<4<~#4-LtY+{LooRRp$*@;Ljaj^5n2V=YEC#HKI z>`=TCnLR6UyYqr5BK1pz+HhiA&-KmqO?W*1afXZ;Gp3YCDUmTUV`S6lrqp!)=sU!+ z&PaUUR74YgN0eyUkKqga195#r=vnVs*CmmoKM*%s^f&llMZF|ev_we%zj4x^M?EH@ zw0I>(`7#4>*h?h$KRqL|;qmI&w%u!Hf`?=b@2Rq6MLYJLm$6YVBZ&9A|45ck}vBKSLrg6e=Su^WJsJsD)jh4Z9dVfTe2gp7Cc8MoQ7 zV-fN5zeOzovPbakT(@&|P41RF;p(`n`PtD;_C(MDiI6Y*9TWo+i(b!O1`?6JERmn} z#C{G(>d<5#2H6Qg_DG0UyBP2&7Q_&nK88II%CXBq4F2-S^2(B356Y{Z5MtDB2oxNXl_A-uL;SLXM>Y1?5T8W*mz0`FNvQ=Stv0)G z)MAH@MrtP-*?UIzijlo#8sh85o-YkRGti%Xf_~vx-P!L&_O)upec~R0UV-0V{r0M5 z_SV_=hwlqNY&vX`T{D)j>xqvj^ZVJ~Vm~qEWj_tsUq$v+IRWj%lD#huvj@gr{IXBV zPOzPQQnp6PzA77|wqb8%kC^S)BDVn9NoWJsW*~d_tXG=s7PJur_w|weeb%a7e%40G zen8@tU4&%6AK4d3xa?IVdkZZ_lUyQYH=CfWKttIjWmNnKXbd|yja0iejX@uU zCcA^l?oA>EW7$J&0$g?(likK-7c1F+Y=W{UDogeqlU>awv76Z6&=mM2wU3zWcQ!MA zs*Ck2=h$j?{#hNrmK}hW;ols; zBYs2tD$31|KOcXFV@#)~TjHCz8@b2a7Vh z`jcI|hDG&@8UXc)8idt9swdvTczQ+kjv5%%7rQr}?y5xBsLr4h_#>(#)EV9$*$Lhr z-jV&`Iw0Gyb6h8=rAlqXo^j%7r#wNu6=|*Uw}6BE`>~HrgJGWGbs)SxGsxqhmWdFOLkkRa0*O*=HbZQs9aQ@4Y zQGXl%GNvk)u2}UnRni3RUAZ3ggi|%AXMLRdzzWFWwEj$b|SS@z>*T z$KQ&-13ij=8h=0jF8W=xr|}QtAH*ktkMSw-H_4Yoo;=Q6&PU|j%5J|)?=HGqKcr#E zi?HWm`wjaHmDm^OyXgx-(aKYs7Vdf#W*5RJb|W-I6$fl@8C6>CSXf%^SXeTu6jo{W zHZ0EGhK1PWun0Y`0K5phA?C*}6qOfQm?McSh*$PR%*XzSd6k||?UtAiUrzQ?6kksE zSIkLzZgyD=h3Cc}s=T3U@5QWY@5LNZnc1IFQnIi&O_r!k@a$0;*`+ZX$bgm!3W-V` z^_6}3-kUzcQ%rA7ZD{WDEa1Y~e>x@IR$ zJsy(;qH&f56+vZo;!MwXo#mYFeCB`QANZvIlZ_cSW?WlhU5SRqhQ@mQLZu$hna)zj z$)>xeJEla_71Kj-({zK{y@}@rJ4)V$Cz`ICZketkeI~!j3p}O-lMC{|o!AK`yD6Sj zx5;6OQ@(g)P>aOqcP*BV!>uTx6{25@d&;M_z_s#Cwr%b_tv{U4YBCygA*R%uLW!i2!fVI`M*|ZZJGaWZA0Y^+nDS6t|$JE=D zntmtWXl3}i@V|5ZmGk)96K~sHYkRFUdn3zkrB&G3vS z?0_1Swb&80jBFR`7NzPdl|st9aY9Q)_$W|AW0>MHAUU?F8FN>B76P$7@HZ8zj6Z)YmzH zoni+%hk%jJQRuR7>=2?#jCb~9KiE;uvCc`(iF{%^=W^#V_LUv(oadV38hd5jl@+g7 zynd7Yb@s>MPs2|{9*Z1p8e^Jnnr1r4JZG|Ll4*r$m1%=%ooOcbGFF=9rv_7KxRQ!i6rQxEo0 z=xXX|>TK$$wC;F1n%ZM`LF>tmy-m@&o7%EhZwsvE##Y7$rS6tmoq28M+|P4AKM^<{ zko`Iu)7qz;r=5eS>nYA@&Q;XrIO=twbFOoqbAfXPScshCod1I_rnVQdpYKZYu26in zlIxsnNm=RK`dvwzsN{=m%xH$Q*;`muk$zWLf{+h{AZld`m@?5*5_U69*RxAjfGQJYeq zt*FsP$ad_8+=SFtYA7uNMaU%_XQ4`)kG%|%&yl=y`J5%ldFu1#lWQTm zq>7vaEhJ?&QogJ7W#J3RF$K~IxUT?9WuFN<|v zbT;)i^X_|a;K9>0kJ5w|%UNu)VUl5%X%;ounGs_uhh))>XbJC^d&G85oZO2&)7kTQ-;Qfa}FN5&i( zlVtRhaZB>aI3?rJc;saDjsE8Q$S5UantZD%_$P9_zxn?Eja(!7CNlC22ZPmEB;(O& zH5SP?9s~Mvh8oKe7BhlgjEs$(p_!`rmgjq(C-0xVf0OhwN$)neWri@``4{8(FwPb& z8MB8n*7s!m@5*T3AKuN`2lP_j-pKB1U+_N}?*}se_rn^%NHqYhFZl-|`(O>g-%s&A zc>Ck~lk~nSe=l`}z8tHU^7Q7|eb9P?Kk@ZYd3&f&>I0YjV)Y@t8=8EV9tu6tyYn4; z;E{a2IevfsgF))dG=l$W0N-dNdz;I5mG3l;GgJ@$yZ+=^PX8?940on?rFA{H^5{zS zSJhs13GEVEuT0%C#UhJG`kABb0V_E>$@zZ=w1v6AR?{}-20PIt3HZH;NQX(MSH(blQF8%WuJwpr!bO8&K^O5SxGNoEM3+z9t~8P2 zlN8b9Q+FzRhx&Z6Bt@irE8+6Jw(=?K_)e?&K5OU`ODMTn$+h_8ljU5ymh3U|PRe{WbNn;cvwQx#{_oCjYR*xI*-MPG0<(`s%x$VcmD%0>H+Eb9 zg*i=iwWoV^^x&N5S2feA$4p4(ICaVQo0{?bhE-eHwXwxlQ(3i;zoFG2wLYIzU8UAk zc3slMUzcN6L)OEps>;dCs)m|Z)lizuwQ5kdIy{Kr%&P|9s~V-NtMaw@g!;_58u5QM zL^fgO)rh0lQ*$Yqmo;D})`A&T9m+IAw&&N~s@=C*F;sWPW^DE+eX$CG}6`D$?PdI1@Wb&)ig?@Efy zbw!ieugq*O|Csy!Hv^Uz;_Xl@>5@ZqyZRKF7d!FAV#T97lx0H~FSpa+ap+FCNSTX^ z7Dovi-~PPm3^)zWGV?u!6#Ek2LFU3Tzn0HhPj7zAygwat;))R!BYK7P3fmg8HDtiO z0rzryb9?0~DHT^ml|dL+B62mN<(fle31>;JNXj^iJBy-~!Y%bntL=K{z?Yr=d(ZS2SfPC;7wwr(9Xm3{V-kk=$BmB$|A>5gEJ&GNPN5&6FM~ z*F;h&W@j{7kjimw6bV&wTAk(T@xSuFR98K)HnOJbz0#B9x~Y+xzt`jFMn;?d93h5d znVhSDR@&N0eIKWm;Mva&&O>>#G%eiMd=ifQZ z>z885d2p#|5wrOvoVNy2YKuD0$;?RRY@zy0`pk}_=78zo{arHI1_Y7_EP68IeYaa?{KbALk9|Sk9o6nV-uX zeG2p!zR9Fdf+mBhKll_CWAb&de?4YUK~+N1c#@4xkg%hB;_EAambl%!Xyo+Yrcn zxq+H@2j{1COf^7FX025~HB(h)s+CO@O%<4@|75a&@}_bo6E~1}$YnB^A|Rtl19T>> zsg$X-LP=9`Q+`unD7Pt#DVr%h$YRQ5`Wl%U?R#X1DTC>A(XMf>31GZyvceSC4A*qme6ZBD%(d3F+O^TO1#EI{hW5Dj zyLP&^f}O4luCuNUq#g&iU3c(ob2aib_pH3V`1aG3*C{3Q7s)?UGeZS%LXr8H z737Z09+?w_@`Ni(WM+7JkSa0_^xgQ?_}!Rd{9ycO{9;TtzB0ZwzBj%wJ~KXtKQ=xx zJ}^Es-Z$Pe-ZI`c-ZUl|uNto#uNVVHALKD6FciCt@kYDxve99THC`~DHC{BHGoCU2 zV?1R%366pzV4rcnahGwsah-7^wA#4XxEw4tE;24NE;PI0>3!Twz>l z>~9=o{9^cQsH*)%+qz)ef_J~&`u61Bi+d^F_ufhF$?o;;4en9y;qG4UzV4;&h3;YQ z5$;J~wtE(-Q;{>>3&33WJogg!V(t*Gbgyx*c5ilXbZ>HRb?-aEyjx5-KHBlz?anzk**9sw9+8sF2V;p#%Q%31JB(5{7yDdp2L)b=CST z^4YQst1~PrI=|>s-80=K!)3!E!vVue!z#lu!*Rn_!zROO!v@0+ZDFwHRAFc&_~@VDVF_$V;kFv2jzFc=Im^fmM`^fL@F^e}WabVu)E=wSE*-pV}`d&rpn^yrG=IYKSsKLncE6&>8fG zvW7CCtf9D}n4tu`prNp#5Im1@wD56J+&N1eL%qo~oWYo?4);r;+D3Pkm1l zPcu(LcneP(PfO6+)6vtx(;n*L=?r!C^z!rsy*&LqeLa8DPD4Ed!4S_V&j`S|p38X7d9Hb`de{gC>$2yK=e5V@33%%GYx=w0 z>3L_#r$wJ;=bn>$UzxpS#_GrEvqywR=p!N`Bm_W#i2Uffh>{UyBf`*&MU;*xjLZw= z1Gzvb$Q+R=B5g#5h}03^_22a&5nuFQdF$#Ul%h}8zlDrZB zufMIo18(WB>u>6B=&$Jg`XsnV??p>My7lpT2RFOp;FtB6K&<|v{;d9t{=DKR_5bKk zf)n~<`a}97&>{T+{eJx}{cinE{dWCo{W|?B{R)Kz`i1%>`bCP*($CP(1&j2H^~3Z- z;fwJv(wEYg(B}!yAHJ&K@`881-~0aa!%7eTNbHh0)jP>M#5>YE(>ul6)!P$mf_IE} zymy>;G}=V(RQ!{`baKr0&OpxcF7hsb7JFBKRbU-+$u;mT-c8;O-i_XE-tFEUSlhgN zz5CI2dG~n_fJ0aZy~n&Kyhpr8y~n+$y#FYi_5SNU<2~=ah`i{H^~|dj;)=_m=m%_p0}{_pbL2)_w02?{nylH`)7Q}(Z9hzqkn;a(xv>sC*23#NAyp+ci=tpgYJzkS=q02Z%KQjdx3n9e5re-dkudL zUN9Pp_CohWX)kn-bx*OL>K;PRb&rtG;P>!9)7{nG(cJ*offx8eB1iyka1)%X;yb;F8ZaW zzCze#ec`^+zT#+QAdOFlRg!c)DH@-Fd?sI{&j>HaYw(f2XkU3>4BQNl!58DR!YeAR z0E7zTeQr zs;8(Pc70z9Uo&4zUn^g0UmITsUwe2P^wz$vzV5!}z9znQzCV2ZeZAFDhx>;3`uO_! ze(_iKFJ^z2-EVfhX^^d9wm~Hamvn30+I7_4a@|VZW$JUKZk=wAZijBa?vQS?ZUb-o z?!w+qj?HLWbnBrlx{c&oM~zFZuh1=})M}2f6k4uZh7?+&TdcH2x&=yKs9U64sGA2Z z*3H+=Mb6jFLC(iFhuse5g4w#6=yP;4z--+ttm(RG&@AjJU^LH$jQ3De_*0+ zqHYox14e#rLE{?zqCAB5Il*G1PIThL3_Th~u_RC7?Xuh`*YsWX4g zl>1e_SGkgMCQbBBr2c>P9rhjc4e$;2jrEQ8E%7b&jq;83&G60i&4T~!n~I!{He1=V zk@I}>eG8#QzGYw$WtLHDsc(gnD|{<`%fU+DYCOVM`&NO~zO}w}zBOp;eH+l%V6FFU z1R^(sO=uf@+koWQj@;zi3N}MKvA3Y@@a;z1iWKYy+mL&}F5iCN0pC&I5#I^lN#AL3 z68sC!`p)1#!6syvRLU{_6{vBllicPoGT`BP+WW9}!Vo|n?<%Ib#d zM(8T)Vs!O%4RlR)jdV41zv_Mmb@9~E)zsD2)kmuV>Y-Oh*3wngRe`Ef?kBXLbmeuG zbd{A9P3&^I3LpkexK(*9ItzZQE?Q^CXF-Z)hMROziW`w8oe?tWB6J40L8rxz)XAR_ zp9XLe#akN9h_{r|^ty0eNnMz(gs!Nrh^_#!dvfRsV&~Fj*X7sc#ma{*R%Tr&B}?f( zXuoRnVb|xsZWG=tJXfItg_fmWm^$H+|IwbSJFb@ZSM92(f0}d-uKz}!IzAlqC6>R zUwvQjee#9)zrjB%`PKIwe1X#VQ{(^UOXW}RPlFcX&w!Q+Z+d?^D7`-|GP6G;GQB@5 zS~~Ple-`qI%#N1XpU0oqpWmO)ALcI%74??@Vwd%Y`-|Yq?=R%Ha^p7}Im`Ns`D^&= zs*=^N)VR|AQQJouQe{pxx!_*~PnSJc)~j`C6Sdd1DcbkiN7^UayWq9T!38BxYtJChY5&olLOZSaDeVdEakRtQ z{n~xngW4V1t=ipao3)#?d%+g8Ent^+k9MPWE!a%X?b=mXziO*#50^e%x?JA!c?t6P zdFQ>I_X;HzPRv974e}54*YVf$SM&ekZwKo5>-!r~pH0!5A{+Qy`rDv2Mz%t>@wfN4 z^|$tS@OSce@pnXa_V+*&+0)+*tt-5{zZYJSz5RXAyTJ#bck}n-6U8$K{E0T$Kg8c3 z3;~0cHP}B44D}D^O@iV6kw5rY|0wh^{;^=3e>B=({)zB${z=I3{>lES{^|Z{{&`>l zSmEZLo&+lfqogutPco9#y zH~b{^-CO&o_780b?I_R_tCzN$wy$;&dRJ`^tRA2ndKXA!2hbjBt!)L`K+Ux+;LWs6 zmDW_-1g)vIp|&wv1MTnd#@hPY-?WX;>mq;G)g=-9^9SzwdtlJ@r2U&-^d_&%q1-Yw!}h^}j)T3tsy_fH(eR ze~SM-{F9QO{9pW^@qhMzhd%j30wIAEeCd=-9mo*)?*E3B9+@VP5t$as0MZ9Cfs7z4 z$P~yN$d1e$2o2;6WCJ+@c>{R@1p@^FMFYh^ao)-(gIy$`M=On92$Tn5_zDK{2U-Q% z1|IqE`L_pl1#;fbd3(mYsqg*`Js&!}#PAYXHJLO?nmd}inj4x2n#USHc&NFjeAhJr z%}vs-X|94R;2IRr_#iJA|4xlt6UPgy@$kzU8{DqBq=^NWl)R)luQ`WyS#wr%N^=qI zH1a&wX{DXg{Hr;sIf3t_<~aO><}mUf_+ia4aESTFVa*Zve&`5%k7gg*Ud=9OKV0M< z&33R8eH+-K*`!&eS+7~kTz`RPC0L|ch`v&@LNi-46S)?9o@O%7EVpa6YDQ^BYyQUa zm-UsMP~@*712Xi>(Bnm~7xS;ryS^r{HZU?UJTM?IDDZoreqcyoD62#}2RefOpj)6v zpm(5qpl@Jc;7``s{)ul8G%zqYFd{IFk|Pzy1V#r&fpK7TU`${_U_9D*q61D;a!TND ztcihXfhqX@MotC7rvzq!Dd_V9bFil)=i`|Xm={>6eDeYe0*j#e@Fn;b1(pKg%fZsX zvcQVKN=2&z>jUcoTfvTi^n^Wu-SEAE{mAWs1A!yZUOc-4{{&744h8l>#{y@tMqM3q zwblKW_dkCA_}Q1+m%B%)9;KF1r=v7uG|}8}?XKyeX#r}1wwgAY-!%<2^)+?jO_i*t z`3=<9)FHjPrlzKbrkbXzri$igB`awvYJSmFhF8-31h1&60LuS>Rb$b_pqn*ON{dDc zMe@0kN=9f5=n)#NMz1kKS_KVL8I7hi&|#O5@ za%r+?vT3qwvT8CTGix$xGC2GG9_j~%Z!~RF-KxHQnMxIN({x5Gch+Z6rLMBXJTF; zTE4_WXgL!LCWa;ES5`qhVVpS&CKgF73I$Q}|M7GdaEa|-v{wO9N~BQ+QEF!Hbobne zJKf#g-QC^Y-QC^Y-9sv(NQ#soV!Xw9|M%|qv({dFp94BxgE;4RH0hX1HPLmPBpR8f z5^5S$Nuz|TH5D}~s1lw|{-dd>sjjK3sSoOEnrZ&kG(c(!{sr|w>;Dmb@;OK|P}5M; z7`LgRX=7+(sOYTdd=Pg(E+!=|B}ZXa>{stse^P%`|55){-vM|lP5ll$1@F{v)GySp z)z8#Vpcl|nq(|!e>c{E_*!Ko>8-7E56GyUNQ(q*`DD^Xkj$E9lN4UsRt} zpMjrOpHiQOpHZJgiu!TrH2jGAC??0%ht!AF$Iu-Hhw#}QBBKBLWo(p=ad#V3Zw^ozo zWcf(WC{DZLw#83#pXN5zHPemOjMN;_9MeqJOwud@>oprR3pMjK%QP#H)@c@NmSVO} zvqrNPtj25;Sg+Zv*`e8`*$UqSqIoZf<~^E&AX<-rec+hpsAj+BkmdyX!bUlp_C(f+tfGQY z!SsX~37aIFB;!=$RMD@bSS?aFQ#Z%guZ_Bia$E^tyCUR{et50Pp?b+z@7 z>uDQ+dfNKhhT6tZeQhIcbEF2^rrH)zW2hxkGtg4o3Tmls1G;ECgD%=WpqI9%wmURX z+aI|%au4k|?PzTWZ10bLnRX@qH3GadSEQYhGb@J~VuX^CSV>pYZlvjyzm+dl?^JhH zH&y3UmsA&3XH?f!caTpaUsYXDomZX3>>TQ8)hXx%nT(O5|u= zp;``Kty%_FLQA3Ls>P~BL|Uj?fV3FCKs6sMP|Z`#LY=Lerkbdl3XM~ZQH@oNRE<)N zM;@*k0fu8fQPmfWz?SLQuequfZZS!9TX9c8PK`-z8Q&)Uy!)&>Y>XIRXrF1Hf_-3( zcAfT;_PlndcCU7icDwd8@;>cu?IHXup3$Dr9?%}qo<%*5?i{vWz}}a%7qwUZ$5riB z?RD)9=&JUn_BPTr_+9NC?Jd+h+WX)R(nIYN?E|C-s87&E`)At6@aN#E_62x`^cuWG zexrQ{y@J2R@?ui!Mx8UUrNSv$8tmI;q(TodaBx3B$%p-SJeknAOd&* zLDUN9E2vVD#VR2rfwNTtkgQ@MG2lrm8g3u0R23N$2JS0fm8hbC1f*CM2~JkUppsPO z%5r5iN0epCI8~XlSQ$~qs7jO})N*B!vXGb*qL1ci4JiFkK_nX9zG|0<2(k5Z$|g#T3PK(za*{ED9xEw=wwJ_8QiHhTYQ8R;3f_&4~T za&LKmM}G&Fjxgro>lUx0;ICCvokUkbC)X)-HFVWbmAZd)m7pk9bybjSVxgDH=<4h0 zV4vE$#=3^c^>s~f+lHVy?xivGFDA`(Eup5mR=U=@7Rap$wbA{D)K=F9wAXcjx7Bsj zbw+Nd>!j<3)Pbm7bzOB`pzgX}x*kOBL!@51Xzi=(r|S&{g1#VH2kHjs2I+?C#_C4s zM(W1vrs&4$Ch4X_6W~*I({!^i9iyACTd13XZisHUZl7+4t|xBQ6`#kw%%_>L`K7Kj zl8m%fv{huvFlDSzu2Ak(?pN+p?pAJ4ZbsUz+= z_a6mU$yW-LT%}J@s9-BuN{TXG8BvrdXiBQGR1u?$ftM*_mF0?3MNkn^M1T(zDgp{G zk-Ulmg@=$w;Z`^mF1TCaP}m_CdYdAT=qw5gvPF@D-ik-%*+>?6rox0|QW$|rk%b;V z-tbHXo)jqb;4hK>;L-;jSQJ}Evc-YecIUW4a~XNsrrXnm-7gtIMQ zD(>QM(IdqJY<-7+`6?w}ZEWx6`-LwAFdya*+?)1d9;3%egyI;{aF1NbfZzn>Boa{V3K|!(ggif z{bcxL{WPTi$4vbU{d6c=XY1z@b+LZ2ei2v&RufvMUx&I*zZqJoU#s7!->KiKUxvIL zoX{U9KIaXFwT6wBt(KZa)rwjqwMr66h0>eyoAN`7BZ`fRb&7F{iHa49rHVz0C5mZ^ z>59dQg^F2<*p6We481tzr> zP0*1QRD7Inif0+GGTw<^iG&mhg;&5TsA;NaVj1WLlis9%kHwE(>7VPhdcFRw{*C?% z_@V!)|EB+n^bPYr#6*YsOaE7I#D03c0k_Nsnffe!u09W)NpI0xk)v4jHoXH=i{7r! zhpgz_dKZ#Y??rOx3-o?eH#*<{ca@9t zhjOviiatwjl4k>hT#qLQdZ3a2fuc_pe#w6m-A^14^b^xx@~`qQnEix*A@Vo)NBIZ% zXZd^Z33`YA1CBI&O-x?OUn0GMUdo@!pA+>lk)FdJ%OA;~5dKj9Q2qejL-{@VUHN_J zuKc?Es{AtiqWp~fwER3cD?f#F7CHq#EclarW17*?rW-Hnqdy=blmd-qRulcMxKdokzqbO z%0j~uOc#P>h82dTs7np23@b5RW>^iDqgw;5GOR;g4d0003v1w;h`bqTyO<4y}TW2CwW_W8>IH~|KzQa+QM5xZRIWG&E%~xX(n%i z+!D1ZQZspTctd$(f`&*9aSTI!czthgb3Ysjm} zD`Qd(RfVViO1VOwCYQ-2@>F>WK`K(JoGWLelfs$u1bHI%NtYL81Tsi+s{DJ#*Npbk z_R_{lO_I)q&xZTt_03zJxjZw)C^p6!NydDG(~yMs#LEpa=*taEBil$bQjH8F%g8Zu zjXa1CPc=%76^tTdx={@>jFpX5p-RSDIBB>lRNq(|mwsqyY;3FtZ)*G(YHn;{{Lk3R z*xK0E*a2#9?1U?|cLv>zeT_Yh-Hm;W1Cjc}2ZQ0pq0lJf7~=?NA{c9&WSnB0W}J$5 zj;0xB8s~vo#`(rYV7_s=afNXySO+#3HyU>vcS3uO2aWrUhmA*xdfIr#coG~#f6I8s zcpCXScxZfVY@gXSvuRGVoZ-IVz8=(W)M8PoXmvWzs4kx?N-lp)TL!nr`0!O2L@ zU}q#{Ffy1KG?0)%%^+vQXOJ>tGRo6Q8Kqbsr8qt6h3P&#p9`iJr01sx(mm<+bO*96 z-IAV%oSSY+&q~jM8`E{^`gAS)Z~CwFpLp~3J5Ch%0{@);4t!33oBk^OdHRd=$KY1_ zO@hnmSJN-0Ur0Znei8M2`r-6r>1WeVrJqbclfE^5Gt!y#C+UyUPsvWp9;ZA`naY^V zm=l^Gs$fmE_R8v=)jYFl=IqS5ne(90nPYGzh6U)Olew9T!4j;&upC;Ixh8X6=IYF~ znHw{=Wp2vcp1C`7Pv*|dgPDgi4}hcKI5-7PgNvCLGS5TTGOvPbnYS}z*~ZMSQhqO=I6|BnO`%%Wc~!dGJj`kAYEo=rUA*6nVXrNX@uKBPNp@} zm06JKgu65SAPCAbOEY7#VzX#jiCOW0k;TeNh8S5%S^O++R!UYXI%bwUOPN(MOOfTz zEXvHu%F242_cYH@=q&6->rP7(r;G367?Z?wTDnzcjb2()mMPQA{>XGl?@<53-^o79 zKFVInUW2!?=dvf@x$L3rzU(%*F1sPSD!V4TEW02(C%Ys&kFWm;*%|n8yfb)=@IA8V zyM()CJ7xQzZL+P<7O);{0IOsx!7AA@uox@?^RT8>bX}|2vYEKb{WLIDHi2NGY^-dQ zY`APR7%m$i8zLJf8!YQ1>nrOo>nH0b>mlnTYcKmx)=t(+)OY&6H$fBe6|9kc`!R zMWz&!&?GTQO{qX(lAB~eZNi}i@XDa7sfMYhsR~rz)WB5BRM*tV)YR0_)Y#O_)WXyp zYGrC;Y7MnFbu_gzbvAV&=wa$<>SO8!?`s-r8UP zT4E2Rsii=~UC^QAMTv!#=zQ>5dhW2A$nL#2bH1EoDd571TGP5Pg-wX}`29lV9K zjB{(9hApqI6um1Hi>zL)(Zn`DkL8?p`Amh8Oj zlI-H_{A_o2C_9XKd3GebJUhWmHq*>ZKr?g9d~+&ZV-}k$m@Ao;<_xH&xhf8VtzoWj zu5GSoZe(s^ZfI_4ZUwb8w>Nh%cQ$u2cLjYxA28TF#5}@03>sygXr5r61fOc2VV-B6 zWu9%GZ(d?vXkKAn39T@1{2$xRJIuSyJI#B|`^-ncQRs~MEOf?v8C(PR%y-R?!AtXN z^C$BM^Ls2_{m1;%{N4Q9tTX>I8_YRoGu&V{nM1&2Hk#jHuW`AfbK5yPICtY%;^Fk+ z^!vj5!Z(sPcq>p{!6WfWN+l6VOoiAA1(E_up~Nq7W9E?LNh}f@JV&CJ7$uny4waOA zll+i;m3)-ElDwC^l{}X`#`OZ9N*+k=Bi)zWk=&NtmE4qEmt2Eikz4>*CFdolC1)h( zBqt>&B*!GjB}c&l$$rT}$zjPZ$qvaL$zI7;$rfn4WV2+0WG&wR*dSRYSs_^?SuR-y zEtf0+izSOBvn6w&*^(Jx8kj7ZC>akQEg3EuDj6XeDCsBZE$IvOmUIVQB%LK)alL@{ zl6I0-lGenmuB48nx#VBmYLX-?#gx*A+n4)={EgP7dZ3u6i+cdWsXqnprYL(jtbjWR&+dj8*ZnxY{xjl1x=k^0b zbBE*(&mDp9hS9krQ77Trpc8Y)=Z?)Cm%Ai)aqbw@*|{@wcjoTS?TgzSwH&v6$^V@H zqU3o=A7+2%cj0HDJhfu#%G4#$n$(4<^HXP~&P`p6IxTfJ^5oPhgpW@hlR7bVQtH^$ zp{XNMN2iWV9RT_f>Yds(wFl^y+9|b5YRA;}sqLUPssE+6NNttcGWFlorm4+R8>Kde z8m86-SUNqmW@?Sps;SjetE5&2RZ^9B;HpSfr>3W-5lB)csp8a>Q~}_FWWY{MN@b)n zQxgdiQc0;~h?H8MQkGJZQl1h?38e&5ic;Ju&JG!2lNGlz);ID%P7l8%V_vG%S15NGQ~38GSxEKGSf2KGRHCt znrm5XS!kJWS!7vhS#DVdt+A|z)><}NHb9#!+rW0P8|(oGEC<1U%Tdb-a0EJMId3^> zIcvFKxn#L$xdN_RZdh)DyOul9UCSfOW6Klh6?kWPZ~11yl6>&LmS2{imOmDa<+sIX z$%62b0Ne@8mTbsuNy8` zQ)nr%DKRN@)Dm&II5wqJ91+K)gv9}Ik+>Kur~AZyxI^p`+r>6A?^-Y4EG-jDMPb`tCmZxe49Zx(M9uM=+) zM~_=vB3>?DB%UvxE1oBwEuJHuA)Y3l3ZEgKA|53kE1oQ#2p=!*E$$_rg*_Hv&%XkL zU?FQZD=sD>W{r1+S7}$-*XFIu+m*L7?;$vocQ)@@-W9C6a69i7{0?}K_bBf%{7K%k zycghg-pjl^hDde45}PWRLNS7Wcn z%Gl}bBw>!&vWjv=E|CYgL>5sVJRdWw$VP0jz%3#(_Q@9EfgQjLMZhT1 zi8PQNt`Yqe{eg6%-=c4jR`gT!9r}sx2lyuXD*7V&Or-as59mL`qrAr@{oae-f~da+ zZwP-W`oBCEJr})zo`{};XQIcVN63#w4+-59-9vw0bVqbkbW3y{TmctF7r#L6YUYP1suUV`aF8)l8z<6-GALHY|Cujtv#*dtYfWR zL4WH2>rm?uYhOI>8EhS79R>y>jYJ)49YNHQ)-l%6$YZS&tP`!{p=g~7CRis~r-3Qf z>F}x68P-`)l-Xbwm`gAZKHIv$x)_=REwL`OF0{@^zXExob(wV~>N0dIp{TF6t^#YU zYtgN>Zh}@?H&{1Y*IU=YH==H`ZbRB+-D=%!-D%xp-3Rtq4_Oae_ahy$9zi+)KS4~6 zTTg*#K8kGztf{sNw!hBb&IZAT!48y;lt0P8lFNlLqTQgCsHJGEXuN2gXtb!esIO?G zXoP66XsD<^@(|R1sQpC);8FUBdSb60qHdtOs0%@7Q72JnJR6D99(01*h}w!ez*|F6 z{|}0Oq?V!5iboE5FMfK6u7u6BfLav8eQ&b(f z7HSQoYEX4iRZ(TA3S5adV-+H~NG3{$q@pxYhDah3qmzrG|4BqCm}iJspaOCueC)Zx z>B8%r+Z-)bLp@q_v}i&8{QP;g1-6&gXZVHSu(~0WHOFfIUuQ#hT0NNCFe|Wnt@*%X z^&$JM0fLaVh@jXSwif@7Qfs-j6uHbAYm25>TeQaDYRP09$rghy(MHFl+)B4GiD|T_ zVVZ!-v@wubwj@-BjblrOvuu1M79_NBZEPFQCWfM2icJ8-s3O9pHmNP$CbP+H3Y*fV zvQ>sMY-;=k`UkU$@JhB?wwlED=-;I#w&uA11lv{HP1^$Z9QWGd^~G}&=O?b@F5$Kn zb`-jV4j~S%z$2Zn!e2P;^SAI1@+aYU;ZIDyfX~8@&=<_!3g5#&3110cBEJzn7rr3$ zLikMh71QSTAlMSov-OL$#)2YXx>UO~MJzk=CK;bm}@m|hZI zLcS!t2rdcF3C|MswD6ShJUA&l15Thj3qMMvW5Os$g@?d?uv@rCxJ$TGxC870TZP-e z7NkAGb;7ksTZQ|;Ea42?>bUTb;E-S{dkXtL=^?34VXwm8&OXkAwu81+ww1PNwi&j` zwu!bywk5Xd=w{hw+7_al1?GcjUWB^9wiv&mmVjlr)e75cf>rRfwsp2O@b$J0|I zX4^*Ot+s8bn`}F5+ihFGPNZ$nZep^>wjXte?EpTL9kyMz!?r`%vJZX;lS7yvwjG5I z+m4|gvmGbuN!uyg3FK3@Ga%ZXvt6*Aww<+Iw%xE@vR%RV!7bZ$!f)H|pkB8Q}u#2z(=q2nSY$a?f zY$0rl@1`#BCc^)Ot%c1oX-?1-v*wsK7B&R+ahtkATowpkS6EwE6N-9uAx_^%ttPAj zRTox8tt70BTuq2WFNFVqXvG0qLM4$D!VIB8C@tv!nqgPi6r&_DL-_L`W~u-8Vaipvt$ zLthIwT<`+3xJ_KVON_$BB9QLoseT!yaNZ`iNFui0H>3~H5BqoU z+5XY~*RDtR$^Of(wQKBu>@6HE9cS`S=N}Fo4mG1RqikSrVE^L(=J&$aj3?j=>I>=! zY6$A$AgnZ;?p+5`36z40f=Zwwa=Ji)q{LRaAVVNSl?fySDR`PJ6^L*z;{R2?K!{$5 zN0}VlGat?sBny&=$`UYv0aZXlP7shGx*%Q<2T=$}f>;3w#0p}dGJZKf zPEgJ-=0{TmwVYqVFXV?&gM2?yA>YII@eBA)em>vJckvyN6Dr`__&Jap*!g;XCbqly z(T`D`U!8AY>)AQ+dGVq!FKl<`yXQD&I>Pp1yy21IsNj$|cn-cJ4JieRy3~>GkYOSt zk^+0BITQ}HLj|wssD#trapIYyilZ7*)T=qFI%|gB?R1!$5z`M>)oUQTRx$9Z61>bGT=? zr%$9$q%a|n@EHr=*5udV-v{Tw9&nm}l7E=c9@G>3L;M3^KYt(pAnIQ1v6sJ_&<_4K zepFitHt{#}w?G^C8~K}{b^P_n>-nqsYf#tmSMXQy*CMX~E0LG;mq9DwOZbcVOTiNU zLZl`91^jvNMf_R(IhfAp&p^L`KNJ02{%mx!kY_;C`BPA5z$f#k5gtur`Qr$U~JCC*(KC>)yKKmU(I z=eX!N?>Ox^<2VT}IW80F9Hy5Y7aY-g1?dvF?zoC{)o}yqrsFoa>9|L5-*F%HzT=_e zvEu>iBgb>c6Le1;&!FevDbj1lOZW@aH{gZimE)b`J1)nP++ zI`WCR)8TUX93E`Xb@&}#2gylsPIQfT_3-ub?Je6~R*PPT{x7#FSHhR^8GI_A&gbyc z`C>kepUmg-S@`QE#GJ-wqh|sJ(a{Ny$NmX`#E%0MsEilGC-Y->NHK3Bk1?kIDlJLS&S&KAx}&MMA&&e}MtqM@@6 zQ5!fLAvb`UAT@$FcQ$o4b~eNQ|01_`wsp32wsN)ut($*Ex$|hI4^)k#i21hV7f3TX7FV zaqokjDehEvaba;`Mw~2eBy%h?h0Eve;~nB{g8Y0^UNTMZCGZdA#}XnY=k*7HdiFo8FkH;Ok74Cjr6kLC?Q8iUzj5SAzH+|C)_0h{1@E8_xZK=l z=SS#^^BedMemZ}^e>pWo)e!ZsQ}6tP*Fp@aT1<3Kqcan(gPWXqqZC`RoVmChuF;u| z7j1J0k5-G*2Iaz?PP_!*v^(*O0@(%ZkjGho&h7Mq0^o5*AJ2N6K4+1$&>3-$jAQfYh>|(hh*v4?tU2IpfYXlfoFtXrN(dVK{l!}zv zNp+Gob2o8WJQg1AJGf*XiC4@m;Rd)KZZWr<8{+ywkXr~3fC3OepU-t7d2kB{*M+AE zc|@{ubCIH0kaD>>+-#y|agC^Et^qY0uH)))n#hmn31!^x+z-%C?q}{>q$sbrFSyU) z&$v&xkHCHIL+(B9E$(&h9r#u5Mea53W$rod8SW+cad3uvjC+J&4|hNJAa^%+BX>L4 z%w5S{!`;ALi&q(zBQN7F}*9q4d*J;;r*G1P=*A>?V=$h+>>!$0z>o)wJ>$&TZ>w)Wu z>lM-~*BjRd*LxQ(REQNvf4F|a|GG3-a^R249nQ}QMG8TmK_ z1*m~4zhZv%{3`kXoJ*7^VCw}H3GZ3{Eqp( z@_RzP^1Fkf`NQ%D=MTajt@5dEntQ)*kIxx#N48NnQ+p@%O=`z!%Q=nT&$~D~Ih(+C z&JNB7&U%7XoYkCFoRypwOyW%7jO2{wjNl9hBRGRN z1HeE|U(l1&i_?SCozsoemD8Tnk<$VGA85sC&1u1D&S?TQ<}?6}IJG&opt_vuSa|6l zPF1KfM+xMF(l{v`DJP91;qW;^Jj~{EcpNT=!C`UOoFonvBybWr6b^|~mRz1(oE*Wj zjlpD3vOn3ET#)R7oXNK2Jjf0il8wpc=!wnX9dLsB$qkZ+F@`es$L))|61p1N=h^ES z<{sg$u%z1=5FI|4gPa?0PR5!cTaa$cV~AWcV9w--2>f2-NW4@ zp)p`A&JUjK9`Bywo(`tEXSrv)=i>FV`R;}8#n3YMa)Pz)weB_U)$YyiE$&V3jqV); zyWD%-yWIx}4!cjd54w-yoSf6{WA1bAi|z~V^X|*wy8D{@hWn=b4!GyO?|$rl;C|$O z?tbZh;(q0R>we>Y?f&fk)Qc0Sw2cCz!>IqX~{6WhSfWM{KA>_6JmyED5R)PdcU-2$m2yFI3_SAY4#f5Gg1rC@&~2i1Cm;@t!z{>Y;fU9wt1=!|`xE$sRoM^N2kugwj0~JW@{v zPVC9>s67>-%AV?;N}j5o+Mt@Jj;DdAp{E|ak*9^Hxu>b8nWweqKTjKYJ5NVXXHN%D zSAy=IKAv7sKhFS9f6ovw+%v*6&NCXC?3v~n@0sG6pdbL-xpo%fD%iMElA8yY|rYz8lE&FXlCN+lF19hMpNmY|-Ce=<-C#jOK zFkX@zD4?{Y^dxCgN|Gq40-T@3!5d;+oae_xVkIRcQIqHpHHnlIlT^+k!;4u(tP)l+ zE6nnmvvES8Ct#mZ#qSq2uCSYZ8O{bGG!ePMlNePX?4y#epA zQo=LVW7Z4S6V`p!Bjg*btE_uS*I5@?=U7)*2UxpVHby>!L8VfwlvOXw2&M-~K9aAi zx0`pGcdB;?80{U4G|@ZGJIOoUJHiy~c?fnG|UY%D9H+eI?IbO3j+nb9AH&$;R-0O9D^SvH# z5C@bljgL$L>J_<g((4?duxo5_li} z7~T@MF;1IkOkBvE$0V`HECXu zI;IKenOY_eu|@rhqyrizme)b|8${C&<}ajg%&(|Fm|u{h{uO*=et>_0e_(!s-ZS4p z@0o9zuZZ-T`GWbH`3BuH=5wUi%qPspNUxYrnGczdFntPt06ju~pLvgYo6rsBb>=PR zP3BeRHTV^94f!(aMdo?r>&(;4vq-0zcW}EI%*o7OIKBTV`4L%OqAYn*_^9x!@4K(1 z?_Xa}eBFlohT`kj&o=-JLmucG1RqM|Vc0s{H^MiH@Ugye$YXpHP{;Zv{*OthQ@{k@ zB;QQmbfl@i83fbN&GOCupXT`H`Q{+c_bo)4jk?6Q7?TCQPx6Zc#T20gq$Q!U_vu_Ku$+z9N1KN&9D!YBVplIFe+vhvr+wa?n$w}V{ zq8`NL5?F=%x$V2<`(F5?a7oFck~`%4? zu1`AzlYY=(%=#kr!!bJjnSGc&pgzoA%<^UqihXWhfzJlAfz#(ea`>#iTwlJ=OQd}4;YR27c`%7) zzc1vA{ue+E`GTMbgmGUH)M8(muLQN!7vqnnGSpaq3@G=J{AA=W$({t8GHK$<_@ zpFz07FGq^H!msqJffA{*zly(-UjV*iCdh7<-Ynf!&OK zj9rYijP;ED;0*3--zQTK{_gCjUls zoBZ4So6&Ff@A7ZQmd*a1NZb5d(d|asfqpNM_MqAB6AsAMqbXI_y76 z=qUQ5{u2bp{Kx&L{b%4O;ZZL5&-u^$FZr+dFT$_-uOnUZU-RGb-@xRG{~pqHU~+g;_&lyFprab7eds;u^%;#AofsV$?HKJDEg7vCZ5jVDS~J=p zH;39VS}>Y1{zYoWXoAVV*t;>28!)0YWYlBS1q~SW8MQ!NMs1?jWK>73$*2ZuAXjDl z166~op=ylE3?-u?ydq8-uZS!ss+^II6mZt(D+JQu z3QVG%l5lmPQb2`V2}}8^0~O&_0+petR}0iaUkP5H= z3j7;r3U7)UrD>p9pjDtPky;}+3M>V6gAIbB2rtq%ZeZNpgxLuT==146=)dS!=$Gk_ z=nugY`fd6R`aSvs`Wq&y7y)JMWR1TKZpK2{S@j&`e|^EeujP$ zI!!+g&d^WLk3gs3htVISA3!=rKSbXT9i|^d-3tz5z6%_n@1yTT-3#AN-;KJJzL~xQ zX&dSmblac}^i9Z{=ga0TfqbS-c*a5r!p+zLDhJPAC7KMp((ya~Jtya>DudC<1&|EY-%}le5g~EJzp>1o=T;FeNAnRtTmBrFed(faF1C z@Sk8sP$gI$su8RWYTzfjVX!`E7HkTd2U`VOKy8BUgRO%df?b21g6)Dmg1v*?f?b0B zg9CznkOu{a1cwF(2ZsfR2gd|Q6HEwB3Qi7A2u=&m2u=rcgR_Hkg7brmgA0Pof-8b6 zp*6v^!A-#p!F9on!JWaa!7cFpP}GkG4}c@V zw1gIkZ4>(@c28`b*dehKa+}2ViT@@xOKg$YDzQmoW6&tEZesn!dWm%st0mS*tO-?4 ztc2x=DkZ8C<%!b73?NMuB}#xWk&DDjWG5yeB_+}m6QD#mIgykYOQ_! z{*Su}ckznUorJ3iHxjPHFC<(_xR`Jb?;4ziP9z+~HA0Uh97s3_-74$arDfBM% z9zWCHk-mq1hkk{AM3;aFX+qjiRwxt73gv`yLpXFeWDnUw&QN~H6LLZLWq}9Zp->oC zTP_NffznWUD87hP6k9|oViYA5CBj({-V!Y0KoyEoibO@JMHxkDAiYQp6h(5Z4p6PA zQc>liT17RAY8TamYQP&5l^2y2U5;Fe)QxEnbCrCNtfp3`KBhjT-lpE8-lATo-bA`W zy+pkX&QUKyr>G~WXQ4yX!_<@1gVf#B==Bv#^i8`4&1sX#gPn|#=O&to2q7I=Bq7I`Dp!Ns-sJ*FuaK>nNYEO83 zYG?fPx1+X&T2Wh4Tfm!9n^K!l8&m638$xxcH3`+AR-yg_RiP@VSY(GPr&ge*Q)Sc? zs(>ni^Qdg9kjkgBs5GP`cswtlSBRI!k0(te)hMr8J|Z$IvM{_L zOb;`{Tp$VyA#qp&WZ^U@BdiK5!s>9PaAgP=iXo^It{tu$ZV+x9t{-j^{ueX}w+goj zw+^=qcL;ZcI)uA~JBPc6dxm=x-akAv+&A0@J~})&JS03aJQisTm>iyhN1@}x)55dE zv%*v1^YIzY4KEBY4lfNa2`>vT53dQYhSr8Rgx80+5Nr+a3h#vWgbxrp56h1+4 zDtrOwDqjd+3SR*?z^(Aj@ZIpe@O|(k{1|!~ejR=leja`i{({$OUSr)BV^|-?qJ_mv zN|%;)iR~1-oV=^wrW~Q{r|h8Yh3}zk0b42CDC;TfC~M)XC@U!|z!J(Lu#_^FGMh4= zvVbxb%%;qwjH66Knn-DfW2<|TyOFQNUXLACHn{9TZNr;5)MpG7@ad>%Sie6jcfxLAA%T!AhYUoF0YbeX6(ifPZ?5#djYpB6ty7xkCm1?p??viMc;8`QVO z@1VEEA5h;Fe=Pn~{2uvp@wehH$X{^5h41k1MEy~Wg|&-+BL6AYK+#TLY`}NQ9|B=S z9BEV1swAt-TtAT3QNR>#9h$4~>sUwvm6{E`n zqE?Pnf~rLRA-o1s?O%8yOFcjEs() zkDQD2D(zibULF&3Kkjy1RdP*ywRVuVLwmpu@;2mM&=#Z}@J-NG@<#GzumNm>){)ne z*Au>qyasg@c^P>%SV3M6mZ7d7FCi}?@*?s=~DDvcgJVkv8oSxA|1QzRRn9l@!f5i^vF6R0c%_J|GGfg^$gB!CO=Y)9YS z_CW3k-b9Pwg-PJW#1n}ri7nY#vZF*=mR5E;=2Xn%xJPl{NIyt4asoL_DkYVXib(-d zkQ4$1Bsb(E<&*3r3uJ?5k<6rQl8Iy_=}CVf4e1x@8}yy@mGlXGgx-+el3tTulAe-Y zK#xiHNe@W(z-`hk(hbs0(lv0AbO}07I!!tYohBUvN1#Kb{iMC51Ek%goupl)J*2G! zn@Q_Qn@MX)D@kie8%WDYOQ02`MWh8_F=;+&E@=*Y25AbIL7IfeCSytCNTb0p(ooV6 z_z==SQa{ols28a_sW+(ysVk`yyfdjCsWqu1avRdWNN3_s$JL0f6^jcUmG3LvQ)({B zDS1@#q~vAEtC9~T?@Qjnzo5Q{e+NInuM&NUw&Zt-uEbQ5S)wU1mgJV?m1LEeOKc_i zC3eE`qEAUdiLa!nBv=y7c*0v!ToNfMEh#A}FCmx4m&TS-O6jExBt|K#l!?qPO+w<7 z@&K1`PN^73N>fTxOVdgfr81~esk$@+s#ID9R4uJmTDP=zX-%kcX#-HVv|efb(k7s3 zY4g%nc>dfLw1+yvJC=4S?OWOt^e!D-+8^Er-oJDJGzg3>9S0v=I=gggsk_u)TEDzz z`P-OJG1NFx+|;<~aSP+-#?6mg44(}a#LbMG6*nzz3bsv!Pk^Sxjf@);H#u%R>cqI= zaf9N9#Erl!KLb&R#&wJ91A3zNit8HJ89%Y@<2r$ks2#w6ac$yS#5Ip=7S{ycG_F2q z6jwX00jL>QBd$(dy|{{TmEx-ZAFAF1Jj&wx<9#<}Nl3_&Y)RV3wA|CQPH1Wnh2612r|Kk%1ILCB;}V@#I7pwbj^cF}9c*+a+6_ ztrk-hsJ2zv&fCgtrM7dnu&o5T+!o*mr4pdnR%k1*<=PI}4%lV@ldOqWcgkl>WK1Lj zNQpE8QXdPmeqtc_i{|a4+?#AS+ODz<1O{U_z;>mrpRG4)d!U`I zgRQl#6?z+6C)?$=j<&Y89GeewOVn&zhRtJZfn6(X+t`}JlWn%Gu`Sb<4tR_kugzt1 z+fr-}TcRx$NV8?xyg(N2QZPGk=dvZ)5^NEx&Pu}EnD<7?ts1L}&p8*ZOIEEF0c)(V zRRX8nI){D^8xB>hGNYDTeyh;XrP!RZ@~tD*Q7ZsljLkpRervY%metrwvtEk67VRG$ z5N&CoNwi(GMYKn>XS8FqGrO$L(eBY+*tCtd2U;Ku`RZuDXkVbeQTt)r9rt~sS4O)c zo$IRTQ1BXX05Bvv81;JG4g!WmZ;lSfbVKyU=ylOy&|{*bqBlivH8x|SBcgXk?~C3Q zy#qHx;En?1VpMb_ZYM`4VKY8DHaZ&f*yzOQI9yMTJ_t;TPQ^SadVh3U^nvKz(I=u$ zMrTD|iN1vTSoGoOv(e`a?`h1>M_-2jD*AKLN1`*LZ{Y4t%&&lNQWkTfpGQ|jmqlx% zbeupYMVw8qox#$lddJqX-~oAJ0DZQTL?ZnzJi z-wWJs-C*4WTyG7>*GOQrbtBx{t$T1g+!|$FM?BZz>T0X6b(Pf{Q-5$EcEhYbRu68s z^s+9qx}*2Ea;(-?3#+YlIX1nlp76U_*II+H>4)EbnB=y-)sctKIzb6qSxo z(WBA5dYC7A2$6V4qlcsWq06HDA{8x+hS2k(r?C46+k?>nK8|88ik?P29X%2~gDTrS zxIYh^i=O1Nf*-#{(eh{ku%9r4gjj`{OMFme(J1xO$f|}@8%?%itTN1jXgF%8v)8g7 zjj`h3T_A-?RvmU>liuoR95xk%6d;~+*ejK#a4$xyqb1Rcgd0IGCmlgt)nXftp5>js z^VpTcsli<-IX?+k{x8Bifqkw?Zz1kW;FbU(@>hU;j1?hOh|eJZ$KjkXdGQlsL9~L< z&ykiQs37hYs?;;Y5h5&=)_!br@m*ll1E#c(nD8r!=>(;BoKh^r#aVou01#%1dIVD; zt`%kpp{m@EM|V=rs;MBMm6MyZguXMnJNhp!ig0@rbz5{7wr8PFGiv@F-5lLPit?$W zgT~z+>U$5=;phQUcLM%lT94MxhcAeod0Xe=OFecD1%*uvK@Pcb^w<< zG3@~M!Q+`2!q`c^cj0?CaqTzhlG}sOa{aHt!=!KrbRN8YCWbONo_YvOkQPi++T(?WbJVlfGTXzw)>d+zE%LXiQ%Bkb?cBP;KEfx!rB(W5gi96Qy-5$q3A zMrsNB(f8x#C>*6UjGugBxk#@3m~%0yKK27aVoU$s}Ye$|b7r26bWd>(;w$n>54#9QBY4&YAZ zlSeoTzXB|TcM6pIeWY=RX-k`Eog1TT4E+ywN}a}awKIi(@V~O%MVs9Y=K$dz0FIIC z&BV7IRPR1aew0q-e(U*+L$aQ^m3kJGBd?pa}Cm?2Z%f9{4-(rLx&cD%DQvM>NH& zQRsvzca0<(krcD$I&sx^a!JK$(sYV4)U2YE7nAl}e4im^`H?H}3b756BF%=XpM#|3 z5bo5rWg8#_wc>n2J4TpF?P1!F+K^h#angOz@D=)5!aYVlG#AvjzanBR!f*WyRX})Z zPwGXQg*D&inRFCmt{-ES6Xi*e%qW-72 zl~$$mAf~<4n);N+u7gm;#F7hWhA1Hfjd2>2{FH&*r--A7)Q90|eA4PB!HTsiqc+wXbwWj(}MR`}>)*3;p4$TIdiO*9j<)rfh>maRr0;WziL#Y)j zlnS_7CDo9Zi(uH$n%iuw%`8@4ishnNQ&r%jkPyz{zs$s~na;9otjDzWlAloLWu6<7%V1yWLlpLk0vyLhwq)c96H{Bf9LtwyX#6A38^J&E;JJgdNX*0E`^}?oYdex3{URM@u|_FoH(?Sjw4o!RBL5afg8;^ zWuW?KecRW1)lW!TS!(?nAP?olbI!O47(ZHj7T`iNq2{HdxKv*A$$d>Ug|bQ}FQxcV zOHhtWNp(5#*O7u$_5&A*RsBsls3VsRDcuNu8<4tclV7F2l=8ZSzYDlk2?dCymJ*Ag z)=~-Ve~}S@t5G26Lb9<#g9wXMcC9u6=_Q#4au|z^;@}9kF(Kw8WF16O%kZOzeJoR zlx-9$+0;T5Hwm=1Sln2&qa@0vF==W*J7{E5r8c5EN~V3q!q@n9k@zc7)sh3ImM)Qs z`t)0rO@-MDSe9l2jggnIOC~KB$hT@cObRvIUNogtiM__5TMQ#LWCtit$IKF$-8 z+Lv}dO5b_H)hK!n-cgTZ|e%HH6)OxYa|o z15>Q(@9M`|`)kb5o>jZMa!Ok*RP9YIPAx+-P$|41v8mUpMskTq?lp>QkEm6lN}&c@ zt%DV|=Ij6Kahl`KkW$Si8tIe{nH00yoMN~HMrgrY86b4kyu#5=F^*OeN6Si}MKz!| zrqTN1P1{mR)vsb`FR{3=0}W`AqU*=27;>J7>qfNJMAO1;*vp;zxrLrcxE}WRHMnbl z%PjW$4NXr=!lp58rpmPVB*I7_Ts*@V*s z*J+GL{D?veiz5UF@x`DgokoNT7&8gN2@W7AIh`#CED#O9a=$Z?R6Zx1FTB4qE!#n%%ZlfHPuOS zcoy{(pcS6hl}B0U>?b$Mzs7`s{A>MFAFukL*3cD%P(*xMIcsgH znh@nDw;JUNh*#s(Ie7J>pn9TuvBtP_xYqhq>oTn_G>)q6=tM%hLCvzcxYv1!=J6mt zwC+BSNu!YFLUDVZdd8+Y)JbP?bfuW)L3@Loo9ch+W9nUYGcIbp)HtI#IM(#rL|VK0yH+Whvn)&v@yADALe@%8Gpo+P!i1hc z&qxIu)6?o_LanHxQ3r7}q(9b?CZ)u}MHBi?2ClRQQ(Ri1HNll;kC@HNeHNtBSzl0wrb<+fQ zT7h0OT! zoQRudxM~VNlQK_%ud+y`waB0Hm1^?nz^)N?>44HMO6A_fl%{GplQZVFR!h#Cb$;x? zoQa)}uv3X4#rR95_M_2MT<4GqH>zq-by-Ji(;Th!s^*&{xOT$VXskBnG;y^d6txAl zJIz9kD4{0M9>UTX??qR-GvKyFb<={}xUZ%K>jbzVtxapcSkjXOSGybaom%45Xs?#1 zHtR5RRUPTE8*UQm*2-6B&Z@&WQ?FVXDh?N*c2Q&MFP8i&Eh&ViUXo%`s?%UE`E5xW zRTgo8X04QQY^VWeZHHG=BD1+!*P zc$#}P&&o|EIZ$iR*8=gI&G{Hp% zDRX11cF_!INe(*mrK2foD}45}uEe#zP_)KR2W;9AQ&$s5SL~bO<4V4ObmiMfS6sHj zWpiWe#YSI5n&Q42-%0wIFD*(<57N<>uP^=hx}q;FefSD9fNwBe2YKP>qOd`V(v=_SHSH-N(S-mM`3pZdJ=vcT&Z5Q!f6k0 z7`)-u5M1`eT_?iphR?p%U~4G6USKy;*q;!(6Z7TPHMqZyP`i;onXkZKTSCny1^OnW zuSrVh<+!-olt?#H)Q}`VfX<=!gIArk=!C30+P|O7~Ud{CZpt;p@`%e6Q*U zt@d;cwgXMv-7%@W`{23@X}ppzSVM5tlknQ%whcU`;xeeV=pAsU+7WaHyOURiq1N4! z@UJ4b1L0pm8ah)xP4Ly4QtFD{9@Oa48r!becg2=R7vcAWqcJF(o})2Atx$EKnV=~x zsVlUHUY$stG=>nx%w8?l+W$kW=` zrQx@Jq|!*&kba=qODjtCSIrhVCUq`+x$)DA{-j=_+Gs&qI^nJb;nj~K>O<|ZPh&*T zh}o1dHA}icrORP@hw{~mR5u4(g4u+v5OYYi3pI-}R7-3M*0*}arn2gYt6r3PXUy#= z#rBkk`kH1M^)MgdD2yyp(9-a=qIQ#KFR>?Mr*T8J$#66-)b9l}O1UT>FZpodLo>HV zJ80u+8VQEbWAwFFBSBxrnnBhr^qlKx{Z~;p8f&@(8U?aV$+e(#nh~;6q*7JcXdkHE zQj$48&|Xs~4mxwy`c3;<8?%~9OS>1X548u>%&r}uPBS#4YR+;|O8>V$(|rK7ZuJ7q zbLR<5`(drw^}S7}=K5B&j(y5s?1MG~Yk+lpKU)_4GrB1HJ=6wvE*o&Yo7J(-AGM#p zM2Q8ULzH4Av`%tJ6s4>0N~&GW_UYs>&6J#zoHwCusI_I0UY*J4EJtTRI?)kmHm)`6 z=QFIi^?gQX2->q~ya9fLonk>$}i)_J%8> z%L(tN=r_^1(GQ}ZML&(c&wYkBqVGoE;ZDPo++%o~xaVSe5Bqn)mvQq&bbj;`((`R} zadaUywTv2EPi=0ec5+$MYhR^rDFI6PBBiWv4uzDR&UGv4XWEJAj6mnKb?jBNFVoKP zxY?~L|7*yX@}}>d+7s!Uqe@3RW_?4{&M|}600k9L?^N9*iU{lsGA(LO2Lv;sF}5X;?y`g2$1eJ`P| zrOww7Rk8%&UT4qEag8L|(soXqyJo-Ad^)@$7KBk5i zlcx2w4fQY8hx)M2h%~Zj+^^rcl~VFL>#2)o&_8wBqSdp`S1#gSUy?S^2DhSaB}J=f zDeJ(UguI#>|ASmF1An0vtRpU!mcCx=%b30eX*^SZ(JZRb=1NL@1Y^@3)W|q zYS%gk*o?i-(bQ*4NktZ8lIDKZa})BXv9tp@)$F8|mF}|)WKD7 z_K{BA5(qGY7J@pP)fb-nbG7=NTytuo1@)knq}KF`w-5DyBlR(Yc5(%v-K6G}T6U%S za-y?Mok{CF9bQr0?Ro%b;)5`qnGA+6`;uPh`~5xj{Ci zs}=Rlz&KjwV8(_%luZY6+#5>k-Tt)6!HgQCtXr+SK&^5$y4UZJ1;!%-2vLBPU!xk4=$$uKaS3(B<9juSHk;)RBVJ=#*Fb3 zJ@q@v=to-S*VrsHWu)(KYF{;^r+$1s2d#6RhGx}~K|DTMWi$GUPB)yWy7^Q;U+P=F zPAc^Uc{g!xWKLR2T;CGY_skuyFowNOY(F!8XogU`*h^g;qJ;KPVtLF<<=m{&i9!ms z-0Nn??5xb$>h`WWd8g0y~Bb|NdY+K*&G#_i-sr8o5 zT6N;Au|PLP^j%mh8=XGu6xmJfX~(Rx)!M!zt#K%$!C-1yuqkCe!$&H!DH65 z+@E~Nnn8OQ!|li0aB~Ok;!gYwF)QL8^lkNJ^%%7R#iegjT50Icoz9_E%F3(8(QTCe zAJp4ITJcZRhQ9tEH|<5=?6qdsx0ME@UtejfIbo{*&Y^vUkNl?6+H`uX?`*mOpl`T` zO#jr~g5%WAO4`A9%+PZv{};@9>SNYhAJ7UG&}V*Q?9!~dlJQids7|nTcTBgFG=pfC z)}AJtdeBZ-dt8mw+Pi7}e;M^~H9YMDWUKLfIOWn0a|S8Y&NBSlg|x5H?!PN3)(*5QBc|r^>%jV*PY+6`wHXbxm(ngv z^{+E?eHG9ub2acQeQyD!Kc7DE18v}UP`eS`D${yXHM)y$GrFgybSNyHB(^7Xoly)X zcVj54p_I7B3GK|Tqx=V<4re|aLm5t^RCI^;K5pu2*Vc=4YoFMW+H6W4=oaQaQ|lw^LtK7feS)v~nC8;EJ|OJpsmMeRc;ak|f|vvBRH^tDfCff^IF-qKly&VV&G>)cx3qqXa)|H@KCT}CNK z5B1oN@O1LlfiQJ)*P1-np9$;KSLatf0hN~4YaOZa?zHNGwDA6v?^V=vXTxug|K_Ah zCrRy0n@^!#Yk#UWZz+9N-U~I^zt31X8)_c=nvYn~Ev7Z76|4tW z(I5XrUrl>h#@MJm(;uulba%4;Ze9J|9PP4_h^Z;5*NKYKsnXn*NV7(_ zL8yaC`vA<>U^9f;7*B29Y1SI*+bt;>onq?jvK_hBJ#6WnOj)T8)C=_Gu{|}SQ_rrb zI?3#e&$hHAwJDw9wx>7rp}YnY=MZMl;lww{mE#yVo+ALbr3$(l!>HDwH&R#Nq0=x}xChdK?8JC9; zr`ApNb<&hHseNeHY)t#8|309TXY~P{@T6i}zcjX=EMR(VA z>Z5ZQo%`uLPA5}38QQH$BYIHBFrAG8?l4YgBW zPRm(}@859s2QGFqrs+1Bz9Q<}K|Q|>DbXo=drjGuQw%UI$z5)^Rd2k=o^UotIp&an^u!W zN;E?2Jh=s_xSTk3|6gb3SD3asjCgOPUT;FZm$Bp}+QDqbi%)?cti{#_Yn8PO{Dn3& zA5~+~Yq)s9w2;?nIqw@k{0(bHuBRB2?qhDblU{HaBh`3X&tP(<@lL1qIr0|; zl4u^NWlYhnEA2`2UDHP2(>EC1A<`FF{Rg1AMW+wC&r^q;<`tdBG^aEL%A>{_eXrE_ zCw+ftYkH8*iZ!!pMAFHv{&H~QMrW%!8FtZjb&8?0L)~c$G2835hHk^?i@d(c@1?bD zV8;KM8UIU8PTpqC^;YyL_KlkVU%>PV=C>Gk-eev2rqSOv=I60}gx&qa?CsUQK4E?K zEi==vz&f+H(K(Lp+4;$>zIJIO)k(6xd8SZ0T4Cust8T*S#)H0uw4?UhplifcFX@Wf z1%E!=M$Me4o5DJW)Ez(FzlgnF2&Nj zlT54{$93D`|NBnbg_!kqq(5Wd&9vaVsqrUS&ArR0GKbmd1I8eYFE7(FrdyBD5+0&u zX!g`UFyqnhW(K{Tv2qOlZ=#(HXKWk5D0(d|wJUAE33E@G4eef1&EOyl(B+-`~;6X#L5K?vLxPfL1OlE1giN zgi29$o~wI-y3G{B{!sIrP8xNuNq5YGrj_Wng6_2+WERohX|q}RX>QRj?L$VC7deM} zigqx8zmdlCx6)W(9B^y&HcYo0oXEM*6xQ?;S=mqFOlT_qFx9u4M_AoIMoZIq-Mh3N zom9;yx=*6ND72eY?Vdu{nWt_i>dzP5ZqRLxbL6FtaVn8MqQ89f z-B4$l5z_`$dOD%d*;)PV58cAj-cW7jByMD?zvHrwlGW{L-O<)t-F-qtIyffLhf&%Btp3+Q^fvgPvd>nu2;8{4uO>Mlgnq zrKM;OHx!fBvzmwX9jp!QM0*mQ&FNf0_tdn0S6%D=`6lY*PfAPsTCEdRTk7GuQN5Oa z{X6S{71%FeMAwd2ClKFI&I=jiHRAsP=)U-Fb55eaICM*Wo7rD&Cd75j6Wc(YvZ)l* zmvzHTx5kc;3f%`$i_|?{-4ps7pE}pj|0OzuTLvs-rScUs(mSkRX3}0B;mme2qlW%` z8_oZ6*YYRb0RFNY%wKXN_|xrX{*W63OfaX2_tK*7g*Sq~=&t3Tx~ut*ZYVZ)(w3$Y z%0rAkkDAd+XN%vmr`Nhz_umgv8ahW-`_=7ymB$M5yUwhx^ly;t)E;!tS^YvivHsqT z&gTB5=JgNEYRYyqz3eaiZzL3*9j!Md^cQihWVBn!ys6WQ-dMuOhmGqFcMVbESWObnfyib+5hrGVo_WXL8D) zYH$m#R^n8zz3$s&!Gi`g1~m7VW_2AxiB8dDEGv`l;@I z*57VFL#x#7d)=f`uRem0{glvZ(y*LVtT1EBJWgW1Cq|v|ze-zq9h}9gcbYj}zR&zG zc{?M`C|bmI{53g{KQH^yCi+Bs(>l7-h6XX_i~z@(vFJ|DqBRzc21db|%qhbY(P#d{ z=goTh7ivs3vP0sWml3hz68!BJs9Onz|LIu}$A*g!OgvFLm>$X~T+ZKI!$p5wvM*5jFS2TRK0AW71!^9_mW55H`Lz}2Puu6 zxLr#<{z#d9LHn9Vt<0s4bOQAjrT2lUmp7o5w>M3_yui$)FHBF<{vIQJ(`Y?2sF|7M z{Y_fwH^jA_{b2)oUROr!fsEu@>E6u__Az!GZ?dlZl0C@}tT2~ae*ycfV^*$p+&al0 z%Ezrk)^6*7wVi+bw*s52ZK&I~J=)&-uezNO(W8 zhMi}9&I;spbI$Y{D^~4y-o*SiE0vGgC+jpyE0zy{m!Mx_H}nki&uidp*0&#npPG}A z&q?DitcO=vD}hzk8d9>!+C{vIdA&*7Mw7k+)=vJcK57+Mc|0dvz(3Z0D+pa+ov}`n z@}0Pz#*SRy{CY(%i~bYY7g-(oJ+d^iJhC{lEb?1qQRJJ*xBR#MF8{N?8hL{M+aXO{WOn2o_;cWV z1m|Puk3jub|2`qT1WX~^@yJpV+(T;b;{X0T`Q!gq{`;3c8oUF%nG_929Y`JqM+P88 zpm*eIqzLp!iolJLtH|ji5ogp9HFul%4}Ottv2CYqmu;_YzwNl~C^8OCf`y1KF0t`! zr7bO{A<`6_G0l*z&nu z2^kG7jcYHm88VU5&;(T>ro_a=)Uh3j+TuXTm`I9=i?Ji0p%HSC5@H&}Bx6cKO@kwu z4vmdW2ZxdEkOAGAa61r3KcFuXBL>7=i`qx8tRqLFFE|M45(8pJ0ArCkF)HSEWKxVb zvMBCHor9&Qk8x%gWjVL*qq>Q8Q;>@>x$eHY33cOM_HG|_r_o8N^^Q0BBK2nX` zk0fM$$aNNW&2a6*Rnxjwb!{uJ(4+yU=f`y|s31?daN(=p$=~*A6pybL|bljl2_m9WcPC z*VJBF+pqR2;L6(GwcTp3sO?qTy>=_oP)5?`zvCS9%a|`>zKWe6djgp-xxmTTQ?dC# zAu?kMV~da>6GVbc2x&5(;IRbbz^0ddy?1LCfayAHT9ZWz=J$R`^WcN6qTWC;mw zF;a%cB5ml-xN%4yk~E^bkxn!LxF4BCQ;}Zu0FsOzLhjKFqd$%OqiMi5vERggZkuO2 z961|JBT|nOXB}&6B_k;BjCo@Q~y#)=Wa8(-b5;-HWWJiO6;uhdikJYQ`Js zP?8KarsfvpMM+}RFhH`U2G;)$iiw@(*}uI?P^*eJxemQe4y0ynyeZx=&Espu9`HU5#XpvsY$3wtZ4|w)Wp@; zYhr8asx9bfbyamubscbt$Ga|KDz7fB4p)~MHCP>}_E!hN;_AHW0`PQoJ|_7NAd~15 zMzcMPFjvI&ifavVu_lfTve`S0>pnv?;@z=%=h#v?JiXR+*BU0q_ zM)!#LVevP|50Aekek3qD{R(fp_EI0p5-O0C{(F;y(uGAP-OQ zS^O7B#rr0He*D+S$@?MxdxJkCJ#Rt$FY!x|rMD1u1yc2XMZVteP|MNlfTCl zMf{)ftBeG`)#z)G%l9C%^6YVmaY?)t@^;+^b*iPF)%~h_Rd=f%P(84^Z}pX+;3}|B zb#G&q-WxanhdryWfbLP<-SE2_y=S#_K@Y;Z4D4Bbd39&>uBcs5FGCkxhN)9^hw6^d zolx5YGIgkKXP}KSx2bM{sY7)u^fuLP(VK&9s#{h!1zTfkicRzCWhLTrcl4oE^Q zKrZ4X@ElMYUygjlaw8|PBEAx;4D-eKDqP5}CcYX~=1Q>INL8$tuqabCvKH%%UKbyQ zu8Efv#yThqS&V#M0i*G;_BiA+M&sk{c6$u`7$i2@;Ym*b;_b1Rlc3^|=;GIAh95?2psE6Up?jY1rqaTDm1pZrf0QeXBK-J!={pkCUfVmIqAJl)Uc4OLa zWMb~cw5w_-^d6*R?yCA5lzAJbzrihrl6f<*1K5N#&89?F80-b<8*w)bVKhDElB_ zn0>f?Bsj!=ll>N`o1sV9M*yQRjj)dfMgn8(x1!#HF3zn6Z!`LB_S-Ryv5!N)6_kFv z{Z9KGMjekn&VCm#&OY9LkNs}+yY2TI{T}-S`@K;2{Rj8M7fi5EH1L3-Cfg@tdcZyf z5IxC0%|0FV0sF)D8K@7~A2s?E)CaMjX7p*$4;y?Gb*lX_`xEwufJg0*0FTJd#$QfqEWV(qKhDTlG}c6HxW~6PTVxChKGHo&+DOdK8rTVW=mo9zlld z3?#SCK$`0exDTN}gxy2HG~hwZ(+p0jnqu_HRS!T-MegeZz+~u&Rrece0&-y|Kub34 z{ebBEt0sYW8U0?Q$KH#}yQ}U%itHq$$&ST50o%K(ZbQQCbW-za^@r6D*F9SIv2Bj+ zXx!mA)!Zuk3j5FYU+s(R-=i-v)B^hg`(mhHF)zkusr@&oC8$e*<@ROJ%kU=`zXP)W z6J0jTp;ltI0(}+KpHM69Yr*CA)%JDZO8XjMwf!$lYf#squLsrw8|)h~Nfl=cupZM^ z^v(87z!v*<`!?v!_8s<}P@6FC2DjM%M&E|ZUG_cL$bKKZ&Cvf~BiFm_`{C`x^YsXsyw{9(FC&#$_vkhgyAfBPRuf; zp{Jv!Lc5Kg3Y~_@flT5wc*#`_(bG_q;5n)qp(mm`;50Cjixbghn@|;xO;VM;Dh@pv zuJlCA@zC*@q{bMu;X0wJrn06o0WMw=eW*EruR)Ur&5fx!vY>O|wrJ1>NzgfP zTf=FNxh2pFXk)0h4cbDr#@rgJ9ePXj4u)!lMCneLS|cU9Q-jW^ZP717ZI9&XE~wjR zQ^`n}ZOLBh0CE#Gzig`9RJpNoL*?Ge{gvA)H&<@0++Mk>a)+U|VcG(B3vSj`uCH8G zxw`WA%H@@dE0@tFI7HU`E=!D zl}}VYT=`(-l*(z96Dsem9A9}~<+#dQD@TGODu-3xPWF`F|ys4BIkVtus&gJ!e5{y!*542 zykx~6N;sHs3@P%tNRcl9g2mCw`OobK;W3pAr`(E=ydQ zxE%Z|abx1T#4U+C6L%+WP28J!IPrMmzrc~iqQt_)#fb|Ow=~$%;EveaVrSMpQ}->k z{wgm3)bi8!IbJp>lPjiH++T4|#dvUR#jO>ifgu$)frBfqtr$>oRYkvwUKPD7x&oI~bgpP!(YB&x zMY9TDMU#qj-mP%*dPQHxOz**o7aKyl2UeDOW zOBy@Nw*YH-Wn(q)TlrF6-&oEY9rf>X{KC5&eJif4u+bI|)g7pN2kE}gCC*G7k~D<3 zM_xB?lzhc2Cv$nPL@%NI%=;$`cppV?s{Fy5D(iSQNY@^Elu@@>gulgB6DoqT`tJ;{@jrzSs~{9y8o8Iphc;{&;Z$jy1sMWk7wGrIHTT^>@ zeQHngKHjA|n4F(n0B>{hr@ZE}v%&TT^SO^Ps%m7_m*roTH{?|rmFJtX)})vYfK)vL^2Xt> zrHe~{;kCoX=1s(fymKNh}!E2Yd^6urG zysLUIuVFp_Jj5HBGaQdQo^?FwnCW=I@rL7N$7_za9CIA+J3e%L?D*0#&+)b68^@2p zLf|*YQpXC%ACA8qs~l?`>l~Ya9gb~|J&yg3gN}b3haATpg^pauNyiz-c}EG)t6g;X z;WTXQXk6+DI2?^UjVjqI_OBgO+oHT_`Tx#;dpz(?!Y#th z!rpL`aC+DkP6Hc-lfwyNd)NxcgEhQSTXpW@xl&%P4WBDISI8^21-x&2l9z3d@Y3zS z=XL{s16z0ncN?l+#$9)AEwAM+J+~NjF)!)e|$msjE`Arf%a^ z;ccnAf&HlmQjeq_PCb@-JoOAvz{|shyf=I6XHM3+^$txw#V|ottc}z2*pEagup~anvc^2HKF)4(ZMtu^_Gnk$zdD5tl zqdr;kSjnSsoI~|;eS?QjH zt~^YJx(|IS`UG&Av7K!AawGeDaWNJCeI@so+*LB(#5$U{x34VeSE6^Bo0c~(Z;8a? zDT&h(`!~M2@z-hJr2UchXWGxaxBYY4!nEJgeob3|z9emN+EPqQfZw2gMO_BHG;KxN za%l0ELe=wrhx!$p-;Jw3(U*ZM33&yuGHsQKV>Q&D&})poCT%VBD%5ppf1$25>ZY{y z#>KX@&1tLQZAD+7wgGh`xFu~9ur+NP)Miw{7I0_U4#VA*w%xee4o^O|V6zP$Tj1|V z+XcNf?QeJ+)BZ_241Yb`ooT1iPNq#xot#?Nu&$wQgfC=1FXFxF;Uz;$d?n2RZ%I2| zJ8#KL=sBqEKwn9-k`^URf%-Smo0K$xZi2mdJ}B8rujlznvW>}0SYBRJmnjp-1~ZK6 z;iYxa=_PI;9nkCRE@)4Qv!pTTHhQXgo4qk_w5I~eyw;wEo&@FK^>&`{g_fyNNyCx^ zFd0=ZyC;<-V$%EWQd3Zq@R5jJBjYBi#EuZ{M#K?cV(}V$JSoU9?$`5LcSS{6#m%;1 zHYacJohM?4)9I{EyTp{z*qQ8X#KtGinc_@^PQsLg-uOQoCLO3$Y-QgVlf%&UJcZ*l zE?iC*wrNf`x)XFcJ%CIeXQnd)N_v(v9a?%ekO_F5O`tN+n>l?@IhcLUrkHZjn>m|< zf~HU{0dZP7TRU4hTNqtbORzOAnnKA%Gt?HwzKyeuF}HKpLlZ*n0$lFw>O7lvI_;6h zk2k(vFIUwKtt;hC(w7-m7KRpu{tfL9?FjuH+7#Ll+77inv<+$l>Ww(R{Izz3;#rn|d&|1U!1Ma5Ks?eYC)}pQstqH9}{VVhbuoik5T$z@G ze}-0seg~I>_3Ce-rN;DY=vQ1W4J`?Yv&6t+c#A^|!Ns8kLYC zC!l416nG5t4Cmv{C($2uK4tXBolm1bVN6dspTYD5>P*ZtozENdOy_e@&tvn9^Ce)W z^I7K$z%1w&uzS(@n$cfzz79Rh`7-=jxR;yfjoa7Y&&2d9_=@ul{5*p?+xZrtR4#Nb za6U|$Zg3281bIE^6sN)q&M!F62f32_L-&U!hbD!lg&qW_h8}>PVmOmR6QL)CCIF)E zN0)j(ZYG58gMPr6WOq;KUSoGJy5K%I;@us(1Dp`LD|CBkJe<2QkAu1sT`&%~BQz#7 z)^Kixx(yfuj1JujcTDIO;8tK1a0@uXaE6D5qmK;T92y31cxWip2w)iWNc@h18iB7{ zj0@Sw|6pLGaeq_j+R#AKbX(|ppb%(M+N4z9)aLSXSD>PUCVX+M8UC=x4w~&ZExLaE>~Uqn|`Q zfi5@+H`jR>>nsEh!!Lq5gX^RCI+(UUEsbHcZEU;P7MGe` zI(hEQxyGTCP$U=$=7gGr;zMzvgiun*9ZCzuVseBUg%U9*h8h^XA-3^gV#p3Qge$$? zJ`S6Pm|_W|0T2%rgBl;Qg)H3J5rP;Aif0+9Ls+KhTAnzG1a0Wm!P;OQSOX=HUKP9) ztTa@4umV#Rs;GYVK<3YUgV0%7NS?qf`STvxmL<3@JG%00saU5Rd7DkeBi6#7r`%~zY2a9oQL@f zqs#Ou<}Xk`G5W{BIq*J5{RrONpv-en=VFpsFb@|WgL82sU!S7C6MWyq_S6y$weHs0A1FvEGx@$J@I@DXh>#jFl@4DW` z-E7x;=(An#82ugB`-cC4>q9v2xaPP%!u%d;J$wlN6W7P6AGtn-ngh-SKf&}FAbPHA z9`G5Q&s|@jegJPC_<`Yk4E33@7xfkJDImY!0P``Aa^2$koc-hjiT5Wiul=+3hth9K zkI~mU2ipgy2B!x{1cwK23=Rq25xhNk6ZCNCQNc06o8jJs?a1J;;9yLnf%-v>!TrtXLs5qq^AP+E25vC=^}*|*t`80h4n(~kbwKbsOoPy`0YqIL><4dP@G9WC zVETc|NL1hAJuUBv);N~UE_VJzLZ+DDwv|_x7@G#I(;9npQIQ}1;b)9vc1kSq7xK6oF z8~uzc7dQhguGG^&F22qHd9DIDxvoN2KBjz^-&KSuAGH`P!nVj2bOj9UcZHz*V2PoE zs3op2Y6vy#I_D|{gQyn`_kz*GE)GLo=Urv4OPEVtymD775!d8mu1=dihedLB3DabJq5o|2z)aLT|EV^?C#EHn+2p!x#= z?1F(p^kASUPzo^VJrp=-_(uYV1A7DinDlK8YzaIPd^GsT`A5!AubEae zE8)cicbYRzW!ui(-hG9;hx=;xHSVtNF7CeWtK9ui`?!09{oL1rJuwe}-xpU`W9|nn z==&c>bboB5UTdgp-2(})KfbPmGXT>-D5--0se|1&pkD935nbvKID=7d0&fIv28OtY zx`(0P1dcGA5$@q|Mgk)M>9@E?L5*^cagWAsq%qwDHOlyu8`+M6GaMJUx^Kg!;9|CU$-Vb~c_#`kl@D;rG z(C1#c z2Hr-UZQzZ-8<=N^$q=4_HEhv$mYl`Nh6cA zfBnn7(!J2V*!{BmCHLpEBDvN-F(bnyT3vG#{Dh$HR}Jt|J>iZe{_F``A7E;&_B3;g8C6v@H6ns zf9fJqDAQtKp?d+i#J$ul?yv6O;4Q+m+;Dz%FN60BoF)H1mp|ib8EzKiyWY(z_g|!D zzI!W>56pGWb(SSvNP3Z5=K6NfEz~u1ePB?aN1#{Wn!r`S)qyLZ`vtBAb9SH!R8wQ?4tPvhZB1-V1EbkhwXJAX@i6bg1^6cLqw71@Dfe0TId{li z>#ox=le^q~!CeiMy35>`fGY5U`=YzXU5Q$Sjkt1g5w|j5{Ld`6wYZaQ1ghGEschcL=IcPt;vZJ6UccJx@(cx>X)8yLEQry;r>ReFLa*^}hqlPGEm z*wE7my&>FGqc{3bm#<_n0Zc*_KMDWIp2koPxQVz=<2WhBgt>#Zb1dz6n!buQBbkctaIDz>PD4T=uPUH3< zE{>reHM}FJa(U2y0N!E$en4*iK|ko<2keLb2itxA-Tu9p_Mq>9+U@__&^wL3%fAhM zw|_hOE?^t56WnSzo1wP*H~TkX-h#RbdW(V0@OS!m82%2Zt=MeGW`ps+$-mLR&cBwB z_mcCK{_p)i`-caH1Uj7Sa_&#w3EvvOBYsii1&uejH@YA6O!o}-+z8y@8RNO#Gt4v8 zGu$%@dKCICp3$C>sJDW*z>_-G=%cU^cNFSa)KU1o!|1np#(8doI|lPOxMPjYot`_< zZ};2<+yUI}xyMj* zr-IXfNuJ42lmBxm9}^7c0raV!2SHI&Jdb)FGHw;md!BbaA$Qna-S|@D=JuBMGG0CJ zc&^jAYVYhFX}1(> z$NBGrzQg418vnKQlzji{@Y?Xant3&|5}!-#>Ab>u8}<03=R40Yo+X}To<*MTJwJO^ zcz*LN^!y4ef?9^nub$-#vc-D}a@N=vC-{ zf~yUPyT-E)TKal$E$|n($+H3O8qa26JuWtQHhQ)gdc9|xXFH}1o~=+@;ch|S32uh} zH~J=YnKpu3Fv;gO)UBRfCfwcVTk)}pa1VG68ds}G>+9~f+}j;n9LHl$#nhG8lz+)i zx4XZmzq7xczmvbaKf|Bq?|`Wp(8QnPZ|QFZ)fB3UzXg;JT_8@*f39S1=5Oxz`m+ru z8_H+E>(7EGekN2FY6j@>yZx!elj3*yll_VQ2L6WrSij|u@yBx|yt25iI8uB8sNfRB zIUrCR)N;8vzc{z}Eb6J^^i%1l)AQ2L zf`#d!^x||sYH4~ny)6Ag`la+ru02<#N7AE47-W1#TtXADFg zo-r(AWX3HSH(;8QF)ibfjE6HmWc3zGE>3=xcRLTCKXCpb`tw6Y4;4KFyi)WU>XSu} z6+HnwUNpVvL2!D}q@o9kCIa^q-CJ~5(fFb}ipCY)Ry3w)G$?gg(FpLSq9H{?i>?E% z2l^HDExM|xe^IZZD~fsomlt&{>QdCXsC`j~qBf|li<%bY6tzOlEJ`o(f*v-ZX+@2S zQi~i#2}SWx_9Ck&2DPrRy0E6OuJB@EMPWJWc@A_-3QG%%3kwPh3;l(;g{KQo6`m

URnLJuFSe7>ss)dtYKLP-;S$1Xi#q3L{5y0|BvtzxnUM~506TA(*sa}V-u{X_| z;dOb_z1iLzuh-ku+sxbC+rr!4+s50@+tJ$@?C$O1?FRJr_VHfj?d$F5?e87ry~aDx zJH&e<)G+TzaH#hd@2%bu-kZInyknq7dB=Kh_m218>Af5EUhhQjB=3FR$=(OO4|%70 zr=dRPeZ>2Y_dRdx>=xOBT|-lG-d%aU^RCK!D(~^U(RpL?M&%99 zyEE_ZyqofF%o~y~hM+yqmKU2B1MqNnZVgzO zTajA^UdS!Y4ds@Aq1+;X=98NToCHn)M{|$l{+oL+_dxFc+&#Jbz@53>3h%jmhUm&)4msdGkq`n-T+?pz2bY__o457-)!Jx;6vX> zzPY|ne4qKg06z20^L^?2#`iVqm%i_T?|`3xg}@?ksc#vu)c1$)Pw)@lTHhMqYH+J> zlW(1Gqi?5gJM?zn9^Y=?UetZSzrKUML%zelW4+f)C*D(~_ujT+MV1eEUqfnm@|-|gwbPOhZbRI7Hhd>GLor*P1#3rJvDQ2@ zOvI9B$~yxyD|;&M6zoji2_W&KdB^cj<{i#EhIcaW2qM`gm34-eYk-y9tOFvf8)e{07?HbmTNDp)m73rh=-YUNzx_4N=u>Pvm z{$T^){lW%<{z?x(e;D>Y-XPZGedT=wf1l{kFkyqjJ_duqQ0y@D5M+4Rr|6+*VIPNm z685>W!_b4NI|Mz1`X6FHeqBS_A4dMjuo0|H@33)U6T+s1O=5JtsMDIG*E{7Jl}miC zeZGmAAV1q~@4Q}l^TE8l@AAgwjn12$_jTS3Fd=WEVqfM>&6}P#DQ_}*in5c5OvRs) z_f_6BB2%$bu~Sr3>?G<=Bx6$Ec-ol)A4A)d^S;a*3&s%@_60J5C61zW3|b)GNFaKI zVxQ%GM&=iJp8zTOSlQuuL-RhRWEgfhks-u~D^K!=Q+E_rYL3kN9A9E0pQu)ckpDRt z4O->3W(4ic^KxEv;NF3I9)9T|cUd~u=}_k)r~U`R_JyquTN}1FY**O!u$^JQhiwh} z2mUvnU?3Esut58e#kKyC%EW3L4-2QQ(o zV=o4|)u-&G;JM)0;Q8Q%p#0?_*}(wF3VMS%Xjjn5GTgyT;0W5#wxA_=2C)WB!4tt# z!P9uhgNK7hf+vGVgNK6u2LD4J3hoc?Q`i&S8~i7@3+xPT5AHy=fxm)VgTDp;4E_=P z1#H3F9Q>8YZ`iGrZw+o#(M`ck*q?%HgBybDu^WTSgG++z@YV*`1V0Q83L5h2<=wt= z<4W|im}g#|B)?zleyvUsogx;2uOeok7e>qj;xCO@60ta9S;Pt@DA=@MVMz%%ljMx*g3%)yIKM;K|;sA0u z;!wnY5l149DLD~wGU9l|>4-BCCd3?JjmU_wpluQE2uFk+@d7utm%5rK%Dh}?*v zLMS3cBwIytRaq$FTm&a65qS~kBQA2A{bIzWi0j~r(l;Y+N8BXpXYaFt`IMYOfkK%# z-8WaAU2?W+Ff4d4_j>Nb+^4yhb1&uI0^9(G2`=Ye$UO@#Uo z*||PgcCI_unVXgC&CSfU!yUO9AT!sJYeKBKr*co`o`E0DJqACXdnoq^{J-3Tumj*A zvJ33Z-Iu!~_wU@Dx&NTI=l-d%E%&$Ft>CxZExEtuZp!@`{F1vN_a`8FZSKn4A9L4X zSAq3l4OmCXYOpGI1z4H;L+N)YrqeY%V57mu8Uk7xgPu)xfy>WkI2w64@@V8qa0D?&n$Tyk8NeE8iOh_2 z0@1EWPoy_83+;={i44GUBlD1)$a9hBBhSJwL|%%#jJ_Os4P1%54)Xa$qB0?Kf%q+= z*CKE7$nV3*$M8#$k0M`27T|~V(n9s9s!=IXsZsqnZ)?M|?~7i3_pZQ!TCy1?qdO614Ds=)HV4`6vc@7`6BnY&$g01$AqY{QR|~NMD2*$ z8MPwnhp0cIw!ybYZH@XpYIoE>$iAq(QM;lJMI8o*qK-rzk2--q9Ca+}B zHE=WPZq)6lJLm^d52GGO-A6x*dKvX3>UmT_{-N0OsKU|wL_4}*bg}3X(Z$iFK`Fq` zA)?DimqS-U$`C2fBWLBK%OaIvwW4b%TN@S?ts$#YbbNFoJR&+gIx>1a-+)~AwJh7Z z>@SahemwNz#~1ep_6HW_EXeWZWan(n`8DVFoWF7o=IqY-8MZBFGqLSCTXHsnUnu=G zXI;*kob`C?a@OYj1XhD}*cCZHidcT{pU_rm`jwof~~I`h4{L=tt4FqOV8ajJ^~71b!bp zj(!^bGWr?*v*?1FSJAJc3-Q{#m(hhaMKuMnMKnz0z>8~2YD#HJXv%2HYWQK4rh=xt zriw;dudJ!5;g+tZwk8}_M-!=uMxu}yO#;v$$skdaqS1k5O}gf7jX{&9X`pGSsRwVY zc?UGsysK%dX{LEk(^As{X{Bik+H2ZrI%qm+IwOJ(pgj=Yma@K@KInFu0h)pE-kL6& z?wZdu!!*4#{WaalS*Tg8iDdko=8hycri(4S{rzpZ0;f0Tcue;OF&AMGFE z{~SHqKOB65eCq$$KLi=-|Ik0k{}D3K|ABunGQj^nvHt$P$N+yY&>QLE@8<8}@9J;w z@8s|7@91yiZ|868Z|!g4Z>iAK-^Bl}zq!AWzrOz+cs+kzSbe|VpYAsxsUQVt{mK3W zf1E$opNNk1N5G=|VIaa^(_hP9+h4<96{+H{=&uB;=r8B5fL9H#(whgW@Jstk`pZyK z4*M#*kiRS_;xFXCpZzGiDw)r+Ut|{v6b@{*oxu>ZWQzu3f z6B&~pQ!ge4n-*h;sT-q@(Z;04G>my0@9mfdlr@TZN1+M2aZIzACPbQI--~%SriDW5 znD#NPLHn4_F&$z$#dL}3iu45CK|jz3`5Jz_W^&9VWNOUJnCUUoV!n!*6Z1{XteDv`-zvWA zv!lp}K}Th4vZJ#jvC-M#Ad-4DvTJA8L03hpWLL_rj4qd5KDz=^3Y5$)on0ckXm$~$ z3uG6By~ui*RUrF$*2AplSufD{vL0vMN8ii3lXX4ow$hhi*Rn2UUC6qebw2AXay~03 zE5LEL+wYxH;=6d0zC9tb^cu*14>uS~Q$!vBP3V#Eyv_ zN68nlV`C@6#-k@AQ)8#aPJ&H`O^%%s`&H}=FpXB`fElr0$Ig%aK6Y;GJoLiY1+hzF z7h{*iu8v)SUKYDHc3td`u`AIVVmB)NbL_9NB3omBL;oK8SL`;}-?6)pKhb+*ccb^m z?u$Jb`!8}J_DJl}*u&tY!im_^v8Gr{>=|?h`~=L5K8`ja=2%;-Bi2fZKQ;t6#d>11 zVz0+urJdukH)1dIsiwz%A2BDQ0>2`?{P5hv;%7^r?Uvm+J2ESpSN*;6UG!b@mCh=e zb;EZX-0(f}J@H-hUG-fD{Or+pfs8BUoX1{3i=2a>^#!@bch;BV<8;X9_xXI;KCjQ? zbAwEu17!LvzzWWQlfKiwQ@&%qBfjIl!{CVTU*BHef9T!5UA_apeZHO8eR%&Uwi~w7 zw;kE-+kyQX_BVD1`cL0q=)aWRhR%=wLwizdpKm{U7oODEN83_o8~KNQhkRRozx(!3 zdW3b^o3$tF*U+z_LiY>Z?^wEH>G<&Y@JoDW%iah$KMFk_J&x!&6`KGXk4%i4q{_aGn+hfpo5I>n0@Lv);muHNDr-C=ZYEmn zEM$tRF_reD)i0?lZBK}s44X{Z__(p;OpBXMt)Z;z;<)ePw#V&`I}o!g=5Xy3wMUou zqJ;CdwNWnZ~LbEX8MNvKK2dpec=1TH_|u6Hy9b}8|M26_A&AS-p9TV zfkY%WjEs-a!{9?z_9w&#;eG5INZu#N03sg|dms4#2+1<0 z=<9>u2i6C_CtCdOz8=2bpeyJ_vQ zdb6}vzUHj=C#tSq zBhC&x6?ZJ|1l$5U6K6(DXcMr+SrHR)iJT-)%1_0eR;8!0W-`uTEpaxOS^3hcv}VOR z;xe(;IA@%PSVo*X&PCLP%_Qc9xhV0Vy>VH}`r`bGWpO~r0vycc&=8js7XW@FSFv2I zlzZZQ)OFCR7oLF*;hC^Gap(B7N8^sfU5~pN7sUT1ZcAKLd_;UwtTwh#jlwlr6>3rF z!>fa@w#os%mcGiqs=gQA=iVZ|BEHh#vG=jJF#MJGh4(4)4F4JB54;aykCe^V;@$V& zQ~HkgmiG?0?Y-f>=Dm)0)qBZ%8FtZo4tB{K!d^uCy=R$z$iW7X9B;PQ1G2qtuhZ*- zIlY-)o7WC9yk?lydj?p%C%q@Ir@e>0$I!>U2fhEnj(88i4toDZ_9KUg9Y!A^<0#QX zuS@o0|MPD5Zd2L2ynDP`ygz&Q5es;|-e=jbvPWL}{8Hd$;N{Y)KUAF_GbQGm_&M?I zz(_D33@dsgtDBDYXKUuqp{72UA z_+#;>;QQl`0&~1om7b0_#b?K7sWl$28Kdb^t$VeWykB_83lipoMG1?6=%oqE6P6?_OIVq(3brC)b;3H>>V&lk zKY{g1HYNO;urcB1gv|+C5`IB`Q}`p{kA%MxwjtXUb|&m3@&}PWDE~8IEBQMT{!zYU z{hM%5mF^*CO|Y|s)mW=)Z@p%%reo#Km6ttR^6dJ#Tj$DUmCowt8R!Z3)bf<~RQBjR z$)1Xy>YkeD+8&Lkil;2m>ewno$^+5m6f29ajFdtuddhf;BV{}#JjFaE;ct10g2J96 zoZndnJyCY*>q8SSpg*iP*Pa`^ShRRf89ZNu?uxN9O++|}LH+?Cyx5EkPu?=I&qi!SXhg_LoZ zbQg0MMVD|Fau;wHcfZ9yOnvDp0)LKu<$4O9yPmloxE{NnxE{Lh!S1{6x^Bbnx^B3x zy6%80t_$!Ru1nZ_Iq%AIopqgeokL$FdYQ~C;Ii^0=bS49SLD)fIH*xHh}C zy5_m(x*z5|$!U12-YsLv1|@rjcMboZt$#z(hNON;eUs9Yj7fu%1}1e%>YCIssa;Z^ zqyb4i;XRUi!}{X)Qy7%=K3;z^2cQQid_Zgv_%La3($J)jl7=9kf?;@{C4G_fIc!AI zn55CLQAy*H#u6KyGyygWJ|Ss5dOTLxgrqOQL}WtJq@>Bnm*^>o=&6cLPnw3EmNWyI zik^`)9r+4AUFCm?9{qX=(#m-FSlanIX%=fVG-+!#tq;Nzc^`-#=o+ASe^)=n`?~rP>5uM>i0uXXV0*fHC?-}! zbT`l)bX5?qi>oudJFGXgq}Bdv2?MAr?e)g{(ACk^mh~Az3%y*;T<^NZyS{Y2=W6Pj z?_S_O#k@z62PGbKDc_-dD)(mINott1CTVTbzexv^&Vp^go|LI@h^TN&k~!%F->n^( zfLV!!$O*o4Q_?AA&k#RD>~vB_l9kx$Bpdb&wQZDGX~#-#W|9LdUOuEvSCUi3U5dGq zyvn+=*b?;1Yifd#do*`4Iku$V2c5JW#mr zybr(cyiL{%RbEK7T)a?W(>z3JY+c}{&y;n9UnwXL<6b4EH+ zJ61bf`>}S3cA0jnb{aBCJ5T$)b_RTw_G|4o$Smz_>}=RvFk3rQMQ3WiB5MYvv$WGy zY&MZus!Vdf*3N+o=1^-6mf4t z*dejj%9hGAdH-sU7i_Flnb1tcCJg zvAp*X@tZl{g~{^YRcPjH>TH5Eb2et(ni7+Bd`F>)v%a&QiW!|ovKr&pSGg_8Z_YCE zZA)xRYBVI$j`Akds>@pUAYycua+Y&ebyamO^)K=_xY^|9gp#95$}le#BGF&z}p z`n4vlMSEL&Lz|@yXoL9YwU@LZZ7w`ddl66U1?@TQS$G~LB9a+Iizk`qV8Q=ooFo4% zu?y6Y)};1D?PaX!E662;$Kt_da7}v?xuWb%*5n#;i`Wg;OY9x(J>}ikKE&SCKEU2p zc%Xf(y-(IX?IZ1VTDVTBv@Y#S-E%De4!A{I@+qEcpV0bstrO^zb;*6T{j|ZTVASj~ zv&y`Er@@^IJd``cHOzI)amaC=pTHG&7I9v4a3j!h&GE?b%yH9kU3qsL_f_nM;|}(w zyyg#m$MF#F7G;kdcd-u~_t3ZDl9iu#*Kvb3q{a<}YqWd=xvJzc`Wnl<bmTe$iUk~g#l+7g|01=e)hjIRD%s~*jx6CC zH?2La=|l2z99D6F|_@y^Lzk~@+i zwsUe#SVwFd)k169ZAqKberuu~)mn8UOX?VsjmaM-4^~U7om?w9EGZ)CSj5qY z`o-!O`}o?&*QWWV`c67dIp#aQbL@5Oa!htibF6l(a;$Nzb<72;9X~o2JC-1e97`Q5 z9LpTvgXOUA9Sa>R$y`EwCHR4|*2EbLpB^&R-u zF%QU+=fl5oeC?Q{yl)({9J5tq25b&|2AECx498T*Ozd>*ESTsyD)U>~kY$Ot5H9uS zdssuS#A?iYN9L@T+3KCT~sN4Bw*cR@Q3^_#OO4@7 zu&u1`U*He0E&1={?TY!)_=BVzd^*RzpSrj>8AcB&r z$}i(6M^068A{@^$pJqnWj?}M1Rwj24%k!(hP?57m-U)9Q-i@p4>&e%W%chh`Nl8gf zDGkCvTuN+8jg*=xnv`f*TuLNJ0P*mIltjf7@nT?F6-i1-hNYyWrRdQ56k|#{`t6ju zu=*(tQ|cj&kp?NvQr?9%Nok()9%!D@GNnaI>y)-BZD8$FI;XTtX_e9?rAJC9Sof6P zDP4*6!uA6FQu-W`oSSnTE`Cv@uhknVOlFsYA4x$*iFk zBxc5B#-ZafBQiCav6<1>$jmShkr|#@J+r2=!lP7XG%Q+W@NiUS<;<#hQRu3fWi!jE z`V}*)WP0sxdpAc9#}5DBex{=C*vi_=eyJI+dC5O?ZkO6NwQOqn)Do$0rADX5rB+X^ zgw#rnOpQtnORbq2o~i*6crmH5WW=T>z#@^v*EWtwLTWs9lT%YuwXn2QBP=O3J+&T8 zmukQ^NPS!BcT(%8HcD*@8l^T)eJ`~ItXXQy)K;m@kv0nLQ`@DsPwkM}Ikh9`oZ2I` zduo@|uBp92PrSaMUuu8QFZKP@L8%|44nz+|2B!8=@&05=p2P=`H6Zmv%0EdRO1xj{ znA9)e!&yeV)Qzc|QZ32mD=tSa4_GDraDklzI-Cm8XSn5UEi`z@nlH9UL zwx4pGa1;y`4z+#O`q^9jM)oJ}wVG1xsio51N_&}FFf9ULAC9QH=^|V^p3fS6d zb>KBXIN--I|Fd zH?3(}^RyPAd0OkVHfbHw+NZTkYn#?3tq0N}t!G+qq(|C-w0^JwX@ijW(*~yvg$+&{ zmiAfN$4ZY#`#fz_+Q_sKX`|D=AUYiX6EFfcoRZPR#->d~My7q4HYIIp+N8Ac_|xF? z(&od*rO7>U^p5tGSA;W2FPj8j{r*Gz4NBfJShmtsXGi(rpG?nnJ2gYfH8z*%ECDAjTGlMBAbe z9+|d<+rn%$ZM8uSTUDeQx{|Gets=IZtt3(jUCdU>R>t<0tte95R>W4oRuEpm_B`W7 zMnUuw@GRqT#-oe};30Slo*~aOB==dyJ@_j!?vr^l<2v!@8P_syWSq+gW|X#-w;j#+ zFXQiwof((8qdfi6v`brxY$8>w_Mo`vCTV?jzk0 zydk8J#i)Wc1Bwp3yR+2WX$s7S<-C3#=8=E~7QN1Ch4q zws>u+(UDlkjBaFg$>^&5t{L4`^o`#EuQR1mr=zm%R2``)Omx0&Pup#jFKx^6WQlDv zTCg6H)dI97L;TiAOV+EEii&QgqRleiRjs_s@?(m1- z%l-f~%xI8NlCjyz+ur-tjhQ#Jr4viLH6Be`nl7!jzNY@5?vT!^Gwa+skIn?Fx(v7z zX3;rxHav$eQ$-y*7yfC*&6JyUr|>dhC*T=y@vOQtcoqe-ire7QuANddE!gp`v}sc@ zu>w00;dYiM$b>mrqC;5^OiFw@uky24TLn+-aXQ6#7I04Q8`8=|YW_Skmj_Zz6 z%Sqb~-9Ne=y7Kx;dT*LDtyWxE+&$i^A9p9|PBmUi*TdG!Ry3n z?To4!rIf9bQ7)qbB~?HrSS5HxP>qZ#8P&|xhwtPleawR5Kbh(T&8Ksq1n)Q-M31v%V6o;3|D3(!@XvvJCaA~~) z%a`@2&YIQ0mvxbKs6|Qn*K1yj+)^3*R2EwmTbL>8j|f>PzU0gA)1*`tsP4`f~bGpuD~;v65(EB|&L@8Oq<%7ghOh>5Hja z#fV6rv>=eYl9Wl!(kfC8R-6_qU`r5__VQ7VWl8%IsX$Mb#jmWd$Z{&MUgB5OS0hpm zUJWTvv^rKiUL6OQEU78&6jMtr#rhRh>mchTYfy^zo?YF z_UiV3bN-n+ya6KjOL2WzOxnxPv}D)t>!TXLGI+V4`Q2~3tC z(f4RsBJZl@i*Bx(;HRfZGAlj z9DRMj#yKXumk*V{muH9 zbvv@n`UkPUK|b3~>2|#B*4@^9$PQ(9S$A3YAiJ!4@pr!V{=x26(SNM}D7N#p-Jx>+ zQF@nZeGlb-!S;~x7p?B0Y#VkjR@P}ZWq(m7rQ4O2wUc%I8`-XMcd(rTyM`z0iGt@IERgEy(hF z;`LJ{QZ78dMqg@o(RXHzTC***WgNAGx52i?>!$Cj##&cmvJPo_t^O1J=lUi3Mfw%o zg_>A+x1vm&1P~V>$bED8{t+QZX zTW4BlSZ5>C!B^I)3RA2Tt&^;ik#W}X)-SCStYctftfP>z*3ZEg;4>h6lyx*2W3l6? z{iSsTS);8ZRozdl!@wujDYVwj+Fg~*WSP?b6vrgTR~KhqtWdOkQ7^w;`!jW0>d*R3 z`fd6@^)9_ne^9?ie^`H9Z_{V!kLi!-PwCCDGgyn>sy_{P{?A(QO?Vb`2CyRBQqbF} z?W8q<2Y3-5kca)V^f~&Fo}X{%gZi`jTyR!@9^q9UdR_vf=M_N;SM}HQSM`_m*Y!8S zb^R^<9sOjB(wRXyK+;R+l z(sJbg!2!!b%VEnQ%Rb8i%YO8JunX+5?6v%3*#UMT+ZDE<|FZmU`OUHoy%qbL z*5CT+R>ATG${*%Q;B@xUN77HGf0I5dePa6L^cCsL)8~V^>EFVZrq4@Xh+T|cO4$#v zCF!fuS7H~ZFH;!{U@IwENcn0aQoaVZ3an25DSa(im%bjhA$?Q&M%d5kzkr|9e@)+# zzE$Zz(*J<}p1uwIf&7*J_iOx<{!jYO^d0HD)As_=`_lKP9{_vP|4l!VehBYK`myx? zV8_yrqyHsxkgVNckJ5)I74|QsC&}4Ko2GO#IHT6;N&2I7ISXsTuXxs!TwPM$_d4D+ z*7d;h&{D=y7L>A7wp6#&vDC14ssd0(&i`2Rb4}tUM z3#yKv9DYDb{Hpns`6SD_!P4)VuVh@xD9CT6mhnGUt-RB!F?VY#8Y&vvg1TTZXk}<= z=wRq%=x^w2Xb&MEfDVC?5#xYxuzMzF`3Jpi6WOhhIaCK;w0CZi`Crop}f zQ(?0WvkWs4v9k?d6P<3D0ww~v|8+0q;ZQ#wZEhu>p))c&JZf0&`e#hL1mK&q%n;V)NpzE_lgSnp4 zI@YlsWt2?28YIJ}BBM zx>dB?$zu@1Hhwx%kn zMotwXHB@brlC`f zMx!39H`X_%6RT%zU^KuQV(Sun2iwrt2;11$1QFd7-3a?G(fZiBw4|d}vT8q3wXCD% z81Ob=vV%3OV{8xPv(|tgFzhxg<=kd7Z%6*+{;&5nK89aoW~cqd_p z@g?i%YkSOe0(JzP01}tT3H+nzV@gZ>P22fxoG_gxc9<4Vs%1#Van@Isc#4?hpN3@s zhskO3m|TcY*-ZRwQx@FK2(j`Sw{v{zOcnEy!)qHX>F#uQjhoeP`pX8&7E36caL{nf zu*taESdbAj(AeA9#n_W?U2E))eGfFHAKDt*DgGWk)56%=*c{s!k$!p`5#4}D1LZYF z*CW=LKCDlFN>uu{F3~2$Wh69uU8AA$WGvK28Um>!VZ7jNQQVTa?q$1` zy>HTB!KI(1*H6(0 zC+WL0jJwnH_$iYKewx@RB4<>@f;~&RqtB;EYHFW&U(*UDcD?dR5=>I$O_(Pl3m&JzD~MxG=^^gc>`tuZ*TdM@9+u zsq&Y?__}1cZ1~9dk?}0&CwYPWlTt~OP^WPmMz+@de*M?TUJJ(opR|zJ7uzU+Z2<$3_P(?BR_7# z7f6n@@TOcc?X+d11=%y$6r^sZ%9lt6T)h0uOv?Dl3$MM%<;W92F0fzW#Y*g30_<6? zGE&^&9Jpz^p_YG+QaNt5V-}@putm`K()F?$@2-=1gAa`#^6iGHBS%kT4`pS$ZpNt5 zvxPQL`v~dDdW;g;`Wy1y>s0@zuwP1rr}F)$pas$w5|c7H@}y#u@#Pq#rNuOQF#MZ91o=LrB`G>BK>Mrdjc!lGZ$1U%Z~QZw=R0cNrsPY+eIC>IwDBi3#6~y z^rsKsiT0wUo*Xm0=>K~(k*MTL@4q?1U)@f^6AoSQ!~|z92m%`+DiucqCEn{o}DQY|9DC%DGGY>mx&) zDvwj=GIBN}`++!mS;Mjtl#N!|vY(S9Nu26+Irovi vNj&8mm63mT6pJr4@@+ns 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⁣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

mSLcwuc5zTu;C3u4?|BwZ$no@cSAQr7ehxw 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ð@(qIBb5CZYBL$c0SGD(m1rjlwNX%T8-ep zIsT1)bIRzQ2ham{bJZ#8blm~2xxw2Ga~V{^=VWEA8|s@B4>e z<#!P`%d#P_#dX7R8s*Zbq^YO{cYu4)QJSK(uxnf=Igyktnu8=iq(ezJyTa4ehQmBV z))@8XJnh`iuQv={WC73^xXKmk!$sh#d6OhhQqMU% zTsGH8mk7{4`6HzFXpJFxqm_~LS&ez=Ueed(2avw4nNZS2=aMhFUJ~vx?@QW$WI>U? zTy~(FyyqDCW$u#C>MWcY;nzo|53;Xl1uCs4YOt#7)J{=C`ubtYN$;0rm2}Z_rpM9} z&(bbQ14%MbTq`!>GTqmq;Iez&(#lZ!gng z$y1G)q)w^1MYFce+mdrcGFHgNc+@!4e5CnY`jdRRnHht!4r|pY+g*aE$rcminVP9J zn`@ma4Jd;aNy4WiYi0G;n35Gs5pU9MHEwb-QsqC?OfL(RV)TkIQi~XwCL6p)uq1$D z(zN2$wX&bcu9lTtJ|211rF%&_$)+!_dseP~%sbMMc8VQR+vUT0ns#UuOKvJ=AU7>i zK%^eSUhwK@$jfq;qmFz6pd6RG?51!&Lq_4=vE}RuYVpRlW36;M_u|b8gC@YRf zk%016d3Zu`etnv{WQ>hGCj!L~SzuhT9->q}o-R+l;yWZ+6}MHId&~30r0+ns*1Gsg zYGawNizTNz*7+JF#Wl)*@hmm6p36?A2rI>b<}nn@10}?J4&s$W3weekaHrfNa!?&Xw0m_$_U0*Uy4&`AqWg7Wrkf(+bV< zKHim&w4H(2C#db46XlAEoGhA$sx*P=F*mU`~23{Xz6r zB$!5+;=VNQi-H}E1Np5BaHrz;cnp?X^CQyo6_p);GrzflV(tfCl;j@@^;9F;z156)>5;NMstugl6X$7k{2!q z+?b5X&`2yqeM#^tKVxIC-PCw^n;Bo5B7$2Pc$M=8aDw6~6)jtrmWv)W=$HKdl97@K zlEg_}QtKw!k*^pzFTFWAH)uA|7?J+w1Y6?ByyWsFSECIYm-WGZE5i?+!NPmsq8k?4 z9#BUf-q+xpHejhG<+hYxq1A2RnR;NNB3xdGRFttV$suoXQTitzpQ0Wr(c;?FiZjdb zTSyx>3fKbScM01D_()$v4zTi zsK}G0cuA%kZ~QOREyAm{5M6OM*C|GOGuqw}+99jlEx74EYt1ZRQl8jiV6i4;`A!v` zk*~UDl0i69j zoKlwOOKxeFlE$ED68S&8^iMq~%N?ShdMb$_fA3Qq742JvewU?Jb!l5Mj`H;+_cJ78 z02M3biVPx%ZvCcS1p5L zion~*UE86^A&xuIyiY+9t>UDyYdx*m!4GG6koQ_m#nFAwaGkD{%~N&;#K4Ptsz%VapkT^YBw9y_+-fB9Da&6!|^Z@W?jC zt?pB_uvV8@cw$NLApiRd^jdK^?;s<4!1bRo0=_gdst035^0EUyt?tHyC?9Wg@S{1d zI#`sYNIIaj2U$F1X_O5o#(Qpods$2#z$vnbpFoDl&nvw@KwC7|mjdUKs){?WMq3mI z)s*%qDQ@pwMx>-?YxqJDAbPXrr~FV}bEW2^Q*fbtFna*SI*bGcj*u}}rf7hw5bu?SpO0rr zk5J6kTlBaWGsg#bbU#HteS>eeFO>P7h||7Aqx7ewI8?Red9czL(Ef=6aEv@+Nz3xT zJozS4(n2=L;v=isO=gY#NapqM&SoUS21e{Le!IvFBJYH(CJAE03qlX+YKnj_05vqz zJ;z(LC!ikhdd`d|$uW&5?N^ens1>UGBzt+o2A;nVicEnI6&<9VZsR!4Ku#^DUh%z4 z;9AjWCRZr#VLh^H7kA3bDY@rF!fOqn-4N2b6h&7N4k*ui8`3`Q7ZF7ymkaZ&BCxb# zQRJdlt6PaX+6;}?@?^yoY&3B&+RZY9Q8U-%8^Oe0-YPGcY>Tp`DMG9^Z;_mrF53Y< z><%S50h-07JA4T}zJV5sK>397CyX79m8P_;A@>Mx;vm^@BsU(xAzI;vXrDYvnpNd5 zRa}(f*nT1Z1x)-&o5jtN41+`QE*( zqh%dA&(pT>+-YEXKI38p-rTOlkM_oQ`<4AMkq@8Y-PNAox9twb%c~s>WAW%N<@&Wq zux+$*AMLzIo8{3;`u}7nksOje=nR~rIE^R9;-)=IS_n)sKwMn)cme!HQd>@pF!LE4JeeaV>9Qt)JL`dti8c!ne$ zDIc@3BB!cQTNFMi3oc~uONXvWuaaU26)CueH!p_YHXyrq(hkM+u7g__Q8yV*ok%W8 zJ`qezglFeLpH`k2(K_D3pPWkWp~#BIL;$L(^-t zMY_5EwA)B+QGAW|8KjfT3n5F!Q_w;-4m+nmM}m4M$Z?6gVl_Y zrMzVkt(?mET*Am$2bFhl-(LDGd$TN%>Fkfw{GnY-C7462ves%2_rHNe(*8Qh@aK@) zEs*2Qp-M%r)?OR!2$JqaNh6GVYL~N zbHL16xOj`f&PlGng{+e&LRJD<1&WhPHg;qk(*^AJKvrobtW}s|jh{zul;Q1-IW~Y! z+P$MlFSR*=Y?2*c7EbN@lPuni?jXO(b>4Ga9$e9%OWG)SL-81J8Q+4m--xVrZi5)5KCXsnnN;X2RQg0DLsp%Jv-VlGL~_m zozh83xtLbX2P@0qTJ1bsj$~f}C;bCu%%Gyib5N-=^L<&alFdl#7TJ_4fE($2va@NAq3|xb zqgavsU|ZvHH*Jw^c^B`NzAfEbddzL~3AJAKdRa-ej?YTp6*H<{76k)Zb7)7NcEl!E z$MtEQ)|++UOZh4b!3PN=SL6YdexrRPiU~Lb2DFyY?kcS@Ho}_+-~q*($UC4266rvS z1(DCuLz1UQ^SCUiwc!B8LUn*An;{p(eUjI;fU1D@*faw>Z2`#=*;6D@rDv)w8b{i* zkc{~xc5jRc*?ys;1tnFF6J zqJ?`6EtB?ct&3z|l}?}?lpfwC3yN&a$(@<90n0+H*j35)5XWR3?I~Jt0Iu9Y8^rbV zn5pLe7b`aeS-u1g*@86L0Zrs@k%!~}W9usK3$Y$b+C3$=wewrCo7w|jpK;k7p6p~W zqloM0k=`$XnU-ijZIC32i*JF*qorxZ$!Dou~mu0q`;R20@w|P%{-qeowbc8=z&>rbt4Hy;TUgPcDJTGg+GAQx0fssgM?TFEyOzqJaLcC{xlD@t$hE;*~0S*9fi{aQ!@a|3{H?(d% z$auP8=9SaTt%_wx=cw7@BA8UPisHNWAr+Uy^&7$J9;m*=Xb%&4w$>cq@vcvqHF~gv z=Obni)oC>{l=?Bec?2+tb}oi$(to$|1bMyW3)WsL*?N-S5hwvZvw&UABDvwpGSEm? z&&p7xHkd07g|w@?4)`yMM00Yz>?gUwjP{r01y7p!6@9081P5b5e)}NrkVRY(+U4N% zYG(Z>ON%VFZ@>}VkyHAD#YlD{=(_>hiTe#}j!#&VXk>~;-Qkq?pjvark@Q{JtICk1 z|A;<{5mNk#yl0Q#0mU3C-s%kV?shQ0$58DL=&+novIbmg-{l1}D&)V`UUtz~W$mYu zw^#N9*&G$iFTbTC9OXxlHBdPG2OMf=)!)!dJTaYdH=a@YKd3U1mESlhFacP~c=;8o zNrFos+-+p2{EV`5Ugn8+jgM7pLE%;sO0r1tv4C{s8J$@9zR!xhyNP!HnB5{{ z*&Q;G{RxYSgkNaBBQOg3^kIegA=fD8^bPJ2UEVaZRkmi?t7?KNjkW^Ju^z_dW9Xn* ztgB#Gd!*#IQY+m2CNKXZ@F=hC5p)XqB~qBTi_kjlXnB_2Ymbq5PCJ)mU)8#>HZ50P zo$IQ>rzN40e97Vgc^OafwmYQjX5L!Q=vfWTmlz#W5^^O}*ucEH3Eq(1<`VeVc-sf3 z97Kbazgv{|8Qja-{UTUaRIPjrFTsH?u?9-Yug26hK#pj?Usv$g1+2USUF4t9uA|bl zy9(6NN=Y6wtz6|5*Iqbz&~rhJOi->GPtj~tkE?6(bkSB8iFT~Izh-P`N81oHLS{jk znd}Ui%#NP9?5i5bo{?^bg569E@Bl`T_8zu18o(=DHH~Y>bDh4#F~NLoW;8Y0=g^bA zCZBNiJ6zu$Xh_dAt~4LXMlXw3G7d~}2l7D5_ph(0WCP=}bot;mH?yv0#$^1geAe=h zO5;}SQ%RmviT-J{YFEDOFOmg{qm|~YU0OBhv%Z;8i8pIZ%RVo^P9yT?p>zeVkuR(` zm@3B=+P$NhG8vU2nWOn3-Mm>5sap4}L0&F{>JyNAW6dI{2S19DK- zG{tpm-MI-^&Du=gy7`@7WMw?T>?==i(sw4AkV5O9HvNzuAu2Z}*B2kOE^Y{a=-Uqs z;h2|5^++1AvM#oR9$L@824%GGPJPjwFMEdYEiZB$`4wZ{8@NRn5zYLB^PAlE(!+aE0O&|<4)f=k&RP(q@)Ka z9#7n=Naf?Z!TR)_T5OwSe7(`YP3AxaNC! zYbg9O1YQ}%e!>Bq4FZO8t~vc(W)1Cs>&&qeGDjb!mKRTcZAD&4SJ7Ht@qP(3dF>h3_g55qCQFR=i|e~V$B_;@ksg(|e)4}v z@+9{bYj>)A>)NN9lkp>4m$0HSWYONFPhKluc?^>>L*KorMDGiN*9_am0Th;@` zc`MRJ(psy~$7U{6G?UiE@~51~2B$T6GMZjrmrw+v;ypD&HHx&~P+w`%$|W~%)*4hk z>RLvZmi_uYsI7fS2tEARQ zRB7f7;S}v}Dq^&L`FUiI(mpfkU?%{rJT#u=9S|>xZqfo}|J42p&G51ztYtN`8QN|A zPY;t8rbsSX736hJYAlM>lBTD4PerYZ;}k2EjJs9*qM{d)HZZLV|hU_Vkj`whE2*17hSy7TJ?X!akW z;djGY+7sJpZ{SmSNB1Vb_wp0AB>5NS1M`qg8+ohjCGy;92ZQ!mX*WYMB2AHL`u>3Y z09sSYey2Ea#bs#ys@On9qiCnAV$H=J+Cg%Hr(J+AWP1^=WcLuKDE3TcX~nYb%L2ZG z{wfAfD@Wm7I|Jm6)y}`$U{Y%|jdA(>6ptcbpymxlUFh3DnoZjxAG!da!yUbiS3(x> zVQ8OUA`i9S?=7^?*65*~N#YenAjqrN0zQ!pEzUC)JtfPFi}^<1Z_-|jyKsSQl3Gh? z<+=kdQ0(9-TB)4^XUrIszgcNHl$*_5Fr7I;I9`s#Uj-%CG80HgIKUcB-m!g11AT`^ zD`8oxWL?v~DSZo1dm0o67$qq_O}35X_hXKsckY2&ySaBWPtv`z53fP0Xg*nHw3Q!_ zF#5{Rr&t8F#(S4tQg7HT>_&DSyMg@zr55%p_G@-aj&H$DT}bc3F>i3)D|TDEDc8Kj z)h+Gj{Ps3{mHh6;XGowyNTX54);kAmtfwc69ZW_-YE)@=kNoQDv2Z54k0>jCkdIIL zk-TpDLXP5hwFXm+-(T1tWJ}#jp2W#kQ`Xb|0HlAdWUiFQSYPm32400(&85Gaw`nI} z(%vNeYM+qy8vPBAX=jyIO+ntO=r742?IM=^k=~^5J}EAxJ|Nq0W22KP#zKBwMOP`- zLf4k(_nJnB&==LTLti?&YzG-wbQF>FFS1kn^W?Mn1IlQ&*II5BD+xs-Xg#?Zx`~3a zbN>kzli$OV4lx@YLSOok7jP2XGnc*zk9*+(;r2W|+YPSvfYD^X|Yrr{8;GNfzK^>4h?ckEmcnI3tZQ!5R`K1Tk_Z60fFW{H2NU|zu zU$?xA+B0+9SS;j4Q3Qu9KpG2*2v`B$R)IZzAK_2v_?P)r(Xp~^DXv7c-2z9ffD5!s zSsqjQM8*R@!TZ|7EFK+78i8F!-|rb}JgoXcNq-|1Kc=6Oi~SfWS+GrbprQ7OYo@OV zjTIf#6)og*tPB4`gYCPuPOBeFC#hj;`a~n zE%d^QJsd15T4M^-_>*UyMuI=0B@rU+l93FG%hRq%`E2C(*Nzw2ROH>0Pg-%9+IhB* zp8Z3cq(f-UehG{mqmKsxMII~0Sp2LVglfIM>!N56$$!PA%H}VKN>vXZasS ztGqY*)~PHAD|qq{_))u7KLXRO`F}8tNE%mF8BeVkTN-s8ki%_|%^fMfMrzM^dY`?= z0~kp^vAUQ8tYai8?nTiynl+Bo2knpihZbqShBPo~CyEnxf-?IL!5Bwh<7U;28XUnlTh(bVuPLjZe)Fzr%GK!Gfr>3;gSQV!NTpAMl{s zxrS%01Agb%1JFu)5zd)j$x^8ZI_c^fhx)#e`~&(%r@m*}3jTkU@$n|KX~3CQZ2D%R z<~Z3<6$x945v#pl@_%S=w{RtGOml>MIofxv_W#A(wT|6K3#4Pnjy)X=&Oo~SN~`sq zQ|(LFSFyB1R}xJUU86bqwN80C6c;J^cN;vN;;JKHYClia7lC&&6D>q;kA@mU!R-L# z^w&nmZOO=J1I{(qya63P1ow&%kjA3X@*XuGAjh;WnnH`zzOA(LBrVd;GU5CJoS>1R zRgAP)t&t?lBvWPI)EZd(=(L|y-@8igGEQo0nnja4oP}@g5S3& callback) const + { + for (const auto& airfieldMapping : this->airfieldMap) { + callback(*airfieldMapping.second); + } + } } // namespace UKControllerPlugin::Airfield diff --git a/src/plugin/airfield/AirfieldCollection.h b/src/plugin/airfield/AirfieldCollection.h index 8d7c4bcf7..81240b418 100644 --- a/src/plugin/airfield/AirfieldCollection.h +++ b/src/plugin/airfield/AirfieldCollection.h @@ -19,6 +19,7 @@ namespace UKControllerPlugin::Airfield { auto operator=(AirfieldCollection&&) noexcept -> AirfieldCollection&; void AddAirfield(std::unique_ptr airfield); [[nodiscard]] auto FetchAirfieldByIcao(const std::string& icao) const -> const AirfieldModel&; + void ForEach(const std::function& callback) const; [[nodiscard]] auto GetSize() const -> size_t; private: diff --git a/src/plugin/bootstrap/CollectionBootstrap.cpp b/src/plugin/bootstrap/CollectionBootstrap.cpp index 87f330f2e..409a314e6 100644 --- a/src/plugin/bootstrap/CollectionBootstrap.cpp +++ b/src/plugin/bootstrap/CollectionBootstrap.cpp @@ -7,7 +7,6 @@ #include "dependency/DependencyLoaderInterface.h" #include "flightplan/FlightPlanEventHandlerCollection.h" #include "flightplan/StoredFlightplanCollection.h" -#include "memory" #include "metar/MetarEventHandlerCollection.h" #include "ownership/AirfieldOwnershipManager.h" #include "radarscreen/RadarRenderableCollection.h" @@ -19,7 +18,6 @@ using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Dependency::DependencyLoaderInterface; using UKControllerPlugin::Flightplan::StoredFlightplanCollection; using UKControllerPlugin::Metar::MetarEventHandlerCollection; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; using UKControllerPlugin::RadarScreen::RadarRenderableCollection; namespace UKControllerPlugin::Bootstrap { diff --git a/src/plugin/bootstrap/HelperBootstrap.cpp b/src/plugin/bootstrap/HelperBootstrap.cpp index 2dabcc91e..e12844e3b 100644 --- a/src/plugin/bootstrap/HelperBootstrap.cpp +++ b/src/plugin/bootstrap/HelperBootstrap.cpp @@ -37,7 +37,8 @@ namespace UKControllerPlugin::Bootstrap { persistence.settingsRepository->GetSetting("api-key")); persistence.api = std::make_unique(*persistence.curl, requestBuilder); - persistence.taskRunner = std::make_unique(3); + persistence.taskRunner = std::make_shared(3); + SetTaskRunner(persistence.taskRunner); } /* diff --git a/src/plugin/bootstrap/InitialisePlugin.cpp b/src/plugin/bootstrap/InitialisePlugin.cpp index b78b0f584..78392f8d2 100644 --- a/src/plugin/bootstrap/InitialisePlugin.cpp +++ b/src/plugin/bootstrap/InitialisePlugin.cpp @@ -10,7 +10,6 @@ #include "datablock/DatablockBoostrap.h" #include "dependency/DependencyLoader.h" #include "dependency/UpdateDependencies.h" -#include "duplicate/DuplicatePlugin.h" #include "euroscope/GeneralSettingsConfigurationBootstrap.h" #include "euroscope/PluginUserSettingBootstrap.h" #include "flightinformationservice/FlightInformationServiceModule.h" @@ -27,6 +26,7 @@ #include "message/UserMessagerBootstrap.h" #include "metar/PressureMonitorBootstrap.h" #include "minstack/MinStackModule.h" +#include "missedapproach/MissedApproachModule.h" #include "navaids/NavaidModule.h" #include "notifications/NotificationsModule.h" #include "oceanic/OceanicModule.h" @@ -231,6 +231,7 @@ namespace UKControllerPlugin { PrenoteModule::BootstrapPlugin(*this->container, loader); Handoff::BootstrapPlugin(*this->container, loader); + MissedApproach::BootstrapPlugin(*this->container); Selcal::BootstrapPlugin(*this->container); // Bootstrap other things diff --git a/src/plugin/bootstrap/PersistenceContainer.cpp b/src/plugin/bootstrap/PersistenceContainer.cpp index ae7b1b3d3..45137acb5 100644 --- a/src/plugin/bootstrap/PersistenceContainer.cpp +++ b/src/plugin/bootstrap/PersistenceContainer.cpp @@ -34,7 +34,7 @@ #include "minstack/MinStackManager.h" #include "navaids/NavaidCollection.h" #include "notifications/NotificationsMenuItem.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "plugin/FunctionCallEventHandler.h" #include "plugin/UKPlugin.h" #include "push/PushEventProcessorCollection.h" diff --git a/src/plugin/bootstrap/PersistenceContainer.h b/src/plugin/bootstrap/PersistenceContainer.h index eede1f15e..cebf1890f 100644 --- a/src/plugin/bootstrap/PersistenceContainer.h +++ b/src/plugin/bootstrap/PersistenceContainer.h @@ -80,7 +80,7 @@ namespace UKControllerPlugin { class NotificationsMenuItem; } // namespace Notifications namespace Ownership { - class AirfieldOwnershipManager; + class AirfieldServiceProviderCollection; } // namespace Ownership namespace Plugin { class FunctionCallEventHandler; @@ -149,7 +149,7 @@ namespace UKControllerPlugin::Bootstrap { // The helpers and collections std::unique_ptr api; - std::unique_ptr taskRunner; + std::shared_ptr taskRunner; std::unique_ptr activeCallsigns; std::unique_ptr flightplans; std::unique_ptr userMessager; @@ -205,7 +205,7 @@ namespace UKControllerPlugin::Bootstrap { // Large collections that we don't want to go onto the stack std::unique_ptr sids; std::unique_ptr airfields; - std::unique_ptr airfieldOwnership; + std::shared_ptr airfieldOwnership; std::unique_ptr controllerPositions; std::unique_ptr sectorExitPoints; std::shared_ptr runways; diff --git a/src/plugin/controller/ActiveCallsign.cpp b/src/plugin/controller/ActiveCallsign.cpp index 2af1d929a..64d75979b 100644 --- a/src/plugin/controller/ActiveCallsign.cpp +++ b/src/plugin/controller/ActiveCallsign.cpp @@ -1,85 +1,73 @@ -#include "pch/pch.h" -#include "controller/ActiveCallsign.h" -#include "controller/ControllerPosition.h" +#include "ActiveCallsign.h" +#include "ControllerPosition.h" using UKControllerPlugin::Controller::ControllerPosition; -namespace UKControllerPlugin { - namespace Controller { +namespace UKControllerPlugin::Controller { - ActiveCallsign::ActiveCallsign( - std::string callsign, - std::string controllerName, - const ControllerPosition & normalisedPosition - ) : normalisedPosition(normalisedPosition) - { - this->callsign = callsign; - this->controllerName = controllerName; - } + ActiveCallsign::ActiveCallsign( + std::string callsign, std::string controllerName, const ControllerPosition& normalisedPosition, bool isUser) + : callsign(std::move(callsign)), controllerName(std::move(controllerName)), + normalisedPosition(normalisedPosition), isUser(isUser) + { + } - /* - Copy constructor - */ - ActiveCallsign::ActiveCallsign(const ActiveCallsign & copyFrom) - : normalisedPosition(copyFrom.normalisedPosition) - { - this->callsign = copyFrom.callsign; - this->controllerName = copyFrom.controllerName; - } + ActiveCallsign::ActiveCallsign(const ActiveCallsign& copyFrom) + : callsign(copyFrom.callsign), controllerName(copyFrom.controllerName), + normalisedPosition(copyFrom.normalisedPosition), isUser(copyFrom.isUser) + { + } - /* - Destructor, nothing to do here at the moment. - */ - ActiveCallsign::~ActiveCallsign(void) - { - } + auto ActiveCallsign::GetCallsign() const -> const std::string& + { + return this->callsign; + } - const std::string ActiveCallsign::GetCallsign(void) const - { - return this->callsign; - } + auto ActiveCallsign::GetControllerName() const -> const std::string& + { + return this->controllerName; + } - const std::string ActiveCallsign::GetControllerName(void) const - { - return this->controllerName; - } + auto ActiveCallsign::GetNormalisedPosition() const -> const ControllerPosition& + { + return this->normalisedPosition; + } - const ControllerPosition & ActiveCallsign::GetNormalisedPosition(void) const - { - return this->normalisedPosition; - } + /* + Compares the objects by their callsign. + */ + auto ActiveCallsign::operator<(const ActiveCallsign& comparator) const -> bool + { + return this->callsign < comparator.callsign; + } - /* - Compares the objects by their callsign. - */ - bool ActiveCallsign::operator<(const ActiveCallsign & comparator) const - { - return this->callsign < comparator.callsign; - } + /* + Compares the objects by their callsign. + */ + auto ActiveCallsign::operator>(const ActiveCallsign& comparator) const -> bool + { + return this->callsign > comparator.callsign; + } - /* - Compares the objects by their callsign. - */ - bool ActiveCallsign::operator>(const ActiveCallsign & comparator) const - { - return this->callsign > comparator.callsign; - } + /* + Returns true if the callsigns match. + */ + auto ActiveCallsign::operator==(const ActiveCallsign& comparator) const -> bool + { + return this->callsign == comparator.callsign && this->normalisedPosition == comparator.normalisedPosition && + this->isUser == comparator.isUser; + } - /* - Returns true if the callsigns match. - */ - bool ActiveCallsign::operator==(const ActiveCallsign & comparator) const - { - return this->callsign == comparator.callsign && - this->normalisedPosition == comparator.normalisedPosition; - } + /* + Literally just the inverse of equality. + */ + auto ActiveCallsign::operator!=(const ActiveCallsign& comparator) const -> bool + { + return !this->operator==(comparator); + } - /* - Literally just the inverse of equality. - */ - bool ActiveCallsign::operator!=(const ActiveCallsign & comparator) const - { - return !this->operator==(comparator); - } - } // namespace Controller -} // namespace UKControllerPlugin + auto ActiveCallsign::GetIsUser() const -> bool + { + return this->isUser; + } +} // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ActiveCallsign.h b/src/plugin/controller/ActiveCallsign.h index ef0c2fbc0..c7af4d1e6 100644 --- a/src/plugin/controller/ActiveCallsign.h +++ b/src/plugin/controller/ActiveCallsign.h @@ -1,47 +1,42 @@ #pragma once -namespace UKControllerPlugin { - namespace Controller { - class ControllerPosition; - } // namespace Controller -} // namespace UKControllerPlugin +namespace UKControllerPlugin::Controller { + class ControllerPosition; -namespace UKControllerPlugin { - namespace Controller { + /* + A class that describes an "Active" controller position, which + will persist longer than the Euroscope equivalent and allow + us to track who's online without having to poll back to ES. + */ + class ActiveCallsign + { + public: + ActiveCallsign( + std::string callsign, + std::string controllerName, + const UKControllerPlugin::Controller::ControllerPosition& normalisedPosition, + bool isUser); + ActiveCallsign(const ActiveCallsign& copyFrom); + [[nodiscard]] auto GetCallsign() const -> const std::string&; + [[nodiscard]] auto GetControllerName() const -> const std::string&; + [[nodiscard]] auto GetNormalisedPosition() const -> const UKControllerPlugin::Controller::ControllerPosition&; + [[nodiscard]] auto GetIsUser() const -> bool; + auto operator<(const ActiveCallsign& comparator) const -> bool; + auto operator>(const ActiveCallsign& comparator) const -> bool; + auto operator==(const ActiveCallsign& comparator) const -> bool; + auto operator!=(const ActiveCallsign& comparator) const -> bool; - /* - A class that describes an "Active" controller position, which - will persist longer than the Euroscope equivalent and allow - us to track who's online without having to poll back to ES. - */ - class ActiveCallsign - { - public: - ActiveCallsign( - std::string callsign, - std::string controllerName, - const UKControllerPlugin::Controller::ControllerPosition & normalisedPosition - ); - ActiveCallsign(const ActiveCallsign & copyFrom); - ~ActiveCallsign(void); - const std::string GetCallsign(void) const; - const std::string GetControllerName(void) const; - const UKControllerPlugin::Controller::ControllerPosition & GetNormalisedPosition(void) const; - bool operator<(const ActiveCallsign & comparator) const; - bool operator>(const ActiveCallsign & comparator) const; - bool operator==(const ActiveCallsign & comparator) const; - bool operator!=(const ActiveCallsign & comparator) const; + private: + // The callsign as it is logged in. + std::string callsign; - private: + // The name of the controller. + std::string controllerName; - // The callsign as it is logged in. - std::string callsign; + // The normalised callsign of the controller - matching it to a recognised controller position. + const UKControllerPlugin::Controller::ControllerPosition& normalisedPosition; - // The name of the controller. - std::string controllerName; - - // The normalised callsign of the controller - matching it to a recognised controller position. - const UKControllerPlugin::Controller::ControllerPosition & normalisedPosition; - }; - } // namespace Controller -} // namespace UKControllerPlugin + // Is this callsign the users? + bool isUser; + }; +} // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ActiveCallsignCollection.cpp b/src/plugin/controller/ActiveCallsignCollection.cpp index b42ed3193..c6f16dc51 100644 --- a/src/plugin/controller/ActiveCallsignCollection.cpp +++ b/src/plugin/controller/ActiveCallsignCollection.cpp @@ -1,211 +1,182 @@ -#include "pch/pch.h" -#include "controller/ActiveCallsignCollection.h" +#include "ActiveCallsign.h" +#include "ActiveCallsignCollection.h" +#include "ControllerPosition.h" #include "euroscope/EuroScopeCControllerInterface.h" -#include "controller/ActiveCallsign.h" -#include "controller/ControllerPosition.h" -using UKControllerPlugin::Euroscope::EuroScopeCControllerInterface; using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::Euroscope::EuroScopeCControllerInterface; -namespace UKControllerPlugin { - namespace Controller { - +namespace UKControllerPlugin::Controller { - ActiveCallsignCollection::ActiveCallsignCollection(void) - { + ActiveCallsignCollection::ActiveCallsignCollection() + { + } + /* + Inserts the callsign into the position set and also stores the iterator + pointing to it in the callsign list for easy access. + */ + void ActiveCallsignCollection::AddCallsign(const ActiveCallsign& controller) + { + if (this->CallsignActive(controller.GetCallsign())) { + throw std::invalid_argument("Controller " + controller.GetCallsign() + " already active."); } - /* - Inserts the callsign into the position set and also stores the iterator - pointing to it in the callsign list for easy access. - */ - void ActiveCallsignCollection::AddCallsign(ActiveCallsign controller) - { - if (this->CallsignActive(controller.GetCallsign())) { - throw std::invalid_argument("Controller " + controller.GetCallsign() + " already active."); - } - - this->activeCallsigns[controller.GetCallsign()] = - this->activePositions[controller.GetNormalisedPosition().GetCallsign()] - .insert(controller).first; - - for ( - std::list>::const_iterator it - = this->handlers.cbegin(); - it != this->handlers.cend(); - ++it - ) { - (*it)->ActiveCallsignAdded(controller, false); - } - } + this->activeCallsigns[controller.GetCallsign()] = + this->activePositions[controller.GetNormalisedPosition().GetCallsign()].insert(controller).first; - /* - Add a callsign for the "current user". - */ - void ActiveCallsignCollection::AddUserCallsign(ActiveCallsign controller) - { - if (this->CallsignActive(controller.GetCallsign())) { - throw std::invalid_argument("Callsign is already active."); - } - - if (this->userActive) { - this->RemoveCallsign(*this->userCallsign); - } - - this->userCallsign = this->activePositions[controller.GetNormalisedPosition().GetCallsign()] - .insert(controller).first; - this->activeCallsigns[controller.GetCallsign()] = this->userCallsign; - this->userActive = true; - - for ( - std::list>::const_iterator it - = this->handlers.cbegin(); - it != this->handlers.cend(); - ++it - ) { - (*it)->ActiveCallsignAdded(controller, true); - } + for (auto it = this->handlers.cbegin(); it != this->handlers.cend(); ++it) { + (*it)->ActiveCallsignAdded(controller); } - - /* - Returns true if a callsign is known to be active, false otherwise. - */ - bool ActiveCallsignCollection::CallsignActive(std::string callsign) const - { - return this->activeCallsigns.count(callsign) != 0; + } + + /* + Add a callsign for the "current user". + */ + void ActiveCallsignCollection::AddUserCallsign(const ActiveCallsign& controller) + { + if (this->CallsignActive(controller.GetCallsign())) { + throw std::invalid_argument("Callsign is already active."); } - /* - Flushes the entire collection. Sad times. - */ - void ActiveCallsignCollection::Flush(void) - { - this->activeCallsigns.clear(); - this->activePositions.clear(); - this->userActive = false; - for ( - std::list>::const_iterator it - = this->handlers.cbegin(); - it != this->handlers.cend(); - ++it - ) { - (*it)->CallsignsFlushed(); - } + if (this->userActive) { + this->RemoveCallsign(*this->userCallsign); } - int ActiveCallsignCollection::GetNumberActiveCallsigns() const - { - return this->activeCallsigns.size(); - } + this->userCallsign = + this->activePositions[controller.GetNormalisedPosition().GetCallsign()].insert(controller).first; + this->activeCallsigns[controller.GetCallsign()] = this->userCallsign; + this->userActive = true; - int ActiveCallsignCollection::GetNumberActivePositions() const - { - return this->activePositions.size(); + for (auto it = this->handlers.cbegin(); it != this->handlers.cend(); ++it) { + (*it)->ActiveCallsignAdded(controller); + } + } + + /* + Returns true if a callsign is known to be active, false otherwise. + */ + auto ActiveCallsignCollection::CallsignActive(const std::string& callsign) const -> bool + { + return this->activeCallsigns.count(callsign) != 0; + } + + /* + Flushes the entire collection. Sad times. + */ + void ActiveCallsignCollection::Flush() + { + this->activeCallsigns.clear(); + this->activePositions.clear(); + this->userActive = false; + for (auto it = this->handlers.cbegin(); it != this->handlers.cend(); ++it) { + (*it)->CallsignsFlushed(); + } + } + + auto ActiveCallsignCollection::GetNumberActiveCallsigns() const -> int + { + return this->activeCallsigns.size(); + } + + auto ActiveCallsignCollection::GetNumberActivePositions() const -> int + { + return this->activePositions.size(); + } + + /* + Returns a particular active callsign. + */ + auto ActiveCallsignCollection::GetCallsign(const std::string& callsign) const -> ActiveCallsign + { + if (!this->CallsignActive(callsign)) { + throw std::out_of_range("Callsign not found"); } - - /* - Returns a particular active callsign. - */ - ActiveCallsign ActiveCallsignCollection::GetCallsign(std::string callsign) const - { - if (!this->CallsignActive(callsign)) { - throw std::out_of_range("Callsign not found"); - } # - return *this->activeCallsigns.find(callsign)->second; + return *this->activeCallsigns.find(callsign)->second; + } + + /* + Returns the "lead" callsign for a given position. + */ + auto ActiveCallsignCollection::GetLeadCallsignForPosition(const std::string& normalisedCallsign) const + -> ActiveCallsign + { + if (!this->PositionActive(normalisedCallsign)) { + throw std::out_of_range("Position not found"); } - /* - Returns the "lead" callsign for a given position. - */ - ActiveCallsign ActiveCallsignCollection::GetLeadCallsignForPosition(std::string normalisedCallsign) const - { - if (!this->PositionActive(normalisedCallsign)) { - throw std::out_of_range("Position not found"); - } + return *this->activePositions.find(normalisedCallsign)->second.begin(); + } - return *this->activePositions.find(normalisedCallsign)->second.begin(); + /* + Returns the users active callsign. Throws exception if not found. + */ + auto ActiveCallsignCollection::GetUserCallsign() const -> ActiveCallsign + { + if (!this->userActive) { + throw std::out_of_range("User has no callsign."); } - /* - Returns the users active callsign. Throws exception if not found. - */ - ActiveCallsign ActiveCallsignCollection::GetUserCallsign(void) const - { - if (!this->userActive) { - throw std::out_of_range("User has no callsign."); - } - - return *this->userCallsign; + return *this->userCallsign; + } + + /* + Returns whether or not there is an active callsign for a given controller position. + */ + auto ActiveCallsignCollection::PositionActive(const std::string& normalisedCallsign) const -> bool + { + return this->activePositions.find(normalisedCallsign) != this->activePositions.end() && + !this->activePositions.find(normalisedCallsign)->second.empty(); + } + + /* + Removes a callsign from the active lists. + */ + void ActiveCallsignCollection::RemoveCallsign(const ActiveCallsign& controller) + { + auto callsign = this->activeCallsigns.find(controller.GetCallsign()); + + if (callsign == this->activeCallsigns.end()) { + LogError("Tried to remove inactive callsign " + controller.GetCallsign()); + return; } - /* - Returns whether or not there is an active callsign for a given controller position. - */ - bool ActiveCallsignCollection::PositionActive(std::string normalisedCallsign) const - { - return this->activePositions.find(normalisedCallsign) != this->activePositions.end() && - this->activePositions.find(normalisedCallsign)->second.size() != 0; + // If they're the current user, mark inactive. + bool isUser = this->userActive && *callsign->second == *this->userCallsign; + if (isUser) { + this->userActive = false; } - /* - Removes a callsign from the active lists. - */ - void ActiveCallsignCollection::RemoveCallsign(ActiveCallsign controller) - { - std::map::iterator>::iterator callsign = this->activeCallsigns.find( - controller.GetCallsign() - ); - - if (callsign == this->activeCallsigns.end()) { - LogError("Tried to remove inactive callsign " + controller.GetCallsign()); - return; - } - - // If they're the current user, mark inactive. - bool isUser = this->userActive && *callsign->second == *this->userCallsign; - if (isUser) { - this->userActive = false; - } - - this->activePositions.find( - controller.GetNormalisedPosition().GetCallsign() - )->second.erase(callsign->second); - this->activeCallsigns.erase(callsign); - - for ( - std::list>::const_iterator it - = this->handlers.cbegin(); - it != this->handlers.cend(); - ++it - ) { - (*it)->ActiveCallsignRemoved(controller, isUser); - } - } + this->activePositions.find(controller.GetNormalisedPosition().GetCallsign())->second.erase(callsign->second); + this->activeCallsigns.erase(callsign); - /* - Returns true if the user has an active callsign. - */ - bool ActiveCallsignCollection::UserHasCallsign(void) const - { - return this->userActive; + for (auto it = this->handlers.cbegin(); it != this->handlers.cend(); ++it) { + (*it)->ActiveCallsignRemoved(controller); } - - void ActiveCallsignCollection::AddHandler(std::shared_ptr handler) - { - if (std::find(this->handlers.cbegin(), this->handlers.cend(), handler) != this->handlers.cend()) { - LogWarning("Duplicate ActiveCallsignEventHandler detected"); - return; - } - - this->handlers.push_back(handler); + } + + /* + Returns true if the user has an active callsign. + */ + auto ActiveCallsignCollection::UserHasCallsign() const -> bool + { + return this->userActive; + } + + void ActiveCallsignCollection::AddHandler(const std::shared_ptr& handler) + { + if (std::find(this->handlers.cbegin(), this->handlers.cend(), handler) != this->handlers.cend()) { + LogWarning("Duplicate ActiveCallsignEventHandler detected"); + return; } - size_t ActiveCallsignCollection::CountHandlers(void) const - { - return this->handlers.size(); - } - } // namespace Controller -} // namespace UKControllerPlugin + this->handlers.push_back(handler); + } + + auto ActiveCallsignCollection::CountHandlers() const -> size_t + { + return this->handlers.size(); + } +} // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ActiveCallsignCollection.h b/src/plugin/controller/ActiveCallsignCollection.h index 95c2e8fc0..3e178d7a7 100644 --- a/src/plugin/controller/ActiveCallsignCollection.h +++ b/src/plugin/controller/ActiveCallsignCollection.h @@ -1,43 +1,36 @@ #pragma once -#include "controller/ActiveCallsign.h" -#include "controller/ActiveCallsignEventHandlerInterface.h" - -namespace UKControllerPlugin { - namespace Controller { - -// Forward declaration -class ActiveCallsign; -// END - -/* - Class that maps connected callsigns to UK controller positions and determines - priority order. -*/ -class ActiveCallsignCollection -{ - public: - ActiveCallsignCollection(void); - void AddCallsign(UKControllerPlugin::Controller::ActiveCallsign controller); - void AddUserCallsign(UKControllerPlugin::Controller::ActiveCallsign controller); - bool CallsignActive(std::string callsign) const; - void Flush(void); +#include "ActiveCallsign.h" +#include "ActiveCallsignEventHandlerInterface.h" + +namespace UKControllerPlugin::Controller { + class ActiveCallsign; + + /* + Class that maps connected callsigns to UK controller positions and determines + priority order. + */ + class ActiveCallsignCollection + { + public: + ActiveCallsignCollection(); + void AddCallsign(const UKControllerPlugin::Controller::ActiveCallsign& controller); + void AddUserCallsign(const UKControllerPlugin::Controller::ActiveCallsign& controller); + bool CallsignActive(const std::string& callsign) const; + void Flush(); int GetNumberActiveCallsigns() const; int GetNumberActivePositions() const; - UKControllerPlugin::Controller::ActiveCallsign GetCallsign(std::string callsign) const; - UKControllerPlugin::Controller::ActiveCallsign GetLeadCallsignForPosition( - std::string normalisedCallsign - ) const; - UKControllerPlugin::Controller::ActiveCallsign GetUserCallsign(void) const; - bool PositionActive(std::string normalisedCallsign) const; - void RemoveCallsign(UKControllerPlugin::Controller::ActiveCallsign controller); - bool UserHasCallsign(void) const; - void AddHandler( - std::shared_ptr handler - ); - size_t CountHandlers(void) const; - - private: - + UKControllerPlugin::Controller::ActiveCallsign GetCallsign(const std::string& callsign) const; + auto GetLeadCallsignForPosition(const std::string& normalisedCallsign) const + -> UKControllerPlugin::Controller::ActiveCallsign; + auto GetUserCallsign() const -> UKControllerPlugin::Controller::ActiveCallsign; + bool PositionActive(const std::string& normalisedCallsign) const; + void RemoveCallsign(const UKControllerPlugin::Controller::ActiveCallsign& controller); + auto UserHasCallsign() const -> bool; + void + AddHandler(const std::shared_ptr& handler); + auto CountHandlers() const -> size_t; + + private: // Whether or not the user is active. bool userActive = false; @@ -51,10 +44,6 @@ class ActiveCallsignCollection std::set::iterator userCallsign; // All the handlers for these events - std::list< - std::shared_ptr - > handlers; -}; - -} // namespace Controller -} // namespace UKControllerPlugin + std::list> handlers; + }; +} // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ActiveCallsignEventHandlerInterface.h b/src/plugin/controller/ActiveCallsignEventHandlerInterface.h index 0e0b7c2f0..fe6c1b51c 100644 --- a/src/plugin/controller/ActiveCallsignEventHandlerInterface.h +++ b/src/plugin/controller/ActiveCallsignEventHandlerInterface.h @@ -19,10 +19,8 @@ namespace UKControllerPlugin::Controller { [[nodiscard]] auto operator=(ActiveCallsignEventHandlerInterface&&) noexcept -> ActiveCallsignEventHandlerInterface&; - virtual void - ActiveCallsignAdded(const UKControllerPlugin::Controller::ActiveCallsign& callsign, bool userCallsign) = 0; - virtual void - ActiveCallsignRemoved(const UKControllerPlugin::Controller::ActiveCallsign& callsign, bool userCallsign) = 0; + virtual void ActiveCallsignAdded(const UKControllerPlugin::Controller::ActiveCallsign& callsign) = 0; + virtual void ActiveCallsignRemoved(const UKControllerPlugin::Controller::ActiveCallsign& callsign) = 0; virtual void CallsignsFlushed(); }; } // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ActiveCallsignMonitor.cpp b/src/plugin/controller/ActiveCallsignMonitor.cpp index 6c9cccf21..043bcfec1 100644 --- a/src/plugin/controller/ActiveCallsignMonitor.cpp +++ b/src/plugin/controller/ActiveCallsignMonitor.cpp @@ -102,11 +102,11 @@ namespace UKControllerPlugin::Controller { "The current user, with callsign " + callsign.GetCallsign() + ", has been marked as active, covering " + matchedPos.GetCallsign()); this->activeCallsigns.AddUserCallsign( - ActiveCallsign(callsign.GetCallsign(), callsign.GetControllerName(), matchedPos)); + ActiveCallsign(callsign.GetCallsign(), callsign.GetControllerName(), matchedPos, true)); } else { LogInfo(callsign.GetCallsign() + " has been marked as active, covering " + matchedPos.GetCallsign()); this->activeCallsigns.AddCallsign( - ActiveCallsign(callsign.GetCallsign(), callsign.GetControllerName(), matchedPos)); + ActiveCallsign(callsign.GetCallsign(), callsign.GetControllerName(), matchedPos, false)); } } } // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ControllerPosition.cpp b/src/plugin/controller/ControllerPosition.cpp index f5ce015e7..849bfe5b9 100644 --- a/src/plugin/controller/ControllerPosition.cpp +++ b/src/plugin/controller/ControllerPosition.cpp @@ -77,4 +77,49 @@ namespace UKControllerPlugin::Controller { { return this->receivesPrenoteMessages; } + + auto ControllerPosition::IsGround() const -> bool + { + return this->GetType() == "GND"; + } + + auto ControllerPosition::IsTower() const -> bool + { + return this->GetType() == "TWR"; + } + + auto ControllerPosition::IsApproach() const -> bool + { + return this->GetType() == "APP"; + } + + auto ControllerPosition::IsEnroute() const -> bool + { + return this->GetType() == "CTR"; + } + + auto ControllerPosition::ProvidesApproachServices() const -> bool + { + return this->IsApproach() || this->IsEnroute(); + } + + auto ControllerPosition::ProvidesGroundServices() const -> bool + { + return this->IsGround() || this->IsTower() || this->IsApproach() || this->IsEnroute(); + } + + auto ControllerPosition::ProvidesTowerServices() const -> bool + { + return this->IsTower() || this->IsApproach() || this->IsEnroute(); + } + + auto ControllerPosition::IsFlightServiceStation() const -> bool + { + return this->GetType() == "FSS"; + } + + auto ControllerPosition::IsDelivery() const -> bool + { + return this->GetType() == "DEL"; + } } // namespace UKControllerPlugin::Controller diff --git a/src/plugin/controller/ControllerPosition.h b/src/plugin/controller/ControllerPosition.h index 50c3bf01e..6ad5551f8 100644 --- a/src/plugin/controller/ControllerPosition.h +++ b/src/plugin/controller/ControllerPosition.h @@ -24,6 +24,15 @@ namespace UKControllerPlugin::Controller { [[nodiscard]] auto GetFrequency() const -> double; [[nodiscard]] auto GetTopdown() const -> std::vector; [[nodiscard]] auto GetType() const -> std::string; + [[nodiscard]] auto IsDelivery() const -> bool; + [[nodiscard]] auto IsGround() const -> bool; + [[nodiscard]] auto IsTower() const -> bool; + [[nodiscard]] auto IsApproach() const -> bool; + [[nodiscard]] auto IsEnroute() const -> bool; + [[nodiscard]] auto IsFlightServiceStation() const -> bool; + [[nodiscard]] auto ProvidesGroundServices() const -> bool; + [[nodiscard]] auto ProvidesTowerServices() const -> bool; + [[nodiscard]] auto ProvidesApproachServices() const -> bool; [[nodiscard]] auto HasTopdownAirfield(const std::string& icao) const -> bool; [[nodiscard]] auto RequestsDepartureReleases() const -> bool; [[nodiscard]] auto ReceivesDepartureReleases() const -> bool; diff --git a/src/plugin/euroscope/EuroScopeCFlightPlanInterface.h b/src/plugin/euroscope/EuroScopeCFlightPlanInterface.h index 42e30e578..5ba6b54f7 100644 --- a/src/plugin/euroscope/EuroScopeCFlightPlanInterface.h +++ b/src/plugin/euroscope/EuroScopeCFlightPlanInterface.h @@ -1,49 +1,47 @@ #pragma once #include "euroscope/EuroscopeExtractedRouteInterface.h" -namespace UKControllerPlugin { - namespace Euroscope { +namespace UKControllerPlugin::Euroscope { - /* - Interface for interacting with the wrapper around the EuroScope CFlightPlan class. - */ - class EuroScopeCFlightPlanInterface - { - public: - virtual ~EuroScopeCFlightPlanInterface(void) - { - } - virtual void AnnotateFlightStrip(int index, std::string data) const = 0; - virtual std::string GetAnnotation(int index) const = 0; - virtual const std::string GetCallsign(void) const = 0; - virtual int GetClearedAltitude(void) const = 0; - virtual int GetCruiseLevel(void) const = 0; - virtual const std::string GetDestination(void) const = 0; - virtual double GetDistanceFromOrigin(void) const = 0; - virtual std::string GetExpectedDepartureTime(void) const = 0; - virtual UKControllerPlugin::Euroscope::EuroscopeExtractedRouteInterface GetExtractedRoute(void) const = 0; - virtual std::string GetFlightRules(void) const = 0; - virtual std::string GetGroundState(void) const = 0; - virtual const std::string GetOrigin(void) const = 0; - virtual std::string GetRawRouteString(void) const = 0; - virtual const std::string GetSidName(void) const = 0; - virtual std::string GetAssignedSquawk(void) const = 0; - virtual std::string GetAircraftType(void) const = 0; - virtual std::string GetIcaoWakeCategory(void) const = 0; - virtual bool HasAssignedSquawk(void) const = 0; - virtual bool HasControllerClearedAltitude(void) const = 0; - virtual bool HasControllerAssignedHeading(void) const = 0; - virtual bool HasSid(void) const = 0; - virtual void SetClearedAltitude(int cleared) = 0; - virtual void SetHeading(int heading) = 0; - virtual void SetSquawk(std::string squawk) = 0; - virtual bool IsSimulated(void) const = 0; - virtual bool IsTracked(void) const = 0; - virtual bool IsTrackedByUser(void) const = 0; - virtual bool IsValid(void) const = 0; - virtual bool IsVfr(void) const = 0; - virtual EuroScopePlugIn::CFlightPlan GetEuroScopeObject() const = 0; - virtual auto GetRemarks() const -> std::string = 0; - }; - } // namespace Euroscope -} // namespace UKControllerPlugin + /* + Interface for interacting with the wrapper around the EuroScope CFlightPlan class. + */ + class EuroScopeCFlightPlanInterface + { + public: + virtual ~EuroScopeCFlightPlanInterface() = default; + virtual void AnnotateFlightStrip(int index, std::string data) const = 0; + [[nodiscard]] virtual std::string GetAnnotation(int index) const = 0; + [[nodiscard]] virtual std::string GetCallsign() const = 0; + [[nodiscard]] virtual int GetClearedAltitude() const = 0; + [[nodiscard]] virtual int GetCruiseLevel() const = 0; + [[nodiscard]] virtual std::string GetDestination() const = 0; + [[nodiscard]] virtual double GetDistanceFromOrigin() const = 0; + [[nodiscard]] virtual double GetDistanceToDestination() const = 0; + [[nodiscard]] virtual std::string GetExpectedDepartureTime() const = 0; + [[nodiscard]] virtual UKControllerPlugin::Euroscope::EuroscopeExtractedRouteInterface + GetExtractedRoute() const = 0; + [[nodiscard]] virtual std::string GetFlightRules() const = 0; + [[nodiscard]] virtual std::string GetGroundState() const = 0; + [[nodiscard]] virtual std::string GetOrigin() const = 0; + [[nodiscard]] virtual std::string GetRawRouteString() const = 0; + [[nodiscard]] virtual std::string GetSidName() const = 0; + [[nodiscard]] virtual std::string GetAssignedSquawk() const = 0; + [[nodiscard]] virtual std::string GetAircraftType() const = 0; + [[nodiscard]] virtual std::string GetIcaoWakeCategory() const = 0; + [[nodiscard]] virtual bool HasAssignedSquawk() const = 0; + [[nodiscard]] virtual bool HasControllerClearedAltitude() const = 0; + [[nodiscard]] virtual bool HasControllerAssignedHeading() const = 0; + [[nodiscard]] virtual bool HasSid() const = 0; + virtual void SetClearedAltitude(int cleared) = 0; + virtual void SetHeading(int heading) = 0; + virtual void SetSquawk(std::string squawk) = 0; + [[nodiscard]] virtual bool IsSimulated() const = 0; + [[nodiscard]] virtual bool IsTracked() const = 0; + [[nodiscard]] virtual bool IsTrackedByUser() const = 0; + [[nodiscard]] virtual bool IsValid() const = 0; + [[nodiscard]] virtual bool IsVfr() const = 0; + [[nodiscard]] virtual EuroScopePlugIn::CFlightPlan GetEuroScopeObject() const = 0; + [[nodiscard]] virtual auto GetRemarks() const -> std::string = 0; + }; +} // namespace UKControllerPlugin::Euroscope diff --git a/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.cpp b/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.cpp index 52377fc8b..29e2a24af 100644 --- a/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.cpp +++ b/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.cpp @@ -4,191 +4,194 @@ using UKControllerPlugin::Euroscope::EuroscopeExtractedRouteInterface; using UKControllerPlugin::Squawk::SquawkValidator; -namespace UKControllerPlugin { - namespace Euroscope { - - EuroScopeCFlightPlanWrapper::EuroScopeCFlightPlanWrapper(EuroScopePlugIn::CFlightPlan originalData) - { - this->originalData = originalData; - } - - /* - Note indexes begin from 0 - */ - void EuroScopeCFlightPlanWrapper::AnnotateFlightStrip(int index, std::string data) const - { - this->originalData.GetControllerAssignedData().SetFlightStripAnnotation(index, data.c_str()); - } - - std::string EuroScopeCFlightPlanWrapper::GetAnnotation(int index) const - { - return this->originalData.GetControllerAssignedData().GetFlightStripAnnotation(index); - } - - std::string EuroScopeCFlightPlanWrapper::GetAircraftType(void) const - { - return this->originalData.GetFlightPlanData().GetAircraftFPType(); - } - - const std::string EuroScopeCFlightPlanWrapper::GetCallsign(void) const - { - return this->originalData.GetCallsign(); - } - - int EuroScopeCFlightPlanWrapper::GetClearedAltitude(void) const - { - return this->originalData.GetControllerAssignedData().GetClearedAltitude(); - } - - int EuroScopeCFlightPlanWrapper::GetCruiseLevel(void) const - { - return this->originalData.GetFinalAltitude(); - } - - const std::string EuroScopeCFlightPlanWrapper::GetDestination(void) const - { - return this->originalData.GetFlightPlanData().GetDestination(); - } - - double EuroScopeCFlightPlanWrapper::GetDistanceFromOrigin(void) const - { - return this->originalData.GetDistanceFromOrigin(); - } - - std::string EuroScopeCFlightPlanWrapper::GetExpectedDepartureTime(void) const - { - return this->originalData.GetFlightPlanData().GetEstimatedDepartureTime(); - } - - EuroscopeExtractedRouteInterface EuroScopeCFlightPlanWrapper::GetExtractedRoute(void) const - { - return EuroscopeExtractedRouteInterface(this->originalData.GetExtractedRoute()); - } - - std::string EuroScopeCFlightPlanWrapper::GetFlightRules(void) const - { - return this->originalData.GetFlightPlanData().GetPlanType(); - } - - std::string EuroScopeCFlightPlanWrapper::GetGroundState(void) const - { - return this->originalData.GetGroundState(); - } - - std::string EuroScopeCFlightPlanWrapper::GetIcaoWakeCategory(void) const - { - return std::string(1, this->originalData.GetFlightPlanData().GetAircraftWtc()); - } - - std::string EuroScopeCFlightPlanWrapper::GetAssignedSquawk(void) const - { - return this->originalData.GetControllerAssignedData().GetSquawk(); - } - - const std::string EuroScopeCFlightPlanWrapper::GetOrigin(void) const - { - return this->originalData.GetFlightPlanData().GetOrigin(); - } - - std::string EuroScopeCFlightPlanWrapper::GetRawRouteString(void) const - { - return this->originalData.GetFlightPlanData().GetRoute(); - } - - const std::string EuroScopeCFlightPlanWrapper::GetSidName(void) const - { - return this->originalData.GetFlightPlanData().GetSidName(); - } - - bool EuroScopeCFlightPlanWrapper::HasAssignedSquawk(void) const - { - std::string squawk = this->originalData.GetControllerAssignedData().GetSquawk(); - return SquawkValidator::ValidSquawk(squawk) && squawk != "0200" && squawk != "2200" && squawk != "1200" && - squawk != "2000" && squawk != "0000"; - } - - bool EuroScopeCFlightPlanWrapper::HasControllerClearedAltitude(void) const - { - return this->originalData.GetControllerAssignedData().GetClearedAltitude() != - this->euroScopeNoControllerClearedAltitude; - } - - bool EuroScopeCFlightPlanWrapper::HasControllerAssignedHeading() const - { - return this->originalData.GetControllerAssignedData().GetAssignedHeading() != 0; - } - - bool EuroScopeCFlightPlanWrapper::HasSid(void) const - { - return !std::string(this->originalData.GetFlightPlanData().GetSidName()).empty(); - } - - /* - Sets the cleared altitude. - */ - void EuroScopeCFlightPlanWrapper::SetClearedAltitude(int cleared) - { - this->originalData.GetControllerAssignedData().SetClearedAltitude(cleared); - } - - void EuroScopeCFlightPlanWrapper::SetHeading(int heading) - { - this->originalData.GetControllerAssignedData().SetAssignedHeading(heading); - } - - void EuroScopeCFlightPlanWrapper::SetSquawk(std::string squawk) - { - this->originalData.GetControllerAssignedData().SetSquawk(squawk.c_str()); - } - - EuroScopePlugIn::CFlightPlan EuroScopeCFlightPlanWrapper::GetEuroScopeObject() const - { - return this->originalData; - } - - /* - Returns true if its actually real and not Euroscope pretending. - */ - bool EuroScopeCFlightPlanWrapper::IsSimulated(void) const - { - return this->originalData.GetSimulated(); - } - - /* - Returns true if the aircraft is being tracked by anyone at all. - */ - bool EuroScopeCFlightPlanWrapper::IsTracked(void) const - { - return this->notTrackedControllerCallsign.compare(this->originalData.GetTrackingControllerCallsign()) != 0; - } - - /* - Returns true if the current controller tracking the flightplan is the user. - */ - bool EuroScopeCFlightPlanWrapper::IsTrackedByUser(void) const - { - return this->originalData.GetTrackingControllerIsMe(); - } - - /* - Returns true if Euroscope deems that the flightplan is valid. - */ - bool EuroScopeCFlightPlanWrapper::IsValid(void) const - { - return this->originalData.IsValid(); - } - - /* - Returns true if the flightplan is VFR - */ - bool EuroScopeCFlightPlanWrapper::IsVfr(void) const - { - return strcmp(this->originalData.GetFlightPlanData().GetPlanType(), "V") == 0; - } - - auto EuroScopeCFlightPlanWrapper::GetRemarks() const -> std::string - { - return this->originalData.GetFlightPlanData().GetRemarks(); - } - } // namespace Euroscope -} // namespace UKControllerPlugin +namespace UKControllerPlugin::Euroscope { + + EuroScopeCFlightPlanWrapper::EuroScopeCFlightPlanWrapper(EuroScopePlugIn::CFlightPlan originalData) + : originalData(originalData) + { + } + + /* + Note indexes begin from 0 + */ + void EuroScopeCFlightPlanWrapper::AnnotateFlightStrip(int index, std::string data) const + { + this->originalData.GetControllerAssignedData().SetFlightStripAnnotation(index, data.c_str()); + } + + auto EuroScopeCFlightPlanWrapper::GetAnnotation(int index) const -> std::string + { + return this->originalData.GetControllerAssignedData().GetFlightStripAnnotation(index); + } + + auto EuroScopeCFlightPlanWrapper::GetAircraftType() const -> std::string + { + return this->originalData.GetFlightPlanData().GetAircraftFPType(); + } + + auto EuroScopeCFlightPlanWrapper::GetCallsign() const -> std::string + { + return this->originalData.GetCallsign(); + } + + auto EuroScopeCFlightPlanWrapper::GetClearedAltitude() const -> int + { + return this->originalData.GetControllerAssignedData().GetClearedAltitude(); + } + + auto EuroScopeCFlightPlanWrapper::GetCruiseLevel() const -> int + { + return this->originalData.GetFinalAltitude(); + } + + auto EuroScopeCFlightPlanWrapper::GetDestination() const -> std::string + { + return this->originalData.GetFlightPlanData().GetDestination(); + } + + auto EuroScopeCFlightPlanWrapper::GetDistanceFromOrigin() const -> double + { + return this->originalData.GetDistanceFromOrigin(); + } + + auto EuroScopeCFlightPlanWrapper::GetExpectedDepartureTime() const -> std::string + { + return this->originalData.GetFlightPlanData().GetEstimatedDepartureTime(); + } + + auto EuroScopeCFlightPlanWrapper::GetExtractedRoute() const -> EuroscopeExtractedRouteInterface + { + return EuroscopeExtractedRouteInterface(this->originalData.GetExtractedRoute()); + } + + auto EuroScopeCFlightPlanWrapper::GetFlightRules() const -> std::string + { + return this->originalData.GetFlightPlanData().GetPlanType(); + } + + auto EuroScopeCFlightPlanWrapper::GetGroundState() const -> std::string + { + return this->originalData.GetGroundState(); + } + + auto EuroScopeCFlightPlanWrapper::GetIcaoWakeCategory() const -> std::string + { + return {this->originalData.GetFlightPlanData().GetAircraftWtc()}; + } + + auto EuroScopeCFlightPlanWrapper::GetAssignedSquawk() const -> std::string + { + return this->originalData.GetControllerAssignedData().GetSquawk(); + } + + auto EuroScopeCFlightPlanWrapper::GetOrigin() const -> std::string + { + return this->originalData.GetFlightPlanData().GetOrigin(); + } + + auto EuroScopeCFlightPlanWrapper::GetRawRouteString() const -> std::string + { + return this->originalData.GetFlightPlanData().GetRoute(); + } + + auto EuroScopeCFlightPlanWrapper::GetSidName() const -> std::string + { + return this->originalData.GetFlightPlanData().GetSidName(); + } + + auto EuroScopeCFlightPlanWrapper::HasAssignedSquawk() const -> bool + { + std::string squawk = this->originalData.GetControllerAssignedData().GetSquawk(); + return SquawkValidator::ValidSquawk(squawk) && squawk != "0200" && squawk != "2200" && squawk != "1200" && + squawk != "2000" && squawk != "0000"; + } + + auto EuroScopeCFlightPlanWrapper::HasControllerClearedAltitude() const -> bool + { + return this->originalData.GetControllerAssignedData().GetClearedAltitude() != + this->euroScopeNoControllerClearedAltitude; + } + + auto EuroScopeCFlightPlanWrapper::HasControllerAssignedHeading() const -> bool + { + return this->originalData.GetControllerAssignedData().GetAssignedHeading() != 0; + } + + auto EuroScopeCFlightPlanWrapper::HasSid() const -> bool + { + return !std::string(this->originalData.GetFlightPlanData().GetSidName()).empty(); + } + + /* + Sets the cleared altitude. + */ + void EuroScopeCFlightPlanWrapper::SetClearedAltitude(int cleared) + { + this->originalData.GetControllerAssignedData().SetClearedAltitude(cleared); + } + + void EuroScopeCFlightPlanWrapper::SetHeading(int heading) + { + this->originalData.GetControllerAssignedData().SetAssignedHeading(heading); + } + + void EuroScopeCFlightPlanWrapper::SetSquawk(std::string squawk) + { + this->originalData.GetControllerAssignedData().SetSquawk(squawk.c_str()); + } + + auto EuroScopeCFlightPlanWrapper::GetEuroScopeObject() const -> EuroScopePlugIn::CFlightPlan + { + return this->originalData; + } + + /* + Returns true if its actually real and not Euroscope pretending. + */ + auto EuroScopeCFlightPlanWrapper::IsSimulated() const -> bool + { + return this->originalData.GetSimulated(); + } + + /* + Returns true if the aircraft is being tracked by anyone at all. + */ + auto EuroScopeCFlightPlanWrapper::IsTracked() const -> bool + { + return this->notTrackedControllerCallsign != this->originalData.GetTrackingControllerCallsign(); + } + + /* + Returns true if the current controller tracking the flightplan is the user. + */ + auto EuroScopeCFlightPlanWrapper::IsTrackedByUser() const -> bool + { + return this->originalData.GetTrackingControllerIsMe(); + } + + /* + Returns true if Euroscope deems that the flightplan is valid. + */ + auto EuroScopeCFlightPlanWrapper::IsValid() const -> bool + { + return this->originalData.IsValid(); + } + + /* + Returns true if the flightplan is VFR + */ + auto EuroScopeCFlightPlanWrapper::IsVfr() const -> bool + { + return strcmp(this->originalData.GetFlightPlanData().GetPlanType(), "V") == 0; + } + + auto EuroScopeCFlightPlanWrapper::GetDistanceToDestination() const -> double + { + return this->originalData.GetDistanceToDestination(); + } + + auto EuroScopeCFlightPlanWrapper::GetRemarks() const -> std::string + { + return this->originalData.GetFlightPlanData().GetRemarks(); + } +} // namespace UKControllerPlugin::Euroscope diff --git a/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.h b/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.h index d0f743116..feba56137 100644 --- a/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.h +++ b/src/plugin/euroscope/EuroScopeCFlightPlanWrapper.h @@ -15,19 +15,20 @@ namespace UKControllerPlugin::Euroscope { std::string GetAnnotation(int index) const override; std::string GetAircraftType() const override; std::string GetAssignedSquawk() const override; - const std::string GetCallsign() const override; + std::string GetCallsign() const override; int GetClearedAltitude() const override; int GetCruiseLevel() const override; - const std::string GetDestination() const override; + std::string GetDestination() const override; double GetDistanceFromOrigin() const override; + double GetDistanceToDestination() const override; std::string GetExpectedDepartureTime() const override; virtual UKControllerPlugin::Euroscope::EuroscopeExtractedRouteInterface GetExtractedRoute() const override; std::string GetFlightRules() const override; std::string GetGroundState() const override; std::string GetIcaoWakeCategory() const override; - const std::string GetOrigin() const override; + std::string GetOrigin() const override; std::string GetRawRouteString() const override; - const std::string GetSidName() const override; + std::string GetSidName() const override; bool HasAssignedSquawk() const override; bool HasControllerClearedAltitude() const override; bool HasControllerAssignedHeading() const override; diff --git a/src/plugin/euroscope/EuroscopeRadarLoopbackInterface.h b/src/plugin/euroscope/EuroscopeRadarLoopbackInterface.h index 5f17ca96b..2bb49e94c 100644 --- a/src/plugin/euroscope/EuroscopeRadarLoopbackInterface.h +++ b/src/plugin/euroscope/EuroscopeRadarLoopbackInterface.h @@ -27,5 +27,6 @@ namespace UKControllerPlugin::Euroscope { TogglePluginTagFunction(std::string callsign, int functionId, POINT mousePos, RECT tagItemArea) = 0; virtual void ToogleMenu(RECT area, std::string title, int numColumns) = 0; [[nodiscard]] virtual auto ConvertCoordinateToScreenPoint(EuroScopePlugIn::CPosition pos) -> POINT = 0; + [[nodiscard]] virtual auto ConvertScreenPointToCoordinate(const POINT& point) -> EuroScopePlugIn::CPosition = 0; }; } // namespace UKControllerPlugin::Euroscope diff --git a/src/plugin/handoff/HandoffEventHandler.cpp b/src/plugin/handoff/HandoffEventHandler.cpp index 39b43591d..a32d24cab 100644 --- a/src/plugin/handoff/HandoffEventHandler.cpp +++ b/src/plugin/handoff/HandoffEventHandler.cpp @@ -106,7 +106,7 @@ namespace UKControllerPlugin::Handoff { /* If a new callsign comes along, we have to clear the cache. */ - void HandoffEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign, bool userCallsign) + void HandoffEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign) { this->cache.clear(); } @@ -114,7 +114,7 @@ namespace UKControllerPlugin::Handoff { /* If a callsign is removed, clear the cache for anything they were involved in. */ - void HandoffEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign, bool userCallsign) + void HandoffEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign) { for (auto it = this->cache.cbegin(); it != this->cache.cend();) { auto keyToRemove = it++; diff --git a/src/plugin/handoff/HandoffEventHandler.h b/src/plugin/handoff/HandoffEventHandler.h index 8c3d91279..daf86e7c4 100644 --- a/src/plugin/handoff/HandoffEventHandler.h +++ b/src/plugin/handoff/HandoffEventHandler.h @@ -38,8 +38,8 @@ namespace UKControllerPlugin::Handoff { void ControllerFlightPlanDataEvent(Euroscope::EuroScopeCFlightPlanInterface& flightPlan, int dataType) override; // Inherited via ActiveCallsignEventHandlerInterface - void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign, bool userCallsign) override; - void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign, bool userCallsign) override; + void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign) override; + void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign) override; void CallsignsFlushed() override; private: diff --git a/src/plugin/headings/Heading.cpp b/src/plugin/headings/Heading.cpp new file mode 100644 index 000000000..c9af4f3db --- /dev/null +++ b/src/plugin/headings/Heading.cpp @@ -0,0 +1,28 @@ +#include "Heading.h" + +namespace UKControllerPlugin::Headings { + auto operator<(Heading first, Heading second) -> bool + { + return static_cast(first) < static_cast(second); + } + + auto operator>=(Heading first, Heading second) -> bool + { + return static_cast(first) >= static_cast(second); + } + + auto operator<(double first, Heading second) -> bool + { + return first < static_cast(second); + } + + auto operator>=(double first, Heading second) -> bool + { + return first >= static_cast(second); + } + + auto operator==(unsigned int first, Heading second) -> bool + { + return first == static_cast(second); + } +} // namespace UKControllerPlugin::Headings diff --git a/src/plugin/headings/Heading.h b/src/plugin/headings/Heading.h new file mode 100644 index 000000000..fb771110e --- /dev/null +++ b/src/plugin/headings/Heading.h @@ -0,0 +1,29 @@ +#pragma once + +namespace UKControllerPlugin::Headings { + enum class Heading : unsigned int + { + North = 360, + N = North, + NorthEast = 45, + NE = NorthEast, + East = 90, + E = East, + SouthEast = 135, + SE = SouthEast, + South = 180, + S = South, + SouthWest = 225, + SW = SouthWest, + West = 270, + W = West, + NorthWest = 315, + NW = NorthWest + }; + + auto operator<(Heading first, Heading second) -> bool; + auto operator>=(Heading first, Heading second) -> bool; + auto operator<(double first, Heading second) -> bool; + auto operator>=(double first, Heading second) -> bool; + auto operator==(unsigned int first, Heading second) -> bool; +} // namespace UKControllerPlugin::Headings diff --git a/src/plugin/initialaltitude/InitialAltitudeEventHandler.cpp b/src/plugin/initialaltitude/InitialAltitudeEventHandler.cpp index 751347bb7..0a0dcf76d 100644 --- a/src/plugin/initialaltitude/InitialAltitudeEventHandler.cpp +++ b/src/plugin/initialaltitude/InitialAltitudeEventHandler.cpp @@ -6,7 +6,7 @@ #include "euroscope/GeneralSettingsEntries.h" #include "euroscope/UserSetting.h" #include "login/Login.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "sid/SidCollection.h" #include "sid/StandardInstrumentDeparture.h" @@ -18,7 +18,7 @@ using UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface; using UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface; using UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface; using UKControllerPlugin::Euroscope::GeneralSettingsEntries; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Sid::SidCollection; using UKControllerPlugin::Sid::StandardInstrumentDeparture; @@ -27,7 +27,7 @@ namespace UKControllerPlugin::InitialAltitude { InitialAltitudeEventHandler::InitialAltitudeEventHandler( const SidCollection& sids, const ActiveCallsignCollection& activeCallsigns, - const AirfieldOwnershipManager& airfieldOwnership, + const AirfieldServiceProviderCollection& airfieldOwnership, const Login& login, EuroscopePluginLoopbackInterface& plugin) : sids(sids), activeCallsigns(activeCallsigns), airfieldOwnership(airfieldOwnership), login(login), @@ -162,7 +162,7 @@ namespace UKControllerPlugin::InitialAltitude { flightPlan.GetDistanceFromOrigin() <= this->assignmentMaxDistanceFromOrigin && radarTarget.GetGroundSpeed() <= this->assignmentMaxSpeed && !flightPlan.HasControllerClearedAltitude() && !flightPlan.IsTracked() && !flightPlan.IsSimulated() && - this->airfieldOwnership.AirfieldOwnedByUser(flightPlan.GetOrigin()); + this->airfieldOwnership.DeliveryControlProvidedByUser(flightPlan.GetOrigin()); } /* @@ -187,9 +187,9 @@ namespace UKControllerPlugin::InitialAltitude { /* If its the user, do some updates */ - void InitialAltitudeEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign, bool userCallsign) + void InitialAltitudeEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign) { - if (!userCallsign) { + if (!callsign.GetIsUser()) { return; } @@ -200,7 +200,7 @@ namespace UKControllerPlugin::InitialAltitude { /* Nothing to see here */ - void InitialAltitudeEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign, bool userCallsign) + void InitialAltitudeEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign) { } diff --git a/src/plugin/initialaltitude/InitialAltitudeEventHandler.h b/src/plugin/initialaltitude/InitialAltitudeEventHandler.h index f273b798d..eb8e0f6f2 100644 --- a/src/plugin/initialaltitude/InitialAltitudeEventHandler.h +++ b/src/plugin/initialaltitude/InitialAltitudeEventHandler.h @@ -12,7 +12,7 @@ namespace UKControllerPlugin { class FlightPlanEventHandlerInterface; } // namespace Flightplan namespace Ownership { - class AirfieldOwnershipManager; + class AirfieldServiceProviderCollection; } // namespace Ownership namespace Controller { @@ -42,7 +42,7 @@ namespace UKControllerPlugin::InitialAltitude { InitialAltitudeEventHandler( const Sid::SidCollection& sids, const Controller::ActiveCallsignCollection& activeCallsigns, - const Ownership::AirfieldOwnershipManager& airfieldOwnership, + const Ownership::AirfieldServiceProviderCollection& airfieldOwnership, const Controller::Login& login, Euroscope::EuroscopePluginLoopbackInterface& plugin); void FlightPlanEvent( @@ -59,8 +59,8 @@ namespace UKControllerPlugin::InitialAltitude { void UserSettingsUpdated(Euroscope::UserSetting& userSettings) override; // Inherited via ActiveCallsignEventHandlerInterface - void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign, bool userCallsign) override; - void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign, bool userCallsign) override; + void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign) override; + void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign) override; void TimedEventTrigger() override; private: @@ -83,7 +83,7 @@ namespace UKControllerPlugin::InitialAltitude { const Controller::ActiveCallsignCollection& activeCallsigns; // Used to find out if the user owns a particular airfield. - const Ownership::AirfieldOwnershipManager& airfieldOwnership; + const Ownership::AirfieldServiceProviderCollection& airfieldOwnership; // For checking how long we've been logged in const Controller::Login& login; diff --git a/src/plugin/initialaltitude/InitialAltitudeModule.cpp b/src/plugin/initialaltitude/InitialAltitudeModule.cpp index 8e242d364..06f0e0911 100644 --- a/src/plugin/initialaltitude/InitialAltitudeModule.cpp +++ b/src/plugin/initialaltitude/InitialAltitudeModule.cpp @@ -30,7 +30,6 @@ namespace UKControllerPlugin::InitialAltitude { *persistence.login, *persistence.plugin)); - persistence.initialAltitudeEvents = initialAltitudeEventHandler; persistence.userSettingHandlers->RegisterHandler(initialAltitudeEventHandler); persistence.flightplanHandler->RegisterHandler(initialAltitudeEventHandler); persistence.activeCallsigns->AddHandler(initialAltitudeEventHandler); diff --git a/src/plugin/initialheading/InitialHeadingEventHandler.cpp b/src/plugin/initialheading/InitialHeadingEventHandler.cpp index a5aa2ca3e..e496c06ec 100644 --- a/src/plugin/initialheading/InitialHeadingEventHandler.cpp +++ b/src/plugin/initialheading/InitialHeadingEventHandler.cpp @@ -6,7 +6,7 @@ #include "euroscope/GeneralSettingsEntries.h" #include "euroscope/UserSetting.h" #include "login/Login.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "sid/SidCollection.h" #include "sid/StandardInstrumentDeparture.h" @@ -18,7 +18,7 @@ using UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface; using UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface; using UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface; using UKControllerPlugin::Euroscope::GeneralSettingsEntries; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Sid::SidCollection; using UKControllerPlugin::Sid::StandardInstrumentDeparture; @@ -27,7 +27,7 @@ namespace UKControllerPlugin::InitialHeading { InitialHeadingEventHandler::InitialHeadingEventHandler( const SidCollection& sids, const ActiveCallsignCollection& activeCallsigns, - const AirfieldOwnershipManager& airfieldOwnership, + const AirfieldServiceProviderCollection& airfieldOwnership, const Login& login, EuroscopePluginLoopbackInterface& plugin) : sids(sids), activeCallsigns(activeCallsigns), airfieldOwnership(airfieldOwnership), login(login), @@ -164,7 +164,7 @@ namespace UKControllerPlugin::InitialHeading { flightPlan.GetDistanceFromOrigin() <= this->assignmentMaxDistanceFromOrigin && radarTarget.GetGroundSpeed() <= this->assignmentMaxSpeed && !flightPlan.HasControllerAssignedHeading() && !flightPlan.IsTracked() && !flightPlan.IsSimulated() && - this->airfieldOwnership.AirfieldOwnedByUser(flightPlan.GetOrigin()); + this->airfieldOwnership.DeliveryControlProvidedByUser(flightPlan.GetOrigin()); } /* @@ -189,9 +189,9 @@ namespace UKControllerPlugin::InitialHeading { /* If its the user, do some updates */ - void InitialHeadingEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign, bool userCallsign) + void InitialHeadingEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign) { - if (!userCallsign) { + if (!callsign.GetIsUser()) { return; } @@ -202,7 +202,7 @@ namespace UKControllerPlugin::InitialHeading { /* Nothing to see here */ - void InitialHeadingEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign, bool userCallsign) + void InitialHeadingEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign) { } diff --git a/src/plugin/initialheading/InitialHeadingEventHandler.h b/src/plugin/initialheading/InitialHeadingEventHandler.h index 079c98819..5e8d51821 100644 --- a/src/plugin/initialheading/InitialHeadingEventHandler.h +++ b/src/plugin/initialheading/InitialHeadingEventHandler.h @@ -12,7 +12,7 @@ namespace UKControllerPlugin { class FlightPlanEventHandlerInterface; } // namespace Flightplan namespace Ownership { - class AirfieldOwnershipManager; + class AirfieldServiceProviderCollection; } // namespace Ownership namespace Controller { @@ -42,7 +42,7 @@ namespace UKControllerPlugin::InitialHeading { InitialHeadingEventHandler( const Sid::SidCollection& sids, const Controller::ActiveCallsignCollection& activeCallsigns, - const Ownership::AirfieldOwnershipManager& airfieldOwnership, + const Ownership::AirfieldServiceProviderCollection& airfieldOwnership, const Controller::Login& login, Euroscope::EuroscopePluginLoopbackInterface& plugin); void FlightPlanEvent( @@ -59,8 +59,8 @@ namespace UKControllerPlugin::InitialHeading { void UserSettingsUpdated(Euroscope::UserSetting& userSettings) override; // Inherited via ActiveCallsignEventHandlerInterface - void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign, bool userCallsign) override; - void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign, bool userCallsign) override; + void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign) override; + void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign) override; void TimedEventTrigger() override; private: @@ -83,7 +83,7 @@ namespace UKControllerPlugin::InitialHeading { const Controller::ActiveCallsignCollection& activeCallsigns; // Used to find out if the user owns a particular airfield. - const Ownership::AirfieldOwnershipManager& airfieldOwnership; + const Ownership::AirfieldServiceProviderCollection& airfieldOwnership; // For checking how long we've been logged in const Controller::Login& login; diff --git a/src/plugin/missedapproach/CompareMissedApproaches.cpp b/src/plugin/missedapproach/CompareMissedApproaches.cpp new file mode 100644 index 000000000..64b4f8bad --- /dev/null +++ b/src/plugin/missedapproach/CompareMissedApproaches.cpp @@ -0,0 +1,20 @@ +#include "CompareMissedApproaches.h" +#include "MissedApproach.h" + +namespace UKControllerPlugin::MissedApproach { + auto CompareMissedApproaches::operator()(const std::shared_ptr& missed, int id) const -> bool + { + return missed->Id() < id; + } + + auto CompareMissedApproaches::operator()(int id, const std::shared_ptr& missed) const -> bool + { + return id < missed->Id(); + } + + auto CompareMissedApproaches::operator()( + const std::shared_ptr& a, const std::shared_ptr& b) const -> bool + { + return a->Id() < b->Id(); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/CompareMissedApproaches.h b/src/plugin/missedapproach/CompareMissedApproaches.h new file mode 100644 index 000000000..54afe558f --- /dev/null +++ b/src/plugin/missedapproach/CompareMissedApproaches.h @@ -0,0 +1,18 @@ +#pragma once + +namespace UKControllerPlugin::MissedApproach { + class MissedApproach; + + /** + * Compares prenotes in a set by id. + */ + using CompareMissedApproaches = struct CompareMissedApproaches + { + using is_transparent = int; + + auto operator()(const std::shared_ptr& missed, int id) const -> bool; + auto operator()(int id, const std::shared_ptr& missed) const -> bool; + auto operator()(const std::shared_ptr& a, const std::shared_ptr& b) const + -> bool; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/ConfigureMissedApproaches.cpp b/src/plugin/missedapproach/ConfigureMissedApproaches.cpp new file mode 100644 index 000000000..035e071f6 --- /dev/null +++ b/src/plugin/missedapproach/ConfigureMissedApproaches.cpp @@ -0,0 +1,33 @@ +#include "ConfigureMissedApproaches.h" +#include "MissedApproachRenderOptions.h" +#include "dialog/DialogManager.h" + +using UKControllerPlugin::Plugin::PopupMenuItem; + +namespace UKControllerPlugin::MissedApproach { + ConfigureMissedApproaches::ConfigureMissedApproaches( + std::shared_ptr renderOptions, + UKControllerPlugin::Dialog::DialogManager& dialogManager, + int configureCallbackId) + : renderOptions(std::move(renderOptions)), dialogManager(dialogManager), + configureCallbackId(configureCallbackId) + { + } + + void ConfigureMissedApproaches::Configure(int functionId, std::string subject, RECT screenObjectArea) + { + this->dialogManager.OpenDialog(IDD_MISSED_APPROACH, reinterpret_cast(&renderOptions)); // NOLINT + } + + auto ConfigureMissedApproaches::GetConfigurationMenuItem() const -> UKControllerPlugin::Plugin::PopupMenuItem + { + PopupMenuItem returnVal; + returnVal.firstValue = "Configure Missed Approaches"; + returnVal.secondValue = ""; + returnVal.callbackFunctionId = this->configureCallbackId; + returnVal.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + returnVal.disabled = false; + returnVal.fixedPosition = false; + return returnVal; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/ConfigureMissedApproaches.h b/src/plugin/missedapproach/ConfigureMissedApproaches.h new file mode 100644 index 000000000..41283345d --- /dev/null +++ b/src/plugin/missedapproach/ConfigureMissedApproaches.h @@ -0,0 +1,35 @@ +#pragma once +#include "radarscreen/ConfigurableDisplayInterface.h" + +namespace UKControllerPlugin::Dialog { + class DialogManager; +} // namespace UKControllerPlugin::Dialog + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachRenderOptions; + + /** + * Toggles the missed approach button for a given ASR through + * an option in the OP menu. + */ + class ConfigureMissedApproaches : public RadarScreen::ConfigurableDisplayInterface + { + public: + ConfigureMissedApproaches( + std::shared_ptr renderOptions, + Dialog::DialogManager& dialogManager, + int configureCallbackId); + void Configure(int functionId, std::string subject, RECT screenObjectArea) override; + [[nodiscard]] auto GetConfigurationMenuItem() const -> UKControllerPlugin::Plugin::PopupMenuItem override; + + private: + // The button we want to toggle + std::shared_ptr renderOptions; + + // Opens dialog + Dialog::DialogManager& dialogManager; + + // Callback id for a click + const int configureCallbackId; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproach.cpp b/src/plugin/missedapproach/MissedApproach.cpp new file mode 100644 index 000000000..983e99046 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproach.cpp @@ -0,0 +1,41 @@ +#include "MissedApproach.h" +#include "time/SystemClock.h" + +namespace UKControllerPlugin::MissedApproach { + MissedApproach::MissedApproach( + int id, std::string callsign, std::chrono::system_clock::time_point expiresAt, bool createdByUser) + : id(id), callsign(std::move(callsign)), createdAt(Time::TimeNow()), expiresAt(expiresAt), + createdByUser(createdByUser) + { + } + + auto MissedApproach::Callsign() const -> const std::string& + { + return callsign; + } + + auto MissedApproach::CreatedAt() const -> const std::chrono::system_clock::time_point& + { + return createdAt; + } + + auto MissedApproach::ExpiresAt() const -> const std::chrono::system_clock::time_point& + { + return expiresAt; + } + + auto MissedApproach::IsExpired() const -> bool + { + return expiresAt < Time::TimeNow(); + } + + auto MissedApproach::Id() const -> int + { + return this->id; + } + + auto MissedApproach::CreatedByUser() const -> bool + { + return this->createdByUser; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproach.h b/src/plugin/missedapproach/MissedApproach.h new file mode 100644 index 000000000..fd252a9c3 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproach.h @@ -0,0 +1,34 @@ +#pragma once + +namespace UKControllerPlugin::MissedApproach { + /* + * Stores details about a missed approach / go around + */ + class MissedApproach + { + public: + MissedApproach( + int id, std::string callsign, std::chrono::system_clock::time_point expiresAt, bool createdByUser); + [[nodiscard]] auto Callsign() const -> const std::string&; + [[nodiscard]] auto CreatedAt() const -> const std::chrono::system_clock::time_point&; + [[nodiscard]] auto CreatedByUser() const -> bool; + [[nodiscard]] auto ExpiresAt() const -> const std::chrono::system_clock::time_point&; + [[nodiscard]] auto Id() const -> int; + [[nodiscard]] auto IsExpired() const -> bool; + + private: + // The id of the missed approach + int id; + + // The callsign associated with the missed approach + std::string callsign; + + // The time the missed approach expires and another can be created + std::chrono::system_clock::time_point createdAt; + + // The time the missed approach expires and another can be created + std::chrono::system_clock::time_point expiresAt; + + bool createdByUser; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachAudioAlert.cpp b/src/plugin/missedapproach/MissedApproachAudioAlert.cpp new file mode 100644 index 000000000..db4afd8b7 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachAudioAlert.cpp @@ -0,0 +1,62 @@ +#include "MissedApproach.h" +#include "MissedApproachAudioAlert.h" +#include "MissedApproachOptions.h" +#include "euroscope/EuroScopeCFlightPlanInterface.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "windows/WinApiInterface.h" + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachAudioAlert::MissedApproachAudioAlert( + std::shared_ptr options, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + const Ownership::AirfieldServiceProviderCollection& airfieldServiceProvisions, + Windows::WinApiInterface& winApi) + : options(std::move(options)), plugin(plugin), airfieldServiceProvisions(airfieldServiceProvisions), + winApi(winApi) + { + } + + void MissedApproachAudioAlert::Play(const std::shared_ptr& missedApproach) const + { + if (!this->ShouldPlay(missedApproach)) { + LogDebug("Skipping missed approach audio alert for " + missedApproach->Callsign()); + return; + } + + LogInfo("Playing missed approach audio alert"); + this->winApi.PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH)); // NOLINT + } + + /** + * Audio alerts should only be played if: + * + * 1. The user has elected to receive alerts for approaches created by them OR it the approach was not created by + * them. + * 2. The flightplan exists. + * 3. The user is providing a specified service at the aircrafts destination airfield. + * @param missedApproach + * @return + */ + auto MissedApproachAudioAlert::ShouldPlay(const std::shared_ptr& missedApproach) const -> bool + { + if (!this->options->AudioAlert()) { + return false; + } + + if (!this->options->AudioAlertForCurrentUser() && missedApproach->CreatedByUser()) { + return false; + } + + const auto flightplan = this->plugin.GetFlightplanForCallsign(missedApproach->Callsign()); + if (!flightplan) { + return false; + } + + const auto airfields = + this->airfieldServiceProvisions.GetAirfieldsWhereUserProvidingServices(this->options->ServiceProvisions()); + + return std::find(airfields.cbegin(), airfields.cend(), flightplan->GetDestination()) != airfields.cend(); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachAudioAlert.h b/src/plugin/missedapproach/MissedApproachAudioAlert.h new file mode 100644 index 000000000..e46d67eba --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachAudioAlert.h @@ -0,0 +1,48 @@ +#pragma once + +namespace UKControllerPlugin { + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope + namespace Ownership { + class AirfieldServiceProviderCollection; + } // namespace Ownership + namespace Windows { + class WinApiInterface; + } // namespace Windows +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachOptions; + class MissedApproach; + + /** + * Class responsible for playing the missed approach audio alert + * of a missed approach. + */ + class MissedApproachAudioAlert + { + public: + MissedApproachAudioAlert( + std::shared_ptr options, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + const Ownership::AirfieldServiceProviderCollection& airfieldServiceProvisions, + Windows::WinApiInterface& winApi); + void Play(const std::shared_ptr& missedApproach) const; + + private: + [[nodiscard]] auto ShouldPlay(const std::shared_ptr& missedApproach) const -> bool; + + // Options for missed approaches + std::shared_ptr options; + + // Plugin for retrieving flightplans + Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // Who's providing what services at airfields + const Ownership::AirfieldServiceProviderCollection& airfieldServiceProvisions; + + // For playing sounds + Windows::WinApiInterface& winApi; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachButton.cpp b/src/plugin/missedapproach/MissedApproachButton.cpp new file mode 100644 index 000000000..60759baf8 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachButton.cpp @@ -0,0 +1,149 @@ +#include "MissedApproach.h" +#include "MissedApproachButton.h" +#include "MissedApproachCollection.h" +#include "TriggerMissedApproach.h" +#include "euroscope/EuroScopeCFlightPlanInterface.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "euroscope/EuroscopeRadarLoopbackInterface.h" +#include "euroscope/UserSetting.h" +#include "graphics/GdiGraphicsInterface.h" +#include "ownership/AirfieldServiceProviderCollection.h" + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachButton::MissedApproachButton( + std::shared_ptr missedApproaches, + std::shared_ptr trigger, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + const Ownership::AirfieldServiceProviderCollection& serviceProviders, + int screenObjectId) + : missedApproaches(std::move(missedApproaches)), trigger(std::move(trigger)), plugin(plugin), + serviceProviders(serviceProviders), screenObjectId(screenObjectId), isVisible(false), + position(DEFAULT_POSITION), ACTIVE_BRUSH(ACTIVE_COLOR), DISABLED_BRUSH(DISABLED_COLOR), + ARMED_BRUSH(ARMED_COLOR), TEXT_BRUSH(TEXT_COLOR), BORDER_PEN(BORDER_COLOR, BORDER_THICKNESS) + { + } + + void MissedApproachButton::AsrLoadedEvent(Euroscope::UserSetting& userSetting) + { + this->isVisible = userSetting.GetBooleanEntry(this->ASR_VISIBILITY_KEY, false); + auto xpos = userSetting.GetIntegerEntry(this->ASR_X_POSITION_KEY, DEFAULT_COORDINATE); + auto ypos = userSetting.GetIntegerEntry(this->ASR_Y_POSITION_KEY, DEFAULT_COORDINATE); + this->Move({xpos, ypos, xpos + DEFAULT_WIDTH, ypos + DEFAULT_HEIGHT}, ""); + } + + void MissedApproachButton::AsrClosingEvent(Euroscope::UserSetting& userSetting) + { + userSetting.Save(this->ASR_VISIBILITY_KEY, this->ASR_VISIBILITY_DESC, this->isVisible); + userSetting.Save(this->ASR_X_POSITION_KEY, this->ASR_X_POSITION_DESC, this->position.left); + userSetting.Save(this->ASR_Y_POSITION_KEY, this->ASR_Y_POSITION_DESC, this->position.top); + } + + auto MissedApproachButton::IsVisible() const -> bool + { + return this->isVisible; + } + + void MissedApproachButton::LeftClick( + Euroscope::EuroscopeRadarLoopbackInterface& radarScreen, + int objectId, + const std::string& objectDescription, + POINT mousePos, + RECT itemArea) + { + const auto flightplan = this->plugin.GetSelectedFlightplan(); + const auto radarTarget = this->plugin.GetSelectedRadarTarget(); + if (!flightplan || !radarTarget) { + return; + } + + this->trigger->Trigger(*flightplan, *radarTarget); + } + + void MissedApproachButton::Move(RECT position, std::string objectDescription) + { + this->position = {position.left, position.top, position.left + DEFAULT_WIDTH, position.top + DEFAULT_HEIGHT}; + this->renderRect = Gdiplus::Rect{this->position.left, this->position.top, DEFAULT_WIDTH, DEFAULT_HEIGHT}; + } + + void MissedApproachButton::Render( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + this->ApplyClickspot(radarScreen); + + const auto flightplan = this->plugin.GetSelectedFlightplan(); + if (!flightplan || !this->serviceProviders.TowerControlProvidedByUser(flightplan->GetDestination())) { + this->RenderDisabledButton(graphics); + return; + } + + const auto& missedApproach = this->missedApproaches->Get(flightplan->GetCallsign()); + if (missedApproach) { + this->RenderActiveButton(graphics); + return; + } + + this->RenderArmedButton(graphics); + } + + void MissedApproachButton::ResetPosition() + { + this->Move(DEFAULT_POSITION, ""); + } + + void MissedApproachButton::RenderDisabledButton(Windows::GdiGraphicsInterface& graphics) + { + this->RenderBackground(graphics, DISABLED_BRUSH); + this->RenderMissedApproachText(graphics); + this->RenderBorder(graphics); + } + + void MissedApproachButton::RenderBorder(Windows::GdiGraphicsInterface& graphics) + { + graphics.DrawRect(this->renderRect, BORDER_PEN); + } + + void MissedApproachButton::RenderBackground(Windows::GdiGraphicsInterface& graphics, const Gdiplus::Brush& brush) + { + graphics.FillRect(this->renderRect, brush); + } + + void MissedApproachButton::RenderMissedApproachText(Windows::GdiGraphicsInterface& graphics) + { + this->RenderText(graphics, L"GO AROUND"); + } + + void MissedApproachButton::RenderText(Windows::GdiGraphicsInterface& graphics, const std::wstring& text) + { + graphics.DrawString(text, this->renderRect, this->TEXT_BRUSH); + } + + void MissedApproachButton::RenderActiveButton(Windows::GdiGraphicsInterface& graphics) + { + this->RenderBackground(graphics, ACTIVE_BRUSH); + this->RenderMissedApproachText(graphics); + this->RenderBorder(graphics); + } + + void MissedApproachButton::RenderArmedButton(Windows::GdiGraphicsInterface& graphics) + { + this->RenderBackground(graphics, ARMED_BRUSH); + this->RenderMissedApproachText(graphics); + this->RenderBorder(graphics); + } + + void MissedApproachButton::ApplyClickspot(Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + radarScreen.RegisterScreenObject(this->screenObjectId, "goAroundButton", this->position, true); + } + + auto MissedApproachButton::Position() const -> RECT + { + return this->position; + } + + void MissedApproachButton::ToggleVisible() + { + this->isVisible = !this->isVisible; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachButton.h b/src/plugin/missedapproach/MissedApproachButton.h new file mode 100644 index 000000000..e98928cdf --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachButton.h @@ -0,0 +1,110 @@ +#pragma once +#include "euroscope/AsrEventHandlerInterface.h" +#include "radarscreen/RadarRenderableInterface.h" + +namespace UKControllerPlugin { + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope + namespace Ownership { + class AirfieldServiceProviderCollection; + } // namespace Ownership +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachCollection; + class TriggerMissedApproach; + + /** + * The button that can be pressed by the controller to trigger a missed + * approach. + */ + class MissedApproachButton : public Euroscope::AsrEventHandlerInterface, + public RadarScreen::RadarRenderableInterface + { + public: + MissedApproachButton( + std::shared_ptr missedApproaches, + std::shared_ptr trigger, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + const Ownership::AirfieldServiceProviderCollection& serviceProviders, + int screenObjectId); + + void AsrLoadedEvent(Euroscope::UserSetting& userSetting) override; + void AsrClosingEvent(Euroscope::UserSetting& userSetting) override; + [[nodiscard]] auto IsVisible() const -> bool override; + void LeftClick( + Euroscope::EuroscopeRadarLoopbackInterface& radarScreen, + int objectId, + const std::string& objectDescription, + POINT mousePos, + RECT itemArea) override; + void Move(RECT position, std::string objectDescription) override; + [[nodiscard]] auto Position() const -> RECT; + void Render( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) override; + void ResetPosition() override; + void ToggleVisible(); + + private: + void RenderActiveButton(Windows::GdiGraphicsInterface& graphics); + void RenderArmedButton(Windows::GdiGraphicsInterface& graphics); + void RenderDisabledButton(Windows::GdiGraphicsInterface& graphics); + void RenderBorder(Windows::GdiGraphicsInterface& graphics); + void RenderMissedApproachText(Windows::GdiGraphicsInterface& graphics); + void RenderText(Windows::GdiGraphicsInterface& graphics, const std::wstring& text); + void RenderBackground(Windows::GdiGraphicsInterface& graphics, const Gdiplus::Brush& brush); + void ApplyClickspot(Euroscope::EuroscopeRadarLoopbackInterface& radarScreen); + + // All the missed appraoches so we know what button to render + const std::shared_ptr missedApproaches; + + // Trigger mechanism + const std::shared_ptr trigger; + + // For getting flightplans + Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // Who's providing what service, where + const Ownership::AirfieldServiceProviderCollection& serviceProviders; + + // The id of the button on screen + const int screenObjectId; + + // Are we displaying the button + bool isVisible; + + // ASR Keys + const std::string ASR_VISIBILITY_KEY = "missedApproachButtonVisibility"; + const std::string ASR_VISIBILITY_DESC = "Missed Approach Button Visibility"; + const std::string ASR_X_POSITION_KEY = "missedApproachButtonXPosition"; + const std::string ASR_X_POSITION_DESC = "Missed Approach Button X Position"; + const std::string ASR_Y_POSITION_KEY = "missedApproachButtonYPosition"; + const std::string ASR_Y_POSITION_DESC = "Missed Approach Button Y Position"; + + // Positional things + static inline const int DEFAULT_COORDINATE = 200; + static inline const int DEFAULT_WIDTH = 125; + static inline const int DEFAULT_HEIGHT = 75; + static inline const RECT DEFAULT_POSITION = { + DEFAULT_COORDINATE, + DEFAULT_COORDINATE, + DEFAULT_COORDINATE + DEFAULT_WIDTH, + DEFAULT_COORDINATE + DEFAULT_HEIGHT}; + RECT position; + Gdiplus::Rect renderRect; + + // Drawing things + const Gdiplus::REAL BORDER_THICKNESS = 3; + const Gdiplus::Color BORDER_COLOR = Gdiplus::Color(255, 255, 255); + const Gdiplus::Color ACTIVE_COLOR = Gdiplus::Color(179, 0, 0); + const Gdiplus::Color ARMED_COLOR = Gdiplus::Color(0, 102, 20); + const Gdiplus::Color DISABLED_COLOR = Gdiplus::Color(85, 85, 85); + const Gdiplus::Color TEXT_COLOR = Gdiplus::Color(255, 255, 255); + const Gdiplus::SolidBrush ACTIVE_BRUSH; + const Gdiplus::SolidBrush DISABLED_BRUSH; + const Gdiplus::SolidBrush ARMED_BRUSH; + const Gdiplus::SolidBrush TEXT_BRUSH; + const Gdiplus::Pen BORDER_PEN; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachCollection.cpp b/src/plugin/missedapproach/MissedApproachCollection.cpp new file mode 100644 index 000000000..234f7db0a --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachCollection.cpp @@ -0,0 +1,78 @@ +#include "MissedApproach.h" +#include "MissedApproachCollection.h" + +namespace UKControllerPlugin::MissedApproach { + + void MissedApproachCollection::Add(const std::shared_ptr& missed) + { + auto lock = this->Lock(); + if (!this->missedApproaches.insert(missed).second) { + LogWarning("Duplicate missed approach added"); + } + } + + auto MissedApproachCollection::Count() const -> size_t + { + return this->missedApproaches.size(); + } + + auto MissedApproachCollection::Get(const std::string& callsign) const -> std::shared_ptr + { + return this->FirstWhere([&callsign](const std::shared_ptr& missed) -> bool { + return missed->Callsign() == callsign; + }); + } + + auto MissedApproachCollection::Lock() const -> std::lock_guard + { + return std::lock_guard(this->collectionLock); + } + + void + MissedApproachCollection::RemoveWhere(const std::function&)>& predicate) + { + auto lock = this->Lock(); + for (auto missedApproach = this->missedApproaches.cbegin(); missedApproach != this->missedApproaches.cend();) { + if (predicate(*missedApproach)) { + missedApproach = this->missedApproaches.erase(missedApproach); + } else { + ++missedApproach; + } + } + } + + auto MissedApproachCollection::FirstWhere( + const std::function&)>& predicate) const + -> std::shared_ptr + { + auto lock = this->Lock(); + for (const auto& approach : this->missedApproaches) { + if (predicate(approach)) { + return approach; + } + } + + return nullptr; + } + + void MissedApproachCollection::Remove(const std::shared_ptr& missed) + { + auto lock = this->Lock(); + this->missedApproaches.erase(missed); + } + + auto MissedApproachCollection::Get(int id) const -> std::shared_ptr + { + return this->FirstWhere( + [&id](const std::shared_ptr& missed) -> bool { return missed->Id() == id; }); + } + + void + MissedApproachCollection::ForEach(const std::function&)>& callback) const + { + auto lock = this->Lock(); + for (const auto& approach : this->missedApproaches) { + callback(approach); + } + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachCollection.h b/src/plugin/missedapproach/MissedApproachCollection.h new file mode 100644 index 000000000..2e7f7642d --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachCollection.h @@ -0,0 +1,34 @@ +#pragma once +#include "CompareMissedApproaches.h" + +namespace UKControllerPlugin::MissedApproach { + class MissedApproach; + + /** + * A collection of all the missed approaches being tracked + * by the plugin. + */ + class MissedApproachCollection + { + public: + void Add(const std::shared_ptr& missed); + [[nodiscard]] auto + FirstWhere(const std::function&)>& predicate) const + -> std::shared_ptr; + [[nodiscard]] auto Get(const std::string& callsign) const -> std::shared_ptr; + [[nodiscard]] auto Get(int id) const -> std::shared_ptr; + [[nodiscard]] auto Count() const -> size_t; + void ForEach(const std::function&)>& callback) const; + void Remove(const std::shared_ptr& missed); + void RemoveWhere(const std::function&)>& predicate); + + private: + [[nodiscard]] auto Lock() const -> std::lock_guard; + + // Locks the collection for async access + mutable std::mutex collectionLock; + + // The approaches + std::set, CompareMissedApproaches> missedApproaches; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachConfigurationDialog.cpp b/src/plugin/missedapproach/MissedApproachConfigurationDialog.cpp new file mode 100644 index 000000000..e8963cf5d --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachConfigurationDialog.cpp @@ -0,0 +1,228 @@ +#include "MissedApproachConfigurationDialog.h" +#include "MissedApproachOptions.h" +#include "MissedApproachRenderOptions.h" +#include "MissedApproachUserSettingHandler.h" +#include "airfield/AirfieldModel.h" +#include "airfield/AirfieldCollection.h" +#include "dialog/DialogCallArgument.h" +#include "helper/HelperFunctions.h" + +using UKControllerPlugin::HelperFunctions; +using UKControllerPlugin::Dialog::DialogCallArgument; + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachConfigurationDialog::MissedApproachConfigurationDialog( + std::shared_ptr missedApproachUserSettings, + const Airfield::AirfieldCollection& airfields) + : missedApproachUserSettings(std::move(missedApproachUserSettings)), airfields(airfields) + { + } + + auto MissedApproachConfigurationDialog::InitDialog(HWND hwnd, LPARAM lParam) -> LRESULT + { + this->renderOptions = *reinterpret_cast*>( + reinterpret_cast(lParam)->contextArgument); + + // Set global settings + // Play sound + CheckDlgButton( + hwnd, + MISSED_APPROACH_PLAY_SOUND, + this->missedApproachUserSettings->GetOptions()->AudioAlert() ? BST_CHECKED : BST_UNCHECKED); + + // Play sound for same user + CheckDlgButton( + hwnd, + MISSED_APPROACH_SOUND_USER, + this->missedApproachUserSettings->GetOptions()->AudioAlertForCurrentUser() ? BST_CHECKED : BST_UNCHECKED); + + // Service provision list + HWND serviceList = GetDlgItem(hwnd, MISSED_APPROACH_SERVICE_LIST); + ListView_SetExtendedListViewStyle(serviceList, LVS_EX_CHECKBOXES); + + const auto& selectedServices = this->missedApproachUserSettings->GetOptions()->ServiceProvisions(); + + LVITEM item; + item.mask = LVIF_TEXT; + item.iItem = 0; + item.iSubItem = 0; + for (const auto& serviceTypeMapping : this->serviceTypeMap) { + std::wstring text = HelperFunctions::ConvertToWideString(serviceTypeMapping.second); + item.pszText = (LPWSTR)text.c_str(); + ListView_InsertItem(serviceList, &item); + + ListView_SetCheckState( + serviceList, item.iItem, (serviceTypeMapping.first & selectedServices) == serviceTypeMapping.first); + + item.iItem++; + } + + // Airfield list + HWND airfieldList = GetDlgItem(hwnd, MISSED_APPROACH_AIRFIELD_LIST); + ListView_SetExtendedListViewStyle(airfieldList, LVS_EX_CHECKBOXES); + + const auto& selectedAirfields = this->missedApproachUserSettings->GetOptions()->Airfields(); + + LVITEM airfieldItem; + airfieldItem.mask = LVIF_TEXT; + airfieldItem.iItem = 0; + airfieldItem.iSubItem = 0; + this->airfields.ForEach( + [&airfieldItem, &airfieldList, &selectedAirfields](const Airfield::AirfieldModel& airfield) { + std::wstring text = HelperFunctions::ConvertToWideString(airfield.GetIcao()); + airfieldItem.pszText = (LPWSTR)text.c_str(); + ListView_InsertItem(airfieldList, &airfieldItem); + + ListView_SetCheckState( + airfieldList, + airfieldItem.iItem, + std::find(selectedAirfields.cbegin(), selectedAirfields.cend(), airfield.GetIcao()) != + selectedAirfields.end()); + + airfieldItem.iItem++; + }); + + // Set per ASR-settings + const auto renderMode = this->renderOptions->Mode(); + + // Line render mode + CheckDlgButton( + hwnd, + MISSED_APPROACH_LINE, + (renderMode & MissedApproachRenderMode::Line) == MissedApproachRenderMode::Line ? BST_CHECKED + : BST_UNCHECKED); + + // Circle + CheckDlgButton( + hwnd, + MISSED_APPROACH_CIRCLE, + (renderMode & MissedApproachRenderMode::Circle) == MissedApproachRenderMode::Circle ? BST_CHECKED + : BST_UNCHECKED); + + // Draw duration + const auto drawDuration = std::to_wstring(this->renderOptions->Duration().count()); + SendDlgItemMessage(hwnd, MISSED_APPROACH_DRAW_DURATION, EM_SETLIMITTEXT, 1, NULL); + SendDlgItemMessage( + hwnd, MISSED_APPROACH_DRAW_DURATION, WM_SETTEXT, NULL, reinterpret_cast(drawDuration.c_str())); + + return TRUE; + } + + auto MissedApproachConfigurationDialog::SaveDialog(HWND hwnd) -> LRESULT + { + // Play sound + this->missedApproachUserSettings->SetAudioAlert( + IsDlgButtonChecked(hwnd, MISSED_APPROACH_PLAY_SOUND) == BST_CHECKED); + + // Play sound for user + this->missedApproachUserSettings->SetAudioAlertForCurrentUser( + IsDlgButtonChecked(hwnd, MISSED_APPROACH_SOUND_USER) == BST_CHECKED); + + // Airfields + HWND airfieldList = GetDlgItem(hwnd, MISSED_APPROACH_AIRFIELD_LIST); + + std::vector selectedAirfields; + int numAirfields = ListView_GetItemCount(airfieldList); + for (int item = 0; item < numAirfields; ++item) { + if (ListView_GetCheckState(airfieldList, item)) { + TCHAR buffer[5]; + ListView_GetItemText(airfieldList, item, 0, buffer, 5); + selectedAirfields.push_back(HelperFunctions::ConvertToRegularString(buffer)); + } + } + this->missedApproachUserSettings->SetAirfields(selectedAirfields); + + // Services + HWND serviceList = GetDlgItem(hwnd, MISSED_APPROACH_SERVICE_LIST); + + Ownership::ServiceType provisions = static_cast(0u); + int numServices = ListView_GetItemCount(serviceList); + for (int item = 0; item < numServices; ++item) { + if (ListView_GetCheckState(serviceList, item)) { + TCHAR buffer[100]; + ListView_GetItemText(serviceList, item, 0, buffer, 100); + const auto serviceString = HelperFunctions::ConvertToRegularString(buffer); + auto service = std::find_if( + this->serviceTypeMap.cbegin(), + this->serviceTypeMap.cend(), + [&serviceString](const std::pair& pair) -> bool { + return pair.second == serviceString; + }); + provisions = provisions | service->first; + } + } + this->missedApproachUserSettings->SetServiceProvisions(provisions); + + // Per-ASR settings + // Mode + MissedApproachRenderMode mode = MissedApproachRenderMode::None; + if (IsDlgButtonChecked(hwnd, MISSED_APPROACH_LINE) == BST_CHECKED) { + mode = mode | MissedApproachRenderMode::Line; + } + + if (IsDlgButtonChecked(hwnd, MISSED_APPROACH_CIRCLE) == BST_CHECKED) { + mode = mode | MissedApproachRenderMode::Circle; + } + + this->renderOptions->SetMode(mode); + + // Duration + TCHAR buffer[2]; + SendDlgItemMessage(hwnd, MISSED_APPROACH_DRAW_DURATION, WM_GETTEXT, 2, reinterpret_cast(buffer)); + this->renderOptions->SetDuration(std::chrono::seconds(std::stoi(buffer))); + + return TRUE; + } + + auto CALLBACK MissedApproachConfigurationDialog::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) + -> LRESULT + { + if (msg == WM_INITDIALOG) { + LogInfo("Missed approach configuration dialog opened"); + SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(lParam)->dialogArgument); + } else if (msg == WM_DESTROY) { + SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL); + LogInfo("Missed approach configuration dialog closed"); + } + + MissedApproachConfigurationDialog* dialog = + reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + return dialog != nullptr ? dialog->PrivateWindowProcedure(hwnd, msg, wParam, lParam) : FALSE; + } + + auto MissedApproachConfigurationDialog::PrivateWindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) + -> LRESULT + { + switch (msg) { + // Initialise + case WM_INITDIALOG: { + this->InitDialog(hwnd, lParam); + return TRUE; + }; + // Window closed + case WM_CLOSE: { + EndDialog(hwnd, wParam); + return TRUE; + } + // Buttons pressed + case WM_COMMAND: { + switch (LOWORD(wParam)) { + case IDOK: { + this->SaveDialog(hwnd); + EndDialog(hwnd, wParam); + return TRUE; + } + case IDCANCEL: { + EndDialog(hwnd, wParam); + return TRUE; + } + default: + return FALSE; + } + } + } + + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachConfigurationDialog.h b/src/plugin/missedapproach/MissedApproachConfigurationDialog.h new file mode 100644 index 000000000..c2bf291cd --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachConfigurationDialog.h @@ -0,0 +1,45 @@ +#pragma once +#include "MissedApproachRenderOptions.h" +#include "ownership/ServiceType.h" + +namespace UKControllerPlugin::Airfield { + class AirfieldCollection; +} // namespace UKControllerPlugin::Airfield + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachUserSettingHandler; + class MissedApproachRenderOptions; + + /** + * Configures missed approaches. + */ + class MissedApproachConfigurationDialog + { + public: + MissedApproachConfigurationDialog( + std::shared_ptr missedApproachUserSettings, + const Airfield::AirfieldCollection& airfields); + static auto CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT; + + private: + auto PrivateWindowProcedure(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT; + auto InitDialog(HWND hwnd, LPARAM lParam) -> LRESULT; + auto SaveDialog(HWND hwnd) -> LRESULT; + + // Global settings + const std::shared_ptr missedApproachUserSettings; + + // Airfields + const Airfield::AirfieldCollection& airfields; + + // ASR specific seettings + std::shared_ptr renderOptions; + + // Map the service types to front-end + const std::map serviceTypeMap{ + {Ownership::ServiceType::Tower, "Tower"}, + {Ownership::ServiceType::FinalApproach, "Final Approach"}, + {Ownership::ServiceType::Approach, "Approach"}, + }; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachModule.cpp b/src/plugin/missedapproach/MissedApproachModule.cpp new file mode 100644 index 000000000..737bcd80a --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachModule.cpp @@ -0,0 +1,133 @@ +#include "ConfigureMissedApproaches.h" +#include "MissedApproachAudioAlert.h" +#include "MissedApproachButton.h" +#include "MissedApproachCollection.h" +#include "MissedApproachConfigurationDialog.h" +#include "MissedApproachModule.h" +#include "MissedApproachOptions.h" +#include "MissedApproachRenderOptions.h" +#include "MissedApproachRenderer.h" +#include "MissedApproachUserSettingHandler.h" +#include "NewMissedApproachPushEventHandler.h" +#include "RemoveExpiredMissedApproaches.h" +#include "ToggleMissedApproachButton.h" +#include "TriggerMissedApproach.h" +#include "bootstrap/PersistenceContainer.h" +#include "dialog/DialogManager.h" +#include "euroscope/AsrEventHandlerCollection.h" +#include "euroscope/CallbackFunction.h" +#include "euroscope/UserSettingAwareCollection.h" +#include "plugin/FunctionCallEventHandler.h" +#include "plugin/UKPlugin.h" +#include "push/PushEventProcessorCollection.h" +#include "radarscreen/ConfigurableDisplayCollection.h" +#include "timedevent/TimedEventCollection.h" + +using UKControllerPlugin::Euroscope::CallbackFunction; +using UKControllerPlugin::Tag::TagFunction; + +namespace UKControllerPlugin::MissedApproach { + + const int REMOVE_APPROACHES_FREQUENCY = 30; + const int TRIGGER_MISSED_APPROACH_TAG_FUNCTION_ID = 9020; + std::shared_ptr collection; // NOLINT + std::shared_ptr audioAlert; // NOLINT + std::shared_ptr options; // NOLINT + std::shared_ptr optionsHandler; // NOLINT + std::shared_ptr triggerHandler; // NOLINT + std::shared_ptr dialog; // NOLINT + + void BootstrapPlugin(const Bootstrap::PersistenceContainer& container) + { + // Global (not per-ASR) options + options = std::make_shared(); + optionsHandler = std::make_shared( + options, *container.userSettingHandlers, *container.pluginUserSettingHandler); + container.userSettingHandlers->RegisterHandler(optionsHandler); + + // The audio alerting service + audioAlert = std::make_shared( + options, *container.plugin, *container.airfieldOwnership, *container.windows); + + // The collectioh of missed approaches + collection = std::make_shared(); + container.timedHandler->RegisterEvent( + std::make_shared(collection), REMOVE_APPROACHES_FREQUENCY); + container.pushEventProcessors->AddProcessor( + std::make_shared(collection, audioAlert)); + + // Trigger missed approach + const auto trigger = std::make_shared( + collection, *container.windows, *container.api, *container.airfieldOwnership, audioAlert); + triggerHandler = trigger; + TagFunction triggerMissedApproachTagFunction( + TRIGGER_MISSED_APPROACH_TAG_FUNCTION_ID, + "Trigger Missed Approach", + [trigger]( + UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& fp, + UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& rt, + const std::string& context, + const POINT& mousePos) { trigger->Trigger(fp, rt); }); + container.pluginFunctionHandlers->RegisterFunctionCall(triggerMissedApproachTagFunction); + + // Dialog + dialog = std::make_shared(optionsHandler, *container.airfields); + + container.dialogManager->AddDialog(Dialog::DialogData{ + IDD_MISSED_APPROACH, + "Missed Approach", + reinterpret_cast(dialog->WndProc), // NOLINT + reinterpret_cast(dialog.get()), // NOLINT + dialog}); + } + + void BootstrapRadarScreen( + const Bootstrap::PersistenceContainer& persistence, + RadarScreen::RadarRenderableCollection& radarRenderables, + RadarScreen::ConfigurableDisplayCollection& configurables, + Euroscope::AsrEventHandlerCollection& asrHandlers) + { + // Alerts + auto renderOptions = std::make_shared(); + asrHandlers.RegisterHandler(renderOptions); + + auto renderer = std::make_shared( + collection, *persistence.airfieldOwnership, *persistence.plugin, renderOptions, options); + radarRenderables.RegisterRenderer( + radarRenderables.ReserveRendererIdentifier(), renderer, RadarScreen::RadarRenderableCollection::afterTags); + + // The button + const auto buttonRendererId = radarRenderables.ReserveRendererIdentifier(); + const auto buttonScreenObject = radarRenderables.ReserveScreenObjectIdentifier(buttonRendererId); + auto button = std::make_shared( + collection, triggerHandler, *persistence.plugin, *persistence.airfieldOwnership, buttonScreenObject); + + radarRenderables.RegisterRenderer(buttonRendererId, button, RadarScreen::RadarRenderableCollection::afterLists); + asrHandlers.RegisterHandler(button); + + // Button toggle + const auto buttonToggleCallbackId = persistence.pluginFunctionHandlers->ReserveNextDynamicFunctionId(); + const auto buttonToggle = std::make_shared(button, buttonToggleCallbackId); + configurables.RegisterDisplay(buttonToggle); + CallbackFunction toggleButtonCallback( + buttonToggleCallbackId, + "Toggle Missed Approach Button", + [buttonToggle](int id, const std::string& context, const RECT& pos) { + buttonToggle->Configure(id, context, pos); + }); + persistence.pluginFunctionHandlers->RegisterFunctionCall(toggleButtonCallback); + + // Configuration menu item + const auto configurationCallbackId = persistence.pluginFunctionHandlers->ReserveNextDynamicFunctionId(); + const auto configuration = std::make_shared( + renderOptions, *persistence.dialogManager, configurationCallbackId); + CallbackFunction configurationCallback( + configurationCallbackId, + "Configure Missed Approaches", + [configuration](int id, const std::string& context, const RECT& pos) { + configuration->Configure(id, context, pos); + }); + configurables.RegisterDisplay(configuration); + persistence.pluginFunctionHandlers->RegisterFunctionCall(configurationCallback); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachModule.h b/src/plugin/missedapproach/MissedApproachModule.h new file mode 100644 index 000000000..df039fb13 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachModule.h @@ -0,0 +1,24 @@ +#pragma once + +namespace UKControllerPlugin { + namespace Bootstrap { + struct PersistenceContainer; + } // namespace Bootstrap + + namespace RadarScreen { + class ConfigurableDisplayCollection; + class RadarRenderableCollection; + } // namespace RadarScreen + namespace Euroscope { + class AsrEventHandlerCollection; + } // namespace Euroscope +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::MissedApproach { + void BootstrapPlugin(const Bootstrap::PersistenceContainer& container); + void BootstrapRadarScreen( + const Bootstrap::PersistenceContainer& persistence, + RadarScreen::RadarRenderableCollection& radarRenderables, + RadarScreen::ConfigurableDisplayCollection& configurables, + Euroscope::AsrEventHandlerCollection& asrHandlers); +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachOptions.cpp b/src/plugin/missedapproach/MissedApproachOptions.cpp new file mode 100644 index 000000000..71644eb18 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachOptions.cpp @@ -0,0 +1,53 @@ +#include "MissedApproachOptions.h" +#include "euroscope/UserSetting.h" + +using UKControllerPlugin::Euroscope::UserSetting; +using UKControllerPlugin::Ownership::ServiceType; + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachOptions::MissedApproachOptions() + : audioAlert(false), alertForCurrentUser(false), seviceProvisions(ServiceType::Invalid) + { + } + + auto MissedApproachOptions::AudioAlertForCurrentUser() const -> bool + { + return alertForCurrentUser; + } + + auto MissedApproachOptions::ServiceProvisions() const -> Ownership::ServiceType + { + return seviceProvisions; + } + + void MissedApproachOptions::SetAudioAlertForCurrentUser(bool audioAlertForCurrentUser) + { + this->alertForCurrentUser = audioAlertForCurrentUser; + } + + void MissedApproachOptions::SetServiceProvisions(Ownership::ServiceType audioAlertServiceProvisions) + { + this->seviceProvisions = audioAlertServiceProvisions; + } + + void MissedApproachOptions::SetAirfields(std::vector airfields) + { + this->airfields = std::move(airfields); + } + + auto MissedApproachOptions::Airfields() const -> const std::vector& + { + return this->airfields; + } + + void MissedApproachOptions::SetAudioAlert(bool audioAlert) + { + this->audioAlert = audioAlert; + } + + auto MissedApproachOptions::AudioAlert() const -> bool + { + return this->audioAlert; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachOptions.h b/src/plugin/missedapproach/MissedApproachOptions.h new file mode 100644 index 000000000..6dc412153 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachOptions.h @@ -0,0 +1,35 @@ +#pragma once +#include "ownership/ServiceType.h" + +namespace UKControllerPlugin::MissedApproach { + /** + * Stores global missed approach options such as when to play + * sound effects. + */ + class MissedApproachOptions + { + public: + MissedApproachOptions(); + [[nodiscard]] auto AudioAlert() const -> bool; + void SetAudioAlert(bool audioAlert); + [[nodiscard]] auto AudioAlertForCurrentUser() const -> bool; + void SetAudioAlertForCurrentUser(bool audioAlertForCurrentUser); + [[nodiscard]] auto ServiceProvisions() const -> Ownership::ServiceType; + void SetServiceProvisions(Ownership::ServiceType audioAlertServiceProvisions); + [[nodiscard]] auto Airfields() const -> const std::vector&; + void SetAirfields(std::vector airfields); + + private: + // Should audio alerts be played if the current user triggered the missed approach + bool audioAlert; + + // Should audio alerts be played if the current user triggered the missed approach + bool alertForCurrentUser; + + // Should we limit audio alerts to certain service provisions + Ownership::ServiceType seviceProvisions; + + // Which airfields we care about + std::vector airfields; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachRenderMode.h b/src/plugin/missedapproach/MissedApproachRenderMode.h new file mode 100644 index 000000000..727adc657 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachRenderMode.h @@ -0,0 +1,28 @@ +#pragma once + +namespace UKControllerPlugin::MissedApproach { + enum class MissedApproachRenderMode : unsigned int + { + None = 0, + Line = 1, + Circle = 2, + Invalid = 4 + }; + + inline MissedApproachRenderMode operator|(MissedApproachRenderMode first, MissedApproachRenderMode second) + { + return static_cast( + static_cast(first) | static_cast(second)); + } + + inline MissedApproachRenderMode operator&(MissedApproachRenderMode first, MissedApproachRenderMode second) + { + return static_cast( + static_cast(first) & static_cast(second)); + } + + inline bool operator==(MissedApproachRenderMode first, MissedApproachRenderMode second) + { + return static_cast(first) == static_cast(second); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachRenderOptions.cpp b/src/plugin/missedapproach/MissedApproachRenderOptions.cpp new file mode 100644 index 000000000..c9f1a5200 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachRenderOptions.cpp @@ -0,0 +1,54 @@ +#include "MissedApproachRenderOptions.h" +#include "euroscope/UserSetting.h" + +using UKControllerPlugin::Ownership::ServiceType; + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachRenderOptions::MissedApproachRenderOptions() : mode(MissedApproachRenderMode::None), duration(0) + { + } + + void MissedApproachRenderOptions::AsrLoadedEvent(Euroscope::UserSetting& userSetting) + { + // What to render + auto asrRenderMode = userSetting.GetUnsignedIntegerEntry( + this->RENDER_MODE_ASR_KEY, static_cast(MissedApproachRenderMode::None)); + + this->mode = asrRenderMode >= static_cast(MissedApproachRenderMode::Invalid) + ? MissedApproachRenderMode::None + : static_cast(asrRenderMode); + + // How long to render + duration = std::chrono::seconds( + userSetting.GetUnsignedIntegerEntry(this->RENDER_DURATION_ASR_KEY, DEFAULT_RENDER_DURATION)); + } + + void MissedApproachRenderOptions::AsrClosingEvent(Euroscope::UserSetting& userSetting) + { + userSetting.Save(this->RENDER_MODE_ASR_KEY, this->RENDER_MODE_ASR_DESC, static_cast(this->mode)); + + userSetting.Save( + this->RENDER_DURATION_ASR_KEY, this->RENDER_DURATION_ASR_DESC, static_cast(duration.count())); + } + + auto MissedApproachRenderOptions::Mode() const -> MissedApproachRenderMode + { + return mode; + } + + void MissedApproachRenderOptions::SetMode(MissedApproachRenderMode mode) + { + this->mode = mode; + } + + auto MissedApproachRenderOptions::Duration() const -> const std::chrono::seconds& + { + return duration; + } + + void MissedApproachRenderOptions::SetDuration(std::chrono::seconds duration) + { + this->duration = duration; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachRenderOptions.h b/src/plugin/missedapproach/MissedApproachRenderOptions.h new file mode 100644 index 000000000..b086133f4 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachRenderOptions.h @@ -0,0 +1,37 @@ +#pragma once +#include "MissedApproachRenderMode.h" +#include "euroscope/AsrEventHandlerInterface.h" +#include "ownership/ServiceType.h" + +namespace UKControllerPlugin::MissedApproach { + /** + * Stores the options for a single missed approach renderer + * instance. + */ + class MissedApproachRenderOptions : public Euroscope::AsrEventHandlerInterface + { + public: + MissedApproachRenderOptions(); + void AsrLoadedEvent(Euroscope::UserSetting& userSetting) override; + void AsrClosingEvent(Euroscope::UserSetting& userSetting) override; + [[nodiscard]] auto Mode() const -> MissedApproachRenderMode; + void SetMode(MissedApproachRenderMode mode); + [[nodiscard]] auto Duration() const -> const std::chrono::seconds&; + void SetDuration(std::chrono::seconds duration); + + private: + // The default render duration + inline static const unsigned int DEFAULT_RENDER_DURATION = 5; + + // The render mode for missed approaches + MissedApproachRenderMode mode; + + // How long to render missed approaches for + std::chrono::seconds duration; + + const std::string RENDER_MODE_ASR_KEY = "missedApproachRenderMode"; + const std::string RENDER_MODE_ASR_DESC = "Missed Approach Render Mode"; + const std::string RENDER_DURATION_ASR_KEY = "missedApproachRenderDuration"; + const std::string RENDER_DURATION_ASR_DESC = "Missed Approach Render Duration"; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachRenderer.cpp b/src/plugin/missedapproach/MissedApproachRenderer.cpp new file mode 100644 index 000000000..a6aa6dafb --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachRenderer.cpp @@ -0,0 +1,136 @@ +#include "MissedApproach.h" +#include "MissedApproachCollection.h" +#include "MissedApproachOptions.h" +#include "MissedApproachRenderer.h" +#include "MissedApproachRenderOptions.h" +#include "euroscope/EuroScopeCFlightPlanInterface.h" +#include "euroscope/EuroScopeCRadarTargetInterface.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "euroscope/EuroscopeRadarLoopbackInterface.h" +#include "euroscope/UserSetting.h" +#include "graphics/GdiGraphicsInterface.h" +#include "headings/Heading.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "time/SystemClock.h" + +using UKControllerPlugin::Headings::Heading; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPlugin::Time::TimeNow; + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachRenderer::MissedApproachRenderer( + std::shared_ptr missedApproaches, + const Ownership::AirfieldServiceProviderCollection& serviceProviders, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + std::shared_ptr renderOptions, + std::shared_ptr options) + : missedApproaches(std::move(missedApproaches)), serviceProviders(serviceProviders), plugin(plugin), + renderOptions(std::move(renderOptions)), options(std::move(options)), DRAW_PEN(CreatePen()) + { + } + + auto MissedApproachRenderer::IsVisible() const -> bool + { + return this->renderOptions->Mode() != MissedApproachRenderMode::None; + } + + void MissedApproachRenderer::Render( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + const auto airfieldsProvidingServices = + this->serviceProviders.GetAirfieldsWhereUserProvidingServices(this->options->ServiceProvisions()); + const auto& renderFor = this->options->Airfields(); + + std::vector relevantAirfields; + std::copy_if( + airfieldsProvidingServices.cbegin(), + airfieldsProvidingServices.cend(), + std::back_inserter(relevantAirfields), + [&renderFor](const std::string& airfield) -> bool { + return std::find(renderFor.cbegin(), renderFor.cend(), airfield) != renderFor.cend(); + }); + + if (relevantAirfields.empty()) { + return; + } + + this->missedApproaches->ForEach([this, &relevantAirfields, &radarScreen, &graphics]( + const std::shared_ptr& missed) { + // Only display for certain duration + if (TimeNow() > missed->CreatedAt() + this->renderOptions->Duration()) { + return; + } + + // Only display if relevant to us + const auto flightplan = this->plugin.GetFlightplanForCallsign(missed->Callsign()); + const auto radarTarget = this->plugin.GetRadarTargetForCallsign(missed->Callsign()); + + if (!flightplan || !radarTarget || + std::find(relevantAirfields.begin(), relevantAirfields.end(), flightplan->GetDestination()) == + relevantAirfields.cend()) { + return; + } + + // Draw the line if we need to + const auto radarTargetPosition = radarTarget->GetPosition(); + const auto screenCoordinate = radarScreen.ConvertCoordinateToScreenPoint(radarTargetPosition); + Gdiplus::Rect boundingRect{ + screenCoordinate.x - (CIRCLE_RENDER_SIZE_PX / 2), + screenCoordinate.y - (CIRCLE_RENDER_SIZE_PX / 2), + CIRCLE_RENDER_SIZE_PX, + CIRCLE_RENDER_SIZE_PX}; + if ((this->renderOptions->Mode() & MissedApproachRenderMode::Line) == MissedApproachRenderMode::Line) { + const auto viewport = radarScreen.GetRadarViewport(); + const Gdiplus::Point radarCentre{ + viewport.left + ((viewport.right - viewport.left) / 2), + viewport.top + ((viewport.bottom - viewport.top) / 2), + }; + + graphics.DrawLine( + *this->DRAW_PEN, + radarCentre, + GetLineEndpointCoordinates(radarScreen, radarCentre, radarTargetPosition, boundingRect)); + } + + // Draw the circle if we need to + if ((this->renderOptions->Mode() & MissedApproachRenderMode::Circle) == MissedApproachRenderMode::Circle) { + graphics.DrawCircle(boundingRect, *this->DRAW_PEN); + } + }); + } + + auto MissedApproachRenderer::CreatePen() const -> std::shared_ptr + { + auto pen = std::make_shared(DRAW_COLOUR, PEN_WIDTH); + pen->SetDashStyle(Gdiplus::DashStyleDashDotDot); + return pen; + } + + auto MissedApproachRenderer::GetLineEndpointCoordinates( + Euroscope::EuroscopeRadarLoopbackInterface& radarScreen, + const Gdiplus::Point& screenCentre, + const EuroScopePlugIn::CPosition& aircraftPosition, + const Gdiplus::Rect& aircraftBoundingRect) -> Gdiplus::Point + { + const auto screenCentreCoordinate = + radarScreen.ConvertScreenPointToCoordinate({screenCentre.X, screenCentre.Y}); + const auto headingFromAircraftToScreenCentre = aircraftPosition.DirectionTo(screenCentreCoordinate); + + // Use the middle of the respective bounding line as the end + if (headingFromAircraftToScreenCentre >= Heading::NE && headingFromAircraftToScreenCentre < Heading::SE) { + return {aircraftBoundingRect.GetRight(), aircraftBoundingRect.GetTop() + (aircraftBoundingRect.Height / 2)}; + } + + if (headingFromAircraftToScreenCentre >= Heading::SE && headingFromAircraftToScreenCentre < Heading::SW) { + return { + aircraftBoundingRect.GetLeft() + (aircraftBoundingRect.Width / 2), aircraftBoundingRect.GetBottom()}; + } + + if (headingFromAircraftToScreenCentre >= Heading::SW && headingFromAircraftToScreenCentre < Heading::NW) { + return {aircraftBoundingRect.GetLeft(), aircraftBoundingRect.GetTop() + (aircraftBoundingRect.Height / 2)}; + } + + return {aircraftBoundingRect.GetLeft() + (aircraftBoundingRect.Width / 2), aircraftBoundingRect.GetTop()}; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachRenderer.h b/src/plugin/missedapproach/MissedApproachRenderer.h new file mode 100644 index 000000000..97bbc15d8 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachRenderer.h @@ -0,0 +1,70 @@ +#pragma once +#include "MissedApproachRenderMode.h" +#include "ownership/ServiceType.h" +#include "radarscreen/RadarRenderableInterface.h" + +namespace UKControllerPlugin { + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope + namespace Ownership { + class AirfieldServiceProviderCollection; + } // namespace Ownership +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachCollection; + class MissedApproachRenderOptions; + class MissedApproachOptions; + + /** + * Renders recent missed approaches to the screen + * so that users can see aircraft conducting missed + * approaches. + */ + class MissedApproachRenderer : public RadarScreen::RadarRenderableInterface + { + public: + MissedApproachRenderer( + std::shared_ptr missedApproaches, + const Ownership::AirfieldServiceProviderCollection& serviceProviders, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + std::shared_ptr renderOptions, + std::shared_ptr options); + [[nodiscard]] auto IsVisible() const -> bool override; + void Render( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) override; + + private: + [[nodiscard]] static auto GetLineEndpointCoordinates( + Euroscope::EuroscopeRadarLoopbackInterface& radarScreen, + const Gdiplus::Point& screenCentre, + const EuroScopePlugIn::CPosition& aircraftPosition, + const Gdiplus::Rect& aircraftBoundingRect) -> Gdiplus::Point; + + [[nodiscard]] auto CreatePen() const -> std::shared_ptr; + + // Contains all the missed approaches currently active + const std::shared_ptr missedApproaches; + + // Who's controlling what and where + const Ownership::AirfieldServiceProviderCollection& serviceProviders; + + // Lets us access flightplans and radar targets + const Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // The options for this renderer + const std::shared_ptr renderOptions; + + // Global settings + const std::shared_ptr options; + + // Render things + static const INT CIRCLE_RENDER_SIZE_PX = 40; + + // The pen to draw with + const Gdiplus::REAL PEN_WIDTH = 3; + const Gdiplus::Color DRAW_COLOUR = Gdiplus::Color(255, 0, 0); + const std::shared_ptr DRAW_PEN; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachUserSettingHandler.cpp b/src/plugin/missedapproach/MissedApproachUserSettingHandler.cpp new file mode 100644 index 000000000..5791c5af5 --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachUserSettingHandler.cpp @@ -0,0 +1,72 @@ +#include "MissedApproachOptions.h" +#include "MissedApproachUserSettingHandler.h" +#include "euroscope/UserSetting.h" +#include "euroscope/UserSettingAwareCollection.h" + +using UKControllerPlugin::Ownership::ServiceType; + +namespace UKControllerPlugin::MissedApproach { + + MissedApproachUserSettingHandler::MissedApproachUserSettingHandler( + std::shared_ptr options, + const Euroscope::UserSettingAwareCollection& userSettingsHandlers, + Euroscope::UserSetting& userSettings) + : options(std::move(options)), userSettingsHandlers(userSettingsHandlers), userSettings(userSettings) + { + } + + void MissedApproachUserSettingHandler::UserSettingsUpdated(Euroscope::UserSetting& userSettings) + { + // Do we play an audio alert + this->options->SetAudioAlert(userSettings.GetBooleanEntry(this->AUDIO_KEY, false)); + + // What service provisions to ping for + auto settingsServiceProvision = userSettings.GetUnsignedIntegerEntry( + this->SERVICE_PROVISION_KEY, static_cast(ServiceType::Invalid)); + + this->options->SetServiceProvisions( + settingsServiceProvision >= static_cast(ServiceType::Invalid) + ? ServiceType::Invalid + : static_cast(settingsServiceProvision)); + + // Should we ping for ourselves + this->options->SetAudioAlertForCurrentUser(userSettings.GetBooleanEntry(this->CURRENT_USER_AUDIO_KEY)); + + // Which airfields + this->options->SetAirfields(userSettings.GetStringListEntry(this->AIRFIELDS_KEY, {})); + } + + void MissedApproachUserSettingHandler::SetAudioAlertForCurrentUser(bool audioAlertForCurrentUser) + { + this->userSettings.Save(this->CURRENT_USER_AUDIO_KEY, this->CURRENT_USER_AUDIO_DESC, audioAlertForCurrentUser); + this->options->SetAudioAlertForCurrentUser(audioAlertForCurrentUser); + this->userSettingsHandlers.UserSettingsUpdateEvent(this->userSettings); + } + + void MissedApproachUserSettingHandler::SetServiceProvisions(Ownership::ServiceType serviceProvisions) + { + this->userSettings.Save( + this->SERVICE_PROVISION_KEY, this->SERVICE_PROVISION_DESC, static_cast(serviceProvisions)); + this->options->SetServiceProvisions(serviceProvisions); + this->userSettingsHandlers.UserSettingsUpdateEvent(this->userSettings); + } + + auto MissedApproachUserSettingHandler::GetOptions() const -> const std::shared_ptr + { + return this->options; + } + + void MissedApproachUserSettingHandler::SetAudioAlert(bool audioAlert) + { + this->userSettings.Save(this->AUDIO_KEY, this->AUDIO_DESC, audioAlert); + this->options->SetAudioAlert(audioAlert); + this->userSettingsHandlers.UserSettingsUpdateEvent(this->userSettings); + } + + void MissedApproachUserSettingHandler::SetAirfields(std::vector airfields) + { + this->userSettings.Save(this->AIRFIELDS_KEY, this->AIRFIELDS_DESC, airfields); + this->options->SetAirfields(std::move(airfields)); + this->userSettingsHandlers.UserSettingsUpdateEvent(this->userSettings); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachUserSettingHandler.h b/src/plugin/missedapproach/MissedApproachUserSettingHandler.h new file mode 100644 index 000000000..f1d3489ad --- /dev/null +++ b/src/plugin/missedapproach/MissedApproachUserSettingHandler.h @@ -0,0 +1,50 @@ +#pragma once +#include "euroscope/UserSettingAwareInterface.h" + +namespace UKControllerPlugin::Euroscope { + class UserSetting; + class UserSettingAwareCollection; +} // namespace UKControllerPlugin::Euroscope + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachOptions; + + /** + * A class that handles the saving the global (non-ASR) settings for missed + * approaches to Euroscope settings. This is separate to reduce the dependencies on + * the main options class. + */ + class MissedApproachUserSettingHandler : public Euroscope::UserSettingAwareInterface + { + public: + MissedApproachUserSettingHandler( + std::shared_ptr options, + const Euroscope::UserSettingAwareCollection& userSettingsHandlers, + Euroscope::UserSetting& userSettings); + void UserSettingsUpdated(Euroscope::UserSetting& userSettings) override; + void SetAudioAlert(bool audioAlert); + void SetAudioAlertForCurrentUser(bool audioAlertForCurrentUser); + void SetServiceProvisions(Ownership::ServiceType audioAlertServiceProvisions); + void SetAirfields(std::vector airfields); + [[nodiscard]] auto GetOptions() const -> const std::shared_ptr; + + private: + // The options + std::shared_ptr options; + + // Who to let know when something changes + const Euroscope::UserSettingAwareCollection& userSettingsHandlers; + + // Where we persist user settings + Euroscope::UserSetting& userSettings; + + const std::string AUDIO_KEY = "missedApproachAudio"; + const std::string AUDIO_DESC = "Play Missed Approach Alarm"; + const std::string CURRENT_USER_AUDIO_KEY = "missedApproachAudioCurrentUser"; + const std::string CURRENT_USER_AUDIO_DESC = "Play Missed Approach Alarm If User Initiated"; + const std::string SERVICE_PROVISION_KEY = "missedApproachServiceProvision"; + const std::string SERVICE_PROVISION_DESC = "Missed Approach Alerts Service Provision"; + const std::string AIRFIELDS_KEY = "missedApproachAirfields"; + const std::string AIRFIELDS_DESC = "Missed Approach Alert Airfields"; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/NewMissedApproachPushEventHandler.cpp b/src/plugin/missedapproach/NewMissedApproachPushEventHandler.cpp new file mode 100644 index 000000000..92443be64 --- /dev/null +++ b/src/plugin/missedapproach/NewMissedApproachPushEventHandler.cpp @@ -0,0 +1,57 @@ +#include "MissedApproach.h" +#include "MissedApproachAudioAlert.h" +#include "MissedApproachCollection.h" +#include "NewMissedApproachPushEventHandler.h" +#include "time/ParseTimeStrings.h" + +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::Push::PushEventSubscription; + +namespace UKControllerPlugin::MissedApproach { + NewMissedApproachPushEventHandler::NewMissedApproachPushEventHandler( + std::shared_ptr missedApproaches, + std::shared_ptr audioAlert) + : missedApproaches(std::move(missedApproaches)), audioAlert(std::move(audioAlert)) + { + } + + void NewMissedApproachPushEventHandler::ProcessPushEvent(const Push::PushEvent& message) + { + auto data = message.data; + if (!MessageValid(data)) { + LogError("Invalid missed approach push event"); + return; + } + + if (CheckForExisting(data.at("id").get())) { + return; + } + + // If we get to this point, the missed approach wasn't user-triggered + const auto missedApproach = std::make_shared( + data.at("id").get(), + data.at("callsign").get(), + Time::ParseTimeString(data.at("expires_at").get()), + false); + this->missedApproaches->Add(missedApproach); + this->audioAlert->Play(missedApproach); + } + + auto NewMissedApproachPushEventHandler::GetPushEventSubscriptions() const -> std::set + { + return {{PushEventSubscription::SUB_TYPE_EVENT, "missed-approach.created"}}; + } + + auto NewMissedApproachPushEventHandler::MessageValid(const nlohmann::json& data) -> bool + { + return data.is_object() && data.contains("id") && data.at("id").is_number_integer() && + data.contains("callsign") && data.at("callsign").is_string() && data.contains("expires_at") && + data.at("expires_at").is_string() && + Time::ParseTimeString(data.at("expires_at").get()) != Time::invalidTime; + } + + auto NewMissedApproachPushEventHandler::CheckForExisting(int id) const -> bool + { + return this->missedApproaches->Get(id) != nullptr; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/NewMissedApproachPushEventHandler.h b/src/plugin/missedapproach/NewMissedApproachPushEventHandler.h new file mode 100644 index 000000000..c982fd735 --- /dev/null +++ b/src/plugin/missedapproach/NewMissedApproachPushEventHandler.h @@ -0,0 +1,31 @@ +#pragma once +#include "push/PushEventProcessorInterface.h" + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachCollection; + class MissedApproachAudioAlert; + + /** + * Handles the new missed approach event to add a missed + * approach to the collection- + */ + class NewMissedApproachPushEventHandler : public Push::PushEventProcessorInterface + { + public: + NewMissedApproachPushEventHandler( + std::shared_ptr missedApproaches, + std::shared_ptr audioAlert); + void ProcessPushEvent(const Push::PushEvent& message) override; + [[nodiscard]] auto GetPushEventSubscriptions() const -> std::set override; + + private: + [[nodiscard]] static auto MessageValid(const nlohmann::json& data) -> bool; + [[nodiscard]] auto CheckForExisting(int id) const -> bool; + + // All the approaches + const std::shared_ptr missedApproaches; + + // For alerting the user + const std::shared_ptr audioAlert; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/RemoveExpiredMissedApproaches.cpp b/src/plugin/missedapproach/RemoveExpiredMissedApproaches.cpp new file mode 100644 index 000000000..e2e772665 --- /dev/null +++ b/src/plugin/missedapproach/RemoveExpiredMissedApproaches.cpp @@ -0,0 +1,17 @@ +#include "MissedApproach.h" +#include "MissedApproachCollection.h" +#include "RemoveExpiredMissedApproaches.h" + +namespace UKControllerPlugin::MissedApproach { + RemoveExpiredMissedApproaches::RemoveExpiredMissedApproaches( + std::shared_ptr missedApproaches) + : missedApproaches(std::move(missedApproaches)) + { + } + + void RemoveExpiredMissedApproaches::TimedEventTrigger() + { + this->missedApproaches->RemoveWhere( + [](const std::shared_ptr& missed) { return missed->IsExpired(); }); + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/RemoveExpiredMissedApproaches.h b/src/plugin/missedapproach/RemoveExpiredMissedApproaches.h new file mode 100644 index 000000000..f7fad62fc --- /dev/null +++ b/src/plugin/missedapproach/RemoveExpiredMissedApproaches.h @@ -0,0 +1,20 @@ +#pragma once +#include "timedevent/AbstractTimedEvent.h" + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachCollection; + + /** + * Removes missed approaches that have expired. + */ + class RemoveExpiredMissedApproaches : public TimedEvent::AbstractTimedEvent + { + public: + RemoveExpiredMissedApproaches(std::shared_ptr missedApproaches); + void TimedEventTrigger() override; + + private: + // All the missed approaches + const std::shared_ptr missedApproaches; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/ToggleMissedApproachButton.cpp b/src/plugin/missedapproach/ToggleMissedApproachButton.cpp new file mode 100644 index 000000000..cb810846b --- /dev/null +++ b/src/plugin/missedapproach/ToggleMissedApproachButton.cpp @@ -0,0 +1,32 @@ +#include "MissedApproachButton.h" +#include "ToggleMissedApproachButton.h" + +using UKControllerPlugin::Plugin::PopupMenuItem; + +namespace UKControllerPlugin::MissedApproach { + + ToggleMissedApproachButton::ToggleMissedApproachButton( + std::shared_ptr button, int configureCallbackId) + : button(std::move(button)), configureCallbackId(configureCallbackId) + { + } + + void ToggleMissedApproachButton::Configure(int functionId, std::string subject, RECT screenObjectArea) + { + this->button->ToggleVisible(); + } + + auto ToggleMissedApproachButton::GetConfigurationMenuItem() const -> Plugin::PopupMenuItem + { + PopupMenuItem returnVal; + returnVal.firstValue = "Display Missed Approach Button"; + returnVal.secondValue = ""; + returnVal.callbackFunctionId = this->configureCallbackId; + returnVal.checked = this->button->IsVisible() ? EuroScopePlugIn::POPUP_ELEMENT_CHECKED + : EuroScopePlugIn::POPUP_ELEMENT_UNCHECKED; + returnVal.disabled = false; + returnVal.fixedPosition = false; + return returnVal; + } + +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/ToggleMissedApproachButton.h b/src/plugin/missedapproach/ToggleMissedApproachButton.h new file mode 100644 index 000000000..6fee8349a --- /dev/null +++ b/src/plugin/missedapproach/ToggleMissedApproachButton.h @@ -0,0 +1,25 @@ +#pragma once +#include "radarscreen/ConfigurableDisplayInterface.h" + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachButton; + + /** + * Toggles the missed approach button for a given ASR through + * an option in the OP menu. + */ + class ToggleMissedApproachButton : public RadarScreen::ConfigurableDisplayInterface + { + public: + ToggleMissedApproachButton(std::shared_ptr button, int configureCallbackId); + void Configure(int functionId, std::string subject, RECT screenObjectArea) override; + [[nodiscard]] auto GetConfigurationMenuItem() const -> UKControllerPlugin::Plugin::PopupMenuItem override; + + private: + // The button we want to toggle + const std::shared_ptr button; + + // Callback id for a click + const int configureCallbackId; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/TriggerMissedApproach.cpp b/src/plugin/missedapproach/TriggerMissedApproach.cpp new file mode 100644 index 000000000..aba963d26 --- /dev/null +++ b/src/plugin/missedapproach/TriggerMissedApproach.cpp @@ -0,0 +1,126 @@ +#include "MissedApproach.h" +#include "MissedApproachAudioAlert.h" +#include "MissedApproachCollection.h" +#include "TriggerMissedApproach.h" +#include "api/ApiException.h" +#include "api/ApiInterface.h" +#include "controller/ActiveCallsign.h" +#include "controller/ControllerPosition.h" +#include "euroscope/EuroScopeCFlightPlanInterface.h" +#include "euroscope/EuroScopeCRadarTargetInterface.h" +#include "helper/HelperFunctions.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" +#include "time/ParseTimeStrings.h" +#include "windows/WinApiInterface.h" + +namespace UKControllerPlugin::MissedApproach { + + TriggerMissedApproach::TriggerMissedApproach( + std::shared_ptr missedApproaches, + Windows::WinApiInterface& windowsApi, + const Api::ApiInterface& api, + const Ownership::AirfieldServiceProviderCollection& serviceProviders, + std::shared_ptr audioAlert) + : missedApproaches(std::move(missedApproaches)), windowsApi(windowsApi), api(api), + serviceProviders(serviceProviders), audioAlert(std::move(audioAlert)) + { + } + + void TriggerMissedApproach::Trigger( + Euroscope::EuroScopeCFlightPlanInterface& flightplan, + Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const + { + if (!AircraftElegibleForMissedApproach(flightplan, radarTarget)) { + return; + } + + if (!this->UserCanTrigger(flightplan)) { + LogWarning("User tried to trigger missed approach, but is not authorised to do so"); + return; + } + + auto callsign = flightplan.GetCallsign(); + if (AlreadyActive(callsign)) { + LogWarning("Tried to create missed approach but one is alread active"); + return; + } + + if (!Confirm(callsign)) { + LogInfo("User did not confirm missed approach"); + return; + } + + this->TriggerMissedApproachInApi(callsign); + } + + auto TriggerMissedApproach::Confirm(const std::string& callsign) const -> bool + { + std::wstring title = HelperFunctions::ConvertToWideString(callsign) + L" Missed Approach"; + return this->windowsApi.OpenMessageBox( + L"Are you sure you want to trigger a missed approach?", + title.c_str(), + MB_YESNO | MB_ICONEXCLAMATION) == IDYES; + } + + /** + * Only users providing tower services and actually logged on to a Tower position can call for a missed + * approach. + */ + auto TriggerMissedApproach::UserCanTrigger(Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> bool + { + const auto serviceProviders = this->serviceProviders.GetServiceProviders(flightplan.GetDestination()); + return std::find_if( + serviceProviders.begin(), + serviceProviders.end(), + [](const std::shared_ptr& provision) { + return provision->serviceProvided == Ownership::ServiceType::Tower && + provision->controller->GetIsUser() && + provision->controller->GetNormalisedPosition().IsTower(); + }) != serviceProviders.cend(); + } + + auto TriggerMissedApproach::ResponseValid(const nlohmann::json& responseData) -> bool + { + return responseData.is_object() && responseData.contains("id") && responseData.at("id").is_number_integer() && + responseData.contains("expires_at") && responseData.at("expires_at").is_string() && + Time::ParseTimeString(responseData.at("expires_at").get()) != Time::invalidTime; + } + + void TriggerMissedApproach::TriggerMissedApproachInApi(const std::string& callsign) const + { + Async([this, callsign]() { + try { + auto response = this->api.CreateMissedApproach(callsign); + if (!ResponseValid(response)) { + LogError("Invalid response from API when creating missed approach"); + return; + } + + const auto missedApproach = std::make_shared( + response.at("id").get(), + callsign, + Time::ParseTimeString(response.at("expires_at").get()), + true); + this->missedApproaches->Add(missedApproach); + this->audioAlert->Play(missedApproach); + } catch (Api::ApiException&) { + LogError("ApiException when creating missed approach"); + } + }); + } + + auto TriggerMissedApproach::AlreadyActive(const std::string& callsign) const -> bool + { + auto existing = this->missedApproaches->Get(callsign); + return existing != nullptr && !existing->IsExpired(); + } + + auto TriggerMissedApproach::AircraftElegibleForMissedApproach( + Euroscope::EuroScopeCFlightPlanInterface& flightplan, Euroscope::EuroScopeCRadarTargetInterface& radarTarget) + -> bool + { + return flightplan.GetDistanceToDestination() < MAX_DISTANCE_FROM_DESTINATION && + radarTarget.GetGroundSpeed() > MIN_GROUNDSPEED && radarTarget.GetFlightLevel() < MAX_ALTITUDE; + } +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/TriggerMissedApproach.h b/src/plugin/missedapproach/TriggerMissedApproach.h new file mode 100644 index 000000000..3dc37f153 --- /dev/null +++ b/src/plugin/missedapproach/TriggerMissedApproach.h @@ -0,0 +1,76 @@ +#pragma once + +namespace UKControllerPlugin { + namespace Api { + class ApiInterface; + } // namespace Api + namespace Controller { + class ActiveCallsignCollection; + } // namespace Controller + namespace Euroscope { + class EuroScopeCFlightPlanInterface; + class EuroScopeCRadarTargetInterface; + } // namespace Euroscope + namespace Ownership { + class AirfieldServiceProviderCollection; + } // namespace Ownership + namespace Windows { + class WinApiInterface; + } // namespace Windows +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::MissedApproach { + class MissedApproachCollection; + class MissedApproachAudioAlert; + + /** + * Triggers a missed approach message to everyone. + */ + class TriggerMissedApproach + { + public: + TriggerMissedApproach( + std::shared_ptr missedApproaches, + Windows::WinApiInterface& windowsApi, + const Api::ApiInterface& api, + const Ownership::AirfieldServiceProviderCollection& serviceProviders, + std::shared_ptr audioAlert); + void Trigger( + Euroscope::EuroScopeCFlightPlanInterface& flightplan, + Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; + + private: + [[nodiscard]] static auto AircraftElegibleForMissedApproach( + Euroscope::EuroScopeCFlightPlanInterface& flightplan, + Euroscope::EuroScopeCRadarTargetInterface& radarTarget) -> bool; + [[nodiscard]] auto UserCanTrigger(Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> bool; + [[nodiscard]] auto Confirm(const std::string& callsign) const -> bool; + [[nodiscard]] static auto ResponseValid(const nlohmann::json& responseData) -> bool; + [[nodiscard]] auto AlreadyActive(const std::string& callsign) const -> bool; + void TriggerMissedApproachInApi(const std::string& callsign) const; + + // All the missed approaches + const std::shared_ptr missedApproaches; + + // For the confirmation dialog + Windows::WinApiInterface& windowsApi; + + // For sending events to other plugins + const Api::ApiInterface& api; + + // Which controllers are online + const Ownership::AirfieldServiceProviderCollection& serviceProviders; + + // Used for alerting the controller to the missed approach + const std::shared_ptr audioAlert; + + // The maximum distance from the destination for which we can trigger a missed approach + inline static const double MAX_DISTANCE_FROM_DESTINATION = 7.0; + + // The maximum altitude at which we can trigger a missed approach + inline static const int MAX_ALTITUDE = 5000; + + // Minimum groundspeed at which we can trigger missed approach + inline static const int MIN_GROUNDSPEED = 60; + }; +} // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/notifications/NotificationsEventHandler.cpp b/src/plugin/notifications/NotificationsEventHandler.cpp index 65604b8fa..c01dda24e 100644 --- a/src/plugin/notifications/NotificationsEventHandler.cpp +++ b/src/plugin/notifications/NotificationsEventHandler.cpp @@ -15,9 +15,9 @@ namespace UKControllerPlugin::Notifications { /* * If the user has connected with a callsign */ - void NotificationsEventHandler::ActiveCallsignAdded(const Controller::ActiveCallsign& callsign, bool userCallsign) + void NotificationsEventHandler::ActiveCallsignAdded(const Controller::ActiveCallsign& callsign) { - if (!userCallsign) { + if (!callsign.GetIsUser()) { return; } diff --git a/src/plugin/notifications/NotificationsEventHandler.h b/src/plugin/notifications/NotificationsEventHandler.h index cfe550050..22c8cbf67 100644 --- a/src/plugin/notifications/NotificationsEventHandler.h +++ b/src/plugin/notifications/NotificationsEventHandler.h @@ -17,8 +17,8 @@ namespace UKControllerPlugin::Notifications { public: NotificationsEventHandler( std::shared_ptr notifications, Message::UserMessager& messager); - void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign, bool userCallsign) override; - void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign, bool userCallsign) override{}; + void ActiveCallsignAdded(const Controller::ActiveCallsign& callsign) override; + void ActiveCallsignRemoved(const Controller::ActiveCallsign& callsign) override{}; private: // The repository of all the notifications diff --git a/src/plugin/ownership/AirfieldOwnershipHandler.cpp b/src/plugin/ownership/AirfieldOwnershipHandler.cpp index 1f6d3d578..1bdb78ef4 100644 --- a/src/plugin/ownership/AirfieldOwnershipHandler.cpp +++ b/src/plugin/ownership/AirfieldOwnershipHandler.cpp @@ -1,6 +1,7 @@ #include "AirfieldOwnerQueryMessage.h" #include "AirfieldOwnershipHandler.h" #include "AirfieldOwnershipManager.h" +#include "AirfieldServiceProviderCollection.h" #include "AirfieldsOwnedQueryMessage.h" #include "airfield/AirfieldCollection.h" #include "controller/ControllerPosition.h" @@ -8,6 +9,7 @@ #include "controller/ControllerPositionParser.h" #include "euroscope/EuroScopeCControllerInterface.h" #include "message/UserMessager.h" +#include "ownership/ServiceProvision.h" using UKControllerPlugin::Airfield::AirfieldCollection; using UKControllerPlugin::Airfield::AirfieldModel; @@ -41,13 +43,13 @@ namespace UKControllerPlugin::Ownership { } } - void AirfieldOwnershipHandler::ActiveCallsignAdded(const ActiveCallsign& callsign, bool userCallsign) + void AirfieldOwnershipHandler::ActiveCallsignAdded(const ActiveCallsign& callsign) { // Refresh the top-down responsibilities for affected airfields this->ProcessAffectedAirfields(callsign.GetNormalisedPosition()); } - void AirfieldOwnershipHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign, bool userCallsign) + void AirfieldOwnershipHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign) { // Refresh the top-down responsibilities for affected airfields this->ProcessAffectedAirfields(callsign.GetNormalisedPosition()); @@ -66,9 +68,14 @@ namespace UKControllerPlugin::Ownership { std::regex ownerRegex(".ukcp owner ([A-Za-z]{4})"); std::smatch ownerMatches; if (std::regex_search(command, ownerMatches, ownerRegex)) { - ActiveCallsign active = this->airfieldOwnership.GetOwner(ownerMatches[1]); + auto owner = this->airfieldOwnership.GetProviders().DeliveryProviderForAirfield(ownerMatches[1]); + if (!owner) { + return true; + } + + const auto active = owner->controller; this->userMessager.SendMessageToUser( - AirfieldOwnerQueryMessage(ownerMatches[1], active.GetCallsign(), active.GetControllerName())); + AirfieldOwnerQueryMessage(ownerMatches[1], active->GetCallsign(), active->GetControllerName())); return true; } diff --git a/src/plugin/ownership/AirfieldOwnershipHandler.h b/src/plugin/ownership/AirfieldOwnershipHandler.h index f7f16d212..56e6861c2 100644 --- a/src/plugin/ownership/AirfieldOwnershipHandler.h +++ b/src/plugin/ownership/AirfieldOwnershipHandler.h @@ -31,10 +31,8 @@ namespace UKControllerPlugin::Ownership { auto ProcessCommand(std::string command) -> bool override; // Inherited via ActiveCallsignEventHandlerInterface - void - ActiveCallsignAdded(const UKControllerPlugin::Controller::ActiveCallsign& callsign, bool userCallsign) override; - void ActiveCallsignRemoved( - const UKControllerPlugin::Controller::ActiveCallsign& callsign, bool userCallsign) override; + void ActiveCallsignAdded(const UKControllerPlugin::Controller::ActiveCallsign& callsign) override; + void ActiveCallsignRemoved(const UKControllerPlugin::Controller::ActiveCallsign& callsign) override; void CallsignsFlushed() override; private: diff --git a/src/plugin/ownership/AirfieldOwnershipManager.cpp b/src/plugin/ownership/AirfieldOwnershipManager.cpp index 9f927ac00..2e07a9b9a 100644 --- a/src/plugin/ownership/AirfieldOwnershipManager.cpp +++ b/src/plugin/ownership/AirfieldOwnershipManager.cpp @@ -1,4 +1,6 @@ #include "AirfieldOwnershipManager.h" +#include "AirfieldServiceProviderCollection.h" +#include "ServiceProvision.h" #include "airfield/AirfieldCollection.h" #include "airfield/AirfieldModel.h" #include "controller/ActiveCallsign.h" @@ -14,60 +16,19 @@ using UKControllerPlugin::Controller::ControllerPosition; namespace UKControllerPlugin::Ownership { AirfieldOwnershipManager::AirfieldOwnershipManager( - const AirfieldCollection& airfields, const ActiveCallsignCollection& activeCallsigns) - : notFoundControllerPosition( - std::make_unique(-1, "", INVALID_FREQUENCY, std::vector{}, true, false)), - notFoundCallsign(std::make_unique("", "", *this->notFoundControllerPosition)), - activeCallsigns(activeCallsigns), airfields(airfields) + std::shared_ptr serviceProviders, + const AirfieldCollection& airfields, + const ActiveCallsignCollection& activeCallsigns) + : activeCallsigns(activeCallsigns), airfields(airfields), serviceProviders(std::move(serviceProviders)) { } - AirfieldOwnershipManager::~AirfieldOwnershipManager() = default; - - /* - Returns true if an airfield is "owned" by the given controller, false otherwise. - */ - auto AirfieldOwnershipManager::AirfieldOwnedBy(const std::string& icao, const ActiveCallsign& position) const - -> bool - { - return this->AirfieldHasOwner(icao) && *this->ownershipMap.find(icao)->second == position; - } - - /* - Returns true if an airfield is owned by the user. - */ - auto AirfieldOwnershipManager::AirfieldOwnedByUser(const std::string& icao) const -> bool - { - return this->AirfieldHasOwner(icao) && this->activeCallsigns.UserHasCallsign() && - *this->ownershipMap.find(icao)->second == this->activeCallsigns.GetUserCallsign(); - } - - /* - Returns true if the airfield has an owner. - */ - auto AirfieldOwnershipManager::AirfieldHasOwner(const std::string& icao) const -> bool - { - return this->ownershipMap.count(icao) > 0; - } - /* Sets no owner on all airfields. */ void AirfieldOwnershipManager::Flush() { - this->ownershipMap.clear(); - } - - /* - Returns the controller that owns a given airfield. - */ - auto AirfieldOwnershipManager::GetOwner(const std::string& icao) const -> const ActiveCallsign& - { - if (!this->AirfieldHasOwner(icao)) { - return *this->notFoundCallsign; - } - - return *this->ownershipMap.find(icao)->second; + this->serviceProviders->Flush(); } /* @@ -82,10 +43,8 @@ namespace UKControllerPlugin::Ownership { return ownedAirfields; } - for (auto it = this->ownershipMap.cbegin(); it != this->ownershipMap.cend(); ++it) { - if (*it->second == this->activeCallsigns.GetCallsign(callsign)) { - ownedAirfields.emplace_back(this->airfields.FetchAirfieldByIcao(it->first)); - } + for (const auto& airfield : this->serviceProviders->GetAirfieldsWhereUserIsProvidingDelivery()) { + ownedAirfields.emplace_back(this->airfields.FetchAirfieldByIcao(airfield)); } return ownedAirfields; @@ -104,36 +63,191 @@ namespace UKControllerPlugin::Ownership { return; } - // Loop through the topdown order - for (auto it = topdownOrder.begin(); it != topdownOrder.end(); ++it) { + const auto activeControllers = this->GetActiveControllersInAirfieldTopdownOrder(icao, topdownOrder); + if (activeControllers.empty()) { + LogInfo("Airfield " + icao + " is now uncontrolled"); + this->serviceProviders->FlushForAirfield(icao); + return; + } + + const auto newServiceProviders = this->GetServiceProvidersAtAirfield(activeControllers); + this->LogProviderChanges(icao, newServiceProviders); + this->serviceProviders->SetProvidersForAirfield(icao, newServiceProviders); + } + + auto AirfieldOwnershipManager::ServiceProviderMatchingConditionExists( + const std::vector>& providers, + const std::function& provider)>& predicate) -> bool + { + return std::find_if(providers.begin(), providers.end(), predicate) != providers.cend(); + } + + auto AirfieldOwnershipManager::MapServiceProvisionToString(const std::shared_ptr& provision) + -> std::string + { + switch (provision->serviceProvided) { + case ServiceType::Delivery: + return "Delivery"; + case ServiceType::Ground: + return "Ground"; + case ServiceType::Tower: + return "Tower"; + case ServiceType::FinalApproach: + return "Final Approach"; + case ServiceType::Approach: + return "Approach"; + default: + return "Invalid"; + } + + return "Unknown"; + } + + void AirfieldOwnershipManager::LogNewServiceProvision( + const std::string& icao, const std::shared_ptr& provision) + { + LogInfo( + provision->controller->GetCallsign() + " is now providing " + MapServiceProvisionToString(provision) + + " services at " + icao); + } + + void AirfieldOwnershipManager::LogRemovedServiceProvision( + const std::string& icao, const std::shared_ptr& provision) + { + LogInfo( + provision->controller->GetCallsign() + " is no longer providing " + MapServiceProvisionToString(provision) + + " services at " + icao); + } + + auto AirfieldOwnershipManager::GetActiveControllersInAirfieldTopdownOrder( + const std::string& icao, const std::vector& topDownOrder) -> std::vector + { + std::vector activeControllers; + for (const auto& controller : topDownOrder) { + if (this->activeCallsigns.PositionActive(controller)) { + activeControllers.push_back(controller); + } + } - // If nobody is covering the position, don't count it, otherwise, take the lead callsign - if (!this->activeCallsigns.PositionActive(*it)) { + return activeControllers; + } + + /** + * This method has the following rationalle: + * + * - The first controller in the top-down order will be providing delivery services. + * - There may be multiple Ground controllers. All of them provide ground services, but only one of them, + * the first one, will be covering Delivery. + * - There may be multiple Tower controllers, but only the first one in the top-down order (ie the primary + * position) will be providing DEL/GND in the absence of those controllers. + * - There may be multiple Approach controllers. Only the first approach controller in the top-down order + * will be providing final approach services and below. + * - If one Approach controller is online, they assume both Final approach and Approach services, regardless + * of whether other controllers (e.g. Enroute) is online. + * - Enroute controllers may provide all services down to the first position to be covered. + * - At all levels apart from Delivery and Final Approach, multiple controllers of the same type can provide + * the same service. + */ + auto AirfieldOwnershipManager::GetServiceProvidersAtAirfield(const std::vector& controllers) + -> std::vector> + { + // Generate the new service providers + std::vector> serviceProviders; + for (const auto& controller : controllers) { + const auto& leadCallsign = this->activeCallsigns.GetLeadCallsignForPosition(controller); + const auto& normalisedPosition = leadCallsign.GetNormalisedPosition(); + + // If nobody's providing any services yet, this controller is providing delivery. + if (serviceProviders.empty()) { + serviceProviders.push_back(std::make_shared( + ServiceType::Delivery, std::make_shared(leadCallsign))); + } + + // If controller can't provide GND, skip this. + if (!normalisedPosition.ProvidesGroundServices()) { continue; } - // Only log when positions have changed hands - bool needsLog = this->ownershipMap.count(icao) == 0 || - this->ownershipMap.at(icao)->GetCallsign() != - this->activeCallsigns.GetLeadCallsignForPosition(*it).GetCallsign(); + // If nobody's providing ground services yet, this controller is. + if (normalisedPosition.IsGround() || + !ServiceProviderMatchingConditionExists( + serviceProviders, [](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == ServiceType::Ground; + })) { + serviceProviders.push_back(std::make_shared( + ServiceType::Ground, std::make_shared(leadCallsign))); + } - this->ownershipMap[icao] = - std::make_unique(this->activeCallsigns.GetLeadCallsignForPosition(*it)); + // If controller can't provide TWR, skip this. + if (!normalisedPosition.ProvidesTowerServices()) { + continue; + } - if (needsLog) { - LogInfo( - "Airfield " + icao + " is now managed by " + this->ownershipMap.find(icao)->second->GetCallsign()); + // Only TWR controllers (or APP+ if there aren't any TWR's) can provide TWR services. + if (normalisedPosition.IsTower() || + !ServiceProviderMatchingConditionExists( + serviceProviders, [](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == ServiceType::Tower; + })) { + serviceProviders.push_back(std::make_shared( + ServiceType::Tower, std::make_shared(leadCallsign))); + } + + // If controller can't provide APP, skip this. + if (!normalisedPosition.ProvidesApproachServices()) { + continue; + } + + // The first controller we find that can provide APP is the final phase controller + if (!ServiceProviderMatchingConditionExists( + serviceProviders, [](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == ServiceType::FinalApproach; + })) { + serviceProviders.push_back(std::make_shared( + ServiceType::FinalApproach, std::make_shared(leadCallsign))); + } + + // Enroute and APP can provide generic app control + if (normalisedPosition.IsApproach() || + !ServiceProviderMatchingConditionExists( + serviceProviders, [](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == ServiceType::Approach; + })) { + serviceProviders.push_back(std::make_shared( + ServiceType::Approach, std::make_shared(leadCallsign))); } - return; } - // We can't find an owner, so set no owner. - LogInfo("Airfield " + icao + " is no longer managed by any controller"); - this->ownershipMap.erase(icao); + return serviceProviders; + } + + void AirfieldOwnershipManager::LogProviderChanges( + const std::string& icao, const std::vector>& newProviders) + { + const auto& currentProviders = this->serviceProviders->GetServiceProviders(icao); + for (const auto& serviceProvider : newProviders) { + if (!ServiceProviderMatchingConditionExists( + currentProviders, [&serviceProvider](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == serviceProvider->serviceProvided && + *provider->controller == *serviceProvider->controller; + })) { + LogNewServiceProvision(icao, serviceProvider); + } + } + + for (const auto& serviceProvider : currentProviders) { + if (!ServiceProviderMatchingConditionExists( + newProviders, [&serviceProvider](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == serviceProvider->serviceProvided && + *provider->controller == *serviceProvider->controller; + })) { + LogRemovedServiceProvision(icao, serviceProvider); + } + } } - auto AirfieldOwnershipManager::NotFoundCallsign() const -> Controller::ActiveCallsign& + auto AirfieldOwnershipManager::GetProviders() const -> const AirfieldServiceProviderCollection& { - return *this->notFoundCallsign; + return *this->serviceProviders; } } // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/ownership/AirfieldOwnershipManager.h b/src/plugin/ownership/AirfieldOwnershipManager.h index dcad2e235..31263c6de 100644 --- a/src/plugin/ownership/AirfieldOwnershipManager.h +++ b/src/plugin/ownership/AirfieldOwnershipManager.h @@ -8,11 +8,12 @@ namespace UKControllerPlugin { namespace Controller { class ActiveCallsign; class ActiveCallsignCollection; - class ControllerPosition; } // namespace Controller } // namespace UKControllerPlugin namespace UKControllerPlugin::Ownership { + struct ServiceProvision; + class AirfieldServiceProviderCollection; /* A class for managing who "owns" a particular airfield. This can then be used @@ -24,30 +25,30 @@ namespace UKControllerPlugin::Ownership { { public: AirfieldOwnershipManager( + std::shared_ptr serviceProviders, const UKControllerPlugin::Airfield::AirfieldCollection& airfields, const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns); - ~AirfieldOwnershipManager(); - [[nodiscard]] auto - AirfieldOwnedBy(const std::string& icao, const UKControllerPlugin::Controller::ActiveCallsign& position) const - -> bool; - [[nodiscard]] auto AirfieldOwnedByUser(const std::string& icao) const -> bool; - [[nodiscard]] auto AirfieldHasOwner(const std::string& icao) const -> bool; void Flush(); - [[nodiscard]] auto GetOwner(const std::string& icao) const - -> const UKControllerPlugin::Controller::ActiveCallsign&; [[nodiscard]] auto GetOwnedAirfields(const std::string& callsign) const -> std::vector>; void RefreshOwner(const std::string& icao); - [[nodiscard]] auto NotFoundCallsign() const -> Controller::ActiveCallsign&; + [[nodiscard]] auto GetProviders() const -> const AirfieldServiceProviderCollection&; private: - constexpr static const double INVALID_FREQUENCY = 199.998; - - // A controller position to return when a lookup is done but the callsign cant be found - const std::unique_ptr notFoundControllerPosition; - - // A callsign to return when a lookup is done but the callsign cant be found - const std::unique_ptr notFoundCallsign; + [[nodiscard]] static auto ServiceProviderMatchingConditionExists( + const std::vector>& providers, + const std::function& provider)>& predicate) -> bool; + [[nodiscard]] static auto MapServiceProvisionToString(const std::shared_ptr& provision) + -> std::string; + static void LogNewServiceProvision(const std::string& icao, const std::shared_ptr& provision); + static void + LogRemovedServiceProvision(const std::string& icao, const std::shared_ptr& provision); + [[nodiscard]] auto GetActiveControllersInAirfieldTopdownOrder( + const std::string& icao, const std::vector& topDownOrder) -> std::vector; + [[nodiscard]] auto GetServiceProvidersAtAirfield(const std::vector& controllers) + -> std::vector>; + void + LogProviderChanges(const std::string& icao, const std::vector>& newProviders); // All the active users. const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns; @@ -55,7 +56,7 @@ namespace UKControllerPlugin::Ownership { // Collection of all airfields const UKControllerPlugin::Airfield::AirfieldCollection& airfields; - // Map of callsign to ownership - std::map> ownershipMap; + // Who's providing services + const std::shared_ptr serviceProviders; }; } // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/ownership/AirfieldOwnershipModule.cpp b/src/plugin/ownership/AirfieldOwnershipModule.cpp index 5103460c5..5d2377eed 100644 --- a/src/plugin/ownership/AirfieldOwnershipModule.cpp +++ b/src/plugin/ownership/AirfieldOwnershipModule.cpp @@ -1,6 +1,7 @@ #include "AirfieldOwnershipHandler.h" #include "AirfieldOwnershipManager.h" #include "AirfieldOwnershipModule.h" +#include "AirfieldServiceProviderCollection.h" #include "bootstrap/PersistenceContainer.h" #include "command/CommandHandlerCollection.h" #include "controller/ControllerPositionCollection.h" @@ -15,14 +16,18 @@ using UKControllerPlugin::Dependency::DependencyLoaderInterface; namespace UKControllerPlugin::Ownership { + std::shared_ptr manager; // NOLINT + void AirfieldOwnershipModule::BootstrapPlugin(PersistenceContainer& persistence, DependencyLoaderInterface& dependency) { - persistence.airfieldOwnership = - std::make_unique(*persistence.airfields, *persistence.activeCallsigns); + + persistence.airfieldOwnership = std::make_shared(); + manager = std::make_shared( + persistence.airfieldOwnership, *persistence.airfields, *persistence.activeCallsigns); std::shared_ptr airfieldOwnership( - new AirfieldOwnershipHandler(*persistence.airfieldOwnership, *persistence.userMessager)); + new AirfieldOwnershipHandler(*manager, *persistence.userMessager)); // Add the handlers to the collections. persistence.activeCallsigns->AddHandler(airfieldOwnership); diff --git a/src/plugin/ownership/AirfieldServiceProviderCollection.cpp b/src/plugin/ownership/AirfieldServiceProviderCollection.cpp new file mode 100644 index 000000000..d6398367f --- /dev/null +++ b/src/plugin/ownership/AirfieldServiceProviderCollection.cpp @@ -0,0 +1,150 @@ +#include "AirfieldServiceProviderCollection.h" +#include "ServiceProvision.h" +#include "controller/ActiveCallsign.h" + +namespace UKControllerPlugin::Ownership { + + void AirfieldServiceProviderCollection::SetProvidersForAirfield( + const std::string& icao, std::vector> providers) + { + this->serviceProviders[icao] = std::move(providers); + } + + auto AirfieldServiceProviderCollection::DeliveryControlProvidedByUser(const std::string& icao) const -> bool + { + return this->ServiceProvidedAtAirfieldByUser(icao, ServiceType::Delivery); + } + + auto AirfieldServiceProviderCollection::AirfieldHasDeliveryProvider(const std::string& icao) const -> bool + { + return this->ServiceProvidedAtAirfield(icao, ServiceType::Delivery); + } + + auto AirfieldServiceProviderCollection::FinalApproachControlProvidedByUser(const std::string& icao) const -> bool + { + return this->ServiceProvidedAtAirfieldByUser(icao, ServiceType::FinalApproach); + } + + auto AirfieldServiceProviderCollection::ApproachControlProvidedByUser(const std::string& icao) const -> bool + { + return this->ServiceProvidedAtAirfieldByUser(icao, ServiceType::Approach); + } + + auto AirfieldServiceProviderCollection::TowerControlProvidedByUser(const std::string& icao) const -> bool + { + return this->ServiceProvidedAtAirfieldByUser(icao, ServiceType::Tower); + } + + void AirfieldServiceProviderCollection::Flush() + { + this->serviceProviders.clear(); + } + + auto AirfieldServiceProviderCollection::GetServiceProviders(const std::string& icao) const + -> const std::vector>& + { + return this->serviceProviders.count(icao) != 0 ? this->serviceProviders.at(icao) : this->noProviders; + } + + auto AirfieldServiceProviderCollection::GetFirstServiceProviderMatchingCondition( + const std::vector>& providers, + const std::function& provider)>& predicate) const + -> const std::shared_ptr& + { + auto provider = std::find_if(providers.cbegin(), providers.cend(), predicate); + return provider == providers.cend() ? this->invalidProvider : *provider; + } + + auto AirfieldServiceProviderCollection::HasServiceProviderMatchingCondition( + const std::vector>& providers, + const std::function&)>& predicate) const -> bool + { + return this->GetFirstServiceProviderMatchingCondition(providers, predicate) != nullptr; + } + + auto + AirfieldServiceProviderCollection::ServiceProvidedAtAirfieldByUser(const std::string& icao, ServiceType type) const + -> bool + { + return this->serviceProviders.count(icao) != 0 && + this->HasServiceProviderMatchingCondition( + this->serviceProviders.at(icao), + [&type](const std::shared_ptr& provision) -> bool { + return provision->serviceProvided == type && provision->controller->GetIsUser(); + }); + } + + auto AirfieldServiceProviderCollection::ServiceProvidedAtAirfield(const std::string& icao, ServiceType type) const + -> bool + { + return this->serviceProviders.count(icao) != 0 && + this->HasServiceProviderMatchingCondition( + this->serviceProviders.at(icao), + [&type](const std::shared_ptr& provision) -> bool { + return provision->serviceProvided == type; + }); + } + + void AirfieldServiceProviderCollection::FlushForAirfield(const std::string& icao) + { + this->serviceProviders.erase(icao); + } + + auto AirfieldServiceProviderCollection::GetAirfieldsWhereUserIsProvidingDelivery() const -> std::vector + { + return this->GetAirfieldsWhereUserProvidingServices(ServiceType::Delivery); + } + + auto AirfieldServiceProviderCollection::DeliveryProviderForAirfield(const std::string& icao) const + -> const std::shared_ptr& + { + if (this->serviceProviders.count(icao) == 0) { + return this->invalidProvider; + } + + return this->GetFirstServiceProviderMatchingCondition( + this->serviceProviders.at(icao), [](const std::shared_ptr& provision) -> bool { + return provision->serviceProvided == ServiceType::Delivery; + }); + } + + auto + AirfieldServiceProviderCollection::GetProvidersForServiceAtAirfield(const std::string& icao, ServiceType type) const + -> std::vector> + { + if (this->serviceProviders.count(icao) == 0) { + return this->noProviders; + } + + std::vector> providers; + std::copy_if( + this->serviceProviders.at(icao).cbegin(), + this->serviceProviders.at(icao).cend(), + std::back_inserter(providers), + [&type](const std::shared_ptr& provider) -> bool { + return provider->serviceProvided == type; + }); + + return providers; + } + + /** + * A bitwise check of airfields where the user is providing a specific service. + */ + auto AirfieldServiceProviderCollection::GetAirfieldsWhereUserProvidingServices(ServiceType service) const + -> std::vector + { + std::vector airfields; + for (const auto& providersAtAirfield : this->serviceProviders) { + for (const auto& serviceProvider : providersAtAirfield.second) { + if ((serviceProvider->serviceProvided & service) == serviceProvider->serviceProvided && + serviceProvider->controller->GetIsUser()) { + airfields.push_back(providersAtAirfield.first); + break; + } + } + } + + return airfields; + } +} // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/ownership/AirfieldServiceProviderCollection.h b/src/plugin/ownership/AirfieldServiceProviderCollection.h new file mode 100644 index 000000000..8ea5bd18c --- /dev/null +++ b/src/plugin/ownership/AirfieldServiceProviderCollection.h @@ -0,0 +1,53 @@ +#pragma once +#include "ServiceType.h" + +namespace UKControllerPlugin::Controller { + class ActiveCallsign; +} // namespace UKControllerPlugin::Controller + +namespace UKControllerPlugin::Ownership { + struct ServiceProvision; + + /** + * A collection of service providers at an airfield, + * e.g providing delivery, tower etc. + */ + class AirfieldServiceProviderCollection + { + public: + void SetProvidersForAirfield(const std::string& icao, std::vector> providers); + [[nodiscard]] auto GetAirfieldsWhereUserIsProvidingDelivery() const -> std::vector; + [[nodiscard]] auto GetAirfieldsWhereUserProvidingServices(ServiceType service) const + -> std::vector; + [[nodiscard]] auto DeliveryProviderForAirfield(const std::string& icao) const + -> const std::shared_ptr&; + [[nodiscard]] auto AirfieldHasDeliveryProvider(const std::string& icao) const -> bool; + [[nodiscard]] auto DeliveryControlProvidedByUser(const std::string& icao) const -> bool; + [[nodiscard]] auto FinalApproachControlProvidedByUser(const std::string& icao) const -> bool; + [[nodiscard]] auto ApproachControlProvidedByUser(const std::string& icao) const -> bool; + [[nodiscard]] auto TowerControlProvidedByUser(const std::string& icao) const -> bool; + [[nodiscard]] auto GetServiceProviders(const std::string& icao) const + -> const std::vector>&; + void Flush(); + void FlushForAirfield(const std::string& icao); + [[nodiscard]] auto ServiceProvidedAtAirfield(const std::string& icao, ServiceType type) const -> bool; + [[nodiscard]] auto GetProvidersForServiceAtAirfield(const std::string& icao, ServiceType type) const + -> std::vector>; + + private: + [[nodiscard]] auto ServiceProvidedAtAirfieldByUser(const std::string& icao, ServiceType type) const -> bool; + [[nodiscard]] auto GetFirstServiceProviderMatchingCondition( + const std::vector>& providers, + const std::function& provider)>& predicate) const + -> const std::shared_ptr&; + [[nodiscard]] auto HasServiceProviderMatchingCondition( + const std::vector>& providers, + const std::function& provider)>& predicate) const -> bool; + const std::vector> noProviders; + + const std::shared_ptr invalidProvider = nullptr; + + // Map of airfield ICAO to who's providing services there + std::map>> serviceProviders; + }; +} // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/ownership/ServiceProvision.cpp b/src/plugin/ownership/ServiceProvision.cpp new file mode 100644 index 000000000..1d0e86a63 --- /dev/null +++ b/src/plugin/ownership/ServiceProvision.cpp @@ -0,0 +1,9 @@ +#include "ServiceProvision.h" + +namespace UKControllerPlugin::Ownership { + ServiceProvision::ServiceProvision( + const ServiceType serviceProvided, const std::shared_ptr controller) + : serviceProvided(serviceProvided), controller(controller) + { + } +} // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/ownership/ServiceProvision.h b/src/plugin/ownership/ServiceProvision.h new file mode 100644 index 000000000..80b77d11b --- /dev/null +++ b/src/plugin/ownership/ServiceProvision.h @@ -0,0 +1,22 @@ +#pragma once +#include "ServiceType.h" + +namespace UKControllerPlugin::Controller { + class ActiveCallsign; +} // namespace UKControllerPlugin::Controller + +namespace UKControllerPlugin::Ownership { + /** + * Represents the provision of a service at a given airfield + */ + using ServiceProvision = struct ServiceProvision + { + ServiceProvision(ServiceType serviceProvided, std::shared_ptr controller); + + // The service being provided + const ServiceType serviceProvided{}; + + // Who's providing the service + const std::shared_ptr controller{}; + }; +} // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/ownership/ServiceType.h b/src/plugin/ownership/ServiceType.h new file mode 100644 index 000000000..a9b6ba00e --- /dev/null +++ b/src/plugin/ownership/ServiceType.h @@ -0,0 +1,23 @@ +#pragma once + +namespace UKControllerPlugin::Ownership { + enum class ServiceType + { + Delivery = 1, + Ground = 2, + Tower = 4, + FinalApproach = 8, + Approach = 16, + Invalid = 32 + }; + + inline ServiceType operator|(ServiceType first, ServiceType second) + { + return static_cast(static_cast(first) | static_cast(second)); + } + + inline ServiceType operator&(ServiceType first, ServiceType second) + { + return static_cast(static_cast(first) & static_cast(second)); + } +} // namespace UKControllerPlugin::Ownership diff --git a/src/plugin/pch/pch.h b/src/plugin/pch/pch.h index 284eee057..9c765b149 100644 --- a/src/plugin/pch/pch.h +++ b/src/plugin/pch/pch.h @@ -22,6 +22,7 @@ #include "euroscope/EuroScopePlugIn.h" #include "log/LoggerFunctions.h" +#include "task/RunAsyncTask.h" #pragma warning(pop) // Standard headers @@ -58,7 +59,6 @@ using std::min; #include #include #include -#include #include #include #include diff --git a/src/plugin/prenote/ComparePrenoteMessages.h b/src/plugin/prenote/ComparePrenoteMessages.h index b9c11ee28..39231fd65 100644 --- a/src/plugin/prenote/ComparePrenoteMessages.h +++ b/src/plugin/prenote/ComparePrenoteMessages.h @@ -6,7 +6,7 @@ namespace UKControllerPlugin::Prenote { /** * Compares prenotes in a set by id. */ - using ComparePrenoteMessages = struct ComparePrenoteMessages + using ComparePrenoteMessages = struct CompareMissedApproaches { using is_transparent = int; diff --git a/src/plugin/prenote/PrenoteEventHandler.cpp b/src/plugin/prenote/PrenoteEventHandler.cpp index c15fe56c4..44ecfe486 100644 --- a/src/plugin/prenote/PrenoteEventHandler.cpp +++ b/src/plugin/prenote/PrenoteEventHandler.cpp @@ -5,7 +5,6 @@ #include "euroscope/GeneralSettingsEntries.h" #include "euroscope/UserSetting.h" #include "message/UserMessager.h" -#include "ownership/AirfieldOwnershipManager.h" #include "prenote/PrenoteEventHandler.h" #include "prenote/PrenoteService.h" @@ -18,7 +17,6 @@ using UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface; using UKControllerPlugin::Euroscope::GeneralSettingsEntries; using UKControllerPlugin::Euroscope::UserSetting; using UKControllerPlugin::Message::UserMessager; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; using UKControllerPlugin::Prenote::PrenoteService; namespace UKControllerPlugin::Prenote { diff --git a/src/plugin/prenote/PrenoteService.cpp b/src/plugin/prenote/PrenoteService.cpp index d8ba165f0..1f66d42ac 100644 --- a/src/plugin/prenote/PrenoteService.cpp +++ b/src/plugin/prenote/PrenoteService.cpp @@ -9,7 +9,7 @@ #include "euroscope/EuroScopeCFlightPlanInterface.h" #include "flightplan/StoredFlightplanCollection.h" #include "message/UserMessager.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; @@ -18,13 +18,13 @@ using UKControllerPlugin::Controller::ControllerPositionHierarchy; using UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface; using UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface; using UKControllerPlugin::Message::UserMessager; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Prenote::PrenoteUserMessage; namespace UKControllerPlugin::Prenote { PrenoteService::PrenoteService( - const AirfieldOwnershipManager& airfieldOwnership, + const AirfieldServiceProviderCollection& airfieldOwnership, const ActiveCallsignCollection& activeCallsigns, UserMessager& userMessager) : activeCallsigns(activeCallsigns), airfieldOwnership(airfieldOwnership), userMessager(userMessager) @@ -74,7 +74,7 @@ namespace UKControllerPlugin::Prenote { void PrenoteService::SendPrenotes(EuroScopeCFlightPlanInterface& flightplan) { - if (!this->airfieldOwnership.AirfieldOwnedByUser(flightplan.GetOrigin())) { + if (!this->airfieldOwnership.DeliveryControlProvidedByUser(flightplan.GetOrigin())) { return; } diff --git a/src/plugin/prenote/PrenoteService.h b/src/plugin/prenote/PrenoteService.h index da446517b..91427e59b 100644 --- a/src/plugin/prenote/PrenoteService.h +++ b/src/plugin/prenote/PrenoteService.h @@ -2,7 +2,7 @@ namespace UKControllerPlugin { namespace Ownership { - class AirfieldOwnershipManager; + class AirfieldServiceProviderCollection; } // namespace Ownership namespace Controller { @@ -28,7 +28,7 @@ namespace UKControllerPlugin::Prenote { { public: PrenoteService( - const UKControllerPlugin::Ownership::AirfieldOwnershipManager& airfieldOwnership, + const UKControllerPlugin::Ownership::AirfieldServiceProviderCollection& airfieldOwnership, const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns, UKControllerPlugin::Message::UserMessager& userMessager); ~PrenoteService(); @@ -53,7 +53,7 @@ namespace UKControllerPlugin::Prenote { const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns; // Who owns what airfield - const UKControllerPlugin::Ownership::AirfieldOwnershipManager& airfieldOwnership; + const UKControllerPlugin::Ownership::AirfieldServiceProviderCollection& airfieldOwnership; // List of callsigns already prenoted std::set alreadyPrenoted; diff --git a/src/plugin/prenote/PrenoteServiceFactory.cpp b/src/plugin/prenote/PrenoteServiceFactory.cpp index 5def9303d..f160aaf0c 100644 --- a/src/plugin/prenote/PrenoteServiceFactory.cpp +++ b/src/plugin/prenote/PrenoteServiceFactory.cpp @@ -5,12 +5,12 @@ #include "bootstrap/BootstrapWarningMessage.h" #include "controller/ActiveCallsignCollection.h" #include "message/UserMessager.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" using UKControllerPlugin::Bootstrap::BootstrapWarningMessage; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Message::UserMessager; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Prenote::PrenoteFactory; using UKControllerPlugin::Prenote::PrenoteService; @@ -25,7 +25,7 @@ namespace UKControllerPlugin::Prenote { Creates a prenote event handler from the specification */ auto PrenoteServiceFactory::Create( - const UKControllerPlugin::Ownership::AirfieldOwnershipManager& airfieldOwnership, + const UKControllerPlugin::Ownership::AirfieldServiceProviderCollection& airfieldOwnership, const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns, const nlohmann::json& json) -> std::unique_ptr { diff --git a/src/plugin/prenote/PrenoteServiceFactory.h b/src/plugin/prenote/PrenoteServiceFactory.h index ef46ff740..490f71ec0 100644 --- a/src/plugin/prenote/PrenoteServiceFactory.h +++ b/src/plugin/prenote/PrenoteServiceFactory.h @@ -4,17 +4,17 @@ namespace UKControllerPlugin { namespace Prenote { class PrenoteService; class PrenoteFactory; - } // namespace Prenote + } // namespace Prenote namespace Ownership { - class AirfieldOwnershipManager; - } // namespace Ownership + class AirfieldServiceProviderCollection; + } // namespace Ownership namespace Controller { class ActiveCallsignCollection; - } // namespace Controller + } // namespace Controller namespace Message { class UserMessager; - } // namespace Message -} // namespace UKControllerPlugin + } // namespace Message +} // namespace UKControllerPlugin namespace UKControllerPlugin { namespace Prenote { @@ -25,24 +25,21 @@ namespace UKControllerPlugin { class PrenoteServiceFactory { public: - PrenoteServiceFactory( - const UKControllerPlugin::Prenote::PrenoteFactory & prenoteFactory, - UKControllerPlugin::Message::UserMessager & userMessager - ); + PrenoteServiceFactory( + const UKControllerPlugin::Prenote::PrenoteFactory& prenoteFactory, + UKControllerPlugin::Message::UserMessager& userMessager); - std::unique_ptr Create( - const UKControllerPlugin::Ownership::AirfieldOwnershipManager & airfieldOwnership, - const UKControllerPlugin::Controller::ActiveCallsignCollection & activeCallsigns, - const nlohmann::json & json - ); + std::unique_ptr Create( + const UKControllerPlugin::Ownership::AirfieldServiceProviderCollection& airfieldOwnership, + const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns, + const nlohmann::json& json); private: + // Creates prenotes + const UKControllerPlugin::Prenote::PrenoteFactory& prenoteFactory; - // Creates prenotes - const UKControllerPlugin::Prenote::PrenoteFactory & prenoteFactory; - - // Deals with messages to the user - UKControllerPlugin::Message::UserMessager & userMessager; + // Deals with messages to the user + UKControllerPlugin::Message::UserMessager& userMessager; }; - } // namespace Prenote -} // namespace UKControllerPlugin + } // namespace Prenote +} // namespace UKControllerPlugin diff --git a/src/plugin/radarscreen/RadarRenderableInterface.cpp b/src/plugin/radarscreen/RadarRenderableInterface.cpp index 4102af2c0..f996391f9 100644 --- a/src/plugin/radarscreen/RadarRenderableInterface.cpp +++ b/src/plugin/radarscreen/RadarRenderableInterface.cpp @@ -38,4 +38,12 @@ namespace UKControllerPlugin::RadarScreen { void RadarRenderableInterface::ResetPosition() { } + + /** + * Some renderers, such as the MissedApproachRenderer and HistoryTrailRenderer aren't draggable + * and therefore not movable. + */ + void RadarRenderableInterface::Move(RECT position, std::string objectDescription) + { + } } // namespace UKControllerPlugin::RadarScreen diff --git a/src/plugin/radarscreen/RadarRenderableInterface.h b/src/plugin/radarscreen/RadarRenderableInterface.h index f94d734c1..e64f0cff6 100644 --- a/src/plugin/radarscreen/RadarRenderableInterface.h +++ b/src/plugin/radarscreen/RadarRenderableInterface.h @@ -52,7 +52,7 @@ namespace UKControllerPlugin::RadarScreen { /* Called when an object is moved. */ - virtual void Move(RECT position, std::string objectDescription) = 0; + virtual void Move(RECT position, std::string objectDescription); /* Render to the screen and possibly add clickable spots. diff --git a/src/plugin/radarscreen/RadarScreenFactory.cpp b/src/plugin/radarscreen/RadarScreenFactory.cpp index 0177cb523..22f928214 100644 --- a/src/plugin/radarscreen/RadarScreenFactory.cpp +++ b/src/plugin/radarscreen/RadarScreenFactory.cpp @@ -11,6 +11,7 @@ #include "historytrail/HistoryTrailModule.h" #include "hold/HoldModule.h" #include "minstack/MinStackModule.h" +#include "missedapproach/MissedApproachModule.h" #include "notifications/NotificationsModule.h" #include "plugin/PluginInformationBootstrap.h" #include "plugin/UKPlugin.h" @@ -103,6 +104,7 @@ namespace UKControllerPlugin::RadarScreen { Notifications::BootstrapRadarScreen(this->persistence, configurableDisplays); Releases::BootstrapRadarScreen(this->persistence, renderers, configurableDisplays, userSettingHandlers); PrenoteModule::BootstrapRadarScreen(this->persistence, renderers, configurableDisplays, userSettingHandlers); + MissedApproach::BootstrapRadarScreen(this->persistence, renderers, configurableDisplays, userSettingHandlers); // Register command for position resets this->persistence.commandHandlers->RegisterHandler(std::make_shared(renderers)); diff --git a/src/plugin/radarscreen/UKRadarScreen.cpp b/src/plugin/radarscreen/UKRadarScreen.cpp index 22a45e56c..bcd70da31 100644 --- a/src/plugin/radarscreen/UKRadarScreen.cpp +++ b/src/plugin/radarscreen/UKRadarScreen.cpp @@ -219,4 +219,9 @@ namespace UKControllerPlugin { mousePos, tagItemArea); } + + auto UKRadarScreen::ConvertScreenPointToCoordinate(const POINT& point) -> EuroScopePlugIn::CPosition + { + return this->ConvertCoordFromPixelToPosition(point); + } } // namespace UKControllerPlugin diff --git a/src/plugin/radarscreen/UKRadarScreen.h b/src/plugin/radarscreen/UKRadarScreen.h index 0e8e8fbb2..5e9b87cc4 100644 --- a/src/plugin/radarscreen/UKRadarScreen.h +++ b/src/plugin/radarscreen/UKRadarScreen.h @@ -45,6 +45,7 @@ namespace UKControllerPlugin { auto operator=(UKRadarScreen&&) -> UKRadarScreen& = delete; void AddMenuItem(UKControllerPlugin::Plugin::PopupMenuItem menuItem) override; auto ConvertCoordinateToScreenPoint(EuroScopePlugIn::CPosition pos) -> POINT override; + [[nodiscard]] auto ConvertScreenPointToCoordinate(const POINT& point) -> EuroScopePlugIn::CPosition override; auto GetAsrData(std::string key) -> std::string override; auto GetGroundspeedForCallsign(std::string cs) -> int override; auto GetKey(std::string key) -> std::string override; diff --git a/src/plugin/squawk/SquawkAssignment.cpp b/src/plugin/squawk/SquawkAssignment.cpp index 2a41caff2..b9d3919ef 100644 --- a/src/plugin/squawk/SquawkAssignment.cpp +++ b/src/plugin/squawk/SquawkAssignment.cpp @@ -8,7 +8,8 @@ #include "euroscope/EuroscopePluginLoopbackInterface.h" #include "flightplan/StoredFlightplan.h" #include "flightplan/StoredFlightplanCollection.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; @@ -18,14 +19,14 @@ using UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface; using UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface; using UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface; using UKControllerPlugin::Flightplan::StoredFlightplanCollection; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; namespace UKControllerPlugin::Squawk { SquawkAssignment::SquawkAssignment( const StoredFlightplanCollection& storedFlightplans, EuroscopePluginLoopbackInterface& plugin, - const AirfieldOwnershipManager& airfieldOwnership, + const AirfieldServiceProviderCollection& airfieldOwnership, const ActiveCallsignCollection& activeCallsigns) : activeCallsigns(activeCallsigns), storedFlightplans(storedFlightplans), airfieldOwnership(airfieldOwnership), plugin(plugin) @@ -87,8 +88,7 @@ namespace UKControllerPlugin::Squawk { return radarTarget.GetGroundSpeed() <= this->untrackedMaxAssignmentSpeed && flightPlan.GetDistanceFromOrigin() <= this->untrackedMaxAssignmentDistanceFromOrigin && !flightPlan.HasAssignedSquawk() && - this->airfieldOwnership.AirfieldOwnedBy( - flightPlan.GetOrigin(), this->activeCallsigns.GetUserCallsign()); + this->airfieldOwnership.DeliveryControlProvidedByUser(flightPlan.GetOrigin()); } return !flightPlan.HasAssignedSquawk(); @@ -136,8 +136,7 @@ namespace UKControllerPlugin::Squawk { (!flightPlan.HasSid() && flightPlan.GetCruiseLevel() <= this->maxAssignmentAltitude)) && radarTarget.GetGroundSpeed() <= this->untrackedMaxAssignmentSpeed && flightPlan.GetDistanceFromOrigin() <= this->untrackedMaxAssignmentDistanceFromOrigin && - this->airfieldOwnership.AirfieldOwnedBy( - flightPlan.GetOrigin(), this->activeCallsigns.GetUserCallsign()) && + this->airfieldOwnership.DeliveryControlProvidedByUser(flightPlan.GetOrigin()) && !flightPlan.HasAssignedSquawk(); } diff --git a/src/plugin/squawk/SquawkAssignment.h b/src/plugin/squawk/SquawkAssignment.h index 7613ccff7..f3cedc6a7 100644 --- a/src/plugin/squawk/SquawkAssignment.h +++ b/src/plugin/squawk/SquawkAssignment.h @@ -5,18 +5,18 @@ namespace UKControllerPlugin { class EuroScopeCFlightPlanInterface; class EuroScopeCRadarTargetInterface; class EuroscopePluginLoopbackInterface; - } // namespace Euroscope + } // namespace Euroscope namespace Controller { class ActiveCallsignCollection; - } // namespace Controller + } // namespace Controller namespace Flightplan { class StoredFlightplan; class StoredFlightplanCollection; - } // namespace Flightplan + } // namespace Flightplan namespace Ownership { - class AirfieldOwnershipManager; - } // namespace Ownership -} // namespace UKControllerPlugin + class AirfieldServiceProviderCollection; + } // namespace Ownership +} // namespace UKControllerPlugin namespace UKControllerPlugin { namespace Squawk { @@ -27,78 +27,65 @@ namespace UKControllerPlugin { class SquawkAssignment { public: - SquawkAssignment( - const UKControllerPlugin::Flightplan::StoredFlightplanCollection & storedFlightplans, - UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface & plugin, - const UKControllerPlugin::Ownership::AirfieldOwnershipManager & airfieldOwnership, - const Controller::ActiveCallsignCollection& activeCallsigns - ); - bool CircuitAssignmentNeeded( - UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightplan, - UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; - bool ForceAssignmentAllowed( - UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightplan - ) const; - bool ForceAssignmentNeeded( - UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightplan - ) const; - bool ForceAssignmentOrNoSquawkAssigned( - UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightplan - ) const; - bool GeneralAssignmentNeeded( - const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightPlan, - const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; - bool LocalAssignmentNeeded( - const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightPlan, - const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; - bool PreviousSquawkNeedsReassignment( - const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightPlan, - const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; - bool AssignmentPossible( - const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightPlan, - const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; + SquawkAssignment( + const UKControllerPlugin::Flightplan::StoredFlightplanCollection& storedFlightplans, + UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface& plugin, + const UKControllerPlugin::Ownership::AirfieldServiceProviderCollection& airfieldOwnership, + const Controller::ActiveCallsignCollection& activeCallsigns); + bool CircuitAssignmentNeeded( + UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan, + UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; + bool ForceAssignmentAllowed(UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const; + bool ForceAssignmentNeeded(UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const; + bool ForceAssignmentOrNoSquawkAssigned( + UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightplan) const; + bool GeneralAssignmentNeeded( + const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightPlan, + const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; + bool LocalAssignmentNeeded( + const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightPlan, + const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; + bool PreviousSquawkNeedsReassignment( + const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightPlan, + const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; + bool AssignmentPossible( + const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightPlan, + const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; - // For untracked aircraft, the maximum speed for which squawks will - // be assigned - limits to just on the ground. - const int untrackedMaxAssignmentSpeed = 30; + // For untracked aircraft, the maximum speed for which squawks will + // be assigned - limits to just on the ground. + const int untrackedMaxAssignmentSpeed = 30; - // For untracked aircraft, the maximum distance from the origin for which squawks will - // be assigned - limits to being at the right airport - const double untrackedMaxAssignmentDistanceFromOrigin = 7.0; + // For untracked aircraft, the maximum distance from the origin for which squawks will + // be assigned - limits to being at the right airport + const double untrackedMaxAssignmentDistanceFromOrigin = 7.0; - // For tracked aircraft, the maximum distance from the controllers airfield for - // which to use a local squawk. - const double trackedLarsRadius = 40.0; + // For tracked aircraft, the maximum distance from the controllers airfield for + // which to use a local squawk. + const double trackedLarsRadius = 40.0; - // For tracked aircraft, the maximum altitude for which to assign local squawks. - const int maxAssignmentAltitude = 6000; + // For tracked aircraft, the maximum altitude for which to assign local squawks. + const int maxAssignmentAltitude = 6000; private: - bool NeedsLocalSquawkTracked( - const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightPlan, - const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; - bool NeedsLocalSquawkUntracked( - const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface & flightPlan, - const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface & radarTarget - ) const; + bool NeedsLocalSquawkTracked( + const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightPlan, + const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; + bool NeedsLocalSquawkUntracked( + const UKControllerPlugin::Euroscope::EuroScopeCFlightPlanInterface& flightPlan, + const UKControllerPlugin::Euroscope::EuroScopeCRadarTargetInterface& radarTarget) const; - // The active callsigns - const UKControllerPlugin::Controller::ActiveCallsignCollection & activeCallsigns; + // The active callsigns + const UKControllerPlugin::Controller::ActiveCallsignCollection& activeCallsigns; - // Collection of current and recent flightplans - const UKControllerPlugin::Flightplan::StoredFlightplanCollection & storedFlightplans; + // Collection of current and recent flightplans + const UKControllerPlugin::Flightplan::StoredFlightplanCollection& storedFlightplans; - // Which controllers own which airfields - const UKControllerPlugin::Ownership::AirfieldOwnershipManager & airfieldOwnership; + // Which controllers own which airfields + const UKControllerPlugin::Ownership::AirfieldServiceProviderCollection& airfieldOwnership; - // Link back to the radar screen, for things like distances - UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface & plugin; + // Link back to the radar screen, for things like distances + UKControllerPlugin::Euroscope::EuroscopePluginLoopbackInterface& plugin; }; - } // namespace Squawk -} // namespace UKControllerPlugin + } // namespace Squawk +} // namespace UKControllerPlugin diff --git a/src/plugin/squawk/SquawkEventHandler.cpp b/src/plugin/squawk/SquawkEventHandler.cpp index 3747a8060..f57e1ef10 100644 --- a/src/plugin/squawk/SquawkEventHandler.cpp +++ b/src/plugin/squawk/SquawkEventHandler.cpp @@ -136,9 +136,9 @@ namespace UKControllerPlugin::Squawk { /* If the ActiveCallsign is the user callsign, then force a squawk regenerate. */ - void SquawkEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign, bool userCallsign) + void SquawkEventHandler::ActiveCallsignAdded(const ActiveCallsign& callsign) { - if (!userCallsign) { + if (!callsign.GetIsUser()) { return; } @@ -149,7 +149,7 @@ namespace UKControllerPlugin::Squawk { /* Nothing to see here */ - void SquawkEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign, bool userCallsign) + void SquawkEventHandler::ActiveCallsignRemoved(const ActiveCallsign& callsign) { } diff --git a/src/plugin/squawk/SquawkEventHandler.h b/src/plugin/squawk/SquawkEventHandler.h index e6c00b079..d6d1c0586 100644 --- a/src/plugin/squawk/SquawkEventHandler.h +++ b/src/plugin/squawk/SquawkEventHandler.h @@ -18,9 +18,6 @@ namespace UKControllerPlugin { class StoredFlightplan; class StoredFlightplanCollection; } // namespace Flightplan - namespace Airfield { - class AirfieldOwnershipManager; - } // namespace Airfield namespace Squawk { class SquawkGenerator; } // namespace Squawk @@ -63,10 +60,8 @@ namespace UKControllerPlugin::Squawk { [[nodiscard]] auto UserAllowedSquawkAssignment() const -> bool; // Inherited via ActiveCallsignEventHandlerInterface - void - ActiveCallsignAdded(const UKControllerPlugin::Controller::ActiveCallsign& callsign, bool userCallsign) override; - void ActiveCallsignRemoved( - const UKControllerPlugin::Controller::ActiveCallsign& callsign, bool userCallsign) override; + void ActiveCallsignAdded(const UKControllerPlugin::Controller::ActiveCallsign& callsign) override; + void ActiveCallsignRemoved(const UKControllerPlugin::Controller::ActiveCallsign& callsign) override; void CallsignsFlushed() override; [[nodiscard]] auto AutomaticAssignmentsDisabled() const -> bool; diff --git a/src/plugin/task/TaskRunner.h b/src/plugin/task/TaskRunner.h deleted file mode 100644 index 28989a216..000000000 --- a/src/plugin/task/TaskRunner.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "task/TaskRunnerInterface.h" - -namespace UKControllerPlugin { - namespace Curl { - class HttpHelper; - } // namespace Curl -} // namespace UKControllerPlugin - -namespace UKControllerPlugin { - namespace TaskManager { - - /* - A class that runs Tasks on a separate thread to the main EuroScope instance. - - The primary use of this class is to run tasks that involve HTTP requests, as waiting - for CURL on the ES thread would lock up the entire application. - */ - class TaskRunner : public UKControllerPlugin::TaskManager::TaskRunnerInterface - { - public: - explicit TaskRunner(int numThreads); - TaskRunner(const TaskRunner&) = delete; - ~TaskRunner(void); - size_t CountThreads(void) const override; - void QueueAsynchronousTask(std::function task) override; - - private: - void ProcessAsynchronousTasks(int threadNumber); - - // Are the threads running - bool threadsRunning = true; - - // A vector for all the threads. - std::vector threads; - - // A lock for the asynchronous queue when picking off tasks. - std::mutex asynchronousQueueLock; - - // The master queue for asynchronous tasks - will be taken off in order. - std::deque > asynchronousTaskQueue; - - // A condition variable for the asynchronous queue. - std::condition_variable asynchronousQueueCondVar; - }; - } // namespace TaskManager -} // namespace UKControllerPlugin diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 57219a475..e5cb05e8d 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -124,6 +124,13 @@ set(srd ) source_group("srd" FILES ${srd}) +set(task + "../utils/task/TaskRunner.cpp" + "../utils/task/TaskRunner.h" + "../utils/task/TaskRunnerInterface.h" + ../utils/task/RunAsyncTask.cpp ../utils/task/RunAsyncTask.h) +source_group("task" FILES ${task}) + set(update "update/CheckDevelopmentVersion.cpp" "update/LoadChangelog.cpp" @@ -156,6 +163,7 @@ set(ALL_FILES ${setting} ${squawk} ${srd} + ${task} ${update} ${windows} ) diff --git a/src/utils/api/ApiHelper.cpp b/src/utils/api/ApiHelper.cpp index e89b6540d..a371c8a7f 100644 --- a/src/utils/api/ApiHelper.cpp +++ b/src/utils/api/ApiHelper.cpp @@ -4,8 +4,6 @@ #include "api/ApiNotFoundException.h" #include "api/ApiResponseFactory.h" #include "curl/CurlInterface.h" -#include "curl/CurlRequest.h" -#include "curl/CurlResponse.h" #include "squawk/SquawkValidator.h" using UKControllerPlugin::Api::ApiException; @@ -374,4 +372,9 @@ namespace UKControllerPlugin::Api { { static_cast(this->MakeApiRequest(this->requestBuilder.BuildDeletePrenoteMessageRequest(messageId))); } + + auto ApiHelper::CreateMissedApproach(const std::string& callsign) const -> nlohmann::json + { + return this->MakeApiRequest(this->requestBuilder.BuildMissedApproachMessage(callsign)).GetRawData(); + } } // namespace UKControllerPlugin::Api diff --git a/src/utils/api/ApiHelper.h b/src/utils/api/ApiHelper.h index 62ffb189a..723fda387 100644 --- a/src/utils/api/ApiHelper.h +++ b/src/utils/api/ApiHelper.h @@ -84,6 +84,7 @@ namespace UKControllerPlugin::Api { int requestExpiry) const -> nlohmann::json override; void AcknowledgePrenoteMessage(int messageId, int controllerId) const override; void DeletePrenoteMessage(int messageId) const override; + [[nodiscard]] auto CreateMissedApproach(const std::string& callsign) const -> nlohmann::json override; // The HTTP status codes that may be returned by the API static const uint64_t STATUS_OK = 200L; diff --git a/src/utils/api/ApiInterface.h b/src/utils/api/ApiInterface.h index 9228d99b5..1d81018b7 100644 --- a/src/utils/api/ApiInterface.h +++ b/src/utils/api/ApiInterface.h @@ -81,6 +81,7 @@ namespace UKControllerPlugin::Api { int requestExpiry) const -> nlohmann::json = 0; virtual void AcknowledgePrenoteMessage(int messageId, int controllerId) const = 0; virtual void DeletePrenoteMessage(int messageId) const = 0; + [[nodiscard]] virtual auto CreateMissedApproach(const std::string& callsign) const -> nlohmann::json = 0; virtual void SetApiKey(std::string key) = 0; virtual void SetApiDomain(std::string domain) = 0; diff --git a/src/utils/api/ApiRequestBuilder.cpp b/src/utils/api/ApiRequestBuilder.cpp index b2933715e..890ae5018 100644 --- a/src/utils/api/ApiRequestBuilder.cpp +++ b/src/utils/api/ApiRequestBuilder.cpp @@ -57,15 +57,6 @@ namespace UKControllerPlugin::Api { return {std::move(uri), CurlRequest::METHOD_GET}; } - /* - Builds a request to check the version of the plugin - */ - auto ApiRequestBuilder::BuildVersionCheckRequest(const std::string& versionString) const -> CurlRequest - { - return this->AddCommonHeaders( - CurlRequest(apiDomain + "/version/" + versionString + "/status", CurlRequest::METHOD_GET)); - } - /* Builds a request for getting minimum stack levels. */ @@ -444,4 +435,16 @@ namespace UKControllerPlugin::Api { return this->AddCommonHeaders( {this->apiDomain + "/prenotes/messages/" + std::to_string(messageId), CurlRequest::METHOD_DELETE}); } + + auto ApiRequestBuilder::BuildMissedApproachMessage(const std::string& callsign) const + -> UKControllerPlugin::Curl::CurlRequest + { + CurlRequest request(this->apiDomain + "/missed-approaches", CurlRequest::METHOD_POST); + nlohmann::json data{ + {"callsign", callsign}, + }; + request.SetBody(data.dump()); + + return this->AddCommonHeaders(request); + } } // namespace UKControllerPlugin::Api diff --git a/src/utils/api/ApiRequestBuilder.h b/src/utils/api/ApiRequestBuilder.h index 6a46c09db..f36b0cbc4 100644 --- a/src/utils/api/ApiRequestBuilder.h +++ b/src/utils/api/ApiRequestBuilder.h @@ -32,8 +32,6 @@ namespace UKControllerPlugin::Api { [[nodiscard]] auto BuildDeleteAssignedHoldRequest(const std::string& callsign) const -> UKControllerPlugin::Curl::CurlRequest; [[nodiscard]] static auto BuildRemoteFileRequest(std::string uri) -> UKControllerPlugin::Curl::CurlRequest; - [[nodiscard]] auto BuildVersionCheckRequest(const std::string& versionString) const - -> UKControllerPlugin::Curl::CurlRequest; [[nodiscard]] auto BuildMinStackLevelRequest() const -> UKControllerPlugin::Curl::CurlRequest; [[nodiscard]] auto BuildRegionalPressureRequest() const -> UKControllerPlugin::Curl::CurlRequest; [[nodiscard]] auto BuildSrdQueryRequest(const UKControllerPlugin::Srd::SrdSearchParameters& parameters) const @@ -87,6 +85,8 @@ namespace UKControllerPlugin::Api { -> UKControllerPlugin::Curl::CurlRequest; [[nodiscard]] auto BuildDeletePrenoteMessageRequest(int messageId) const -> UKControllerPlugin::Curl::CurlRequest; + [[nodiscard]] auto BuildMissedApproachMessage(const std::string& callsign) const + -> UKControllerPlugin::Curl::CurlRequest; [[nodiscard]] auto GetApiDomain() const -> std::string; [[nodiscard]] auto GetApiKey() const -> std::string; diff --git a/src/utils/pch/pch.h b/src/utils/pch/pch.h index 7a4125cb6..a74b23307 100644 --- a/src/utils/pch/pch.h +++ b/src/utils/pch/pch.h @@ -33,6 +33,7 @@ #include #include #include +#include // Custom headers #include "log/LoggerFunctions.h" diff --git a/src/utils/task/RunAsyncTask.cpp b/src/utils/task/RunAsyncTask.cpp new file mode 100644 index 000000000..490b5a236 --- /dev/null +++ b/src/utils/task/RunAsyncTask.cpp @@ -0,0 +1,24 @@ +#include "RunAsyncTask.h" +#include "TaskRunnerInterface.h" + +using UKControllerPlugin::TaskManager::TaskRunnerInterface; + +std::shared_ptr taskRunner; // NOLINT + +void Async(const std::function& function) +{ + if (!taskRunner) { + return; + } + + taskRunner->QueueAsynchronousTask(function); +} + +void SetTaskRunner(std::shared_ptr runner) +{ + if (taskRunner) { + return; + } + + taskRunner = std::move(runner); +} diff --git a/src/utils/task/RunAsyncTask.h b/src/utils/task/RunAsyncTask.h new file mode 100644 index 000000000..b5e700c73 --- /dev/null +++ b/src/utils/task/RunAsyncTask.h @@ -0,0 +1,8 @@ +#pragma once + +namespace UKControllerPlugin::TaskManager { + class TaskRunnerInterface; +} // namespace UKControllerPlugin::TaskManager + +void Async(const std::function& function); +void SetTaskRunner(std::shared_ptr taskRunner); diff --git a/src/plugin/task/TaskRunner.cpp b/src/utils/task/TaskRunner.cpp similarity index 85% rename from src/plugin/task/TaskRunner.cpp rename to src/utils/task/TaskRunner.cpp index b9047a572..32982db91 100644 --- a/src/plugin/task/TaskRunner.cpp +++ b/src/utils/task/TaskRunner.cpp @@ -9,7 +9,7 @@ namespace UKControllerPlugin { // Create the threads for asynchronous tasks. std::unique_lock uniqueLock(this->asynchronousQueueLock); for (int i = 0; i < numThreads; i++) { - this->threads.push_back(std::thread(&TaskRunner::ProcessAsynchronousTasks, this, i)); + this->threads.push_back(std::thread(&TaskRunner::ProcessAsynchronousTasks, this)); } LogInfo("TaskRunner created with " + std::to_string(numThreads) + " threads"); @@ -25,13 +25,11 @@ namespace UKControllerPlugin { this->asynchronousQueueCondVar.notify_all(); uniqueLock.unlock(); - - for (auto &thread : this->threads) { + for (auto& thread : this->threads) { if (thread.joinable()) { thread.join(); } } - LogInfo("All TaskRunner threads shut down"); } size_t TaskRunner::CountThreads(void) const @@ -56,7 +54,7 @@ namespace UKControllerPlugin { loop of EuroScope execution. For example, tasks that require HTTP requests, which make take a significant amount of time. */ - void TaskRunner::ProcessAsynchronousTasks(int threadNumber) + void TaskRunner::ProcessAsynchronousTasks() { std::unique_lock uniqueLock(this->asynchronousQueueLock, std::defer_lock_t()); std::function currentTask; @@ -88,13 +86,10 @@ namespace UKControllerPlugin { // Do the task try { currentTask(); - } - catch (std::exception exception) { + } catch (std::exception exception) { LogError("Unhandled exception in task runner " + std::string(exception.what())); } } - - LogInfo("Task runner thread " + std::to_string(threadNumber) + " stopped"); } - } // namespace TaskManager -} // namespace UKControllerPlugin + } // namespace TaskManager +} // namespace UKControllerPlugin diff --git a/src/utils/task/TaskRunner.h b/src/utils/task/TaskRunner.h new file mode 100644 index 000000000..ba97a128a --- /dev/null +++ b/src/utils/task/TaskRunner.h @@ -0,0 +1,47 @@ +#pragma once +#include "task/TaskRunnerInterface.h" + +namespace UKControllerPlugin { + namespace Curl { + class HttpHelper; + } // namespace Curl +} // namespace UKControllerPlugin + +namespace UKControllerPlugin { + namespace TaskManager { + + /* + A class that runs Tasks on a separate thread to the main EuroScope instance. + + The primary use of this class is to run tasks that involve HTTP requests, as waiting + for CURL on the ES thread would lock up the entire application. + */ + class TaskRunner : public UKControllerPlugin::TaskManager::TaskRunnerInterface + { + public: + explicit TaskRunner(int numThreads); + TaskRunner(const TaskRunner&) = delete; + ~TaskRunner(void); + size_t CountThreads(void) const override; + void QueueAsynchronousTask(std::function task) override; + + private: + void ProcessAsynchronousTasks(); + + // Are the threads running + bool threadsRunning = true; + + // A vector for all the threads. + std::vector threads; + + // A lock for the asynchronous queue when picking off tasks. + std::mutex asynchronousQueueLock; + + // The master queue for asynchronous tasks - will be taken off in order. + std::deque> asynchronousTaskQueue; + + // A condition variable for the asynchronous queue. + std::condition_variable asynchronousQueueCondVar; + }; + } // namespace TaskManager +} // namespace UKControllerPlugin diff --git a/src/plugin/task/TaskRunnerInterface.h b/src/utils/task/TaskRunnerInterface.h similarity index 100% rename from src/plugin/task/TaskRunnerInterface.h rename to src/utils/task/TaskRunnerInterface.h diff --git a/test/plugin/CMakeLists.txt b/test/plugin/CMakeLists.txt index 69a79283b..49683a5f9 100644 --- a/test/plugin/CMakeLists.txt +++ b/test/plugin/CMakeLists.txt @@ -116,6 +116,11 @@ set(test__handoff ) source_group("test\\handoff" FILES ${test__handoff}) +set(test__headings + "headings/HeadingTest.cpp" + headings/HeadingTest.cpp) +source_group("test\\headings" FILES ${test__headings}) + set(test__historytrail "historytrail/AircraftHistoryTrailTest.cpp" "historytrail/HistoryTrailEventHandlerTest.cpp" @@ -232,6 +237,22 @@ set(test__minstack ) source_group("test\\minstack" FILES ${test__minstack}) +set(test__missedapproach + missedapproach/MissedApproachTest.cpp + missedapproach/CompareMissedApproachesTest.cpp + missedapproach/MissedApproachCollectionTest.cpp + missedapproach/RemoveExpiredMissedApproachesTest.cpp + missedapproach/MissedApproachModuleTest.cpp + missedapproach/NewMissedApproachPushEventHandlerTest.cpp + missedapproach/TriggerMissedApproachTest.cpp + missedapproach/MissedApproachRendererTest.cpp + missedapproach/MissedApproachRenderOptionsTest.cpp + missedapproach/MissedApproachOptionsTest.cpp + missedapproach/MissedApproachAudioAlertTest.cpp + missedapproach/MissedApproachUserSettingHandlerTest.cpp + missedapproach/MissedApproachButtonTest.cpp missedapproach/ToggleMissedApproachButtonTest.cpp missedapproach/ConfigureMissedApproachesTest.cpp) +source_group("test\\missedapproach" FILES ${test__missedapproach}) + set(test__mock "mock/MockAbstractTimedEvent.h" "mock/MockActiveCallsignEventHandler.h" @@ -265,7 +286,6 @@ set(test__mock "mock/MockRunwayDialogAwareInterface.h" "mock/MockSectorFileProviderInterface.h" "mock/MockSocket.h" - "mock/MockTaskRunnerInterface.h" "mock/MockUserSettingAwareInterface.h" "mock/MockUserSettingProviderInterface.h" ) @@ -312,7 +332,7 @@ set(test__ownership "ownership/AirfieldOwnershipManagerTest.cpp" "ownership/AirfieldOwnershipModuleTest.cpp" "ownership/AirfieldsOwnedQueryMessageTest.cpp" -) + ownership/AirfieldServiceProviderCollectionTest.cpp) source_group("test\\ownership" FILES ${test__ownership}) set(test__plugin @@ -489,6 +509,7 @@ set(ALL_FILES ${test__flightinformationservice} ${test__flightplan} ${test__handoff} + ${test__headings} ${test__historytrail} ${test__hold} ${test__initialaltitude} @@ -499,6 +520,7 @@ set(ALL_FILES ${test__message} ${test__metar} ${test__minstack} + ${test__missedapproach} ${test__mock} ${test__navaids} ${test__notifications} @@ -522,8 +544,7 @@ set(ALL_FILES ${test__timedevent} ${test__timer} ${test__wake} - $ -) + $) ################################################################################ # Target diff --git a/test/plugin/airfield/AirfieldCollectionTest.cpp b/test/plugin/airfield/AirfieldCollectionTest.cpp index 3fa8962c5..0e5d90764 100644 --- a/test/plugin/airfield/AirfieldCollectionTest.cpp +++ b/test/plugin/airfield/AirfieldCollectionTest.cpp @@ -57,5 +57,19 @@ namespace UKControllerPluginTest { collection.AddAirfield(std::unique_ptr(new AirfieldModel("EGLL", {}))); EXPECT_EQ(2, collection.GetSize()); } + + TEST(AirfieldCollection, ItIteratesTheCollection) + { + AirfieldCollection collection; + collection.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {}))); + collection.AddAirfield(std::unique_ptr(new AirfieldModel("EGLL", {}))); + + std::vector airfields; + collection.ForEach( + [&airfields](const AirfieldModel& airfield) { airfields.push_back(airfield.GetIcao()); }); + + std::vector expected({"EGKK", "EGLL"}); + EXPECT_EQ(expected, airfields); + } } // namespace AirfieldOwnership } // namespace UKControllerPluginTest diff --git a/test/plugin/controller/ActiveCallsignCollectionTest.cpp b/test/plugin/controller/ActiveCallsignCollectionTest.cpp index 3ca64612f..8a61fcc47 100644 --- a/test/plugin/controller/ActiveCallsignCollectionTest.cpp +++ b/test/plugin/controller/ActiveCallsignCollectionTest.cpp @@ -1,5 +1,4 @@ #include "controller/ActiveCallsignCollection.h" -#include "controller/ActiveCallsign.h" #include "controller/ControllerPosition.h" using ::testing::NiceMock; @@ -11,293 +10,289 @@ using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; using UKControllerPluginTest::Controller::MockActiveCallsignEventHandler; -namespace UKControllerPluginTest { - namespace Controller { +namespace UKControllerPluginTest::Controller { - class ActiveCallsignCollectionTest : public Test + class ActiveCallsignCollectionTest : public Test + { + public: + ActiveCallsignCollectionTest() + : testPosition(1, "LON_S_CTR", 129.420, {}, true, false), + testCallsign("LON_S_CTR", "Testy Boi", testPosition, true) { - public: - ActiveCallsignCollectionTest() - : testPosition(1, "LON_S_CTR", 129.420, {}, true, false), - testCallsign("LON_S_CTR", "Testy Boi", testPosition) - { - handler1.reset(new NiceMock()); - handler2.reset(new NiceMock()); - } - - std::shared_ptr> handler1; - std::shared_ptr> handler2; - ActiveCallsignCollection collection; - ControllerPosition testPosition; - ActiveCallsign testCallsign; - }; - - TEST_F(ActiveCallsignCollectionTest, ItStartsWithNoHandlers) - { - EXPECT_EQ(0, this->collection.CountHandlers()); - } - - TEST_F(ActiveCallsignCollectionTest, ItAddsHandlers) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler2); - EXPECT_EQ(2, this->collection.CountHandlers()); - } - - TEST_F(ActiveCallsignCollectionTest, ItDoesntAddDuplicateHandlers) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler1); - EXPECT_EQ(1, this->collection.CountHandlers()); - } - - TEST_F(ActiveCallsignCollectionTest, ItHandlesNewCallsignEvents) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler2); - - EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign, false)).Times(1); - EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign, false)).Times(1); - - this->collection.AddCallsign(this->testCallsign); - } - - TEST_F(ActiveCallsignCollectionTest, ItHandlesNewUserCallsignEvents) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler2); - - EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign, true)).Times(1); - EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign, true)).Times(1); - - this->collection.AddUserCallsign(this->testCallsign); + handler1.reset(new NiceMock()); + handler2.reset(new NiceMock()); } - TEST_F(ActiveCallsignCollectionTest, ItHandlesRemovedCallsignEvents) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler2); - - EXPECT_CALL(*this->handler1, ActiveCallsignRemoved(this->testCallsign, false)).Times(1); - EXPECT_CALL(*this->handler2, ActiveCallsignRemoved(this->testCallsign, false)).Times(1); - - EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign, false)).Times(1); - EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign, false)).Times(1); - - this->collection.AddCallsign(this->testCallsign); - this->collection.RemoveCallsign(this->testCallsign); - } - - TEST_F(ActiveCallsignCollectionTest, ItHandlesRemovedUserCallsignEvents) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler2); - - EXPECT_CALL(*this->handler1, ActiveCallsignRemoved(this->testCallsign, true)).Times(1); - EXPECT_CALL(*this->handler2, ActiveCallsignRemoved(this->testCallsign, true)).Times(1); - - EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign, true)).Times(1); - EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign, true)).Times(1); - - this->collection.AddUserCallsign(this->testCallsign); - this->collection.RemoveCallsign(this->testCallsign); - } - - TEST_F(ActiveCallsignCollectionTest, ItHandlesCallsignFlushEvents) - { - this->collection.AddHandler(handler1); - this->collection.AddHandler(handler2); - - EXPECT_CALL(*this->handler1, CallsignsFlushed()).Times(1); - EXPECT_CALL(*this->handler2, CallsignsFlushed()).Times(1); - - this->collection.Flush(); - } - - TEST_F(ActiveCallsignCollectionTest, AddCallsignThrowsExceptionIfAlreadyAdded) - { - EXPECT_NO_THROW(collection.AddCallsign(this->testCallsign)); - EXPECT_THROW(collection.AddCallsign(this->testCallsign), std::invalid_argument); - } - - TEST_F(ActiveCallsignCollectionTest, PositionActiveDefaultsToFalse) - { - EXPECT_FALSE(collection.PositionActive("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, PositionActiveReturnsTrueIfCallsignActive) - { - collection.AddCallsign(this->testCallsign); - EXPECT_TRUE(collection.PositionActive("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, PositionActiveReturnsFalseIfCallsignInactive) - { - collection.AddCallsign(this->testCallsign); - EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); - collection.RemoveCallsign(this->testCallsign); - EXPECT_FALSE(collection.PositionActive("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, CallsignActiveReturnsTrueIfCallsignActive) - { - collection.AddCallsign(this->testCallsign); - EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, CallsignActiveReturnsTrueIfCallsignInactive) - { - collection.AddCallsign(this->testCallsign); - EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); - collection.RemoveCallsign(this->testCallsign); - EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, GetLeadCallsignForPositionThrowsExceptionIfNotFound) - { - EXPECT_THROW(collection.GetLeadCallsignForPosition("LON_S_CTR"), std::out_of_range); - } - - TEST_F(ActiveCallsignCollectionTest, GetLeadCallsignForPositionKeepsItemsInOrderBasedOnCallsign) - { - ActiveCallsign cs1("LON_S1_CTR", "Bob Jones", this->testPosition); - ActiveCallsign cs2("LON_S__CTR", "Jones Bob", this->testPosition); - - collection.AddCallsign(cs1); - collection.AddCallsign(cs2); - EXPECT_EQ(0, collection.GetLeadCallsignForPosition("LON_S_CTR").GetCallsign().compare("LON_S1_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, GetLeadCallsignForPositionKeepsItemsInOrderIfAddedInWrongOrder) - { - ActiveCallsign cs1("LON_S1_CTR", "Bob Jones", this->testPosition); - ActiveCallsign cs2("LON_S__CTR", "Jones Bob", this->testPosition); - - collection.AddCallsign(cs2); - collection.AddCallsign(cs1); - EXPECT_EQ(0, collection.GetLeadCallsignForPosition("LON_S_CTR").GetCallsign().compare("LON_S1_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, GetCallsignThrowsExceptionIfNotFound) - { - EXPECT_THROW(collection.GetCallsign("LON_S_CTR"), std::out_of_range); - } - - TEST_F(ActiveCallsignCollectionTest, GetCallsignReturnsCorrectItem) - { - collection.AddCallsign(this->testCallsign); - EXPECT_EQ(this->testCallsign, collection.GetCallsign("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, GetUserCallsignThrowsExceptionIfNotFound) - { - EXPECT_THROW(collection.GetUserCallsign(), std::out_of_range); - } - - TEST_F(ActiveCallsignCollectionTest, GetUserCallsignReturnsUserCallsign) - { - collection.AddUserCallsign(this->testCallsign); - EXPECT_EQ(collection.GetUserCallsign(), this->testCallsign); - } - - TEST_F(ActiveCallsignCollectionTest, AddingExtraUserCallsignRemovesTheFirst) - { - ActiveCallsign callsign("LON_S_CTR", "Testy McTest", this->testPosition); - ActiveCallsign callsign2("LON_S1_CTR", "Testy McTest", this->testPosition); - collection.AddUserCallsign(callsign); - collection.AddUserCallsign(callsign2); - EXPECT_EQ(collection.GetUserCallsign(), callsign2); - EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, UserHasCallsignReturnsFalseIfNoneSet) - { - EXPECT_FALSE(collection.UserHasCallsign()); - } - - TEST_F(ActiveCallsignCollectionTest, UserHasCallsignReturnsTrueIfSet) - { - collection.AddUserCallsign(this->testCallsign); - EXPECT_TRUE(collection.UserHasCallsign()); - } - - TEST_F(ActiveCallsignCollectionTest, FlushEmptiesTheCollection) - { - ControllerPosition controller1(1, "LON_S_CTR", 129.420, {}, true, false); - ControllerPosition controller2(2, "LON_C_CTR", 127.100, {}, true, false); - ControllerPosition controller3(3, "LON_E_CTR", 121.200, {}, true, false); - ActiveCallsign cs1("LON_S_CTR", "Bob Jones", controller1); - ActiveCallsign cs2("LON_C_CTR", "Jones Bob", controller2); - ActiveCallsign cs3("LON_E_CTR", "Jimbob Jump", controller3); - - collection.AddCallsign(cs1); - collection.AddCallsign(cs2); - collection.AddCallsign(cs3); - - collection.Flush(); - EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); - EXPECT_FALSE(collection.PositionActive("LON_S_CTR")); - EXPECT_FALSE(collection.CallsignActive("LON_C_CTR")); - EXPECT_FALSE(collection.PositionActive("LON_C_CTR")); - EXPECT_FALSE(collection.CallsignActive("LON_E_CTR")); - EXPECT_FALSE(collection.PositionActive("LON_E_CTR")); - } - - TEST_F(ActiveCallsignCollectionTest, FlushMarksUserAsInactive) - { - collection.AddUserCallsign(this->testCallsign); - EXPECT_TRUE(collection.UserHasCallsign()); - collection.Flush(); - EXPECT_FALSE(collection.UserHasCallsign()); - } - - TEST_F(ActiveCallsignCollectionTest, CollectionCanHandlePositionChanges) - { - collection.AddUserCallsign(this->testCallsign); - EXPECT_TRUE(collection.UserHasCallsign()); - collection.Flush(); - EXPECT_FALSE(collection.UserHasCallsign()); - collection.AddUserCallsign(this->testCallsign); - EXPECT_TRUE(collection.UserHasCallsign()); - } - - TEST_F(ActiveCallsignCollectionTest, RemoveCallsignSetsUserInactiveIfCurrentUser) - { - collection.AddUserCallsign(this->testCallsign); - EXPECT_TRUE(collection.UserHasCallsign()); - collection.RemoveCallsign(this->testCallsign); - EXPECT_FALSE(collection.UserHasCallsign()); - } - - TEST_F(ActiveCallsignCollectionTest, RemoveCallsignDoesNotChangeUserIfNotUserLoggingOff) - { - ControllerPosition pos2(2, "LON_E_CTR", 121.220, {}, true, false); - ActiveCallsign callsign2("LON_E_CTR", "Testy McTestingon", pos2); - collection.AddUserCallsign(this->testCallsign); - collection.AddCallsign(callsign2); - EXPECT_TRUE(collection.UserHasCallsign()); - collection.RemoveCallsign(callsign2); - EXPECT_TRUE(collection.UserHasCallsign()); - } - - TEST_F(ActiveCallsignCollectionTest, CorrectlyHandlesMismatchingCallsigns) - { - ControllerPosition pos(1, "LON_S_CTR", 129.420, {"EGKK", "EGLL", "EGLC"}, true, false); - ControllerPosition pos2(2, "LON_N_CTR", 133.700, {"EGCC", "EGGP"}, true, false); - ActiveCallsign callsign("LON_S_CTR", "Testy McTest", pos2); - ActiveCallsign callsign2("LON_S_CTR", "Testy McTest", pos2); - collection.AddUserCallsign(callsign); - EXPECT_TRUE(collection.PositionActive("LON_N_CTR")); - EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); - collection.RemoveCallsign(callsign); - EXPECT_FALSE(collection.PositionActive("LON_N_CTR")); - EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); - collection.AddUserCallsign(callsign); - EXPECT_TRUE(collection.PositionActive("LON_N_CTR")); - EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); - collection.RemoveCallsign(callsign); - EXPECT_FALSE(collection.PositionActive("LON_N_CTR")); - EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); - } - } // namespace Controller -} // namespace UKControllerPluginTest + std::shared_ptr> handler1; + std::shared_ptr> handler2; + ActiveCallsignCollection collection; + ControllerPosition testPosition; + ActiveCallsign testCallsign; + }; + + TEST_F(ActiveCallsignCollectionTest, ItStartsWithNoHandlers) + { + EXPECT_EQ(0, this->collection.CountHandlers()); + } + + TEST_F(ActiveCallsignCollectionTest, ItAddsHandlers) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler2); + EXPECT_EQ(2, this->collection.CountHandlers()); + } + + TEST_F(ActiveCallsignCollectionTest, ItDoesntAddDuplicateHandlers) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler1); + EXPECT_EQ(1, this->collection.CountHandlers()); + } + + TEST_F(ActiveCallsignCollectionTest, ItHandlesNewCallsignEvents) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler2); + + EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign)).Times(1); + EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign)).Times(1); + + this->collection.AddCallsign(this->testCallsign); + } + + TEST_F(ActiveCallsignCollectionTest, ItHandlesNewUserCallsignEvents) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler2); + + EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign)).Times(1); + EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign)).Times(1); + + this->collection.AddUserCallsign(this->testCallsign); + } + + TEST_F(ActiveCallsignCollectionTest, ItHandlesRemovedCallsignEvents) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler2); + + EXPECT_CALL(*this->handler1, ActiveCallsignRemoved(this->testCallsign)).Times(1); + EXPECT_CALL(*this->handler2, ActiveCallsignRemoved(this->testCallsign)).Times(1); + + EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign)).Times(1); + EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign)).Times(1); + + this->collection.AddCallsign(this->testCallsign); + this->collection.RemoveCallsign(this->testCallsign); + } + + TEST_F(ActiveCallsignCollectionTest, ItHandlesRemovedUserCallsignEvents) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler2); + + EXPECT_CALL(*this->handler1, ActiveCallsignRemoved(this->testCallsign)).Times(1); + EXPECT_CALL(*this->handler2, ActiveCallsignRemoved(this->testCallsign)).Times(1); + + EXPECT_CALL(*this->handler1, ActiveCallsignAdded(this->testCallsign)).Times(1); + EXPECT_CALL(*this->handler2, ActiveCallsignAdded(this->testCallsign)).Times(1); + + this->collection.AddUserCallsign(this->testCallsign); + this->collection.RemoveCallsign(this->testCallsign); + } + + TEST_F(ActiveCallsignCollectionTest, ItHandlesCallsignFlushEvents) + { + this->collection.AddHandler(handler1); + this->collection.AddHandler(handler2); + + EXPECT_CALL(*this->handler1, CallsignsFlushed()).Times(1); + EXPECT_CALL(*this->handler2, CallsignsFlushed()).Times(1); + + this->collection.Flush(); + } + + TEST_F(ActiveCallsignCollectionTest, AddCallsignThrowsExceptionIfAlreadyAdded) + { + EXPECT_NO_THROW(collection.AddCallsign(this->testCallsign)); + EXPECT_THROW(collection.AddCallsign(this->testCallsign), std::invalid_argument); + } + + TEST_F(ActiveCallsignCollectionTest, PositionActiveDefaultsToFalse) + { + EXPECT_FALSE(collection.PositionActive("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, PositionActiveReturnsTrueIfCallsignActive) + { + collection.AddCallsign(this->testCallsign); + EXPECT_TRUE(collection.PositionActive("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, PositionActiveReturnsFalseIfCallsignInactive) + { + collection.AddCallsign(this->testCallsign); + EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); + collection.RemoveCallsign(this->testCallsign); + EXPECT_FALSE(collection.PositionActive("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, CallsignActiveReturnsTrueIfCallsignActive) + { + collection.AddCallsign(this->testCallsign); + EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, CallsignActiveReturnsTrueIfCallsignInactive) + { + collection.AddCallsign(this->testCallsign); + EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); + collection.RemoveCallsign(this->testCallsign); + EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, GetLeadCallsignForPositionThrowsExceptionIfNotFound) + { + EXPECT_THROW(collection.GetLeadCallsignForPosition("LON_S_CTR"), std::out_of_range); + } + + TEST_F(ActiveCallsignCollectionTest, GetLeadCallsignForPositionKeepsItemsInOrderBasedOnCallsign) + { + ActiveCallsign cs1("LON_S1_CTR", "Bob Jones", this->testPosition, true); + ActiveCallsign cs2("LON_S__CTR", "Jones Bob", this->testPosition, true); + + collection.AddCallsign(cs1); + collection.AddCallsign(cs2); + EXPECT_EQ(0, collection.GetLeadCallsignForPosition("LON_S_CTR").GetCallsign().compare("LON_S1_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, GetLeadCallsignForPositionKeepsItemsInOrderIfAddedInWrongOrder) + { + ActiveCallsign cs1("LON_S1_CTR", "Bob Jones", this->testPosition, true); + ActiveCallsign cs2("LON_S__CTR", "Jones Bob", this->testPosition, true); + + collection.AddCallsign(cs2); + collection.AddCallsign(cs1); + EXPECT_EQ(0, collection.GetLeadCallsignForPosition("LON_S_CTR").GetCallsign().compare("LON_S1_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, GetCallsignThrowsExceptionIfNotFound) + { + EXPECT_THROW(collection.GetCallsign("LON_S_CTR"), std::out_of_range); + } + + TEST_F(ActiveCallsignCollectionTest, GetCallsignReturnsCorrectItem) + { + collection.AddCallsign(this->testCallsign); + EXPECT_EQ(this->testCallsign, collection.GetCallsign("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, GetUserCallsignThrowsExceptionIfNotFound) + { + EXPECT_THROW(collection.GetUserCallsign(), std::out_of_range); + } + + TEST_F(ActiveCallsignCollectionTest, GetUserCallsignReturnsUserCallsign) + { + collection.AddUserCallsign(this->testCallsign); + EXPECT_EQ(collection.GetUserCallsign(), this->testCallsign); + } + + TEST_F(ActiveCallsignCollectionTest, AddingExtraUserCallsignRemovesTheFirst) + { + ActiveCallsign callsign("LON_S_CTR", "Testy McTest", this->testPosition, true); + ActiveCallsign callsign2("LON_S1_CTR", "Testy McTest", this->testPosition, true); + collection.AddUserCallsign(callsign); + collection.AddUserCallsign(callsign2); + EXPECT_EQ(collection.GetUserCallsign(), callsign2); + EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, UserHasCallsignReturnsFalseIfNoneSet) + { + EXPECT_FALSE(collection.UserHasCallsign()); + } + + TEST_F(ActiveCallsignCollectionTest, UserHasCallsignReturnsTrueIfSet) + { + collection.AddUserCallsign(this->testCallsign); + EXPECT_TRUE(collection.UserHasCallsign()); + } + + TEST_F(ActiveCallsignCollectionTest, FlushEmptiesTheCollection) + { + ControllerPosition controller1(1, "LON_S_CTR", 129.420, {}, true, false); + ControllerPosition controller2(2, "LON_C_CTR", 127.100, {}, true, false); + ControllerPosition controller3(3, "LON_E_CTR", 121.200, {}, true, false); + ActiveCallsign cs1("LON_S_CTR", "Bob Jones", controller1, true); + ActiveCallsign cs2("LON_C_CTR", "Jones Bob", controller2, true); + ActiveCallsign cs3("LON_E_CTR", "Jimbob Jump", controller3, true); + + collection.AddCallsign(cs1); + collection.AddCallsign(cs2); + collection.AddCallsign(cs3); + + collection.Flush(); + EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); + EXPECT_FALSE(collection.PositionActive("LON_S_CTR")); + EXPECT_FALSE(collection.CallsignActive("LON_C_CTR")); + EXPECT_FALSE(collection.PositionActive("LON_C_CTR")); + EXPECT_FALSE(collection.CallsignActive("LON_E_CTR")); + EXPECT_FALSE(collection.PositionActive("LON_E_CTR")); + } + + TEST_F(ActiveCallsignCollectionTest, FlushMarksUserAsInactive) + { + collection.AddUserCallsign(this->testCallsign); + EXPECT_TRUE(collection.UserHasCallsign()); + collection.Flush(); + EXPECT_FALSE(collection.UserHasCallsign()); + } + + TEST_F(ActiveCallsignCollectionTest, CollectionCanHandlePositionChanges) + { + collection.AddUserCallsign(this->testCallsign); + EXPECT_TRUE(collection.UserHasCallsign()); + collection.Flush(); + EXPECT_FALSE(collection.UserHasCallsign()); + collection.AddUserCallsign(this->testCallsign); + EXPECT_TRUE(collection.UserHasCallsign()); + } + + TEST_F(ActiveCallsignCollectionTest, RemoveCallsignSetsUserInactiveIfCurrentUser) + { + collection.AddUserCallsign(this->testCallsign); + EXPECT_TRUE(collection.UserHasCallsign()); + collection.RemoveCallsign(this->testCallsign); + EXPECT_FALSE(collection.UserHasCallsign()); + } + + TEST_F(ActiveCallsignCollectionTest, RemoveCallsignDoesNotChangeUserIfNotUserLoggingOff) + { + ControllerPosition pos2(2, "LON_E_CTR", 121.220, {}, true, false); + ActiveCallsign callsign2("LON_E_CTR", "Testy McTestingon", pos2, true); + collection.AddUserCallsign(this->testCallsign); + collection.AddCallsign(callsign2); + EXPECT_TRUE(collection.UserHasCallsign()); + collection.RemoveCallsign(callsign2); + EXPECT_TRUE(collection.UserHasCallsign()); + } + + TEST_F(ActiveCallsignCollectionTest, CorrectlyHandlesMismatchingCallsigns) + { + ControllerPosition pos2(2, "LON_N_CTR", 133.700, {"EGCC", "EGGP"}, true, false); + ActiveCallsign callsign("LON_S_CTR", "Testy McTest", pos2, true); + collection.AddUserCallsign(callsign); + EXPECT_TRUE(collection.PositionActive("LON_N_CTR")); + EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); + collection.RemoveCallsign(callsign); + EXPECT_FALSE(collection.PositionActive("LON_N_CTR")); + EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); + collection.AddUserCallsign(callsign); + EXPECT_TRUE(collection.PositionActive("LON_N_CTR")); + EXPECT_TRUE(collection.CallsignActive("LON_S_CTR")); + collection.RemoveCallsign(callsign); + EXPECT_FALSE(collection.PositionActive("LON_N_CTR")); + EXPECT_FALSE(collection.CallsignActive("LON_S_CTR")); + } +} // namespace UKControllerPluginTest::Controller diff --git a/test/plugin/controller/ActiveCallsignMonitorTest.cpp b/test/plugin/controller/ActiveCallsignMonitorTest.cpp index af35b0e25..fd3a1f3c2 100644 --- a/test/plugin/controller/ActiveCallsignMonitorTest.cpp +++ b/test/plugin/controller/ActiveCallsignMonitorTest.cpp @@ -1,12 +1,10 @@ #include "controller/ActiveCallsignMonitor.h" #include "controller/ControllerPosition.h" #include "controller/ControllerPositionCollection.h" -#include "controller/ActiveCallsign.h" #include "controller/ActiveCallsignCollection.h" +#include "controller/ControllerStatusEventHandlerCollection.h" #include "flightplan/StoredFlightplanCollection.h" -#include "flightplan/StoredFlightplan.h" #include "login/Login.h" -#include "controller/ControllerStatusEventHandlerCollection.h" using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; @@ -69,10 +67,10 @@ namespace UKControllerPluginTest { this->llApp = std::unique_ptr( new ControllerPosition(6, "EGLL_N_APP", 199.998, {"EGLL"}, true, false)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_TWR", "Testy McTest", *kkTwr)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_APP", "Testy McTest", *kkApp)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_S_TWR", "Testy McTest", *llTwr)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_N_APP", "Testy McTest", *llApp)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_TWR", "Testy McTest", *kkTwr, false)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_APP", "Testy McTest", *kkApp, false)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_S_TWR", "Testy McTest", *llTwr, false)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_N_APP", "Testy McTest", *llApp, false)); this->login.SetLoginTime(std::chrono::system_clock::now() - std::chrono::minutes(15)); } @@ -162,7 +160,7 @@ namespace UKControllerPluginTest { EXPECT_EQ(*this->kkTwr, this->activeCallsigns.GetCallsign("EGKK_TWR").GetNormalisedPosition()); } - TEST_F(ActiveCallsignMonitorTest, ControllerUpdateEventAddsActiveCallsign) + TEST_F(ActiveCallsignMonitorTest, ControllerUpdateEventAddsUserActiveCallsign) { NiceMock euroscopeMock; @@ -179,6 +177,37 @@ namespace UKControllerPluginTest { this->handler.ControllerUpdateEvent(euroscopeMock); EXPECT_TRUE(this->activeCallsigns.CallsignActive("EGKK_DEL")); EXPECT_TRUE(this->activeCallsigns.PositionActive("EGKK_DEL")); + EXPECT_TRUE(this->activeCallsigns.UserHasCallsign()); + auto callsign = this->activeCallsigns.GetCallsign("EGKK_DEL"); + EXPECT_EQ("EGKK_DEL", callsign.GetCallsign()); + EXPECT_EQ("Testy McTestington", callsign.GetControllerName()); + EXPECT_EQ(this->controllerCollection.FetchPositionByCallsign("EGKK_DEL"), callsign.GetNormalisedPosition()); + EXPECT_TRUE(callsign.GetIsUser()); + } + + TEST_F(ActiveCallsignMonitorTest, ControllerUpdateEventAddsNonUserActiveCallsign) + { + NiceMock euroscopeMock; + + EXPECT_CALL(euroscopeMock, GetCallsign()).WillRepeatedly(Return("EGKK_DEL")); + + EXPECT_CALL(euroscopeMock, HasActiveFrequency()).Times(1).WillOnce(Return(true)); + + EXPECT_CALL(euroscopeMock, GetControllerName()).Times(1).WillOnce(Return("Testy McTestington")); + + EXPECT_CALL(euroscopeMock, GetFrequency()).Times(1).WillOnce(Return(199.998)); + + EXPECT_CALL(euroscopeMock, IsCurrentUser()).Times(1).WillRepeatedly(Return(false)); + + this->handler.ControllerUpdateEvent(euroscopeMock); + EXPECT_TRUE(this->activeCallsigns.CallsignActive("EGKK_DEL")); + EXPECT_TRUE(this->activeCallsigns.PositionActive("EGKK_DEL")); + EXPECT_FALSE(this->activeCallsigns.UserHasCallsign()); + auto callsign = this->activeCallsigns.GetCallsign("EGKK_DEL"); + EXPECT_EQ("EGKK_DEL", callsign.GetCallsign()); + EXPECT_EQ("Testy McTestington", callsign.GetControllerName()); + EXPECT_EQ(this->controllerCollection.FetchPositionByCallsign("EGKK_DEL"), callsign.GetNormalisedPosition()); + EXPECT_FALSE(callsign.GetIsUser()); } TEST_F(ActiveCallsignMonitorTest, ControllerUpdateEventHandlesHyphenatedCallsigns) diff --git a/test/plugin/controller/ActiveCallsignTest.cpp b/test/plugin/controller/ActiveCallsignTest.cpp index e0bcbe6a4..cd95e0c72 100644 --- a/test/plugin/controller/ActiveCallsignTest.cpp +++ b/test/plugin/controller/ActiveCallsignTest.cpp @@ -1,114 +1,150 @@ -#include "pch/pch.h" #include "controller/ActiveCallsign.h" #include "controller/ControllerPosition.h" using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ControllerPosition; -namespace UKControllerPluginTest { - namespace Controller { +namespace UKControllerPluginTest::Controller { - class ActiveCallsignTest : public testing::Test + class ActiveCallsignTest : public testing::Test + { + public: + ActiveCallsignTest() + : controller(1, "LON_S_CTR", 129.420, {"EGKK"}, true, false), + controller2(2, "LON_C_CTR", 127.100, {"EGSS"}, true, false) { - public: - ActiveCallsignTest() - : controller(1, "LON_S_CTR", 129.420, {"EGKK"}, true, false), - controller2(2, "LON_C_CTR", 127.100, {"EGSS"}, true, false) - { } - - ControllerPosition controller; - ControllerPosition controller2; - }; - - TEST_F(ActiveCallsignTest, LessThanOperatorReturnsTrueIfCallsignLess) - { - ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_TRUE(pos1 < pos2); - } - - TEST_F(ActiveCallsignTest, LessThanOperatorReturnsFalseIfCallsignSame) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_FALSE(pos1 < pos2); - } - - TEST_F(ActiveCallsignTest, LessThanOperatorReturnsFalseIfCallsignMore) - { - ActiveCallsign pos1("LON_S__CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_FALSE(pos1 < pos2); - } - - TEST_F(ActiveCallsignTest, GreaterThanOperatorReturnsFalseIfCallsignLess) - { - ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_FALSE(pos1 > pos2); - } - - TEST_F(ActiveCallsignTest, GreaterThanOperatorReturnsFalseIfCallsignSame) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_FALSE(pos1 > pos2); - } - - TEST_F(ActiveCallsignTest, GreaterThanOperatorReturnsTrueIfCallsignMore) - { - ActiveCallsign pos1("LON_S__CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_TRUE(pos1 > pos2); - } - - TEST_F(ActiveCallsignTest, EqualityOperatorReturnsTrueIfAllEqual) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_TRUE(pos1 == pos2); - } - - TEST_F(ActiveCallsignTest, EqualityOperatorReturnsFalseIfCallsignDifferent) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S__CTR", "Testy McTest", controller); - EXPECT_FALSE(pos1 == pos2); } - TEST_F(ActiveCallsignTest, EqualityOperatorReturnsFalseIfControllerDifferent) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller2); - EXPECT_FALSE(pos1 == pos2); - } - - TEST_F(ActiveCallsignTest, InequalityOperatorReturnsFalseIfAllSame) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_FALSE(pos1 != pos2); - } - - TEST_F(ActiveCallsignTest, InequalityOperatorReturnsTrueIfCallsignNotSame) - { - ActiveCallsign pos1("LON_S__CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller); - EXPECT_TRUE(pos1 != pos2); - } - - TEST_F(ActiveCallsignTest, InequalityOperatorReturnsTrueIfControllerDifferent) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller2); - EXPECT_TRUE(pos1 != pos2); - } - - TEST_F(ActiveCallsignTest, CopyConstructorCopiesAllItems) - { - ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller); - ActiveCallsign pos2 = pos1; - EXPECT_TRUE(pos1 == pos2); - } - } // namespace Controller -} // namespace UKControllerPluginTest + ControllerPosition controller; + ControllerPosition controller2; + }; + + TEST_F(ActiveCallsignTest, ItReturnsCallsign) + { + ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller, true); + EXPECT_EQ("LON_S1_CTR", pos1.GetCallsign()); + } + + TEST_F(ActiveCallsignTest, ItReturnsControllerName) + { + ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller, true); + EXPECT_EQ("Testy McTest", pos1.GetControllerName()); + } + + TEST_F(ActiveCallsignTest, ItReturnsNormalisedPosition) + { + ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller, true); + EXPECT_EQ(controller, pos1.GetNormalisedPosition()); + } + + TEST_F(ActiveCallsignTest, ItReturnsIsUser) + { + ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller, true); + EXPECT_TRUE(pos1.GetIsUser()); + } + + TEST_F(ActiveCallsignTest, LessThanOperatorReturnsTrueIfCallsignLess) + { + ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_TRUE(pos1 < pos2); + } + + TEST_F(ActiveCallsignTest, LessThanOperatorReturnsFalseIfCallsignSame) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_FALSE(pos1 < pos2); + } + + TEST_F(ActiveCallsignTest, LessThanOperatorReturnsFalseIfCallsignMore) + { + ActiveCallsign pos1("LON_S__CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_FALSE(pos1 < pos2); + } + + TEST_F(ActiveCallsignTest, GreaterThanOperatorReturnsFalseIfCallsignLess) + { + ActiveCallsign pos1("LON_S1_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_FALSE(pos1 > pos2); + } + + TEST_F(ActiveCallsignTest, GreaterThanOperatorReturnsFalseIfCallsignSame) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_FALSE(pos1 > pos2); + } + + TEST_F(ActiveCallsignTest, GreaterThanOperatorReturnsTrueIfCallsignMore) + { + ActiveCallsign pos1("LON_S__CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_TRUE(pos1 > pos2); + } + + TEST_F(ActiveCallsignTest, EqualityOperatorReturnsTrueIfAllEqual) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_TRUE(pos1 == pos2); + } + + TEST_F(ActiveCallsignTest, EqualityOperatorReturnsFalseIfCallsignDifferent) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S__CTR", "Testy McTest", controller, true); + EXPECT_FALSE(pos1 == pos2); + } + + TEST_F(ActiveCallsignTest, EqualityOperatorReturnsFalseIfControllerDifferent) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller2, true); + EXPECT_FALSE(pos1 == pos2); + } + + TEST_F(ActiveCallsignTest, EqualityOperatorReturnsFalseIfIsUserDifferent) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, false); + EXPECT_FALSE(pos1 == pos2); + } + + TEST_F(ActiveCallsignTest, InequalityOperatorReturnsFalseIfAllSame) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_FALSE(pos1 != pos2); + } + + TEST_F(ActiveCallsignTest, InequalityOperatorReturnsTrueIfCallsignNotSame) + { + ActiveCallsign pos1("LON_S__CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, true); + EXPECT_TRUE(pos1 != pos2); + } + + TEST_F(ActiveCallsignTest, InequalityOperatorReturnsTrueIfControllerDifferent) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller2, true); + EXPECT_TRUE(pos1 != pos2); + } + + TEST_F(ActiveCallsignTest, InequalityOperatorReturnsTrueIfIsUser) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2("LON_S_CTR", "Testy McTest", controller, false); + EXPECT_TRUE(pos1 != pos2); + } + + TEST_F(ActiveCallsignTest, CopyConstructorCopiesAllItems) + { + ActiveCallsign pos1("LON_S_CTR", "Testy McTest", controller, true); + ActiveCallsign pos2 = pos1; + EXPECT_TRUE(pos1 == pos2); + } +} // namespace UKControllerPluginTest::Controller diff --git a/test/plugin/controller/ControllerPositionTest.cpp b/test/plugin/controller/ControllerPositionTest.cpp index 1f2424977..586dfcabc 100644 --- a/test/plugin/controller/ControllerPositionTest.cpp +++ b/test/plugin/controller/ControllerPositionTest.cpp @@ -8,96 +8,261 @@ namespace UKControllerPluginTest::Controller { { public: ControllerPositionTest() - : controller(1, "EGFF_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true) + : deliveryController( + 1, "EGFF_DEL", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true), + groundController( + 1, "EGFF_GND", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true), + towerController( + 1, "EGFF_TWR", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true), + approachController( + 1, "EGFF_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true), + enrouteController( + 1, "EGFF_CTR", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true), + fssController(1, "EGFF_FSS", 125.850, std::vector{"EGGD", "EGFF"}, true, false, false, true) { } - ControllerPosition controller; + ControllerPosition deliveryController; + ControllerPosition groundController; + ControllerPosition towerController; + ControllerPosition approachController; + ControllerPosition enrouteController; + ControllerPosition fssController; }; TEST_F(ControllerPositionTest, GetIdReturnsId) { - EXPECT_EQ(1, controller.GetId()); + EXPECT_EQ(1, approachController.GetId()); } TEST_F(ControllerPositionTest, GetCallsignReturnsCallsign) { - EXPECT_EQ(0, controller.GetCallsign().compare("EGFF_APP")); + EXPECT_EQ(0, approachController.GetCallsign().compare("EGFF_APP")); } TEST_F(ControllerPositionTest, GetFrequencyReturnsFrequency) { - EXPECT_EQ(125.850, controller.GetFrequency()); + EXPECT_EQ(125.850, approachController.GetFrequency()); } TEST_F(ControllerPositionTest, GetTypeReturnsType) { - EXPECT_EQ(0, controller.GetType().compare("APP")); + EXPECT_EQ(0, approachController.GetType().compare("APP")); } TEST_F(ControllerPositionTest, GetTopdownReturnsTopdown) { std::vector expected = {"EGGD", "EGFF"}; - EXPECT_EQ(expected, controller.GetTopdown()); + EXPECT_EQ(expected, approachController.GetTopdown()); } TEST_F(ControllerPositionTest, RequestsDepartureReleasesReturnsWhetherItCan) { - EXPECT_TRUE(controller.RequestsDepartureReleases()); + EXPECT_TRUE(approachController.RequestsDepartureReleases()); } TEST_F(ControllerPositionTest, ReceivesDepartureReleasesReturnsWhetherItCan) { - EXPECT_FALSE(controller.ReceivesDepartureReleases()); + EXPECT_FALSE(approachController.ReceivesDepartureReleases()); } TEST_F(ControllerPositionTest, ComparisonOperatorReturnsTrueIfSame) { ControllerPosition controller2(1, "EGFF_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, false); - EXPECT_TRUE(controller == controller2); + EXPECT_TRUE(approachController == controller2); } TEST_F(ControllerPositionTest, ComparisonOperatorReturnsTrueIfSameFrequencyDelta) { ControllerPosition controller2(1, "EGFF_APP", 125.8501, std::vector{"EGGD", "EGFF"}, true, false); - EXPECT_TRUE(controller == controller2); + EXPECT_TRUE(approachController == controller2); } TEST_F(ControllerPositionTest, ComparisonOperatorReturnsFalseIfDifferentCallsign) { ControllerPosition controller2(1, "EGFF_R_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, false); - EXPECT_FALSE(controller == controller2); + EXPECT_FALSE(approachController == controller2); } TEST_F(ControllerPositionTest, ComparisonOperatorReturnsFalseIfDifferentFrequency) { ControllerPosition controller2(1, "EGFF_APP", 125.650, std::vector{"EGGD", "EGFF"}, true, false); - EXPECT_FALSE(controller == controller2); + EXPECT_FALSE(approachController == controller2); } TEST_F(ControllerPositionTest, GetUnitReturnsTheAirfieldOrAreaUnit) { - EXPECT_TRUE("EGFF" == controller.GetUnit()); + EXPECT_TRUE("EGFF" == approachController.GetUnit()); } TEST_F(ControllerPositionTest, ItReturnsTrueOnTopdownAirfield) { - EXPECT_TRUE(controller.HasTopdownAirfield("EGFF")); - EXPECT_TRUE(controller.HasTopdownAirfield("EGGD")); + EXPECT_TRUE(approachController.HasTopdownAirfield("EGFF")); + EXPECT_TRUE(approachController.HasTopdownAirfield("EGGD")); } TEST_F(ControllerPositionTest, ItReturnsFalseOnNoTopdownAirfield) { - EXPECT_FALSE(controller.HasTopdownAirfield("EGLL")); + EXPECT_FALSE(approachController.HasTopdownAirfield("EGLL")); } TEST_F(ControllerPositionTest, SendsPrenoteMessagesReturnsWhetherItCan) { - EXPECT_FALSE(controller.SendsPrenoteMessages()); + EXPECT_FALSE(approachController.SendsPrenoteMessages()); } TEST_F(ControllerPositionTest, ReceivesPrenoteMessagesReturnsWhetherItCan) { - EXPECT_TRUE(controller.ReceivesPrenoteMessages()); + EXPECT_TRUE(approachController.ReceivesPrenoteMessages()); + } + + TEST_F(ControllerPositionTest, IsDeliveryReturnsFalseNotDelivery) + { + EXPECT_FALSE(approachController.IsDelivery()); + } + + TEST_F(ControllerPositionTest, IsDeliveryReturnsTrueIfDelivery) + { + EXPECT_TRUE(deliveryController.IsDelivery()); + } + + TEST_F(ControllerPositionTest, IsGroundReturnsFalseNotGround) + { + EXPECT_FALSE(approachController.IsGround()); + } + + TEST_F(ControllerPositionTest, IsGroundReturnsTrueIfGround) + { + EXPECT_TRUE(groundController.IsGround()); + } + + TEST_F(ControllerPositionTest, IsTowerReturnsFalseNotTower) + { + EXPECT_FALSE(approachController.IsTower()); + } + + TEST_F(ControllerPositionTest, IsTowerReturnsTrueIfTower) + { + EXPECT_TRUE(towerController.IsTower()); + } + + TEST_F(ControllerPositionTest, IsApproachReturnsTrueIfApproach) + { + EXPECT_TRUE(approachController.IsApproach()); + } + + TEST_F(ControllerPositionTest, IsApproachReturnsFalseIfNotApproach) + { + EXPECT_FALSE(towerController.IsApproach()); + } + + TEST_F(ControllerPositionTest, IsEnrouteReturnsFalseNotEnroute) + { + EXPECT_FALSE(groundController.IsEnroute()); + } + + TEST_F(ControllerPositionTest, IsEnrouteReturnsTrueIfEnroute) + { + EXPECT_TRUE(enrouteController.IsEnroute()); + } + + TEST_F(ControllerPositionTest, IsFlightServiceStationReturnsFalseNotFss) + { + EXPECT_FALSE(groundController.IsFlightServiceStation()); + } + + TEST_F(ControllerPositionTest, IsFlightServiceStationReturnsTrueIfFss) + { + EXPECT_TRUE(fssController.IsFlightServiceStation()); + } + + TEST_F(ControllerPositionTest, ProvidesApproachServicesReturnsFalseForDelivery) + { + EXPECT_FALSE(deliveryController.ProvidesApproachServices()); + } + + TEST_F(ControllerPositionTest, ProvidesApproachServicesReturnsFalseForGround) + { + EXPECT_FALSE(groundController.ProvidesApproachServices()); + } + + TEST_F(ControllerPositionTest, ProvidesApproachServicesReturnsFalseForTower) + { + EXPECT_FALSE(towerController.ProvidesApproachServices()); + } + + TEST_F(ControllerPositionTest, ProvidesApproachServicesReturnsTrueForApproach) + { + EXPECT_TRUE(approachController.ProvidesApproachServices()); + } + + TEST_F(ControllerPositionTest, ProvidesApproachServicesReturnsTrueForEnroute) + { + EXPECT_TRUE(enrouteController.ProvidesApproachServices()); + } + + TEST_F(ControllerPositionTest, ProvidesApproachServicesReturnsFalseForFlightServiceStation) + { + EXPECT_FALSE(fssController.ProvidesApproachServices()); + } + + TEST_F(ControllerPositionTest, ProvidesGroundServicesReturnsFalseForDelivery) + { + EXPECT_FALSE(deliveryController.ProvidesGroundServices()); + } + + TEST_F(ControllerPositionTest, ProvidesGroundServicesReturnsFalseForFss) + { + EXPECT_FALSE(fssController.ProvidesGroundServices()); + } + + TEST_F(ControllerPositionTest, ProvidesGroundServicesReturnsTrueForGround) + { + EXPECT_TRUE(groundController.ProvidesGroundServices()); + } + + TEST_F(ControllerPositionTest, ProvidesGroundServicesReturnsTrueForTower) + { + EXPECT_TRUE(towerController.ProvidesGroundServices()); + } + + TEST_F(ControllerPositionTest, ProvidesGroundServicesReturnsTrueForApproach) + { + EXPECT_TRUE(approachController.ProvidesGroundServices()); + } + + TEST_F(ControllerPositionTest, ProvidesGroundServicesReturnsTrueForEnroute) + { + EXPECT_TRUE(enrouteController.ProvidesGroundServices()); + } + + TEST_F(ControllerPositionTest, ProvidesTowerServicesReturnsFalseForDelivery) + { + EXPECT_FALSE(deliveryController.ProvidesTowerServices()); + } + + TEST_F(ControllerPositionTest, ProvidesTowerServicesReturnsFalseForFss) + { + EXPECT_FALSE(fssController.ProvidesTowerServices()); + } + + TEST_F(ControllerPositionTest, ProvidesTowerServicesReturnsFalseForGround) + { + EXPECT_FALSE(groundController.ProvidesTowerServices()); + } + + TEST_F(ControllerPositionTest, ProvidesTowerServicesReturnsTrueForTower) + { + EXPECT_TRUE(towerController.ProvidesTowerServices()); + } + + TEST_F(ControllerPositionTest, ProvidesTowerServicesReturnsTrueForApproach) + { + EXPECT_TRUE(approachController.ProvidesTowerServices()); + } + + TEST_F(ControllerPositionTest, ProvidesTowerServicesReturnsTrueForEnroute) + { + EXPECT_TRUE(enrouteController.ProvidesTowerServices()); } } // namespace UKControllerPluginTest::Controller diff --git a/test/plugin/handoff/HandoffEventHandlerTest.cpp b/test/plugin/handoff/HandoffEventHandlerTest.cpp index 739c723eb..3d8fe06b2 100644 --- a/test/plugin/handoff/HandoffEventHandlerTest.cpp +++ b/test/plugin/handoff/HandoffEventHandlerTest.cpp @@ -116,7 +116,7 @@ namespace UKControllerPluginTest { { this->handoffs.AddHandoffOrder("EGKK_ADMAG2X", this->hierarchy); this->handoffs.AddSidMapping("EGKK", "ADMAG2X", "EGKK_ADMAG2X"); - this->activeCallsigns.AddCallsign(ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2)); + this->activeCallsigns.AddCallsign(ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2, false)); std::shared_ptr expectedMessage = std::make_shared("BAW123", "132.600"); @@ -131,7 +131,7 @@ namespace UKControllerPluginTest { { this->handoffs.AddHandoffOrder("EGKK_ADMAG2X", this->hierarchy); this->handoffs.AddSidMapping("EGKK", "ADMAG2X", "EGKK_ADMAG2X"); - this->activeCallsigns.AddCallsign(ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2)); + this->activeCallsigns.AddCallsign(ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2, false)); this->handler.SetTagItemData(this->tagData); EXPECT_EQ(CachedHandoff("132.600", "LON_SC_CTR"), this->handler.GetCachedItem("BAW123")); } @@ -140,7 +140,8 @@ namespace UKControllerPluginTest { { this->handoffs.AddHandoffOrder("EGKK_ADMAG2X", this->hierarchy); this->handoffs.AddSidMapping("EGKK", "ADMAG2X", "EGKK_ADMAG2X"); - this->activeCallsigns.AddUserCallsign(ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2)); + this->activeCallsigns.AddUserCallsign( + ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2, true)); this->handler.SetTagItemData(this->tagData); EXPECT_EQ(DEFAULT_TAG_VALUE.frequency, this->tagData.GetItemString()); } @@ -149,7 +150,8 @@ namespace UKControllerPluginTest { { this->handoffs.AddHandoffOrder("EGKK_ADMAG2X", this->hierarchy); this->handoffs.AddSidMapping("EGKK", "ADMAG2X", "EGKK_ADMAG2X"); - this->activeCallsigns.AddUserCallsign(ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2)); + this->activeCallsigns.AddUserCallsign( + ActiveCallsign("LON_SC_CTR", "Testy McTestFace", this->position2, true)); this->handler.SetTagItemData(this->tagData); EXPECT_EQ(DEFAULT_TAG_VALUE, this->handler.GetCachedItem("BAW123")); } @@ -182,7 +184,7 @@ namespace UKControllerPluginTest { { this->handler.AddCachedItem("BAW123", CachedHandoff("123.456", "LON_S_CTR")); this->handler.AddCachedItem("BAW456", CachedHandoff("123.456", "LON_S_CTR")); - this->handler.ActiveCallsignAdded(ActiveCallsign("LON_S_CTR", "Testy", this->position1), false); + this->handler.ActiveCallsignAdded(ActiveCallsign("LON_S_CTR", "Testy", this->position1, false)); EXPECT_EQ(this->DEFAULT_TAG_VALUE, this->handler.GetCachedItem("BAW123")); EXPECT_EQ(this->DEFAULT_TAG_VALUE, this->handler.GetCachedItem("BAW456")); } @@ -191,7 +193,7 @@ namespace UKControllerPluginTest { { this->handler.AddCachedItem("BAW123", CachedHandoff("123.456", "LON_S_CTR")); this->handler.AddCachedItem("BAW456", CachedHandoff("123.456", "LON_S_CTR")); - this->handler.ActiveCallsignAdded(ActiveCallsign("LON_S_CTR", "Testy", this->position1), false); + this->handler.ActiveCallsignAdded(ActiveCallsign("LON_S_CTR", "Testy", this->position1, false)); EXPECT_EQ(this->DEFAULT_TAG_VALUE, this->handler.GetCachedItem("BAW123")); EXPECT_EQ(this->DEFAULT_TAG_VALUE, this->handler.GetCachedItem("BAW456")); } @@ -200,7 +202,7 @@ namespace UKControllerPluginTest { { this->handler.AddCachedItem("BAW123", CachedHandoff("123.456", "LON_S_CTR")); this->handler.AddCachedItem("BAW456", CachedHandoff("123.456", "LON_SC_CTR")); - this->handler.ActiveCallsignRemoved(ActiveCallsign("LON_SC_CTR", "Testy", this->position1), false); + this->handler.ActiveCallsignRemoved(ActiveCallsign("LON_SC_CTR", "Testy", this->position1, false)); EXPECT_EQ(CachedHandoff("123.456", "LON_S_CTR"), this->handler.GetCachedItem("BAW123")); EXPECT_EQ(this->DEFAULT_TAG_VALUE, this->handler.GetCachedItem("BAW456")); } diff --git a/test/plugin/headings/HeadingTest.cpp b/test/plugin/headings/HeadingTest.cpp new file mode 100644 index 000000000..0d55a4503 --- /dev/null +++ b/test/plugin/headings/HeadingTest.cpp @@ -0,0 +1,139 @@ +#include "headings/Heading.h" + +using UKControllerPlugin::Headings::Heading; + +namespace UKControllerPluginTest::Headings { + class HeadingTest : public testing::Test + { + }; + + TEST_F(HeadingTest, ItKnowsNorthExplicit) + { + EXPECT_EQ(360, Heading::North); + } + + TEST_F(HeadingTest, ItKnowsNorthShort) + { + EXPECT_EQ(360, Heading::N); + } + + TEST_F(HeadingTest, ItKnowsNorthEastExplicit) + { + EXPECT_EQ(45, Heading::NorthEast); + } + + TEST_F(HeadingTest, ItKnowsNorthEastShort) + { + EXPECT_EQ(45, Heading::NE); + } + + TEST_F(HeadingTest, ItKnowsEastExplicit) + { + EXPECT_EQ(90, Heading::East); + } + + TEST_F(HeadingTest, ItKnowsEastShort) + { + EXPECT_EQ(90, Heading::E); + } + + TEST_F(HeadingTest, ItKnowsSouthEastExplicit) + { + EXPECT_EQ(135, Heading::SouthEast); + } + + TEST_F(HeadingTest, ItKnowsSouthEastShort) + { + EXPECT_EQ(135, Heading::SE); + } + + TEST_F(HeadingTest, ItKnowsSouthExplicit) + { + EXPECT_EQ(180, Heading::South); + } + + TEST_F(HeadingTest, ItKnowsSouthShort) + { + EXPECT_EQ(180, Heading::S); + } + + TEST_F(HeadingTest, ItKnowsSouthWestExplicit) + { + EXPECT_EQ(225, Heading::SouthWest); + } + + TEST_F(HeadingTest, ItKnowsSouthWestShort) + { + EXPECT_EQ(225, Heading::SW); + } + + TEST_F(HeadingTest, ItKnowsWestExplicit) + { + EXPECT_EQ(270, Heading::West); + } + + TEST_F(HeadingTest, ItKnowsWestShort) + { + EXPECT_EQ(270, Heading::W); + } + + TEST_F(HeadingTest, ItKnowsNorthWestExplicit) + { + EXPECT_EQ(315, Heading::NorthWest); + } + + TEST_F(HeadingTest, ItKnowsNorthWestShort) + { + EXPECT_EQ(315, Heading::NW); + } + + TEST_F(HeadingTest, LessThanReturnsTrueForHeadingLessThanHeading) + { + EXPECT_TRUE(Heading::SE < Heading::NW); + } + + TEST_F(HeadingTest, LessThanReturnsFalseForHeadingNotLessThanHeading) + { + EXPECT_FALSE(Heading::NW < Heading::SW); + } + + TEST_F(HeadingTest, GreaterThanEqualToReturnsTrueForHeadingGreaterThanHeading) + { + EXPECT_TRUE(Heading::SE >= Heading::E); + } + + TEST_F(HeadingTest, GreaterThanEqualToReturnsTrueForHeadingEqualToHeading) + { + EXPECT_TRUE(Heading::E >= Heading::E); + } + + TEST_F(HeadingTest, GreaterThanEqualToReturnsFalseForHeadingLessThanHeading) + { + EXPECT_FALSE(Heading::SW >= Heading::W); + } + + TEST_F(HeadingTest, LessThanReturnsTrueForDoubleLessThanHeading) + { + EXPECT_TRUE(180.0 < Heading::NW); + } + + TEST_F(HeadingTest, LessThanReturnsFalseForDoubleNotLessThanHeading) + { + EXPECT_FALSE(315.0 < Heading::SW); + } + + TEST_F(HeadingTest, GreaterThanEqualToReturnsTrueForDoubleGreaterThanHeading) + { + EXPECT_TRUE(180.0 >= Heading::E); + } + + TEST_F(HeadingTest, GreaterThanEqualToReturnsTrueForDoubleEqualToHeading) + { + EXPECT_TRUE(90.0 >= Heading::E); + } + + TEST_F(HeadingTest, GreaterThanEqualToReturnsFalseForDoubleLessThanHeading) + { + EXPECT_FALSE(225.0 >= Heading::W); + } +} // namespace UKControllerPluginTest::Headings diff --git a/test/plugin/initialaltitude/InitialAltitudeEventHandlerTest.cpp b/test/plugin/initialaltitude/InitialAltitudeEventHandlerTest.cpp index 10d53cb19..00d96eb76 100644 --- a/test/plugin/initialaltitude/InitialAltitudeEventHandlerTest.cpp +++ b/test/plugin/initialaltitude/InitialAltitudeEventHandlerTest.cpp @@ -1,19 +1,14 @@ #include "initialaltitude/InitialAltitudeEventHandler.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" #include "controller/ActiveCallsignCollection.h" -#include "airfield/AirfieldCollection.h" -#include "controller/ActiveCallsign.h" #include "controller/ControllerPosition.h" -#include "airfield/AirfieldModel.h" #include "login/Login.h" #include "controller/ControllerStatusEventHandlerCollection.h" -#include "euroscope/UserSetting.h" #include "euroscope/GeneralSettingsEntries.h" #include "sid/SidCollection.h" #include "sid/StandardInstrumentDeparture.h" -using UKControllerPlugin::Airfield::AirfieldCollection; -using UKControllerPlugin::Airfield::AirfieldModel; using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; @@ -22,7 +17,9 @@ using UKControllerPlugin::Controller::Login; using UKControllerPlugin::Euroscope::GeneralSettingsEntries; using UKControllerPlugin::Euroscope::UserSetting; using UKControllerPlugin::InitialAltitude::InitialAltitudeEventHandler; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; using UKControllerPlugin::Sid::SidCollection; using UKControllerPlugin::Sid::StandardInstrumentDeparture; using UKControllerPluginTest::Euroscope::MockEuroScopeCFlightPlanInterface; @@ -45,13 +42,14 @@ namespace UKControllerPluginTest { public: InitialAltitudeEventHandlerTest() : controller(1, "LON_S_CTR", 129.420, {"EGKK"}, true, false), - userCallsign("LON_S_CTR", "Test", controller), - login(plugin, ControllerStatusEventHandlerCollection()), owners(airfields, callsigns), + userCallsign("LON_S_CTR", "Test", controller, true), + notUserCallsign("LON_S_CTR", "Test", controller, false), + login(plugin, ControllerStatusEventHandlerCollection()), handler(sids, callsigns, owners, login, plugin) { } - virtual void SetUp() + void SetUp() override { // Pretend we've been logged in a while login.SetLoginStatus(EuroScopePlugIn::CONNECTION_TYPE_DIRECT); @@ -59,19 +57,29 @@ namespace UKControllerPluginTest { sids.AddSid(std::make_shared("EGKK", "ADMAG2X", 6000, 0)); sids.AddSid(std::make_shared("EGKK", "CLN3X", 5000, 0)); - this->mockFlightplanPointer.reset(new NiceMock); - this->mockRadarTargetPointer.reset(new NiceMock); + this->mockFlightplanPointer = std::make_shared>(); + this->mockRadarTargetPointer = std::make_shared>(); ON_CALL(mockFlightPlan, GetCallsign()).WillByDefault(Return("BAW123")); ON_CALL(*mockFlightplanPointer, GetCallsign()).WillByDefault(Return("BAW123")); } + void SetServiceProvision(bool isUser) + { + auto callsign = isUser ? this->userCallsign : this->notUserCallsign; + this->owners.SetProvidersForAirfield( + "EGKK", + std::vector>{std::make_shared( + ServiceType::Delivery, + std::make_shared(callsign))}); + } + inline static const double MAX_DISTANCE_FROM_ORIGIN = 3.0; inline static const int MAX_ASSIGNMENT_ALTITUDE = 1000; inline static const int MAX_ASSIGNMENT_SPEED = 40; ControllerPosition controller; ActiveCallsign userCallsign; - AirfieldCollection airfields; + ActiveCallsign notUserCallsign; std::shared_ptr> mockFlightplanPointer; std::shared_ptr> mockRadarTargetPointer; NiceMock mockFlightPlan; @@ -79,7 +87,7 @@ namespace UKControllerPluginTest { NiceMock plugin; Login login; ActiveCallsignCollection callsigns; - AirfieldOwnershipManager owners; + AirfieldServiceProviderCollection owners; SidCollection sids; InitialAltitudeEventHandler handler; }; @@ -308,8 +316,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -336,8 +343,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -371,8 +377,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -405,8 +410,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -439,8 +443,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(4) @@ -482,8 +485,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(4) @@ -608,8 +610,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); ON_CALL(this->plugin, GetFlightplanForCallsign("BAW123")).WillByDefault(Return(mockFlightplanPointer)); @@ -642,26 +643,24 @@ namespace UKControllerPluginTest { .Times(2) .WillRepeatedly(Return(MAX_ASSIGNMENT_SPEED)); - handler.ActiveCallsignAdded(userCallsign, true); + handler.ActiveCallsignAdded(userCallsign); } TEST_F(InitialAltitudeEventHandlerTest, NewActiveCallsignDoesNotAssignIfNotUserCallsign) { callsigns.AddCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(false); this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); this->plugin.ExpectNoFlightplanLoop(); - handler.ActiveCallsignAdded(userCallsign, false); + handler.ActiveCallsignAdded(notUserCallsign); } TEST_F(InitialAltitudeEventHandlerTest, TimedEventAssignsIfUserCallsign) { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::make_unique("EGKK", std::vector{"LON_S_CTR"})); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); ON_CALL(this->plugin, GetFlightplanForCallsign("BAW123")).WillByDefault(Return(mockFlightplanPointer)); @@ -700,8 +699,7 @@ namespace UKControllerPluginTest { TEST_F(InitialAltitudeEventHandlerTest, TimedEventDoesNotAssignIfNoUserCallsign) { callsigns.AddCallsign(userCallsign); - airfields.AddAirfield(std::make_unique("EGKK", std::vector{"LON_S_CTR"})); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(false); this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); this->plugin.ExpectNoFlightplanLoop(); diff --git a/test/plugin/initialheading/InitialHeadingEventHandlerTest.cpp b/test/plugin/initialheading/InitialHeadingEventHandlerTest.cpp index bf382513a..07f6d0a69 100644 --- a/test/plugin/initialheading/InitialHeadingEventHandlerTest.cpp +++ b/test/plugin/initialheading/InitialHeadingEventHandlerTest.cpp @@ -1,19 +1,14 @@ #include "initialheading/InitialHeadingEventHandler.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" #include "controller/ActiveCallsignCollection.h" -#include "airfield/AirfieldCollection.h" -#include "controller/ActiveCallsign.h" #include "controller/ControllerPosition.h" -#include "airfield/AirfieldModel.h" #include "login/Login.h" #include "controller/ControllerStatusEventHandlerCollection.h" -#include "euroscope/UserSetting.h" #include "euroscope/GeneralSettingsEntries.h" #include "sid/SidCollection.h" #include "sid/StandardInstrumentDeparture.h" -using UKControllerPlugin::Airfield::AirfieldCollection; -using UKControllerPlugin::Airfield::AirfieldModel; using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; @@ -22,7 +17,9 @@ using UKControllerPlugin::Controller::Login; using UKControllerPlugin::Euroscope::GeneralSettingsEntries; using UKControllerPlugin::Euroscope::UserSetting; using UKControllerPlugin::InitialHeading::InitialHeadingEventHandler; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; using UKControllerPlugin::Sid::SidCollection; using UKControllerPlugin::Sid::StandardInstrumentDeparture; using UKControllerPluginTest::Euroscope::MockEuroScopeCFlightPlanInterface; @@ -45,8 +42,9 @@ namespace UKControllerPluginTest { public: InitialHeadingEventHandlerTest() : controller(1, "LON_S_CTR", 129.420, {"EGKK"}, true, false), - userCallsign("LON_S_CTR", "Test", controller), - login(plugin, ControllerStatusEventHandlerCollection()), owners(airfields, callsigns), + userCallsign("LON_S_CTR", "Test", controller, true), + notUserCallsign("LON_S_CTR", "Test", controller, false), + login(plugin, ControllerStatusEventHandlerCollection()), handler(sids, callsigns, owners, login, plugin) { } @@ -67,12 +65,22 @@ namespace UKControllerPluginTest { ON_CALL(*mockFlightplanPointer, GetCallsign()).WillByDefault(Return("BAW123")); } + void SetServiceProvision(bool isUser) + { + auto callsign = isUser ? this->userCallsign : this->notUserCallsign; + this->owners.SetProvidersForAirfield( + "EGKK", + std::vector>{std::make_shared( + ServiceType::Delivery, + std::make_shared(callsign))}); + } + inline static const double MAX_DISTANCE_FROM_ORIGIN = 3.0; inline static const int MAX_ASSIGNMENT_ALTITUDE = 1000; inline static const int MAX_ASSIGNMENT_SPEED = 40; ControllerPosition controller; ActiveCallsign userCallsign; - AirfieldCollection airfields; + ActiveCallsign notUserCallsign; std::shared_ptr> mockFlightplanPointer; std::shared_ptr> mockRadarTargetPointer; NiceMock mockFlightPlan; @@ -80,7 +88,7 @@ namespace UKControllerPluginTest { NiceMock plugin; Login login; ActiveCallsignCollection callsigns; - AirfieldOwnershipManager owners; + AirfieldServiceProviderCollection owners; SidCollection sids; InitialHeadingEventHandler handler; }; @@ -319,8 +327,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -347,8 +354,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -380,8 +386,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -412,8 +417,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(2) @@ -444,8 +448,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(4) @@ -485,8 +488,7 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"LON_S_CTR"}))); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); EXPECT_CALL(mockFlightPlan, GetDistanceFromOrigin()) .Times(4) @@ -607,8 +609,8 @@ namespace UKControllerPluginTest { { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::make_unique("EGKK", std::vector{"LON_S_CTR"})); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); + this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); EXPECT_CALL(*mockFlightplanPointer, GetDistanceFromOrigin()) @@ -635,26 +637,25 @@ namespace UKControllerPluginTest { .Times(2) .WillRepeatedly(Return(MAX_ASSIGNMENT_SPEED)); - handler.ActiveCallsignAdded(userCallsign, true); + handler.ActiveCallsignAdded(userCallsign); } TEST_F(InitialHeadingEventHandlerTest, NewActiveCallsignDoesNotAssignIfNotUserCallsign) { - callsigns.AddCallsign(userCallsign); - airfields.AddAirfield(std::make_unique("EGKK", std::vector{"LON_S_CTR"})); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(false); + this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); this->plugin.ExpectNoFlightplanLoop(); - handler.ActiveCallsignAdded(userCallsign, false); + handler.ActiveCallsignAdded(notUserCallsign); } TEST_F(InitialHeadingEventHandlerTest, TimedEventAssignsIfUserCallsign) { callsigns.AddUserCallsign(userCallsign); - airfields.AddAirfield(std::make_unique("EGKK", std::vector{"LON_S_CTR"})); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(true); + this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); EXPECT_CALL(*mockFlightplanPointer, GetDistanceFromOrigin()) @@ -687,8 +688,8 @@ namespace UKControllerPluginTest { TEST_F(InitialHeadingEventHandlerTest, TimedEventDoesNotAssignIfNoUserCallsign) { callsigns.AddCallsign(userCallsign); - airfields.AddAirfield(std::make_unique("EGKK", std::vector{"LON_S_CTR"})); - owners.RefreshOwner("EGKK"); + this->SetServiceProvision(false); + this->plugin.AddAllFlightplansItem({this->mockFlightplanPointer, this->mockRadarTargetPointer}); this->plugin.ExpectNoFlightplanLoop(); diff --git a/test/plugin/metar/PressureMonitorTest.cpp b/test/plugin/metar/PressureMonitorTest.cpp index e322ff32f..a6f272a0d 100644 --- a/test/plugin/metar/PressureMonitorTest.cpp +++ b/test/plugin/metar/PressureMonitorTest.cpp @@ -26,7 +26,7 @@ namespace UKControllerPluginTest { : gatwickTower(1, "EGKK_TWR", 124.22, {"EGKK"}, true, false), messager(mockPlugin), userSetting(mockUserSettingProvider), monitor(messager, activeCallsigns) { - this->activeCallsigns.AddUserCallsign(ActiveCallsign("EGKK_TWR", "Testy", this->gatwickTower)); + this->activeCallsigns.AddUserCallsign(ActiveCallsign("EGKK_TWR", "Testy", this->gatwickTower, true)); } NiceMock mockPlugin; NiceMock mockUserSettingProvider; diff --git a/test/plugin/missedapproach/CompareMissedApproachesTest.cpp b/test/plugin/missedapproach/CompareMissedApproachesTest.cpp new file mode 100644 index 000000000..73e891726 --- /dev/null +++ b/test/plugin/missedapproach/CompareMissedApproachesTest.cpp @@ -0,0 +1,53 @@ +#include "missedapproach/CompareMissedApproaches.h" +#include "missedapproach/MissedApproach.h" +#include "time/SystemClock.h" + +using UKControllerPlugin::MissedApproach::CompareMissedApproaches; +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::Time::TimeNow; + +namespace UKControllerPluginTest::MissedApproach { + class CompareMissedApproachesTest : public ::testing::Test + { + public: + CompareMissedApproachesTest() + { + missed1 = std::make_shared(3, "BAW123", TimeNow(), true); + missed2 = std::make_shared(4, "BAW456", TimeNow(), true); + } + + CompareMissedApproaches compare; + std::shared_ptr missed1; + std::shared_ptr missed2; + }; + + TEST_F(CompareMissedApproachesTest, LessThanIntReturnsTrueIfLessThan) + { + EXPECT_TRUE(compare(missed1, 4)); + } + + TEST_F(CompareMissedApproachesTest, LessThanIntReturnsFalseIfNotLessThan) + { + EXPECT_FALSE(compare(missed2, 3)); + } + + TEST_F(CompareMissedApproachesTest, LessThanMessageReturnsTrueIfLessThan) + { + EXPECT_TRUE(compare(3, missed2)); + } + + TEST_F(CompareMissedApproachesTest, LessThanMessageReturnsFalseIfNotLessThan) + { + EXPECT_FALSE(compare(4, missed1)); + } + + TEST_F(CompareMissedApproachesTest, LessThanCompareMessagesReturnsTrueIfLessThan) + { + EXPECT_TRUE(compare(missed1, missed2)); + } + + TEST_F(CompareMissedApproachesTest, LessThanCompareMessagesReturnsFalseIfNotLessThan) + { + EXPECT_FALSE(compare(missed2, missed1)); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/ConfigureMissedApproachesTest.cpp b/test/plugin/missedapproach/ConfigureMissedApproachesTest.cpp new file mode 100644 index 000000000..d0666aa6f --- /dev/null +++ b/test/plugin/missedapproach/ConfigureMissedApproachesTest.cpp @@ -0,0 +1,45 @@ +#include "dialog/DialogManager.h" +#include "missedapproach/ConfigureMissedApproaches.h" + +using testing::NiceMock; +using UKControllerPlugin::Dialog::DialogData; +using UKControllerPlugin::Dialog::DialogManager; +using UKControllerPlugin::MissedApproach::ConfigureMissedApproaches; +using UKControllerPlugin::Plugin::PopupMenuItem; +using UKControllerPluginTest::Dialog::MockDialogProvider; + +namespace UKControllerPluginTest::MissedApproach { + class ConfigureMissedApproachesTest : public testing::Test + { + public: + ConfigureMissedApproachesTest() : dialogManager(mockProvider), menuItem(nullptr, dialogManager, 5) + { + this->dialogManager.AddDialog(this->dialogData); + } + + DialogData dialogData = {IDD_MISSED_APPROACH, "", NULL, NULL, NULL}; + NiceMock mockProvider; + DialogManager dialogManager; + ConfigureMissedApproaches menuItem; + }; + + TEST_F(ConfigureMissedApproachesTest, ItReturnsMenuItem) + { + PopupMenuItem expected; + expected.firstValue = "Configure Missed Approaches"; + expected.secondValue = ""; + expected.callbackFunctionId = 5; + expected.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + expected.disabled = false; + expected.fixedPosition = false; + + EXPECT_EQ(expected, menuItem.GetConfigurationMenuItem()); + } + + TEST_F(ConfigureMissedApproachesTest, MenuItemConfigurationOpensDialog) + { + EXPECT_CALL(this->mockProvider, OpenDialog(this->dialogData, testing::_)).Times(1); + + this->menuItem.Configure(0, "", {}); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp b/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp new file mode 100644 index 000000000..be32de63d --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp @@ -0,0 +1,119 @@ +#include "controller/ActiveCallsign.h" +#include "controller/ControllerPosition.h" +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachAudioAlert.h" +#include "missedapproach/MissedApproachOptions.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" + +using testing::NiceMock; +using UKControllerPlugin::Controller::ActiveCallsign; +using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachAudioAlert; +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPluginTest::Euroscope::MockEuroScopeCFlightPlanInterface; +using UKControllerPluginTest::Euroscope::MockEuroscopePluginLoopbackInterface; +using UKControllerPluginTest::Euroscope::MockUserSettingProviderInterface; +using UKControllerPluginTest::Windows::MockWinApi; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachAudioAlertTest : public testing::Test + { + public: + MissedApproachAudioAlertTest() + : mockFlightplan(std::make_shared()), + options(std::make_shared()), + alert(options, mockPlugin, serviceProviders, mockWindows), + kkTwr(2, "EGKK_TWR", 199.999, {"EGKK"}, true, false), + userTowerCallsign(std::make_shared("EGKK_TWR", "Testy McTest", kkTwr, true)) + { + ON_CALL(mockPlugin, GetFlightplanForCallsign("BAW123")).WillByDefault(testing::Return(mockFlightplan)); + + ON_CALL(*mockFlightplan, GetDestination).WillByDefault(testing::Return("EGKK")); + + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Tower, userTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + this->options->SetServiceProvisions(ServiceType::Tower); + this->options->SetAudioAlertForCurrentUser(false); + this->options->SetAudioAlert(true); + } + + [[nodiscard]] static auto Create(bool isUserCreated) -> std::shared_ptr + { + return std::make_shared(1, "BAW123", std::chrono::system_clock::now(), isUserCreated); + } + + AirfieldServiceProviderCollection serviceProviders; + std::shared_ptr mockFlightplan; + NiceMock mockWindows; + NiceMock mockPlugin; + std::shared_ptr options; + MissedApproachAudioAlert alert; + ControllerPosition kkTwr; + std::shared_ptr userTowerCallsign; + }; + + TEST_F(MissedApproachAudioAlertTest, ItPlaysAlertIfNotCreatedByUser) + { + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(1); + + alert.Play(Create(false)); + } + + TEST_F(MissedApproachAudioAlertTest, ItPlaysAlertIfCreatedByUser) + { + this->options->SetAudioAlertForCurrentUser(true); + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(1); + + alert.Play(Create(true)); + } + + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfTurnedOff) + { + this->options->SetAudioAlert(false); + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(0); + + alert.Play(Create(false)); + } + + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfUserNotProvidingServices) + { + this->serviceProviders.Flush(); + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(0); + + alert.Play(Create(false)); + } + + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfUserNotProvidingCorrectServices) + { + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::FinalApproach, userTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(0); + + alert.Play(Create(false)); + } + + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfNoFlightplan) + { + ON_CALL(mockPlugin, GetFlightplanForCallsign("BAW123")).WillByDefault(testing::Return(nullptr)); + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(0); + + alert.Play(Create(false)); + } + + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfCreatedByUserAndUserDoesntWantAlerts) + { + this->options->SetAudioAlertForCurrentUser(false); + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(0); + + alert.Play(Create(true)); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachButtonTest.cpp b/test/plugin/missedapproach/MissedApproachButtonTest.cpp new file mode 100644 index 000000000..6d194aeca --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachButtonTest.cpp @@ -0,0 +1,263 @@ +#include "api/ApiException.h" +#include "controller/ControllerPosition.h" +#include "euroscope/UserSetting.h" +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachAudioAlert.h" +#include "missedapproach/MissedApproachButton.h" +#include "missedapproach/MissedApproachCollection.h" +#include "missedapproach/MissedApproachOptions.h" +#include "missedapproach/TriggerMissedApproach.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" +#include "time/ParseTimeStrings.h" +#include "time/SystemClock.h" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; +using UKControllerPlugin::Api::ApiException; +using UKControllerPlugin::Controller::ActiveCallsign; +using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::Euroscope::UserSetting; +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachAudioAlert; +using UKControllerPlugin::MissedApproach::MissedApproachButton; +using UKControllerPlugin::MissedApproach::MissedApproachCollection; +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::MissedApproach::TriggerMissedApproach; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPlugin::Time::ParseTimeString; +using UKControllerPlugin::Time::SetTestNow; +using UKControllerPlugin::Time::TimeNow; +using UKControllerPluginTest::Api::MockApiInterface; +using UKControllerPluginTest::Euroscope::MockEuroscopePluginLoopbackInterface; +using UKControllerPluginTest::Euroscope::MockUserSettingProviderInterface; +using UKControllerPluginTest::Windows::MockWinApi; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachButtonTest : public testing::Test + { + public: + MissedApproachButtonTest() + : settings(mockUserSettingProvider), + mockRadarTarget(std::make_shared>()), + mockFlightplan(std::make_shared>()), + kkTwr(2, "EGKK_TWR", 199.999, {"EGKK"}, true, false), + userTowerCallsign(std::make_shared("EGKK_TWR", "Testy McTest", kkTwr, true)), + collection(std::make_shared()), + options(std::make_shared()), + audioAlert(std::make_shared(options, plugin, serviceProviders, windows)), + trigger(std::make_shared(collection, windows, api, serviceProviders, audioAlert)), + button(collection, trigger, plugin, serviceProviders, 55) + { + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Tower, userTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + ON_CALL(*mockFlightplan, GetCallsign).WillByDefault(Return("BAW123")); + ON_CALL(*mockFlightplan, GetDestination).WillByDefault(Return("EGKK")); + + ON_CALL(*mockFlightplan, GetDistanceToDestination()).WillByDefault(Return(5.0)); + ON_CALL(*mockRadarTarget, GetFlightLevel()).WillByDefault(Return(3000)); + ON_CALL(*mockRadarTarget, GetGroundSpeed()).WillByDefault(Return(100)); + + ON_CALL(plugin, GetSelectedFlightplan).WillByDefault(Return(mockFlightplan)); + + ON_CALL(plugin, GetSelectedRadarTarget).WillByDefault(Return(mockRadarTarget)); + } + + NiceMock mockRadarScreen; + NiceMock mockUserSettingProvider; + UserSetting settings; + AirfieldServiceProviderCollection serviceProviders; + std::shared_ptr> mockRadarTarget; + std::shared_ptr> mockFlightplan; + ControllerPosition kkTwr; + std::shared_ptr userTowerCallsign; + NiceMock windows; + NiceMock api; + NiceMock plugin; + std::shared_ptr collection; + std::shared_ptr options; + std::shared_ptr audioAlert; + std::shared_ptr trigger; + MissedApproachButton button; + }; + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsDefaultVisibilityIfNoSetting) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonVisibility")).Times(1).WillOnce(Return("")); + + button.AsrLoadedEvent(settings); + EXPECT_FALSE(button.IsVisible()); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsDefaultVisibilityIfInvalid) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonVisibility")).Times(1).WillOnce(Return("abc")); + + button.AsrLoadedEvent(settings); + EXPECT_FALSE(button.IsVisible()); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsVisibility) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonVisibility")).Times(1).WillOnce(Return("1")); + + button.AsrLoadedEvent(settings); + EXPECT_TRUE(button.IsVisible()); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsDefaultXPositionsIfNoSetting) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonXPosition")).Times(1).WillOnce(Return("")); + + button.AsrLoadedEvent(settings); + + EXPECT_EQ(200, button.Position().left); + EXPECT_EQ(325, button.Position().right); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsDefaultXPositionsIfInvalid) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonXPosition")).Times(1).WillOnce(Return("abc")); + + button.AsrLoadedEvent(settings); + + EXPECT_EQ(200, button.Position().left); + EXPECT_EQ(325, button.Position().right); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsXPositions) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonXPosition")).Times(1).WillOnce(Return("50")); + + button.AsrLoadedEvent(settings); + + EXPECT_EQ(50, button.Position().left); + EXPECT_EQ(175, button.Position().right); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsDefaultYPositionsIfNoSetting) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonYPosition")).Times(1).WillOnce(Return("")); + + button.AsrLoadedEvent(settings); + + EXPECT_EQ(200, button.Position().top); + EXPECT_EQ(275, button.Position().bottom); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsDefaultYPositionsIfInvalid) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonYPosition")).Times(1).WillOnce(Return("abc")); + + button.AsrLoadedEvent(settings); + + EXPECT_EQ(200, button.Position().top); + EXPECT_EQ(275, button.Position().bottom); + } + + TEST_F(MissedApproachButtonTest, LoadingAsrSetsYPositions) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachButtonYPosition")).Times(1).WillOnce(Return("50")); + + button.AsrLoadedEvent(settings); + + EXPECT_EQ(50, button.Position().top); + EXPECT_EQ(125, button.Position().bottom); + } + + TEST_F(MissedApproachButtonTest, SavingAsrSavesEverything) + { + EXPECT_CALL( + mockUserSettingProvider, + SetKey( + "missedApproachButtonVisibility", + "Missed Approach Button " + "Visibility", + "0")) + .Times(1); + + EXPECT_CALL( + mockUserSettingProvider, + SetKey("missedApproachButtonXPosition", "Missed Approach Button X Position", "200")) + .Times(1); + + EXPECT_CALL( + mockUserSettingProvider, + SetKey("missedApproachButtonYPosition", "Missed Approach Button Y Position", "200")) + .Times(1); + + button.AsrClosingEvent(settings); + } + + TEST_F(MissedApproachButtonTest, ItMovesThings) + { + button.Move({100, 300, 1, 2}, ""); + + EXPECT_EQ(100, button.Position().left); + EXPECT_EQ(300, button.Position().top); + EXPECT_EQ(225, button.Position().right); + EXPECT_EQ(375, button.Position().bottom); + } + + TEST_F(MissedApproachButtonTest, ItResetsPosition) + { + button.Move({100, 300, 1, 2}, ""); + button.ResetPosition(); + + EXPECT_EQ(200, button.Position().left); + EXPECT_EQ(200, button.Position().top); + EXPECT_EQ(325, button.Position().right); + EXPECT_EQ(275, button.Position().bottom); + } + + TEST_F(MissedApproachButtonTest, LeftClickTriggersMissedApproach) + { + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDNO)); + + button.LeftClick(mockRadarScreen, 1, "", POINT{}, RECT{}); + } + + TEST_F(MissedApproachButtonTest, LeftClickDoesntTriggerApproachIfNoFlightplanFound) + { + ON_CALL(this->plugin, GetSelectedFlightplan).WillByDefault(Return(nullptr)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + button.LeftClick(mockRadarScreen, 1, "", POINT{}, RECT{}); + } + + TEST_F(MissedApproachButtonTest, LeftClickDoesntTriggerApproachIfNoRadarTargetFound) + { + ON_CALL(this->plugin, GetSelectedRadarTarget).WillByDefault(Return(nullptr)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + button.LeftClick(mockRadarScreen, 1, "", POINT{}, RECT{}); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachCollectionTest.cpp b/test/plugin/missedapproach/MissedApproachCollectionTest.cpp new file mode 100644 index 000000000..b33a47591 --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachCollectionTest.cpp @@ -0,0 +1,143 @@ +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachCollection.h" + +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachCollection; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachCollectionTest : public testing::Test + { + public: + MissedApproachCollectionTest() + : missed1(std::make_shared(1, "BAW123", std::chrono::system_clock::now(), true)), + missed2(std::make_shared(2, "BAW456", std::chrono::system_clock::now(), true)) + { + } + std::shared_ptr missed1; + std::shared_ptr missed2; + MissedApproachCollection collection; + }; + + TEST_F(MissedApproachCollectionTest, ItStartsEmpty) + { + EXPECT_EQ(0, collection.Count()); + } + + TEST_F(MissedApproachCollectionTest, ItAddsMissed) + { + collection.Add(missed1); + collection.Add(missed2); + EXPECT_EQ(2, collection.Count()); + EXPECT_EQ(missed1, collection.Get("BAW123")); + EXPECT_EQ(missed2, collection.Get("BAW456")); + } + + TEST_F(MissedApproachCollectionTest, ItDoesntAddDuplicateMissed) + { + collection.Add(missed1); + collection.Add(missed1); + collection.Add(missed1); + collection.Add(missed2); + collection.Add(missed2); + collection.Add(missed2); + EXPECT_EQ(2, collection.Count()); + } + + TEST_F(MissedApproachCollectionTest, ItReturnsApproachByCallsign) + { + collection.Add(missed1); + EXPECT_EQ(missed1, collection.Get("BAW123")); + } + + TEST_F(MissedApproachCollectionTest, ItReturnsNullptrOnNonExistentApproachByCallsign) + { + collection.Add(missed1); + collection.Add(missed2); + EXPECT_EQ(nullptr, collection.Get("BAW999")); + } + + TEST_F(MissedApproachCollectionTest, ItReturnsApproachById) + { + collection.Add(missed1); + EXPECT_EQ(missed1, collection.Get(1)); + } + + TEST_F(MissedApproachCollectionTest, ItReturnsNullptrOnNonExistentApproachById) + { + collection.Add(missed1); + collection.Add(missed2); + EXPECT_EQ(nullptr, collection.Get(999)); + } + + TEST_F(MissedApproachCollectionTest, ItHandlesNoRemovalsNothingToRemove) + { + EXPECT_NO_THROW( + collection.RemoveWhere([](const std::shared_ptr&) -> bool { return false; })); + } + + TEST_F(MissedApproachCollectionTest, ItHandlesNoRemovals) + { + collection.Add(missed1); + collection.Add(missed2); + EXPECT_NO_THROW( + collection.RemoveWhere([](const std::shared_ptr&) -> bool { return false; })); + } + + TEST_F(MissedApproachCollectionTest, ItRemovesBasedOnPredicate) + { + collection.Add(missed1); + collection.Add(missed2); + collection.RemoveWhere( + [](const std::shared_ptr& app) -> bool { return app->Callsign() == "BAW123"; }); + EXPECT_EQ(1, collection.Count()); + EXPECT_EQ(nullptr, collection.Get("BAW123")); + EXPECT_EQ(missed2, collection.Get("BAW456")); + } + + TEST_F(MissedApproachCollectionTest, FirstWhereReturnsItem) + { + collection.Add(missed1); + collection.Add(missed2); + auto result = collection.FirstWhere( + [](const std::shared_ptr& app) -> bool { return app->Callsign() == "BAW123"; }); + EXPECT_EQ(missed1, result); + } + + TEST_F(MissedApproachCollectionTest, FirstWhereReturnsNullptrIfNotFound) + { + collection.Add(missed1); + collection.Add(missed2); + auto result = collection.FirstWhere( + [](const std::shared_ptr& app) -> bool { return app->Callsign() == "BAW999"; }); + EXPECT_EQ(nullptr, result); + } + + TEST_F(MissedApproachCollectionTest, ItRemovesBasedOnValue) + { + collection.Add(missed1); + collection.Add(missed2); + collection.Remove(missed1); + EXPECT_EQ(1, collection.Count()); + EXPECT_EQ(nullptr, collection.Get("BAW123")); + EXPECT_EQ(missed2, collection.Get("BAW456")); + } + + TEST_F(MissedApproachCollectionTest, ItHandlesRemoveWhenNoValue) + { + collection.Remove(missed1); + EXPECT_NO_THROW(collection.Remove(missed1)); + } + + TEST_F(MissedApproachCollectionTest, ItIteratesTheCollection) + { + collection.Add(missed1); + collection.Add(missed2); + + std::vector expected({"BAW123", "BAW456"}); + std::vector actual; + collection.ForEach( + [&actual](const std::shared_ptr& missed) { actual.push_back(missed->Callsign()); }); + + EXPECT_EQ(expected, actual); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachModuleTest.cpp b/test/plugin/missedapproach/MissedApproachModuleTest.cpp new file mode 100644 index 000000000..f1e1544ef --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachModuleTest.cpp @@ -0,0 +1,112 @@ +#include "airfield/AirfieldCollection.h" +#include "bootstrap/PersistenceContainer.h" +#include "dialog/DialogManager.h" +#include "euroscope/UserSettingAwareCollection.h" +#include "euroscope/AsrEventHandlerCollection.h" +#include "missedapproach/MissedApproachModule.h" +#include "plugin/FunctionCallEventHandler.h" +#include "push/PushEventProcessorCollection.h" +#include "timedevent/TimedEventCollection.h" +#include "radarscreen/ConfigurableDisplayCollection.h" +#include "radarscreen/RadarRenderableCollection.h" + +using UKControllerPlugin::Airfield::AirfieldCollection; +using UKControllerPlugin::Bootstrap::PersistenceContainer; +using UKControllerPlugin::Dialog::DialogManager; +using UKControllerPlugin::Euroscope::AsrEventHandlerCollection; +using UKControllerPlugin::Euroscope::UserSettingAwareCollection; +using UKControllerPlugin::MissedApproach::BootstrapPlugin; +using UKControllerPlugin::MissedApproach::BootstrapRadarScreen; +using UKControllerPlugin::Plugin::FunctionCallEventHandler; +using UKControllerPlugin::Push::PushEventProcessorCollection; +using UKControllerPlugin::RadarScreen::ConfigurableDisplayCollection; +using UKControllerPlugin::RadarScreen::RadarRenderableCollection; +using UKControllerPlugin::TimedEvent::TimedEventCollection; +using UKControllerPluginTest::Dialog::MockDialogProvider; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachModuleTest : public testing::Test + { + public: + MissedApproachModuleTest() + { + container.timedHandler = std::make_unique(); + container.pushEventProcessors = std::make_shared(); + container.pluginFunctionHandlers = std::make_unique(); + container.userSettingHandlers = std::make_unique(); + container.dialogManager = std::make_unique(mockProvider); + container.airfields = std::make_unique(); + } + + testing::NiceMock mockProvider; + AsrEventHandlerCollection asrHandlers; + ConfigurableDisplayCollection configurableDisplays; + RadarRenderableCollection renderers; + PersistenceContainer container; + }; + + TEST_F(MissedApproachModuleTest, ItRegistersTheExpiredApproachRemover) + { + BootstrapPlugin(container); + EXPECT_EQ(1, container.timedHandler->CountHandlers()); + EXPECT_EQ(1, container.timedHandler->CountHandlersForFrequency(30)); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheNewMissedApproachPushEvent) + { + BootstrapPlugin(container); + EXPECT_EQ(1, container.pushEventProcessors->CountProcessorsForEvent("missed-approach.created")); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheTriggerMissedApproachTagFunction) + { + BootstrapPlugin(container); + EXPECT_TRUE(container.pluginFunctionHandlers->HasTagFunction(9020)); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheOptionsForUserSettingEvents) + { + BootstrapPlugin(container); + EXPECT_EQ(1, container.userSettingHandlers->Count()); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheConfigurationDialog) + { + BootstrapPlugin(container); + EXPECT_EQ(1, container.dialogManager->CountDialogs()); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheRenderers) + { + BootstrapRadarScreen(container, renderers, configurableDisplays, asrHandlers); + EXPECT_EQ(2, renderers.CountRenderers()); + EXPECT_EQ(1, renderers.CountRenderersInPhase(RadarRenderableCollection::afterTags)); + EXPECT_EQ(1, renderers.CountRenderersInPhase(RadarRenderableCollection::afterLists)); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheScreenObjects) + { + BootstrapRadarScreen(container, renderers, configurableDisplays, asrHandlers); + EXPECT_EQ(1, renderers.CountScreenObjects()); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheRenderersForAsrEvents) + { + BootstrapRadarScreen(container, renderers, configurableDisplays, asrHandlers); + EXPECT_EQ(2, asrHandlers.CountHandlers()); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheToggleButtonConfigurable) + { + BootstrapRadarScreen(container, renderers, configurableDisplays, asrHandlers); + EXPECT_EQ(2, configurableDisplays.CountDisplays()); + EXPECT_TRUE(container.pluginFunctionHandlers->HasCallbackByDescription("Toggle Missed Approach Button")); + } + + TEST_F(MissedApproachModuleTest, ItRegistersTheConfigurationConfigurable) + { + BootstrapRadarScreen(container, renderers, configurableDisplays, asrHandlers); + EXPECT_EQ(2, configurableDisplays.CountDisplays()); + EXPECT_TRUE(container.pluginFunctionHandlers->HasCallbackByDescription("Configure Missed Approaches")); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachOptionsTest.cpp b/test/plugin/missedapproach/MissedApproachOptionsTest.cpp new file mode 100644 index 000000000..095125527 --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachOptionsTest.cpp @@ -0,0 +1,57 @@ +#include "missedapproach/MissedApproachOptions.h" + +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::Ownership::ServiceType; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachOptionsTest : public testing::Test + { + public: + MissedApproachOptions options; + }; + + TEST_F(MissedApproachOptionsTest, ItHasDefaultAudioAlert) + { + EXPECT_FALSE(options.AudioAlert()); + } + + TEST_F(MissedApproachOptionsTest, ItHasDefaultAudioAlertCurrentUser) + { + EXPECT_FALSE(options.AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachOptionsTest, ItHasDefaultAudioAlertServiceProvisions) + { + EXPECT_EQ(ServiceType::Invalid, options.ServiceProvisions()); + } + + TEST_F(MissedApproachOptionsTest, ItHasDefaultAirfields) + { + EXPECT_TRUE(options.Airfields().empty()); + } + + TEST_F(MissedApproachOptionsTest, ItSetsAudioAlert) + { + options.SetAudioAlert(true); + EXPECT_TRUE(options.AudioAlert()); + } + + TEST_F(MissedApproachOptionsTest, ItSetsAudioAlertCurrentUser) + { + options.SetAudioAlertForCurrentUser(true); + EXPECT_TRUE(options.AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachOptionsTest, ItSetsAudioAlertServiceProvisions) + { + options.SetServiceProvisions(ServiceType::FinalApproach); + EXPECT_EQ(ServiceType::FinalApproach, options.ServiceProvisions()); + } + + TEST_F(MissedApproachOptionsTest, ItSetsAirfields) + { + std::vector expected({"EGGD", "EGFF"}); + options.SetAirfields(expected); + EXPECT_EQ(expected, options.Airfields()); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachRenderOptionsTest.cpp b/test/plugin/missedapproach/MissedApproachRenderOptionsTest.cpp new file mode 100644 index 000000000..a5d60e73a --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachRenderOptionsTest.cpp @@ -0,0 +1,112 @@ +#include "euroscope/UserSetting.h" +#include "missedapproach/MissedApproachRenderOptions.h" +#include "ownership/ServiceType.h" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; + +using UKControllerPlugin::Euroscope::UserSetting; +using UKControllerPlugin::MissedApproach::MissedApproachRenderMode; +using UKControllerPlugin::MissedApproach::MissedApproachRenderOptions; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPluginTest::Euroscope::MockUserSettingProviderInterface; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachRenderOptionsTest : public testing::Test + { + public: + MissedApproachRenderOptionsTest() : settings(mockUserSettingProvider) + { + } + NiceMock mockUserSettingProvider; + UserSetting settings; + MissedApproachRenderOptions options; + }; + + TEST_F(MissedApproachRenderOptionsTest, ItHasADefaultMode) + { + EXPECT_EQ(MissedApproachRenderMode::None, options.Mode()); + } + + TEST_F(MissedApproachRenderOptionsTest, ItHasADefaultDuration) + { + EXPECT_EQ(std::chrono::seconds(0), options.Duration()); + } + + TEST_F(MissedApproachRenderOptionsTest, LoadingAsrSetsDefaultRenderModeIfNoSetting) + { + options.SetMode(MissedApproachRenderMode::Line); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachRenderMode")).Times(1).WillOnce(Return("")); + + options.AsrLoadedEvent(settings); + EXPECT_EQ(MissedApproachRenderMode::None, options.Mode()); + } + + TEST_F(MissedApproachRenderOptionsTest, LoadingAsrSetsDefaultRenderModeIfInvalid) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachRenderMode")).Times(1).WillOnce(Return("66")); + + options.AsrLoadedEvent(settings); + EXPECT_EQ(MissedApproachRenderMode::None, options.Mode()); + } + + TEST_F(MissedApproachRenderOptionsTest, LoadingAsrSetsRenderMode) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachRenderMode")).Times(1).WillOnce(Return("2")); + + options.AsrLoadedEvent(settings); + EXPECT_EQ(MissedApproachRenderMode::Circle, options.Mode()); + } + + TEST_F(MissedApproachRenderOptionsTest, LoadingAsrSetsDefaultDurationIfNoSetting) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachRenderDuration")).Times(1).WillOnce(Return("")); + + options.AsrLoadedEvent(settings); + EXPECT_EQ(std::chrono::seconds(5), options.Duration()); + } + + TEST_F(MissedApproachRenderOptionsTest, LoadingAsrSetsDefaultDurationIfInvalid) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachRenderDuration")).Times(1).WillOnce(Return("abc")); + + options.AsrLoadedEvent(settings); + EXPECT_EQ(std::chrono::seconds(5), options.Duration()); + } + + TEST_F(MissedApproachRenderOptionsTest, LoadingAsrSetsDuration) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachRenderDuration")).Times(1).WillOnce(Return("10")); + + options.AsrLoadedEvent(settings); + EXPECT_EQ(std::chrono::seconds(10), options.Duration()); + } + + TEST_F(MissedApproachRenderOptionsTest, SavingAsrSavesEverything) + { + options.SetMode(MissedApproachRenderMode::Line); + options.SetDuration(std::chrono::seconds(15)); + + EXPECT_CALL(mockUserSettingProvider, SetKey("missedApproachRenderMode", "Missed Approach Render Mode", "1")) + .Times(1); + + EXPECT_CALL( + mockUserSettingProvider, SetKey("missedApproachRenderDuration", "Missed Approach Render Duration", "15")) + .Times(1); + + options.AsrClosingEvent(settings); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachRendererTest.cpp b/test/plugin/missedapproach/MissedApproachRendererTest.cpp new file mode 100644 index 000000000..affe7acdb --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachRendererTest.cpp @@ -0,0 +1,53 @@ +#include "missedapproach/MissedApproachCollection.h" +#include "missedapproach/MissedApproachRenderer.h" +#include "missedapproach/MissedApproachRenderOptions.h" +#include "ownership/AirfieldServiceProviderCollection.h" + +using UKControllerPlugin::MissedApproach::MissedApproachCollection; +using UKControllerPlugin::MissedApproach::MissedApproachRenderer; +using UKControllerPlugin::MissedApproach::MissedApproachRenderMode; +using UKControllerPlugin::MissedApproach::MissedApproachRenderOptions; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachRendererTest : public testing::Test + { + public: + MissedApproachRendererTest() + : missedApproaches(std::make_shared()), + options(std::make_shared()), + renderer(missedApproaches, serviceProviders, mockPlugin, options, nullptr) + { + } + + AirfieldServiceProviderCollection serviceProviders; + testing::NiceMock mockPlugin; + std::shared_ptr missedApproaches; + std::shared_ptr options; + MissedApproachRenderer renderer; + }; + + TEST_F(MissedApproachRendererTest, ItRendersIfModeSetToCircle) + { + options->SetMode(MissedApproachRenderMode::Circle); + EXPECT_TRUE(renderer.IsVisible()); + } + + TEST_F(MissedApproachRendererTest, ItRendersIfModeSetToLine) + { + options->SetMode(MissedApproachRenderMode::Line); + EXPECT_TRUE(renderer.IsVisible()); + } + + TEST_F(MissedApproachRendererTest, ItRendersIfModeSetToCircleAndLine) + { + options->SetMode(MissedApproachRenderMode::Circle | MissedApproachRenderMode::Line); + EXPECT_TRUE(renderer.IsVisible()); + } + + TEST_F(MissedApproachRendererTest, ItDoesntRenderIfModeSetToNone) + { + options->SetMode(MissedApproachRenderMode::None); + EXPECT_FALSE(renderer.IsVisible()); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachTest.cpp b/test/plugin/missedapproach/MissedApproachTest.cpp new file mode 100644 index 000000000..0d54972fb --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachTest.cpp @@ -0,0 +1,61 @@ +#include "missedapproach/MissedApproach.h" +#include "time/SystemClock.h" + +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::Time::SetTestNow; +using UKControllerPlugin::Time::TimeNow; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachTest : public testing::Test + { + public: + MissedApproachTest() : expiresAt(GetExpiresAt()), missed(1, "BAW123", expiresAt, true) + { + } + + [[nodiscard]] static auto GetExpiresAt() -> std::chrono::system_clock::time_point + { + SetTestNow(std::chrono::system_clock::now()); + return TimeNow() + std::chrono::minutes(5); + } + + std::chrono::system_clock::time_point expiresAt; + class MissedApproach missed; + }; + + TEST_F(MissedApproachTest, ItHasAnId) + { + EXPECT_EQ(1, missed.Id()); + } + + TEST_F(MissedApproachTest, ItHasACallsign) + { + EXPECT_EQ("BAW123", missed.Callsign()); + } + + TEST_F(MissedApproachTest, ItHasAnExpiryTime) + { + EXPECT_EQ(expiresAt, missed.ExpiresAt()); + } + + TEST_F(MissedApproachTest, ItsNotExpiredIfExpiryTimeHasntPassed) + { + EXPECT_FALSE(missed.IsExpired()); + } + + TEST_F(MissedApproachTest, ItsExpiredIfExpiryTimeHasPassed) + { + class MissedApproach missed2(2, "BAW123", std::chrono::system_clock::now() - std::chrono::seconds(5), false); + EXPECT_TRUE(missed2.IsExpired()); + } + + TEST_F(MissedApproachTest, ItHasACreatedAtTimeWhichIsSetToCurrentTime) + { + EXPECT_EQ(TimeNow(), missed.CreatedAt()); + } + + TEST_F(MissedApproachTest, ItHasACreatedByUser) + { + EXPECT_TRUE(missed.CreatedByUser()); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/MissedApproachUserSettingHandlerTest.cpp b/test/plugin/missedapproach/MissedApproachUserSettingHandlerTest.cpp new file mode 100644 index 000000000..049bd0f55 --- /dev/null +++ b/test/plugin/missedapproach/MissedApproachUserSettingHandlerTest.cpp @@ -0,0 +1,203 @@ +#include "euroscope/UserSettingAwareCollection.h" +#include "missedapproach/MissedApproachOptions.h" +#include "missedapproach/MissedApproachUserSettingHandler.h" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; + +using UKControllerPlugin::Euroscope::UserSetting; +using UKControllerPlugin::Euroscope::UserSettingAwareCollection; +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::MissedApproach::MissedApproachUserSettingHandler; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPluginTest::Euroscope::MockUserSettingProviderInterface; + +namespace UKControllerPluginTest::MissedApproach { + class MissedApproachUserSettingHandlerTest : public testing::Test + { + public: + MissedApproachUserSettingHandlerTest() + : mockUserSettingAware(std::make_shared>()), + settings(mockUserSettingProvider), options(std::make_shared()), + handler(options, collection, settings) + { + collection.RegisterHandler(mockUserSettingAware); + } + + UserSettingAwareCollection collection; + std::shared_ptr> mockUserSettingAware; + NiceMock mockUserSettingProvider; + UserSetting settings; + std::shared_ptr options; + MissedApproachUserSettingHandler handler; + }; + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingsSetsDefaultServiceProvisionsIfNoSetting) + { + options->SetServiceProvisions(ServiceType::FinalApproach); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachServiceProvision")).Times(1).WillOnce(Return("")); + + handler.UserSettingsUpdated(settings); + EXPECT_EQ(ServiceType::Invalid, options->ServiceProvisions()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingSetsDefaultServiceProvisionsIfInvalid) + { + options->SetServiceProvisions(ServiceType::FinalApproach); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachServiceProvision")).Times(1).WillOnce(Return("66")); + + handler.UserSettingsUpdated(settings); + EXPECT_EQ(ServiceType::Invalid, options->ServiceProvisions()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingSetsServiceProvisions) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachServiceProvision")).Times(1).WillOnce(Return("8")); + + handler.UserSettingsUpdated(settings); + EXPECT_EQ(ServiceType::FinalApproach, options->ServiceProvisions()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingsSetsDefaultCurrentUserIfNoSetting) + { + options->SetAudioAlertForCurrentUser(true); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAudioCurrentUser")).Times(1).WillOnce(Return("")); + + handler.UserSettingsUpdated(settings); + EXPECT_FALSE(options->AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingSetsDefaultCurrentUsersIfInvalid) + { + options->SetAudioAlertForCurrentUser(true); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAudioCurrentUser")).Times(1).WillOnce(Return("66")); + + handler.UserSettingsUpdated(settings); + EXPECT_FALSE(options->AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingSetsCurrentUser) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAudioCurrentUser")).Times(1).WillOnce(Return("1")); + + handler.UserSettingsUpdated(settings); + EXPECT_TRUE(options->AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingsSetsDefaultAudioAlertIfNoSetting) + { + options->SetAudioAlertForCurrentUser(true); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAudio")).Times(1).WillOnce(Return("")); + + handler.UserSettingsUpdated(settings); + EXPECT_FALSE(options->AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingSetsDefaultAudioAlertIfInvalid) + { + options->SetAudioAlertForCurrentUser(true); + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAudio")).Times(1).WillOnce(Return("66")); + + handler.UserSettingsUpdated(settings); + EXPECT_FALSE(options->AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingSettingSetsAudioAlert) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAudio")).Times(1).WillOnce(Return("1")); + + handler.UserSettingsUpdated(settings); + EXPECT_TRUE(options->AudioAlert()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingAsrSetsDefaulAirfieldsIfNoSetting) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAirfields")).Times(1).WillOnce(Return("")); + + handler.UserSettingsUpdated(settings); + EXPECT_EQ(std::vector{}, options->Airfields()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, LoadingAsrSetsAirfields) + { + EXPECT_CALL(mockUserSettingProvider, GetKey(_)).WillRepeatedly(Return("")); + + EXPECT_CALL(mockUserSettingProvider, GetKey("missedApproachAirfields")).Times(1).WillOnce(Return("EGGD;EGLL")); + + handler.UserSettingsUpdated(settings); + EXPECT_EQ(std::vector({"EGGD", "EGLL"}), options->Airfields()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, ItSetsAudioAlert) + { + EXPECT_CALL(mockUserSettingProvider, SetKey("missedApproachAudio", "Play Missed Approach Alarm", "1")).Times(1); + + EXPECT_CALL(*mockUserSettingAware, UserSettingsUpdated(testing::_)).Times(1); + + handler.SetAudioAlert(true); + + EXPECT_TRUE(options->AudioAlert()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, ItSetsAudioAlertCurrentUser) + { + EXPECT_CALL( + mockUserSettingProvider, + SetKey("missedApproachAudioCurrentUser", "Play Missed Approach Alarm If User Initiated", "1")) + .Times(1); + + EXPECT_CALL(*mockUserSettingAware, UserSettingsUpdated(testing::_)).Times(1); + + handler.SetAudioAlertForCurrentUser(true); + + EXPECT_TRUE(options->AudioAlertForCurrentUser()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, ItSetsAudioServiceProvisions) + { + EXPECT_CALL( + mockUserSettingProvider, + SetKey("missedApproachServiceProvision", "Missed Approach Alerts Service Provision", "8")) + .Times(1); + + EXPECT_CALL(*mockUserSettingAware, UserSettingsUpdated(testing::_)).Times(1); + + handler.SetServiceProvisions(ServiceType::FinalApproach); + + EXPECT_EQ(ServiceType::FinalApproach, options->ServiceProvisions()); + } + + TEST_F(MissedApproachUserSettingHandlerTest, ItSetsAirfields) + { + EXPECT_CALL( + mockUserSettingProvider, SetKey("missedApproachAirfields", "Missed Approach Alert Airfields", "EGGD;EGLL")) + .Times(1); + + EXPECT_CALL(*mockUserSettingAware, UserSettingsUpdated(testing::_)).Times(1); + + handler.SetAirfields(std::vector({"EGGD", "EGLL"})); + + EXPECT_EQ(std::vector({"EGGD", "EGLL"}), options->Airfields()); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/NewMissedApproachPushEventHandlerTest.cpp b/test/plugin/missedapproach/NewMissedApproachPushEventHandlerTest.cpp new file mode 100644 index 000000000..6861c6dc2 --- /dev/null +++ b/test/plugin/missedapproach/NewMissedApproachPushEventHandlerTest.cpp @@ -0,0 +1,153 @@ +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachAudioAlert.h" +#include "missedapproach/MissedApproachCollection.h" +#include "missedapproach/MissedApproachOptions.h" +#include "missedapproach/NewMissedApproachPushEventHandler.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "time/ParseTimeStrings.h" +#include "time/SystemClock.h" + +using testing::NiceMock; +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachAudioAlert; +using UKControllerPlugin::MissedApproach::MissedApproachCollection; +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::MissedApproach::NewMissedApproachPushEventHandler; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Push::PushEvent; +using UKControllerPlugin::Push::PushEventSubscription; +using UKControllerPlugin::Time::ParseTimeString; +using UKControllerPlugin::Time::SetTestNow; +using UKControllerPlugin::Time::TimeNow; +using UKControllerPluginTest::Euroscope::MockEuroscopePluginLoopbackInterface; +using UKControllerPluginTest::Windows::MockWinApi; + +namespace UKControllerPluginTest::MissedApproach { + class NewMissedApproachPushEventHandlerTest : public testing::Test + { + public: + NewMissedApproachPushEventHandlerTest() + : missed1(std::make_shared(1, "BAW123", std::chrono::system_clock::now(), true)), + missed2(std::make_shared(2, "BAW456", std::chrono::system_clock::now(), true)), + collection(std::make_shared()), + options(std::make_shared()), + audioAlert(std::make_shared(options, plugin, ownership, windows)), + handler(collection, audioAlert) + { + SetTestNow(ParseTimeString("2021-08-23 13:55:00")); + } + + /* + * Make an event based on the merge of some base data and overriding data so we dont + * have to repeat ourselves + */ + static PushEvent MakePushEvent( + const nlohmann::json& overridingData = nlohmann::json::object(), const std::string& keyToRemove = "") + { + nlohmann::json eventData{{"id", 1}, {"callsign", "BAW123"}, {"expires_at", "2021-08-23 14:00:00"}}; + if (overridingData.is_object()) { + eventData.update(overridingData); + } else { + eventData = overridingData; + } + + if (!keyToRemove.empty()) { + eventData.erase(eventData.find(keyToRemove)); + } + + return {"missed-approach.created", "test", eventData, eventData.dump()}; + }; + + AirfieldServiceProviderCollection ownership; + NiceMock plugin; + NiceMock windows; + std::shared_ptr missed1; + std::shared_ptr missed2; + std::shared_ptr collection; + std::shared_ptr options; + std::shared_ptr audioAlert; + NewMissedApproachPushEventHandler handler; + }; + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHasPushEvents) + { + std::set expected = {{PushEventSubscription::SUB_TYPE_EVENT, "missed-approach.created"}}; + EXPECT_EQ(expected, handler.GetPushEventSubscriptions()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItAddsAMissedApproach) + { + handler.ProcessPushEvent(MakePushEvent()); + EXPECT_EQ(1, collection->Count()); + + auto message = collection->Get("BAW123"); + EXPECT_NE(nullptr, message); + EXPECT_EQ(1, message->Id()); + EXPECT_EQ("BAW123", message->Callsign()); + EXPECT_EQ(ParseTimeString("2021-08-23 14:00:00"), message->ExpiresAt()); + EXPECT_FALSE(message->CreatedByUser()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItDoesntOverwriteExistingMissedApproach) + { + collection->Add( + std::make_shared(1, "BAW123", ParseTimeString("2021-08-23 14:05:00"), true)); + + handler.ProcessPushEvent(MakePushEvent()); + EXPECT_EQ(1, collection->Count()); + + auto message = collection->Get("BAW123"); + EXPECT_NE(nullptr, message); + EXPECT_EQ("BAW123", message->Callsign()); + EXPECT_EQ(ParseTimeString("2021-08-23 14:05:00"), message->ExpiresAt()); + EXPECT_TRUE(message->CreatedByUser()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesMessageNotObject) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json::array())); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesMissingIdInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json::object(), "id")); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesIdNotIntegerInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json{{"id", "123"}})); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesMissingCallsignInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json::object(), "callsign")); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesCallsignNotStringInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json{{"callsign", 123}})); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesExpiresAtMissingInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json::object(), "expires_at")); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesExpiresAtNotStringInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json{{"expires_at", 123}})); + EXPECT_EQ(0, collection->Count()); + } + + TEST_F(NewMissedApproachPushEventHandlerTest, ItHandlesExpiresAtNotValidTimestampInMessage) + { + handler.ProcessPushEvent(MakePushEvent(nlohmann::json{{"expires_at", "abc"}})); + EXPECT_EQ(0, collection->Count()); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/RemoveExpiredMissedApproachesTest.cpp b/test/plugin/missedapproach/RemoveExpiredMissedApproachesTest.cpp new file mode 100644 index 000000000..9c07675b8 --- /dev/null +++ b/test/plugin/missedapproach/RemoveExpiredMissedApproachesTest.cpp @@ -0,0 +1,42 @@ +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachCollection.h" +#include "missedapproach/RemoveExpiredMissedApproaches.h" +#include "time/SystemClock.h" + +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachCollection; +using UKControllerPlugin::MissedApproach::RemoveExpiredMissedApproaches; +using UKControllerPlugin::Time::SetTestNow; +using UKControllerPlugin::Time::TimeNow; + +namespace UKControllerPluginTest::MissedApproach { + class RemoveExpiredMissedApproachesTest : public testing::Test + { + public: + RemoveExpiredMissedApproachesTest() + : collection(std::make_shared()), remove(collection) + { + SetTestNow(std::chrono::system_clock::now()); + } + + std::shared_ptr collection; + RemoveExpiredMissedApproaches remove; + }; + + TEST_F(RemoveExpiredMissedApproachesTest, ItRemovesNothing) + { + EXPECT_NO_THROW(remove.TimedEventTrigger()); + } + + TEST_F(RemoveExpiredMissedApproachesTest, ItRemovesExpiredApproaches) + { + collection->Add(std::make_shared(1, "BAW123", TimeNow() + std::chrono::seconds(5), true)); + collection->Add(std::make_shared(2, "BAW456", TimeNow() - std::chrono::seconds(1), true)); + collection->Add(std::make_shared(3, "BAW789", TimeNow(), true)); + + remove.TimedEventTrigger(); + EXPECT_EQ(nullptr, collection->Get("BAW456")); + EXPECT_NE(nullptr, collection->Get("BAW123")); + EXPECT_NE(nullptr, collection->Get("BAW789")); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/ToggleMissedApproachButtonTest.cpp b/test/plugin/missedapproach/ToggleMissedApproachButtonTest.cpp new file mode 100644 index 000000000..e70f89df7 --- /dev/null +++ b/test/plugin/missedapproach/ToggleMissedApproachButtonTest.cpp @@ -0,0 +1,128 @@ +#include "api/ApiException.h" +#include "controller/ControllerPosition.h" +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachAudioAlert.h" +#include "missedapproach/MissedApproachButton.h" +#include "missedapproach/MissedApproachCollection.h" +#include "missedapproach/MissedApproachOptions.h" +#include "missedapproach/ToggleMissedApproachButton.h" +#include "missedapproach/TriggerMissedApproach.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" +#include "time/ParseTimeStrings.h" +#include "time/SystemClock.h" + +using ::testing::_; +using ::testing::NiceMock; +using ::testing::Return; +using UKControllerPlugin::Api::ApiException; +using UKControllerPlugin::Controller::ActiveCallsign; +using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::Euroscope::UserSetting; +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachAudioAlert; +using UKControllerPlugin::MissedApproach::MissedApproachButton; +using UKControllerPlugin::MissedApproach::MissedApproachCollection; +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::MissedApproach::ToggleMissedApproachButton; +using UKControllerPlugin::MissedApproach::TriggerMissedApproach; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPlugin::Plugin::PopupMenuItem; +using UKControllerPlugin::Time::ParseTimeString; +using UKControllerPlugin::Time::SetTestNow; +using UKControllerPlugin::Time::TimeNow; +using UKControllerPluginTest::Api::MockApiInterface; +using UKControllerPluginTest::Euroscope::MockEuroscopePluginLoopbackInterface; +using UKControllerPluginTest::Euroscope::MockUserSettingProviderInterface; +using UKControllerPluginTest::Windows::MockWinApi; + +namespace UKControllerPluginTest::MissedApproach { + class ToggleMissedApproachButtonTest : public testing::Test + { + public: + ToggleMissedApproachButtonTest() + : settings(mockUserSettingProvider), + mockRadarTarget(std::make_shared>()), + mockFlightplan(std::make_shared>()), + kkTwr(2, "EGKK_TWR", 199.999, {"EGKK"}, true, false), + userTowerCallsign(std::make_shared("EGKK_TWR", "Testy McTest", kkTwr, true)), + collection(std::make_shared()), + options(std::make_shared()), + audioAlert(std::make_shared(options, plugin, serviceProviders, windows)), + trigger(std::make_shared(collection, windows, api, serviceProviders, audioAlert)), + button(std::make_shared(collection, trigger, plugin, serviceProviders, 55)), + toggle(button, 5) + { + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Tower, userTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + ON_CALL(*mockFlightplan, GetCallsign).WillByDefault(Return("BAW123")); + ON_CALL(*mockFlightplan, GetDestination).WillByDefault(Return("EGKK")); + + ON_CALL(*mockFlightplan, GetDistanceToDestination()).WillByDefault(Return(5.0)); + ON_CALL(*mockRadarTarget, GetFlightLevel()).WillByDefault(Return(3000)); + ON_CALL(*mockRadarTarget, GetGroundSpeed()).WillByDefault(Return(100)); + + ON_CALL(plugin, GetSelectedFlightplan).WillByDefault(Return(mockFlightplan)); + + ON_CALL(plugin, GetSelectedRadarTarget).WillByDefault(Return(mockRadarTarget)); + } + + NiceMock mockRadarScreen; + NiceMock mockUserSettingProvider; + UserSetting settings; + AirfieldServiceProviderCollection serviceProviders; + std::shared_ptr> mockRadarTarget; + std::shared_ptr> mockFlightplan; + ControllerPosition kkTwr; + std::shared_ptr userTowerCallsign; + NiceMock windows; + NiceMock api; + NiceMock plugin; + std::shared_ptr collection; + std::shared_ptr options; + std::shared_ptr audioAlert; + std::shared_ptr trigger; + std::shared_ptr button; + ToggleMissedApproachButton toggle; + }; + + TEST_F(ToggleMissedApproachButtonTest, ItTogglesTheButton) + { + EXPECT_FALSE(button->IsVisible()); + toggle.Configure(1, "", {}); + EXPECT_TRUE(button->IsVisible()); + toggle.Configure(1, "", {}); + EXPECT_FALSE(button->IsVisible()); + } + + TEST_F(ToggleMissedApproachButtonTest, ItHasTheMenuItemWithButtonInvisible) + { + PopupMenuItem expected; + expected.firstValue = "Display Missed Approach Button"; + expected.secondValue = ""; + expected.callbackFunctionId = 5; + expected.checked = EuroScopePlugIn::POPUP_ELEMENT_UNCHECKED; + expected.disabled = false; + expected.fixedPosition = false; + + EXPECT_EQ(expected, toggle.GetConfigurationMenuItem()); + } + + TEST_F(ToggleMissedApproachButtonTest, ItHasTheMenuItemWithButtonVisible) + { + button->ToggleVisible(); + PopupMenuItem expected; + expected.firstValue = "Display Missed Approach Button"; + expected.secondValue = ""; + expected.callbackFunctionId = 5; + expected.checked = EuroScopePlugIn::POPUP_ELEMENT_CHECKED; + expected.disabled = false; + expected.fixedPosition = false; + + EXPECT_EQ(expected, toggle.GetConfigurationMenuItem()); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/missedapproach/TriggerMissedApproachTest.cpp b/test/plugin/missedapproach/TriggerMissedApproachTest.cpp new file mode 100644 index 000000000..15f6ecd9f --- /dev/null +++ b/test/plugin/missedapproach/TriggerMissedApproachTest.cpp @@ -0,0 +1,355 @@ +#include "api/ApiException.h" +#include "controller/ControllerPosition.h" +#include "missedapproach/MissedApproach.h" +#include "missedapproach/MissedApproachAudioAlert.h" +#include "missedapproach/MissedApproachCollection.h" +#include "missedapproach/MissedApproachOptions.h" +#include "missedapproach/TriggerMissedApproach.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" +#include "time/ParseTimeStrings.h" +#include "time/SystemClock.h" + +using ::testing::NiceMock; +using UKControllerPlugin::Api::ApiException; +using UKControllerPlugin::Controller::ActiveCallsign; +using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::MissedApproach::MissedApproach; +using UKControllerPlugin::MissedApproach::MissedApproachAudioAlert; +using UKControllerPlugin::MissedApproach::MissedApproachCollection; +using UKControllerPlugin::MissedApproach::MissedApproachOptions; +using UKControllerPlugin::MissedApproach::TriggerMissedApproach; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPlugin::Time::ParseTimeString; +using UKControllerPlugin::Time::SetTestNow; +using UKControllerPlugin::Time::TimeNow; +using UKControllerPluginTest::Api::MockApiInterface; +using UKControllerPluginTest::Euroscope::MockEuroscopePluginLoopbackInterface; +using UKControllerPluginTest::Windows::MockWinApi; + +namespace UKControllerPluginTest::MissedApproach { + class TriggerMissedApproachTest : public testing::Test + { + public: + TriggerMissedApproachTest() + : kkTwr(2, "EGKK_TWR", 199.999, {"EGKK"}, true, false), + kkApp(3, "EGKK_APP", 199.999, {"EGKK"}, true, false), + userTowerCallsign(std::make_shared("EGKK_TWR", "Testy McTest", kkTwr, true)), + notUserTowerCallsign(std::make_shared("EGKK_TWR", "Testy McTest", kkTwr, false)), + userAppCallsign(std::make_shared("EGKK_TWR", "Testy McTest", kkApp, true)), + missed1( + std::make_shared(1, "BAW123", ParseTimeString("2021-08-23 13:56:00"), false)), + missed2( + std::make_shared(2, "BAW456", ParseTimeString("2021-08-23 13:56:00"), false)), + missed3( + std::make_shared(3, "BAW123", ParseTimeString("2021-08-23 13:54:00"), false)), + collection(std::make_shared()), + options(std::make_shared()), + audioAlert(std::make_shared(options, plugin, serviceProviders, windows)), + trigger(collection, windows, api, serviceProviders, audioAlert) + { + SetTestNow(ParseTimeString("2021-08-23 13:55:00")); + collection->Add(missed2); + collection->Add(missed3); + + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Tower, userTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + ON_CALL(mockFlightplan, GetCallsign).WillByDefault(testing::Return("BAW123")); + ON_CALL(mockFlightplan, GetDestination).WillByDefault(testing::Return("EGKK")); + ON_CALL(mockFlightplanBristol, GetCallsign).WillByDefault(testing::Return("BAW123")); + ON_CALL(mockFlightplanBristol, GetDestination).WillByDefault(testing::Return("EGGD")); + + ON_CALL(mockFlightplan, GetDistanceToDestination()).WillByDefault(testing::Return(5.0)); + ON_CALL(mockRadarTarget, GetFlightLevel()).WillByDefault(testing::Return(3000)); + ON_CALL(mockRadarTarget, GetGroundSpeed()).WillByDefault(testing::Return(100)); + } + + AirfieldServiceProviderCollection serviceProviders; + NiceMock mockRadarTarget; + NiceMock mockFlightplan; + NiceMock mockFlightplanBristol; + ControllerPosition kkTwr; + ControllerPosition kkApp; + std::shared_ptr userTowerCallsign; + std::shared_ptr notUserTowerCallsign; + std::shared_ptr userAppCallsign; + NiceMock windows; + NiceMock api; + NiceMock plugin; + std::shared_ptr missed1; + std::shared_ptr missed2; + std::shared_ptr missed3; + std::shared_ptr collection; + std::shared_ptr options; + std::shared_ptr audioAlert; + TriggerMissedApproach trigger; + }; + + TEST_F(TriggerMissedApproachTest, ItTriggersAMissedApproach) + { + nlohmann::json responseData = {{"id", 55}, {"expires_at", "2021-08-23 14:00:00"}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(3, collection->Count()); + auto missed = collection->Get(55); + EXPECT_NE(nullptr, missed); + EXPECT_EQ(55, missed->Id()); + EXPECT_EQ("BAW123", missed->Callsign()); + EXPECT_EQ(ParseTimeString("2021-08-23 14:00:00"), missed->ExpiresAt()); + } + + TEST_F(TriggerMissedApproachTest, ItTriggersAMissedApproachWhenNoneExistForCallsign) + { + collection->Remove(missed3); + nlohmann::json responseData = {{"id", 55}, {"expires_at", "2021-08-23 14:00:00"}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + auto missed = collection->Get(55); + EXPECT_NE(nullptr, missed); + EXPECT_EQ(55, missed->Id()); + EXPECT_EQ("BAW123", missed->Callsign()); + EXPECT_EQ(ParseTimeString("2021-08-23 14:00:00"), missed->ExpiresAt()); + EXPECT_TRUE(missed->CreatedByUser()); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesExpiresAtNotValidTimestampInResponse) + { + nlohmann::json responseData = {{"id", 55}, {"expires_at", "abc"}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesExpiresAtNotStringInResponse) + { + nlohmann::json responseData = {{"id", 55}, {"expires_at", 123}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesNoExpiresAtInResponse) + { + nlohmann::json responseData = {{"id", 55}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesIdNotIntInResponse) + { + nlohmann::json responseData = {{"id", "abc"}, {"expires_at", "2021-08-23 14:00:00"}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesIdMissingInResponse) + { + nlohmann::json responseData = {{"expires_at", "2021-08-23 14:00:00"}}; + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesResponseNotObject) + { + nlohmann::json responseData = nlohmann::json::array(); + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Return(responseData)); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItHandlesApiException) + { + EXPECT_CALL(api, CreateMissedApproach("BAW123")).Times(1).WillOnce(testing::Throw(ApiException("Foo"))); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDYES)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerIfUserDoesNotConfirm) + { + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)) + .Times(1) + .WillOnce(testing::Return(IDNO)); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerIfAlreadyActive) + { + collection->Add(missed1); + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(3, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerUserDoesntCoverArrivalAirfield) + { + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplanBristol, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerUserIsNotTower) + { + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Tower, userAppCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerNonUserProvidingTower) + { + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Tower, notUserTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerUserNotProvidingTower) + { + std::vector> provisions; + provisions.push_back(std::make_shared(ServiceType::Approach, userTowerCallsign)); + serviceProviders.SetProvidersForAirfield("EGKK", provisions); + + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerUserNotActive) + { + serviceProviders.Flush(); + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerAircraftTooFarFromDestination) + { + ON_CALL(mockFlightplan, GetDistanceToDestination()).WillByDefault(testing::Return(9.0)); + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerAircraftTooHigh) + { + ON_CALL(mockRadarTarget, GetFlightLevel()).WillByDefault(testing::Return(9000)); + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } + + TEST_F(TriggerMissedApproachTest, ItDoesntTriggerAircraftTooSlow) + { + ON_CALL(mockRadarTarget, GetGroundSpeed()).WillByDefault(testing::Return(30)); + EXPECT_CALL(api, CreateMissedApproach(testing::_)).Times(0); + + EXPECT_CALL(windows, OpenMessageBox(testing::_, testing::_, testing::_)).Times(0); + + trigger.Trigger(mockFlightplan, mockRadarTarget); + EXPECT_EQ(2, collection->Count()); + EXPECT_EQ(nullptr, collection->Get(55)); + } +} // namespace UKControllerPluginTest::MissedApproach diff --git a/test/plugin/mock/MockActiveCallsignEventHandler.h b/test/plugin/mock/MockActiveCallsignEventHandler.h index 5f666ca47..919431243 100644 --- a/test/plugin/mock/MockActiveCallsignEventHandler.h +++ b/test/plugin/mock/MockActiveCallsignEventHandler.h @@ -1,29 +1,14 @@ #pragma once -#include "pch/pch.h" #include "controller/ActiveCallsignEventHandlerInterface.h" #include "controller/ActiveCallsign.h" -namespace UKControllerPluginTest { - namespace Controller { +namespace UKControllerPluginTest::Controller { - class MockActiveCallsignEventHandler - : public UKControllerPlugin::Controller::ActiveCallsignEventHandlerInterface - { - public: - MOCK_METHOD( - void, - ActiveCallsignAdded, - (const UKControllerPlugin::Controller::ActiveCallsign &, bool), - () - ); - MOCK_METHOD( - void, - ActiveCallsignRemoved, - (const UKControllerPlugin::Controller::ActiveCallsign &, bool), - () - ); - MOCK_METHOD(void, CallsignsFlushed, (), ()); - - }; - } // namespace Controller -} // namespace UKControllerPluginTest + class MockActiveCallsignEventHandler : public UKControllerPlugin::Controller::ActiveCallsignEventHandlerInterface + { + public: + MOCK_METHOD(void, ActiveCallsignAdded, (const UKControllerPlugin::Controller::ActiveCallsign&), ()); + MOCK_METHOD(void, ActiveCallsignRemoved, (const UKControllerPlugin::Controller::ActiveCallsign&), ()); + MOCK_METHOD(void, CallsignsFlushed, (), ()); + }; +} // namespace UKControllerPluginTest::Controller diff --git a/test/plugin/mock/MockEuroScopeCFlightplanInterface.h b/test/plugin/mock/MockEuroScopeCFlightplanInterface.h index d431a1f59..35b48e0b4 100644 --- a/test/plugin/mock/MockEuroScopeCFlightplanInterface.h +++ b/test/plugin/mock/MockEuroScopeCFlightplanInterface.h @@ -11,20 +11,21 @@ namespace UKControllerPluginTest { MOCK_CONST_METHOD2(AnnotateFlightStrip, void(int, std::string)); MOCK_CONST_METHOD1(GetAnnotation, std::string(int)); MOCK_CONST_METHOD0(GetAircraftType, std::string(void)); - MOCK_CONST_METHOD0(GetCallsign, const std::string(void)); + MOCK_CONST_METHOD0(GetCallsign, std::string(void)); MOCK_CONST_METHOD0(GetClearedAltitude, int(void)); MOCK_CONST_METHOD0(GetCruiseLevel, int(void)); - MOCK_CONST_METHOD0(GetDestination, const std::string(void)); + MOCK_CONST_METHOD0(GetDestination, std::string(void)); MOCK_CONST_METHOD0(GetDistanceFromOrigin, double(void)); + MOCK_CONST_METHOD0(GetDistanceToDestination, double(void)); MOCK_CONST_METHOD0(GetExpectedDepartureTime, std::string(void)); MOCK_CONST_METHOD0( GetExtractedRoute, UKControllerPlugin::Euroscope::EuroscopeExtractedRouteInterface(void)); MOCK_CONST_METHOD0(GetFlightRules, std::string(void)); MOCK_CONST_METHOD0(GetGroundState, std::string(void)); MOCK_CONST_METHOD0(GetIcaoWakeCategory, std::string(void)); - MOCK_CONST_METHOD0(GetOrigin, const std::string(void)); + MOCK_CONST_METHOD0(GetOrigin, std::string(void)); MOCK_CONST_METHOD0(GetRawRouteString, std::string(void)); - MOCK_CONST_METHOD0(GetSidName, const std::string(void)); + MOCK_CONST_METHOD0(GetSidName, std::string(void)); MOCK_CONST_METHOD0(GetAssignedSquawk, std::string(void)); MOCK_CONST_METHOD0(GetRemarks, std::string()); MOCK_CONST_METHOD0(HasControllerClearedAltitude, bool(void)); diff --git a/test/plugin/mock/MockEuroscopeRadarScreenLoopbackInterface.h b/test/plugin/mock/MockEuroscopeRadarScreenLoopbackInterface.h index 58e823fa4..bdd44976d 100644 --- a/test/plugin/mock/MockEuroscopeRadarScreenLoopbackInterface.h +++ b/test/plugin/mock/MockEuroscopeRadarScreenLoopbackInterface.h @@ -2,23 +2,22 @@ #include "euroscope/EuroscopeRadarLoopbackInterface.h" #include "plugin/PopupMenuItem.h" -namespace UKControllerPluginTest { - namespace Euroscope { +namespace UKControllerPluginTest::Euroscope { - class MockEuroscopeRadarScreenLoopbackInterface - : public UKControllerPlugin::Euroscope::EuroscopeRadarLoopbackInterface - { - public: - MOCK_METHOD1(AddMenuItem, void(UKControllerPlugin::Plugin::PopupMenuItem menuItem)); - MOCK_METHOD0(GetRadarViewport, RECT(void)); - MOCK_METHOD4(RegisterScreenObject, void(int, std::string, RECT, bool)); - MOCK_METHOD1(GetAsrData, std::string(std::string)); - MOCK_METHOD1(GetGroundspeedForCallsign, int(std::string)); - MOCK_METHOD1(PositionOffScreen, bool(EuroScopePlugIn::CPosition)); - MOCK_METHOD3(ToogleMenu, void(RECT, std::string, int)); - MOCK_METHOD1(ConvertCoordinateToScreenPoint, POINT(EuroScopePlugIn::CPosition)); - MOCK_METHOD3(ToggleTemporaryAltitudePopupList, void(std::string, POINT, RECT)); - MOCK_METHOD4(TogglePluginTagFunction, void(std::string, int, POINT, RECT)); - }; - } // namespace Euroscope -} // namespace UKControllerPluginTest + class MockEuroscopeRadarScreenLoopbackInterface + : public UKControllerPlugin::Euroscope::EuroscopeRadarLoopbackInterface + { + public: + MOCK_METHOD1(AddMenuItem, void(UKControllerPlugin::Plugin::PopupMenuItem menuItem)); + MOCK_METHOD0(GetRadarViewport, RECT(void)); + MOCK_METHOD4(RegisterScreenObject, void(int, std::string, RECT, bool)); + MOCK_METHOD1(GetAsrData, std::string(std::string)); + MOCK_METHOD1(GetGroundspeedForCallsign, int(std::string)); + MOCK_METHOD1(PositionOffScreen, bool(EuroScopePlugIn::CPosition)); + MOCK_METHOD3(ToogleMenu, void(RECT, std::string, int)); + MOCK_METHOD1(ConvertCoordinateToScreenPoint, POINT(EuroScopePlugIn::CPosition)); + MOCK_METHOD1(ConvertScreenPointToCoordinate, EuroScopePlugIn::CPosition(const POINT&)); + MOCK_METHOD3(ToggleTemporaryAltitudePopupList, void(std::string, POINT, RECT)); + MOCK_METHOD4(TogglePluginTagFunction, void(std::string, int, POINT, RECT)); + }; +} // namespace UKControllerPluginTest::Euroscope diff --git a/test/plugin/notifications/NotificationsEventHandlerTest.cpp b/test/plugin/notifications/NotificationsEventHandlerTest.cpp index 00588f3f9..7cee78d8f 100644 --- a/test/plugin/notifications/NotificationsEventHandlerTest.cpp +++ b/test/plugin/notifications/NotificationsEventHandlerTest.cpp @@ -31,7 +31,8 @@ namespace UKControllerPluginTest { std::shared_ptr controller2 = std::make_shared(2, "EGLL_S_TWR", 199.998, topDown, true, false); std::vector topDown = {"EGLL"}; - ActiveCallsign callsign = ActiveCallsign("EGLL_S_TWR", "Bobby", *controller1); + ActiveCallsign callsign = ActiveCallsign("EGLL_S_TWR", "Bobby", *controller1, true); + ActiveCallsign notUserCallsign = ActiveCallsign("EGLL_S_TWR", "Bobby", *controller1, false); UserMessager messager; NiceMock mockPlugin; std::shared_ptr repository; @@ -42,7 +43,7 @@ namespace UKControllerPluginTest { { EXPECT_CALL(this->mockPlugin, ChatAreaMessage(_, _, _, _, _, _, _, _)).Times(0); - this->handler->ActiveCallsignAdded(this->callsign, false); + this->handler->ActiveCallsignAdded(this->notUserCallsign); } TEST_F(NotificationsEventHandlerTest, ItDoesNothingIfNoRelevantNotifications) @@ -59,7 +60,7 @@ namespace UKControllerPluginTest { EXPECT_CALL(this->mockPlugin, ChatAreaMessage(_, _, _, _, _, _, _, _)).Times(0); - this->handler->ActiveCallsignAdded(this->callsign, true); + this->handler->ActiveCallsignAdded(this->callsign); } TEST_F(NotificationsEventHandlerTest, ItNotifiesForRelevantNotifications) @@ -117,7 +118,7 @@ namespace UKControllerPluginTest { _)) .Times(1); - this->handler->ActiveCallsignAdded(this->callsign, true); + this->handler->ActiveCallsignAdded(this->callsign); } } // namespace Notifications } // namespace UKControllerPluginTest diff --git a/test/plugin/offblock/ActualOffBlockTimeEventHandlerTest.cpp b/test/plugin/offblock/ActualOffBlockTimeEventHandlerTest.cpp index fe529a28f..d965ab2cc 100644 --- a/test/plugin/offblock/ActualOffBlockTimeEventHandlerTest.cpp +++ b/test/plugin/offblock/ActualOffBlockTimeEventHandlerTest.cpp @@ -18,7 +18,6 @@ using UKControllerPlugin::Datablock::ActualOffBlockTimeEventHandler; using UKControllerPlugin::Datablock::DisplayTime; using UKControllerPlugin::Flightplan::StoredFlightplan; using UKControllerPlugin::Flightplan::StoredFlightplanCollection; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; using UKControllerPlugin::Tag::TagData; using UKControllerPluginTest::Euroscope::MockEuroScopeCFlightPlanInterface; using UKControllerPluginTest::Euroscope::MockEuroScopeCRadarTargetInterface; diff --git a/test/plugin/ownership/AirfieldOwnershipHandlerTest.cpp b/test/plugin/ownership/AirfieldOwnershipHandlerTest.cpp index 650e05843..bc4aa5d2a 100644 --- a/test/plugin/ownership/AirfieldOwnershipHandlerTest.cpp +++ b/test/plugin/ownership/AirfieldOwnershipHandlerTest.cpp @@ -11,6 +11,8 @@ #include "sid/SidCollection.h" #include "sid/StandardInstrumentDeparture.h" #include "login/Login.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" using UKControllerPlugin::Airfield::AirfieldCollection; using UKControllerPlugin::Airfield::AirfieldModel; @@ -26,6 +28,7 @@ using UKControllerPlugin::InitialAltitude::InitialAltitudeEventHandler; using UKControllerPlugin::Message::UserMessager; using UKControllerPlugin::Ownership::AirfieldOwnershipHandler; using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Sid::SidCollection; using UKControllerPlugin::Sid::StandardInstrumentDeparture; using UKControllerPluginTest::Euroscope::MockEuroScopeCControllerInterface; @@ -45,9 +48,10 @@ namespace UKControllerPluginTest { { public: ControllerAirfieldOwnershipHandlerTest() - : ownership(this->airfieldCollection, this->activeCallsigns), + : serviceProviders(std::make_shared()), + ownership(serviceProviders, this->airfieldCollection, this->activeCallsigns), initialAltitudes(new InitialAltitudeEventHandler( - this->sids, this->activeCallsigns, this->ownership, this->login, this->plugin)), + this->sids, this->activeCallsigns, *serviceProviders, this->login, this->plugin)), login(plugin, ControllerStatusEventHandlerCollection()), userMessager(this->plugin), handler(this->ownership, this->userMessager) { @@ -95,10 +99,10 @@ namespace UKControllerPluginTest { this->llApp = std::unique_ptr( new ControllerPosition(5, "EGLL_N_APP", 199.998, {"EGLL"}, true, false)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_TWR", "Testy McTest", *kkTwr)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_APP", "Testy McTest", *kkApp)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_S_TWR", "Testy McTest", *llTwr)); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_N_APP", "Testy McTest", *llApp)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_TWR", "Testy McTest", *kkTwr, false)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_APP", "Testy McTest", *kkApp, false)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_S_TWR", "Testy McTest", *llTwr, false)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGLL_N_APP", "Testy McTest", *llApp, false)); this->ownership.RefreshOwner("EGKK"); // Create a dummy initial altitude @@ -108,6 +112,7 @@ namespace UKControllerPluginTest { AirfieldCollection airfieldCollection; ControllerPositionCollection controllerCollection; + std::shared_ptr serviceProviders; AirfieldOwnershipManager ownership; StoredFlightplanCollection flightplans; SidCollection sids; @@ -180,16 +185,18 @@ namespace UKControllerPluginTest { this->ownership.RefreshOwner("EGLL"); this->handler.CallsignsFlushed(); - EXPECT_FALSE(this->ownership.AirfieldHasOwner("EGKK")); - EXPECT_FALSE(this->ownership.AirfieldHasOwner("EGLL")); + EXPECT_FALSE(this->serviceProviders->AirfieldHasDeliveryProvider("EGKK")); + EXPECT_FALSE(this->serviceProviders->AirfieldHasDeliveryProvider("EGLL")); } TEST_F(ControllerAirfieldOwnershipHandlerTest, ActiveCallsignDisconnectRefreshesOwnership) { ActiveCallsign gatwick = this->activeCallsigns.GetCallsign("EGKK_TWR"); this->activeCallsigns.RemoveCallsign(this->activeCallsigns.GetCallsign("EGKK_TWR")); - this->handler.ActiveCallsignRemoved(gatwick, false); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGKK", this->activeCallsigns.GetCallsign("EGKK_APP"))); + this->handler.ActiveCallsignRemoved(gatwick); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGKK_APP"), + *this->serviceProviders->DeliveryProviderForAirfield("EGKK")->controller); } TEST_F( @@ -198,33 +205,49 @@ namespace UKControllerPluginTest { { // Add the spare controller ControllerPosition pos(1, "EGKK_TWR", 199.998, {"EGKK"}, true, false); - this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_1_TWR", "Another Guy", pos)); + this->activeCallsigns.AddCallsign(ActiveCallsign("EGKK_1_TWR", "Another Guy", pos, false)); ActiveCallsign gatwick = this->activeCallsigns.GetCallsign("EGKK_TWR"); this->activeCallsigns.RemoveCallsign(this->activeCallsigns.GetCallsign("EGKK_TWR")); - this->handler.ActiveCallsignRemoved(gatwick, false); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGKK", this->activeCallsigns.GetCallsign("EGKK_1_TWR"))); + this->handler.ActiveCallsignRemoved(gatwick); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGKK_1_TWR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGKK")->controller); } TEST_F(ControllerAirfieldOwnershipHandlerTest, NewActiveCallsignEventRefreshesTopDown) { - this->activeCallsigns.AddCallsign( - ActiveCallsign("EGKK_DEL", "Test", this->controllerCollection.FetchPositionByCallsign("EGKK_DEL"))); - this->handler.ActiveCallsignAdded(this->activeCallsigns.GetCallsign("EGKK_DEL"), true); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGKK", this->activeCallsigns.GetCallsign("EGKK_DEL"))); + this->activeCallsigns.AddCallsign(ActiveCallsign( + "EGKK_DEL", "Test", this->controllerCollection.FetchPositionByCallsign("EGKK_DEL"), false)); + this->handler.ActiveCallsignAdded(this->activeCallsigns.GetCallsign("EGKK_DEL")); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGKK_DEL"), + *this->serviceProviders->DeliveryProviderForAirfield("EGKK")->controller); } TEST_F(ControllerAirfieldOwnershipHandlerTest, ControllerUpdateEventUpdatesTopDownMultiple) { - this->activeCallsigns.AddCallsign( - ActiveCallsign("LTC_S_CTR", "Test", this->controllerCollection.FetchPositionByCallsign("LTC_S_CTR"))); - this->handler.ActiveCallsignAdded(this->activeCallsigns.GetCallsign("LTC_S_CTR"), true); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGKK", this->activeCallsigns.GetCallsign("EGKK_TWR"))); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGLL", this->activeCallsigns.GetCallsign("EGLL_S_TWR"))); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGLC", this->activeCallsigns.GetCallsign("LTC_S_CTR"))); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGKB", this->activeCallsigns.GetCallsign("LTC_S_CTR"))); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGMC", this->activeCallsigns.GetCallsign("LTC_S_CTR"))); - EXPECT_TRUE(this->ownership.AirfieldOwnedBy("EGMD", this->activeCallsigns.GetCallsign("LTC_S_CTR"))); + this->activeCallsigns.AddCallsign(ActiveCallsign( + "LTC_S_CTR", "Test", this->controllerCollection.FetchPositionByCallsign("LTC_S_CTR"), false)); + this->handler.ActiveCallsignAdded(this->activeCallsigns.GetCallsign("LTC_S_CTR")); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGKK_TWR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGKK")->controller); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGLL_S_TWR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGLL")->controller); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("LTC_S_CTR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGLC")->controller); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("LTC_S_CTR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGKB")->controller); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("LTC_S_CTR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGMC")->controller); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("LTC_S_CTR"), + *this->serviceProviders->DeliveryProviderForAirfield("EGMD")->controller); } } // namespace Ownership } // namespace UKControllerPluginTest diff --git a/test/plugin/ownership/AirfieldOwnershipManagerTest.cpp b/test/plugin/ownership/AirfieldOwnershipManagerTest.cpp index 073a297a7..ef45fc4fb 100644 --- a/test/plugin/ownership/AirfieldOwnershipManagerTest.cpp +++ b/test/plugin/ownership/AirfieldOwnershipManagerTest.cpp @@ -1,9 +1,10 @@ #include "airfield/AirfieldCollection.h" #include "airfield/AirfieldModel.h" -#include "controller/ActiveCallsign.h" #include "controller/ActiveCallsignCollection.h" #include "controller/ControllerPosition.h" #include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" using ::testing::Test; using UKControllerPlugin::Airfield::AirfieldCollection; @@ -12,6 +13,9 @@ using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; namespace UKControllerPluginTest::Ownership { @@ -19,160 +23,375 @@ namespace UKControllerPluginTest::Ownership { { public: AirfieldOwnershipManagerTest() - : controller1(1, "EGGD_TWR", 133.850, {"EGGD"}, true, true), - controller2(2, "EGGD_GND", 121.920, {"EGGD"}, true, true), manager(this->airfields, this->activeCallsigns) + : providers(std::make_shared()), + towerController(1, "EGGD_TWR", 133.850, {"EGGD"}, true, true), + towerController2(8, "EGGD_2_TWR", 133.850, {"EGGD"}, true, true), + groundController(2, "EGGD_GND", 121.920, {"EGGD"}, true, true), + groundController2(9, "EGGD_2_GND", 121.920, {"EGGD"}, true, true), + finalApproachController(3, "EGGD_F_APP", 121.920, {"EGGD"}, true, true), + approachController(4, "EGGD_APP", 121.920, {"EGGD"}, true, true), + enrouteController(5, "LON_W_CTR", 121.920, {"EGGD"}, true, true), + enrouteController2(10, "LON_CTR", 121.920, {"EGGD"}, true, true), + deliveryController(7, "EGGD_DEL", 121.920, {"EGGD"}, true, true), + towerCallsign("EGGD_TWR", "Testy McTestface", towerController, false), + towerCallsign2("EGGD_2_TWR", "Testy McTestface", towerController2, false), + groundCallsign("EGGD_GND", "Testy McTestface", groundController, false), + groundCallsign2("EGGD_2_GND", "Testy McTestface", groundController2, false), + finalApproachCallsign("EGGD_F_APP", "Testy McTestface", finalApproachController, false), + approachCallsign("EGGD_APP", "Testy McTestface", approachController, false), + enrouteCallsign("LON_W_CTR", "Testy McTestface", enrouteController, false), + enrouteCallsign2("LON_CTR", "Testy McTestface", enrouteController2, false), + deliveryCallsign("EGGD_DEL", "Testy McTestface", deliveryController, false), + manager(providers, this->airfields, this->activeCallsigns) { } void SetUp() override { - std::vector topDown = {"EGGD_GND", "EGGD_TWR", "EGGD_GND", "EGFF_APP", "LON_W_CTR"}; + std::vector topDown = { + "EGGD_DEL", + "EGGD_GND", + "EGGD_2_GND", + "EGGD_TWR", + "EGGD_2_TWR", + "EGGD_F_APP", + "EGGD_APP", + "LON_W_CTR", + "LON_CTR"}; this->airfields.AddAirfield(std::make_unique("EGGD", topDown)); }; - ControllerPosition controller1; - ControllerPosition controller2; + std::shared_ptr providers; + ControllerPosition towerController; + ControllerPosition towerController2; + ControllerPosition groundController; + ControllerPosition groundController2; + ControllerPosition finalApproachController; + ControllerPosition approachController; + ControllerPosition enrouteController; + ControllerPosition enrouteController2; + ControllerPosition deliveryController; + ActiveCallsign towerCallsign; + ActiveCallsign towerCallsign2; + ActiveCallsign groundCallsign; + ActiveCallsign groundCallsign2; + ActiveCallsign finalApproachCallsign; + ActiveCallsign approachCallsign; + ActiveCallsign enrouteCallsign; + ActiveCallsign enrouteCallsign2; + ActiveCallsign deliveryCallsign; AirfieldOwnershipManager manager; ActiveCallsignCollection activeCallsigns; AirfieldCollection airfields; }; - TEST_F(AirfieldOwnershipManagerTest, AirfieldsStartWithNoOwner) + TEST_F(AirfieldOwnershipManagerTest, AirfieldsStartWithNoDeliveryProvider) { - EXPECT_FALSE(this->manager.AirfieldHasOwner("EGGD")); + EXPECT_FALSE(this->providers->AirfieldHasDeliveryProvider("EGGD")); + EXPECT_TRUE(this->providers->GetServiceProviders("EGGD").empty()); } - TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsOwner) + TEST_F(AirfieldOwnershipManagerTest, RefreshHandlesInvalidAirfields) { - this->activeCallsigns.AddCallsign(ActiveCallsign("EGGD_TWR", "Testy McTestface", controller1)); + EXPECT_NO_THROW(this->manager.RefreshOwner("EGFF")); + } + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsDeliveryProvider) + { + this->activeCallsigns.AddCallsign(ActiveCallsign("EGGD_TWR", "Testy McTestface", towerController, false)); this->manager.RefreshOwner("EGGD"); - EXPECT_TRUE(this->manager.AirfieldHasOwner("EGGD")); + EXPECT_TRUE(this->providers->AirfieldHasDeliveryProvider("EGGD")); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_TWR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); } - TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsOwnerToCorrectcontroller1) + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsDeliveryProviderWithCorrectPriority) { - ActiveCallsign active("EGGD_TWR", "Testy McTestface", controller1); - this->activeCallsigns.AddCallsign(active); + this->activeCallsigns.AddCallsign(towerCallsign); + this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_TWR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + this->activeCallsigns.AddCallsign(groundCallsign); this->manager.RefreshOwner("EGGD"); - EXPECT_TRUE(this->manager.AirfieldOwnedBy("EGGD", active)); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_GND"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); } - TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsOwnerWithCorrectPriority) + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsOnlyOneDeliveryProvider) { - ActiveCallsign active1("EGGD_TWR", "Testy McTestface", controller1); - ActiveCallsign active2("EGGD_GND", "Testy McTestface 2", controller2); - - this->activeCallsigns.AddCallsign(active1); + this->activeCallsigns.AddCallsign(towerCallsign); + this->activeCallsigns.AddCallsign(groundCallsign); + this->activeCallsigns.AddCallsign(approachCallsign); this->manager.RefreshOwner("EGGD"); - EXPECT_TRUE(this->manager.AirfieldOwnedBy("EGGD", active1)); + const auto& providers = this->providers->GetServiceProviders("EGGD"); + auto count = std::count_if( + providers.cbegin(), providers.cend(), [](const std::shared_ptr& provision) -> bool { + return provision->serviceProvided == ServiceType::Delivery; + }); + EXPECT_EQ(1, count); + } - this->activeCallsigns.AddCallsign(active2); + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsProvidersForDeliveryPositions) + { + this->activeCallsigns.AddCallsign(deliveryCallsign); this->manager.RefreshOwner("EGGD"); - - EXPECT_TRUE(this->manager.AirfieldOwnedBy("EGGD", active2)); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_DEL"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::Ground)); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::Tower)); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::FinalApproach)); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::Approach)); } - TEST_F(AirfieldOwnershipManagerTest, RefreshAfterLogOffChangesOwner) + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsProvidersForGroundPositions) { - ActiveCallsign active1("EGGD_TWR", "Testy McTestface", controller1); - ActiveCallsign active2("EGGD_GND", "Testy McTestface 2", controller2); - - this->activeCallsigns.AddCallsign(active1); - this->activeCallsigns.AddCallsign(active2); - + this->activeCallsigns.AddCallsign(groundCallsign); this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_GND"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_GND"), *(*groundProviders.cbegin())->controller); + + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::Tower)); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::FinalApproach)); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::Approach)); + } - EXPECT_TRUE(this->manager.AirfieldOwnedBy("EGGD", active2)); - - this->activeCallsigns.RemoveCallsign(active2); + TEST_F(AirfieldOwnershipManagerTest, MultipleGroundControllersCanProvideServices) + { + this->activeCallsigns.AddCallsign(groundCallsign); + this->activeCallsigns.AddCallsign(groundCallsign2); this->manager.RefreshOwner("EGGD"); - EXPECT_TRUE(this->manager.AirfieldOwnedBy("EGGD", active1)); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_GND"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(2, groundProviders.size()); + auto providerIterator = groundProviders.cbegin(); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_GND"), *(*providerIterator)->controller); + providerIterator++; + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_2_GND"), *(*providerIterator)->controller); } - TEST_F(AirfieldOwnershipManagerTest, FlushRemovesAllOwners) + TEST_F(AirfieldOwnershipManagerTest, OnlyGroundControllersProvideGroundServicesIfOnline) { - ActiveCallsign active1("EGGD_TWR", "Testy McTestface", controller1); - ActiveCallsign active2("EGGD_GND", "Testy McTestface 2", controller2); - - this->activeCallsigns.AddCallsign(active1); - this->activeCallsigns.AddCallsign(active2); - + this->activeCallsigns.AddCallsign(groundCallsign); + this->activeCallsigns.AddCallsign(towerCallsign); this->manager.RefreshOwner("EGGD"); - this->manager.Flush(); - - EXPECT_FALSE(this->manager.AirfieldHasOwner("EGGD")); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_GND"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_GND"), *(*groundProviders.cbegin())->controller); } - TEST_F(AirfieldOwnershipManagerTest, AirfieldOwnedByUserReturnsTrueIfActiveCallsignIsUser) + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsProvidersForTowerPositions) { - ActiveCallsign active("EGGD_TWR", "Testy McTestface", controller1); + this->activeCallsigns.AddCallsign(towerCallsign); + this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_TWR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_TWR"), *(*groundProviders.cbegin())->controller); + + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(1, towerProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_TWR"), *(*towerProviders.cbegin())->controller); + + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::FinalApproach)); + EXPECT_FALSE(this->providers->ServiceProvidedAtAirfield("EGGD", ServiceType::Approach)); + } - this->activeCallsigns.AddUserCallsign(active); + TEST_F(AirfieldOwnershipManagerTest, MultipleTowerControllersCanProvideServices) + { + this->activeCallsigns.AddCallsign(towerCallsign); + this->activeCallsigns.AddCallsign(towerCallsign2); + this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_TWR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_TWR"), *groundProviders[0]->controller); + + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(2, towerProviders.size()); + auto towerProviderIterator = towerProviders.cbegin(); + + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_TWR"), *(*towerProviderIterator)->controller); + towerProviderIterator++; + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_2_TWR"), *(*towerProviderIterator)->controller); + } + TEST_F(AirfieldOwnershipManagerTest, OnlyTowerControllersProvideTowerServicesIfOnline) + { + this->activeCallsigns.AddCallsign(approachCallsign); + this->activeCallsigns.AddCallsign(towerCallsign); this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_TWR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(1, towerProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_TWR"), *(*towerProviders.cbegin())->controller); + } - EXPECT_TRUE(this->manager.AirfieldOwnedByUser("EGGD")); + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsProvidersForApproachPositions) + { + this->activeCallsigns.AddCallsign(approachCallsign); + this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_APP"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_APP"), *(*groundProviders.cbegin())->controller); + + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(1, towerProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_APP"), *(*towerProviders.cbegin())->controller); + + const auto approachProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Approach); + EXPECT_EQ(1, approachProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_APP"), *(*approachProviders.cbegin())->controller); } - TEST_F(AirfieldOwnershipManagerTest, AirfieldOwnedByUserReturnsFalseIfActiveCallsignNotUser) + TEST_F(AirfieldOwnershipManagerTest, MultipleApproachControllersCanProvideServices) { - ActiveCallsign active("EGGD_TWR", "Testy McTestface", controller1); + this->activeCallsigns.AddCallsign(approachCallsign); + this->activeCallsigns.AddCallsign(finalApproachCallsign); + this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_F_APP"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_F_APP"), *groundProviders[0]->controller); + + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(1, towerProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_F_APP"), *towerProviders[0]->controller); + + const auto approachProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Approach); + auto providerIterator = approachProviders.cbegin(); + EXPECT_EQ(2, approachProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_F_APP"), *(*providerIterator)->controller); + providerIterator++; + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_APP"), *(*providerIterator)->controller); + } - this->activeCallsigns.AddCallsign(active); + TEST_F(AirfieldOwnershipManagerTest, OnlyApproachControllersCanProvideApproachServicesIfOnline) + { + this->activeCallsigns.AddCallsign(approachCallsign); + this->activeCallsigns.AddCallsign(enrouteCallsign); + this->manager.RefreshOwner("EGGD"); + const auto approachProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Approach); + EXPECT_EQ(1, approachProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_APP"), *(*approachProviders.cbegin())->controller); + } + TEST_F(AirfieldOwnershipManagerTest, OnlyTheFirstApproachControllerProvidesFinalApproachServices) + { + this->activeCallsigns.AddCallsign(approachCallsign); + this->activeCallsigns.AddCallsign(finalApproachCallsign); this->manager.RefreshOwner("EGGD"); - EXPECT_FALSE(this->manager.AirfieldOwnedByUser("EGGD")); + const auto finalApproachProviders = + this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::FinalApproach); + EXPECT_EQ(1, finalApproachProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("EGGD_F_APP"), *(*finalApproachProviders.cbegin())->controller); } - TEST_F(AirfieldOwnershipManagerTest, GetOwnerReturnsOwnerIfFound) + TEST_F(AirfieldOwnershipManagerTest, RefreshOwnerSetsProvidersForEnroutePositions) { - ActiveCallsign active("EGGD_TWR", "Testy McTestface", controller1); - this->activeCallsigns.AddCallsign(active); - + this->activeCallsigns.AddCallsign(enrouteCallsign); this->manager.RefreshOwner("EGGD"); - - EXPECT_EQ(active, this->manager.GetOwner("EGGD")); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("LON_W_CTR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("LON_W_CTR"), *(*groundProviders.cbegin())->controller); + + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(1, towerProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("LON_W_CTR"), *(*towerProviders.cbegin())->controller); + + const auto approachProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Approach); + EXPECT_EQ(1, approachProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("LON_W_CTR"), *(*approachProviders.cbegin())->controller); } - TEST_F(AirfieldOwnershipManagerTest, GetOwnerReturnsNoneIfNotFound) + TEST_F(AirfieldOwnershipManagerTest, OnlyOneEnroutePositionProvidesServices) { - ActiveCallsign active("EGGD_TWR", "Testy McTestface", controller1); - this->activeCallsigns.AddCallsign(active); + this->activeCallsigns.AddCallsign(enrouteCallsign); + this->activeCallsigns.AddCallsign(enrouteCallsign2); + this->manager.RefreshOwner("EGGD"); + + EXPECT_EQ( + this->activeCallsigns.GetCallsign("LON_W_CTR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + const auto groundProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(1, groundProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("LON_W_CTR"), *(*groundProviders.cbegin())->controller); + + const auto towerProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Tower); + EXPECT_EQ(1, towerProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("LON_W_CTR"), *(*towerProviders.cbegin())->controller); - EXPECT_EQ(this->manager.NotFoundCallsign(), this->manager.GetOwner("EGPF")); + const auto approachProviders = this->providers->GetProvidersForServiceAtAirfield("EGGD", ServiceType::Approach); + EXPECT_EQ(1, approachProviders.size()); + EXPECT_EQ(this->activeCallsigns.GetCallsign("LON_W_CTR"), *(*approachProviders.cbegin())->controller); } - TEST_F(AirfieldOwnershipManagerTest, GetOwnedReturnsNothingIfNotFound) + TEST_F(AirfieldOwnershipManagerTest, RefreshRemovesAllProvidersIfNoneOnline) { - ActiveCallsign active1("EGGD_TWR", "Testy McTestface", controller1); - ActiveCallsign active2("EGGD_GND", "Testy McTestface 2", controller2); - this->activeCallsigns.AddCallsign(active1); - this->activeCallsigns.AddCallsign(active2); + this->activeCallsigns.AddCallsign(towerCallsign); + this->manager.RefreshOwner("EGGD"); + this->activeCallsigns.RemoveCallsign(towerCallsign); this->manager.RefreshOwner("EGGD"); - EXPECT_EQ(0, this->manager.GetOwnedAirfields("EGGD_TWR").size()); + EXPECT_TRUE(this->providers->GetServiceProviders("EGGD").empty()); } - TEST_F(AirfieldOwnershipManagerTest, GetOwnedReturnsOwnedAirfields) + TEST_F(AirfieldOwnershipManagerTest, RefreshAfterLogOffChangesDeliveryProvider) { - ActiveCallsign active1("EGGD_TWR", "Testy McTestface", controller1); - ActiveCallsign active2("EGGD_GND", "Testy McTestface 2", controller2); - this->activeCallsigns.AddCallsign(active1); - this->activeCallsigns.AddCallsign(active2); + this->activeCallsigns.AddCallsign(towerCallsign); + this->activeCallsigns.AddCallsign(groundCallsign); + this->manager.RefreshOwner("EGGD"); - EXPECT_EQ(1, this->manager.GetOwnedAirfields("EGGD_GND").size()); - EXPECT_TRUE("EGGD" == this->manager.GetOwnedAirfields("EGGD_GND").begin()->get().GetIcao()); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_GND"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); + + this->activeCallsigns.RemoveCallsign(groundCallsign); + this->manager.RefreshOwner("EGGD"); + EXPECT_EQ( + this->activeCallsigns.GetCallsign("EGGD_TWR"), + *this->providers->DeliveryProviderForAirfield("EGGD")->controller); } - TEST_F(AirfieldOwnershipManagerTest, ItHasANoOwnerObject) + TEST_F(AirfieldOwnershipManagerTest, FlushRemovesAllProviders) { - EXPECT_TRUE("" == this->manager.NotFoundCallsign().GetCallsign()); - EXPECT_TRUE("" == this->manager.NotFoundCallsign().GetControllerName()); + this->activeCallsigns.AddCallsign(towerCallsign); + this->activeCallsigns.AddCallsign(groundCallsign); + + this->manager.RefreshOwner("EGGD"); + this->manager.Flush(); + + EXPECT_TRUE(this->providers->GetServiceProviders("EGGD").empty()); } } // namespace UKControllerPluginTest::Ownership diff --git a/test/plugin/ownership/AirfieldOwnershipModuleTest.cpp b/test/plugin/ownership/AirfieldOwnershipModuleTest.cpp index e4f75ab29..60d5bd11e 100644 --- a/test/plugin/ownership/AirfieldOwnershipModuleTest.cpp +++ b/test/plugin/ownership/AirfieldOwnershipModuleTest.cpp @@ -1,7 +1,7 @@ #include "bootstrap/PersistenceContainer.h" #include "command/CommandHandlerCollection.h" #include "controller/ActiveCallsignCollection.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "ownership/AirfieldOwnershipModule.h" using ::testing::NiceMock; diff --git a/test/plugin/ownership/AirfieldServiceProviderCollectionTest.cpp b/test/plugin/ownership/AirfieldServiceProviderCollectionTest.cpp new file mode 100644 index 000000000..899ed9a0e --- /dev/null +++ b/test/plugin/ownership/AirfieldServiceProviderCollectionTest.cpp @@ -0,0 +1,293 @@ +#include "controller/ActiveCallsign.h" +#include "controller/ControllerPosition.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" + +using ::testing::Test; +using UKControllerPlugin::Controller::ActiveCallsign; +using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; + +namespace UKControllerPluginTest::Ownership { + + class AirfieldServiceProviderCollectionTest : public Test + { + public: + AirfieldServiceProviderCollectionTest() + : towerController(1, "EGGD_TWR", 133.850, {"EGGD"}, true, true), + groundController(2, "EGGD_GND", 121.920, {"EGGD"}, true, true), + approachController(4, "EGGD_APP", 121.920, {"EGGD"}, true, true) + { + } + + [[nodiscard]] static auto + CreateProvision(const ControllerPosition& position, ServiceType serviceType, bool isUser) + -> std::shared_ptr + { + return std::make_shared( + serviceType, std::make_shared(position.GetCallsign(), "Test", position, isUser)); + } + + AirfieldServiceProviderCollection collection; + ControllerPosition towerController; + ControllerPosition groundController; + ControllerPosition approachController; + }; + + TEST_F(AirfieldServiceProviderCollectionTest, AirfieldsStartWithNoProviders) + { + EXPECT_TRUE(collection.GetServiceProviders("EGGD").empty()); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItSetsProvidersForAirfield) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Ground, true)); + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_EQ(providers, collection.GetServiceProviders("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItFlushesAllProviders) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Ground, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, false)); + collection.SetProvidersForAirfield("EGGD", providers); + collection.SetProvidersForAirfield("EGFF", providers); + + collection.Flush(); + + EXPECT_TRUE(collection.GetServiceProviders("EGGD").empty()); + EXPECT_TRUE(collection.GetServiceProviders("EGFF").empty()); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItFlushesProvidersForAirfield) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Ground, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, false)); + collection.SetProvidersForAirfield("EGGD", providers); + collection.SetProvidersForAirfield("EGFF", providers); + + collection.FlushForAirfield("EGGD"); + + EXPECT_TRUE(collection.GetServiceProviders("EGGD").empty()); + EXPECT_EQ(providers, collection.GetServiceProviders("EGFF")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsWhenUserIsntProvidingDelivery) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, false)); + providers.push_back(CreateProvision(towerController, ServiceType::Delivery, false)); + providers.push_back(CreateProvision(towerController, ServiceType::Ground, true)); + collection.SetProvidersForAirfield("EGGD", providers); + collection.SetProvidersForAirfield("EGFF", providers); + + EXPECT_TRUE(collection.GetAirfieldsWhereUserIsProvidingDelivery().empty()); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsWhereUserIsProvidingDelivery) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Delivery, true)); + collection.SetProvidersForAirfield("EGGD", providers); + collection.SetProvidersForAirfield("EGFF", providers); + + std::vector expected{"EGFF", "EGGD"}; + EXPECT_EQ(expected, collection.GetAirfieldsWhereUserIsProvidingDelivery()); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsDeliveryProviderForAirfield) + { + auto provider1 = CreateProvision(groundController, ServiceType::Delivery, true); + + std::vector> providers; + providers.push_back(provider1); + providers.push_back(CreateProvision(towerController, ServiceType::Ground, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_EQ(provider1, collection.DeliveryProviderForAirfield("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsNullptrIfNobodyProvidingDelivery) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_EQ(nullptr, collection.DeliveryProviderForAirfield("EGFF")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ReturnsIfDeliveryIsProvidedAtAirfield) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Ground, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_TRUE(collection.AirfieldHasDeliveryProvider("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ReturnsIfDeliveryIsNotProvidedAtAirfield) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Ground, true)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_FALSE(collection.AirfieldHasDeliveryProvider("EGFF")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsDeliveryProviderForAirfieldIsUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_TRUE(collection.DeliveryControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsDeliveryProviderForAirfieldIsNotUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Delivery, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_FALSE(collection.DeliveryControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsFinalApproachProviderForAirfieldIsUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::FinalApproach, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_TRUE(collection.FinalApproachControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsFinalApproachProviderForAirfieldIsNotUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::FinalApproach, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_FALSE(collection.FinalApproachControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsApproachProviderForAirfieldIsUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Approach, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_TRUE(collection.ApproachControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsApproachProviderForAirfieldIsNotUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Approach, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_FALSE(collection.ApproachControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsTowerProviderForAirfieldIsUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Tower, true)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_TRUE(collection.TowerControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsTowerProviderForAirfieldIsNotUser) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Tower, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_FALSE(collection.TowerControlProvidedByUser("EGGD")); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsTrueIfServiceProvidedAtAirfield) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Ground, false)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, false)); + providers.push_back(CreateProvision(approachController, ServiceType::Approach, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_TRUE(collection.ServiceProvidedAtAirfield("EGGD", ServiceType::Ground)); + EXPECT_TRUE(collection.ServiceProvidedAtAirfield("EGGD", ServiceType::Tower)); + EXPECT_TRUE(collection.ServiceProvidedAtAirfield("EGGD", ServiceType::Approach)); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsFalseNotProvidedAtAirfield) + { + std::vector> providers; + providers.push_back(CreateProvision(groundController, ServiceType::Ground, false)); + providers.push_back(CreateProvision(towerController, ServiceType::Tower, false)); + providers.push_back(CreateProvision(approachController, ServiceType::Approach, false)); + collection.SetProvidersForAirfield("EGGD", providers); + + EXPECT_FALSE(collection.ServiceProvidedAtAirfield("EGGD", ServiceType::Delivery)); + EXPECT_FALSE(collection.ServiceProvidedAtAirfield("EGGD", ServiceType::FinalApproach)); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsProvidersOfServiceAtAirfield) + { + auto groundProvision = CreateProvision(groundController, ServiceType::Ground, false); + auto groundProvision2 = CreateProvision(towerController, ServiceType::Ground, false); + auto approachProvision = CreateProvision(approachController, ServiceType::Approach, false); + std::vector> providers; + providers.push_back(groundProvision); + providers.push_back(groundProvision2); + providers.push_back(approachProvision); + collection.SetProvidersForAirfield("EGGD", providers); + + auto groundProviders = collection.GetProvidersForServiceAtAirfield("EGGD", ServiceType::Ground); + EXPECT_EQ(2, groundProviders.size()); + EXPECT_EQ(groundProvision, groundProviders[0]); + EXPECT_EQ(groundProvision2, groundProviders[1]); + } + + TEST_F(AirfieldServiceProviderCollectionTest, ItReturnsAirfieldsWhereUserProvidingServicesInBitwise) + { + // Will show - right service, is user + collection.SetProvidersForAirfield("EGGD", {CreateProvision(groundController, ServiceType::Ground, true)}); + + // Won't show - right service, not user + collection.SetProvidersForAirfield("EGFF", {CreateProvision(groundController, ServiceType::Ground, false)}); + + // Won't show - wrong service, is user + collection.SetProvidersForAirfield("EGKK", {CreateProvision(groundController, ServiceType::Delivery, true)}); + + // Will show - right service, is user, to test bitwise + collection.SetProvidersForAirfield("EGLL", {CreateProvision(groundController, ServiceType::Approach, true)}); + + // Will show, at least one service matches + collection.SetProvidersForAirfield( + "EGLC", + {CreateProvision(groundController, ServiceType::Ground, true), + CreateProvision(groundController, ServiceType::Delivery, true)}); + + // Will show, two matches + collection.SetProvidersForAirfield("EGMC", {CreateProvision(groundController, ServiceType::Ground, true)}); + collection.SetProvidersForAirfield("EGMC", {CreateProvision(groundController, ServiceType::Approach, true)}); + + EXPECT_EQ( + std::vector({"EGGD", "EGLC", "EGLL", "EGMC"}), + collection.GetAirfieldsWhereUserProvidingServices(ServiceType::Ground | ServiceType::Approach)); + } +} // namespace UKControllerPluginTest::Ownership diff --git a/test/plugin/pch/pch.h b/test/plugin/pch/pch.h index dac994784..11f550294 100644 --- a/test/plugin/pch/pch.h +++ b/test/plugin/pch/pch.h @@ -49,6 +49,6 @@ #include "../mock/MockRunwayDialogAwareInterface.h" #include "../mock/MockSectorFileProviderInterface.h" #include "../mock/MockSocket.h" -#include "../mock/MockTaskRunnerInterface.h" +#include "mock/MockTaskRunnerInterface.h" #include "../mock/MockUserSettingAwareInterface.h" #include "../mock/MockUserSettingProviderInterface.h" diff --git a/test/plugin/prenote/AcknowledgePrenoteMessageTest.cpp b/test/plugin/prenote/AcknowledgePrenoteMessageTest.cpp index eba5c4c8e..983fc046a 100644 --- a/test/plugin/prenote/AcknowledgePrenoteMessageTest.cpp +++ b/test/plugin/prenote/AcknowledgePrenoteMessageTest.cpp @@ -37,7 +37,7 @@ namespace UKControllerPluginTest::Prenote { // Default the user to active userPosition = std::make_shared( 2, "LON_S_CTR", 129.420, std::vector{"EGKK"}, true, false, true); - callsigns.AddUserCallsign(ActiveCallsign("LON_S_CTR", "Test", *userPosition)); + callsigns.AddUserCallsign(ActiveCallsign("LON_S_CTR", "Test", *userPosition, true)); } ActiveCallsignCollection callsigns; diff --git a/test/plugin/prenote/CancelPrenoteMessageMenuTest.cpp b/test/plugin/prenote/CancelPrenoteMessageMenuTest.cpp index 9a16b7eaa..dd1399ed7 100644 --- a/test/plugin/prenote/CancelPrenoteMessageMenuTest.cpp +++ b/test/plugin/prenote/CancelPrenoteMessageMenuTest.cpp @@ -53,7 +53,7 @@ namespace UKControllerPluginTest::Prenote { 3, "LON_SC_CTR", 132.600, std::vector{"EGKK"}, true, false)); // Default the user to active - callsigns.AddUserCallsign(ActiveCallsign("EGKK_TWR", "Test", *controllers.FetchPositionById(1))); + callsigns.AddUserCallsign(ActiveCallsign("EGKK_TWR", "Test", *controllers.FetchPositionById(1), true)); } ActiveCallsignCollection callsigns; @@ -182,7 +182,7 @@ namespace UKControllerPluginTest::Prenote { TEST_F(CancelPrenoteMessageMenuTest, ItDoesntDisplayTheCancelMenuIfUserCannotMakePrenotes) { this->callsigns.Flush(); - callsigns.AddUserCallsign(ActiveCallsign("LON_S_CTR", "Test", *controllers.FetchPositionById(2))); + callsigns.AddUserCallsign(ActiveCallsign("LON_S_CTR", "Test", *controllers.FetchPositionById(2), true)); EXPECT_CALL(mockPlugin, TriggerPopupList(testing::_, testing::_, testing::_)).Times(0); diff --git a/test/plugin/prenote/NewPrenotePushEventHandlerTest.cpp b/test/plugin/prenote/NewPrenotePushEventHandlerTest.cpp index d77835a79..f76c9f7c5 100644 --- a/test/plugin/prenote/NewPrenotePushEventHandlerTest.cpp +++ b/test/plugin/prenote/NewPrenotePushEventHandlerTest.cpp @@ -34,7 +34,7 @@ namespace UKControllerPluginTest::Prenote { controllers.AddPosition(std::make_shared( 2, "LON_W_CTR", 126.020, std::vector{"EGGD", "EGFF"}, true, true)); - callsigns.AddUserCallsign(ActiveCallsign("LON_W_CTR", "Foo", *controllers.FetchPositionById(2))); + callsigns.AddUserCallsign(ActiveCallsign("LON_W_CTR", "Foo", *controllers.FetchPositionById(2), true)); }; /* @@ -138,7 +138,7 @@ namespace UKControllerPluginTest::Prenote { TEST_F(NewPrenotePushEventHandlerTest, ItDoesntPlaySoundIfUserIsADifferentController) { callsigns.Flush(); - callsigns.AddUserCallsign(ActiveCallsign("LON_W_CTR", "Foo", *controllers.FetchPositionById(1))); + callsigns.AddUserCallsign(ActiveCallsign("LON_W_CTR", "Foo", *controllers.FetchPositionById(1), true)); EXPECT_CALL(mockWindows, PlayWave(testing::_)).Times(0); this->handler.ProcessPushEvent(MakePushEvent()); diff --git a/test/plugin/prenote/PendingPrenoteListTest.cpp b/test/plugin/prenote/PendingPrenoteListTest.cpp index 62b47717f..08917a9f6 100644 --- a/test/plugin/prenote/PendingPrenoteListTest.cpp +++ b/test/plugin/prenote/PendingPrenoteListTest.cpp @@ -37,7 +37,7 @@ namespace UKControllerPluginTest::Prenote { 2, "EGFF_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, true); controllers.AddPosition(position); auto controllerCallsign = - std::make_shared("EGFF_APP", "Test 1", *position); + std::make_shared("EGFF_APP", "Test 1", *position, true); this->activeCallsigns.AddUserCallsign(*controllerCallsign); } diff --git a/test/plugin/prenote/PrenoteEventHandlerTest.cpp b/test/plugin/prenote/PrenoteEventHandlerTest.cpp index 632d1a650..0a6dabc82 100644 --- a/test/plugin/prenote/PrenoteEventHandlerTest.cpp +++ b/test/plugin/prenote/PrenoteEventHandlerTest.cpp @@ -1,24 +1,18 @@ #include "prenote/PrenoteEventHandler.h" #include "prenote/PrenoteService.h" -#include "ownership/AirfieldOwnershipManager.h" -#include "airfield/AirfieldCollection.h" #include "controller/ActiveCallsignCollection.h" #include "message/UserMessager.h" -#include "prenote/AbstractPrenote.h" -#include "airfield/AirfieldModel.h" -#include "euroscope/UserSetting.h" #include "euroscope/GeneralSettingsEntries.h" +#include "ownership/AirfieldServiceProviderCollection.h" using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; -using UKControllerPlugin::Airfield::AirfieldCollection; -using UKControllerPlugin::Airfield::AirfieldModel; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Euroscope::GeneralSettingsEntries; using UKControllerPlugin::Euroscope::UserSetting; using UKControllerPlugin::Message::UserMessager; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Prenote::AbstractPrenote; using UKControllerPlugin::Prenote::PrenoteEventHandler; using UKControllerPlugin::Prenote::PrenoteService; @@ -34,10 +28,7 @@ namespace UKControllerPluginTest { public: void SetUp(void) { - this->airfields = std::make_unique(); - this->airfields->AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"EGKK_GND"}))); - this->airfieldOwnership = - std::make_unique(*this->airfields, this->activeCallsigns); + this->airfieldOwnership = std::make_unique(); this->messager = std::make_unique(this->mockPlugin); std::unique_ptr service = @@ -50,8 +41,7 @@ namespace UKControllerPluginTest { NiceMock mockSettingProvider; std::unique_ptr eventHandler; std::unique_ptr service; - std::unique_ptr airfields; - std::unique_ptr airfieldOwnership; + std::unique_ptr airfieldOwnership; ActiveCallsignCollection activeCallsigns; std::unique_ptr messager; NiceMock mockFlightplan; diff --git a/test/plugin/prenote/PrenoteServiceFactoryTest.cpp b/test/plugin/prenote/PrenoteServiceFactoryTest.cpp index dc0a093dc..d70a63d8a 100644 --- a/test/plugin/prenote/PrenoteServiceFactoryTest.cpp +++ b/test/plugin/prenote/PrenoteServiceFactoryTest.cpp @@ -4,8 +4,7 @@ #include "controller/ControllerPositionHierarchyFactory.h" #include "prenote/PrenoteFactory.h" #include "controller/ActiveCallsignCollection.h" -#include "ownership/AirfieldOwnershipManager.h" -#include "airfield/AirfieldCollection.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "message/UserMessager.h" #include "prenote/PrenoteService.h" #include "bootstrap/BootstrapWarningMessage.h" @@ -13,14 +12,13 @@ using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; -using UKControllerPlugin::Airfield::AirfieldCollection; using UKControllerPlugin::Bootstrap::BootstrapWarningMessage; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; using UKControllerPlugin::Controller::ControllerPositionCollection; using UKControllerPlugin::Controller::ControllerPositionHierarchyFactory; using UKControllerPlugin::Message::UserMessager; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; using UKControllerPlugin::Prenote::PrenoteFactory; using UKControllerPlugin::Prenote::PrenoteServiceFactory; using UKControllerPluginTest::Euroscope::MockEuroscopePluginLoopbackInterface; @@ -40,8 +38,7 @@ namespace UKControllerPluginTest { new ControllerPosition(2, "EGKK_TWR", 124.220, {"EGKK"}, true, false))); this->hierarchyFactory = std::make_unique(*this->collection); this->prenoteFactory = std::make_unique(*this->hierarchyFactory); - this->airfieldOwnership = std::unique_ptr( - new AirfieldOwnershipManager(AirfieldCollection(), this->activeCallsigns)); + this->airfieldOwnership = std::make_unique(); this->userMessager = std::make_unique(this->mockPlugin); this->serviceFactory = std::make_unique(*this->prenoteFactory, *this->userMessager); @@ -51,7 +48,7 @@ namespace UKControllerPluginTest { std::unique_ptr hierarchyFactory; std::unique_ptr prenoteFactory; std::unique_ptr serviceFactory; - std::unique_ptr airfieldOwnership; + std::unique_ptr airfieldOwnership; std::unique_ptr userMessager; NiceMock mockPlugin; ActiveCallsignCollection activeCallsigns; diff --git a/test/plugin/prenote/PrenoteServiceTest.cpp b/test/plugin/prenote/PrenoteServiceTest.cpp index 5d5e8b78e..98f9ef9b6 100644 --- a/test/plugin/prenote/PrenoteServiceTest.cpp +++ b/test/plugin/prenote/PrenoteServiceTest.cpp @@ -1,9 +1,9 @@ #include "prenote/PrenoteService.h" -#include "ownership/AirfieldOwnershipManager.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" #include "controller/ActiveCallsignCollection.h" #include "airfield/AirfieldCollection.h" #include "controller/ControllerPosition.h" -#include "controller/ActiveCallsign.h" #include "message/UserMessager.h" #include "prenote/AbstractPrenote.h" #include "controller/ControllerPositionHierarchy.h" @@ -19,7 +19,9 @@ using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; using UKControllerPlugin::Controller::ControllerPositionHierarchy; using UKControllerPlugin::Message::UserMessager; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; using UKControllerPlugin::Prenote::AbstractPrenote; using UKControllerPlugin::Prenote::PrenoteService; using UKControllerPluginTest::Euroscope::MockEuroScopeCFlightPlanInterface; @@ -59,10 +61,7 @@ namespace UKControllerPluginTest { public: void SetUp(void) { - this->airfields = std::make_unique(); - this->airfields->AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"EGKK_GND"}))); - this->airfieldOwnership = - std::make_unique(*this->airfields, this->activeCallsigns); + this->airfieldOwnership = std::make_unique(); // Add controllers this->controllerUser = std::unique_ptr( @@ -72,23 +71,29 @@ namespace UKControllerPluginTest { this->controllerNoLondon = std::unique_ptr( new ControllerPosition(3, "LON_S_CTR", 129.420, {"EGKK"}, true, false)); this->activeCallsigns.AddUserCallsign( - ActiveCallsign("EGKK_GND", "Testy McTestface", *this->controllerUser)); + ActiveCallsign("EGKK_GND", "Testy McTestface", *this->controllerUser, true)); this->activeCallsigns.AddCallsign( - ActiveCallsign("EGKK_APP", "Testy McTestface II", *this->controllerOther)); + ActiveCallsign("EGKK_APP", "Testy McTestface II", *this->controllerOther, false)); this->activeCallsigns.AddCallsign( - ActiveCallsign("LON_S_CTR", "Testy McTestface III", *this->controllerNoLondon)); + ActiveCallsign("LON_S_CTR", "Testy McTestface III", *this->controllerNoLondon, false)); this->hierarchy = std::make_unique(); - this->airfieldOwnership->RefreshOwner("EGKK"); + + this->airfieldOwnership->SetProvidersForAirfield( + "EGKK", + std::vector>{std::make_shared( + ServiceType::Delivery, + std::make_shared( + this->activeCallsigns.GetUserCallsign()))}); + this->messager = std::make_unique(this->mockPlugin); this->service = std::make_unique(*this->airfieldOwnership, this->activeCallsigns, *this->messager); }; std::unique_ptr service; - std::unique_ptr airfields; - std::unique_ptr airfieldOwnership; + std::unique_ptr airfieldOwnership; ActiveCallsignCollection activeCallsigns; std::unique_ptr controllerUser; std::unique_ptr controllerOther; @@ -147,7 +152,7 @@ namespace UKControllerPluginTest { // The target controller is offline. this->hierarchy->AddPosition(*this->controllerOther); this->activeCallsigns.RemoveCallsign( - ActiveCallsign("EGKK_APP", "Testy McTestface II", *this->controllerOther)); + ActiveCallsign("EGKK_APP", "Testy McTestface II", *this->controllerOther, false)); this->service->AddPrenote(std::make_unique(std::move(this->hierarchy), true)); EXPECT_NO_THROW(this->service->SendPrenotes(this->mockFlightplan)); diff --git a/test/plugin/prenote/PrenoteUserMessageTest.cpp b/test/plugin/prenote/PrenoteUserMessageTest.cpp index aa6a8f57d..c7458c005 100644 --- a/test/plugin/prenote/PrenoteUserMessageTest.cpp +++ b/test/plugin/prenote/PrenoteUserMessageTest.cpp @@ -24,7 +24,7 @@ namespace UKControllerPluginTest::Prenote { ON_CALL(mockFlightplan, GetCallsign).WillByDefault(Return("BAW123")); this->callsign = std::make_unique( - "LTC_SE_CTR", "Testy McTestface", ControllerPosition(1, "LTC_SE_CTR", 121.225, {}, true, false)); + "LTC_SE_CTR", "Testy McTestface", ControllerPosition(1, "LTC_SE_CTR", 121.225, {}, true, false), false); this->departurePrenote = std::make_unique(nullptr, "EGKK", "BIG2X"); this->prenoteMessage = std::make_unique(*this->departurePrenote, *this->callsign, this->mockFlightplan); diff --git a/test/plugin/prenote/SendPrenoteMenuTest.cpp b/test/plugin/prenote/SendPrenoteMenuTest.cpp index 3feb311d4..543d24926 100644 --- a/test/plugin/prenote/SendPrenoteMenuTest.cpp +++ b/test/plugin/prenote/SendPrenoteMenuTest.cpp @@ -75,13 +75,15 @@ namespace UKControllerPluginTest::Prenote { callsigns.AddCallsign(ActiveCallsign( this->controllers.FetchPositionById(positionId)->GetCallsign(), "Test", - *controllers.FetchPositionById(positionId))); + *controllers.FetchPositionById(positionId), + false)); this->mockPlugin.AddAllControllersItem(mockController); } else { callsigns.AddUserCallsign(ActiveCallsign( this->controllers.FetchPositionById(positionId)->GetCallsign(), "Test", - *controllers.FetchPositionById(positionId))); + *controllers.FetchPositionById(positionId), + true)); this->mockPlugin.AddAllControllersItem(mockController); } } @@ -134,7 +136,7 @@ namespace UKControllerPluginTest::Prenote { TEST_F(SendPrenoteMenuTest, ItDoesntDisplayControllerMenuIfUserIsNotActive) { - callsigns.RemoveCallsign(ActiveCallsign("EGKK_APP", "Test", *controllers.FetchPositionById(1))); + callsigns.RemoveCallsign(ActiveCallsign("EGKK_APP", "Test", *controllers.FetchPositionById(1), true)); EXPECT_CALL(mockPlugin, TriggerPopupList(testing::_, testing::_, testing::_)).Times(0); menu.DisplayControllerSelectionMenu(mockFlightplan, {0, 0}); @@ -142,9 +144,9 @@ namespace UKControllerPluginTest::Prenote { TEST_F(SendPrenoteMenuTest, ItDoesntDisplayControllerMenuIfUserCantSendPrenotes) { - callsigns.RemoveCallsign(ActiveCallsign("EGKK_APP", "Test", *controllers.FetchPositionById(1))); - callsigns.RemoveCallsign(ActiveCallsign("LON_SC_CTR", "Test", *controllers.FetchPositionById(3))); - callsigns.AddUserCallsign(ActiveCallsign("LON_SC_CTR", "Test", *controllers.FetchPositionById(3))); + callsigns.RemoveCallsign(ActiveCallsign("EGKK_APP", "Test", *controllers.FetchPositionById(1), true)); + callsigns.RemoveCallsign(ActiveCallsign("LON_SC_CTR", "Test", *controllers.FetchPositionById(3), false)); + callsigns.AddUserCallsign(ActiveCallsign("LON_SC_CTR", "Test", *controllers.FetchPositionById(3), false)); EXPECT_CALL(mockPlugin, TriggerPopupList(testing::_, testing::_, testing::_)).Times(0); menu.DisplayControllerSelectionMenu(mockFlightplan, {0, 0}); diff --git a/test/plugin/releases/DepartureReleaseDecisionListTest.cpp b/test/plugin/releases/DepartureReleaseDecisionListTest.cpp index 53f7811e4..3777ae6fa 100644 --- a/test/plugin/releases/DepartureReleaseDecisionListTest.cpp +++ b/test/plugin/releases/DepartureReleaseDecisionListTest.cpp @@ -37,8 +37,8 @@ namespace UKControllerPluginTest { auto position = std::make_shared( 2, "EGFF_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, true); controllers.AddPosition(position); - auto controllerCallsign = - std::make_shared("EGFF_APP", "Test 1", *position); + auto controllerCallsign = std::make_shared( + "EGFF_APP", "Test 1", *position, true); this->activeCallsigns.AddUserCallsign(*controllerCallsign); } diff --git a/test/plugin/releases/DepartureReleaseEventHandlerTest.cpp b/test/plugin/releases/DepartureReleaseEventHandlerTest.cpp index 47f48ab59..5b22aa221 100644 --- a/test/plugin/releases/DepartureReleaseEventHandlerTest.cpp +++ b/test/plugin/releases/DepartureReleaseEventHandlerTest.cpp @@ -40,13 +40,13 @@ namespace UKControllerPluginTest::Releases { position1 = std::make_shared( 2, "EGFF_APP", 125.850, std::vector{"EGGD", "EGFF"}, true, true); controllers.AddPosition(position1); - controller1Callsign = std::make_shared("EGFF_APP", "Test 1", *position1); + controller1Callsign = std::make_shared("EGFF_APP", "Test 1", *position1, false); // Add position 2 position2 = std::make_shared( 3, "EGFF_TWR", 123.450, std::vector{"EGGD", "EGFF"}, false, false); controllers.AddPosition(position2); - controller2Callsign = std::make_shared("EGFF_1_TWR", "Test 2", *position2); + controller2Callsign = std::make_shared("EGFF_1_TWR", "Test 2", *position2, false); // Add position 3 position3 = std::make_shared( diff --git a/test/plugin/squawk/SquawkAssignmentTest.cpp b/test/plugin/squawk/SquawkAssignmentTest.cpp index 7d334d830..4fdfda3cf 100644 --- a/test/plugin/squawk/SquawkAssignmentTest.cpp +++ b/test/plugin/squawk/SquawkAssignmentTest.cpp @@ -3,23 +3,23 @@ #include "airfield/AirfieldCollection.h" #include "controller/ControllerPosition.h" #include "controller/ActiveCallsign.h" -#include "ownership/AirfieldOwnershipManager.h" -#include "airfield/AirfieldModel.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "controller/ActiveCallsignCollection.h" #include "flightplan/StoredFlightplan.h" +#include "ownership/ServiceProvision.h" using ::testing::_; using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; -using UKControllerPlugin::Airfield::AirfieldCollection; -using UKControllerPlugin::Airfield::AirfieldModel; using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; using UKControllerPlugin::Flightplan::StoredFlightplan; using UKControllerPlugin::Flightplan::StoredFlightplanCollection; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; using UKControllerPlugin::Squawk::SquawkAssignment; using UKControllerPluginTest::Curl::MockCurlApi; using UKControllerPluginTest::Euroscope::MockEuroScopeCControllerInterface; @@ -35,8 +35,7 @@ namespace UKControllerPluginTest { { public: SquawkAssignmentTest(void) - : airfieldOwnership(this->airfields, this->activeCallsigns), - kkApp(1, "EGKK_APP", 126.820, {"EGKK"}, true, false), + : kkApp(1, "EGKK_APP", 126.820, {"EGKK"}, true, false), lonS(2, "LON_S_CTR", 129.420, {"EGLL"}, true, false), assignment(this->plans, this->pluginLoopback, this->airfieldOwnership, this->activeCallsigns){ @@ -51,10 +50,15 @@ namespace UKControllerPluginTest { this->mockSelfController = std::shared_ptr(new NiceMock); - this->airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"EGKK_APP"}))); - this->activeCallsigns.AddUserCallsign(ActiveCallsign("EGKK_APP", "Testy McTestface", this->kkApp)); - this->activeCallsigns.AddCallsign(ActiveCallsign("LON_S_CTR", "Boaty McBoatface", this->lonS)); - this->airfieldOwnership.RefreshOwner("EGKK"); + this->activeCallsigns.AddUserCallsign( + ActiveCallsign("EGKK_APP", "Testy McTestface", this->kkApp, true)); + this->activeCallsigns.AddCallsign(ActiveCallsign("LON_S_CTR", "Boaty McBoatface", this->lonS, false)); + this->airfieldOwnership.SetProvidersForAirfield( + "EGKK", + std::vector>{std::make_shared( + ServiceType::Delivery, + std::make_shared( + this->activeCallsigns.GetUserCallsign()))}); ON_CALL(*this->mockSelfController, IsVatsimRecognisedController()).WillByDefault(Return(true)); } @@ -66,8 +70,7 @@ namespace UKControllerPluginTest { std::shared_ptr mockSelfController; StoredFlightplanCollection plans; ActiveCallsignCollection activeCallsigns; - AirfieldOwnershipManager airfieldOwnership; - AirfieldCollection airfields; + AirfieldServiceProviderCollection airfieldOwnership; ControllerPosition kkApp; ControllerPosition lonS; SquawkAssignment assignment; @@ -271,7 +274,7 @@ namespace UKControllerPluginTest { this->activeCallsigns.Flush(); ControllerPosition controller(55, "LON_S_CTR", 129.420, {"EGLL"}, true, false); - this->activeCallsigns.AddUserCallsign(ActiveCallsign("LON_S_CTR", "Boaty McBoatface", controller)); + this->activeCallsigns.AddUserCallsign(ActiveCallsign("LON_S_CTR", "Boaty McBoatface", controller, true)); EXPECT_FALSE(this->assignment.LocalAssignmentNeeded(*this->mockFlightplan, *this->mockRadarTarget)); } diff --git a/test/plugin/squawk/SquawkEventHandlerTest.cpp b/test/plugin/squawk/SquawkEventHandlerTest.cpp index 5f1d9110b..d744cb7a9 100644 --- a/test/plugin/squawk/SquawkEventHandlerTest.cpp +++ b/test/plugin/squawk/SquawkEventHandlerTest.cpp @@ -1,26 +1,17 @@ #include "squawk/SquawkEventHandler.h" #include "squawk/SquawkGenerator.h" -#include "curl/CurlResponse.h" #include "flightplan/StoredFlightplanCollection.h" -#include "flightplan/StoredFlightplan.h" #include "controller/ActiveCallsignCollection.h" -#include "ownership/AirfieldOwnershipManager.h" -#include "airfield/AirfieldCollection.h" -#include "controller/ActiveCallsign.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" #include "controller/ControllerPosition.h" -#include "airfield/AirfieldModel.h" #include "api/ApiRequestBuilder.h" -#include "curl/CurlRequest.h" #include "squawk/SquawkAssignment.h" #include "login/Login.h" #include "controller/ControllerStatusEventHandlerCollection.h" -#include "euroscope/UserSetting.h" #include "euroscope/GeneralSettingsEntries.h" -#include "squawk/ApiSquawkAllocation.h" #include "squawk/ApiSquawkAllocationHandler.h" -using UKControllerPlugin::Airfield::AirfieldCollection; -using UKControllerPlugin::Airfield::AirfieldModel; using UKControllerPlugin::Api::ApiRequestBuilder; using UKControllerPlugin::Controller::ActiveCallsign; using UKControllerPlugin::Controller::ActiveCallsignCollection; @@ -33,7 +24,9 @@ using UKControllerPlugin::Euroscope::GeneralSettingsEntries; using UKControllerPlugin::Euroscope::UserSetting; using UKControllerPlugin::Flightplan::StoredFlightplan; using UKControllerPlugin::Flightplan::StoredFlightplanCollection; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; using UKControllerPlugin::Squawk::ApiSquawkAllocation; using UKControllerPlugin::Squawk::ApiSquawkAllocationHandler; using UKControllerPlugin::Squawk::SquawkAssignment; @@ -73,8 +66,8 @@ namespace UKControllerPluginTest { this->apiSquawkAllocations), assignmentRules(this->plans, this->pluginLoopback, this->airfieldOwnership, this->activeCallsigns), controller(1, "EGKK_APP", 126.820, {"EGKK"}, true, false), - userCallsign("EGKK_APP", "Testy McTestface", this->controller), - airfieldOwnership(this->airfields, this->activeCallsigns), + userCallsign("EGKK_APP", "Testy McTestface", this->controller, true), + notUserCallsign("EGKK_APP", "Testy McTestface", this->controller, false), handler(this->generator, this->activeCallsigns, this->plans, this->pluginLoopback, this->login, false) { @@ -88,9 +81,12 @@ namespace UKControllerPluginTest { ON_CALL(*this->mockSelfController, IsVatsimRecognisedController()).WillByDefault(Return(true)); - this->airfields.AddAirfield(std::unique_ptr(new AirfieldModel("EGKK", {"EGKK_APP"}))); this->activeCallsigns.AddUserCallsign(this->userCallsign); - this->airfieldOwnership.RefreshOwner("EGKK"); + this->airfieldOwnership.SetProvidersForAirfield( + "EGKK", + std::vector>{std::make_shared( + ServiceType::Delivery, + std::make_shared(this->userCallsign))}); // By default, lets assume we've been logged in for a while. this->login.SetLoginTime(std::chrono::system_clock::now() - std::chrono::minutes(5)); @@ -187,8 +183,8 @@ namespace UKControllerPluginTest { ActiveCallsignCollection activeCallsigns; ControllerPosition controller; ActiveCallsign userCallsign; - AirfieldOwnershipManager airfieldOwnership; - AirfieldCollection airfields; + ActiveCallsign notUserCallsign; + AirfieldServiceProviderCollection airfieldOwnership; SquawkEventHandler handler; }; @@ -469,7 +465,7 @@ namespace UKControllerPluginTest { EXPECT_CALL(*this->mockFlightplan, SetSquawk(testing::_)).Times(0); this->expectGeneralAssignment(); - handler.ActiveCallsignAdded(this->userCallsign, true); + handler.ActiveCallsignAdded(this->userCallsign); this->AssertGeneralAssignment(); } @@ -485,7 +481,7 @@ namespace UKControllerPluginTest { this->ExpectNoAssignment(); - handler.ActiveCallsignAdded(this->userCallsign, false); + handler.ActiveCallsignAdded(this->userCallsign); } } // namespace Squawk } // namespace UKControllerPluginTest diff --git a/test/plugin/squawk/SquawkGeneratorTest.cpp b/test/plugin/squawk/SquawkGeneratorTest.cpp index c915cf650..5644d24f0 100644 --- a/test/plugin/squawk/SquawkGeneratorTest.cpp +++ b/test/plugin/squawk/SquawkGeneratorTest.cpp @@ -1,23 +1,19 @@ #include "squawk/SquawkGenerator.h" -#include "api/ApiInterface.h" #include "flightplan/StoredFlightplanCollection.h" -#include "flightplan/StoredFlightplan.h" #include "controller/ActiveCallsignCollection.h" #include "squawk/SquawkAssignment.h" -#include "ownership/AirfieldOwnershipManager.h" -#include "airfield/AirfieldCollection.h" +#include "ownership/AirfieldServiceProviderCollection.h" #include "controller/ControllerPosition.h" -#include "controller/ActiveCallsign.h" #include "api/ApiNotFoundException.h" -#include "squawk/ApiSquawkAllocation.h" #include "squawk/ApiSquawkAllocationHandler.h" +#include "ownership/ServiceProvision.h" using ::testing::_; using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; using ::testing::Throw; -using UKControllerPlugin::Airfield::AirfieldCollection; + using UKControllerPlugin::Api::ApiInterface; using UKControllerPlugin::Api::ApiNotFoundException; using UKControllerPlugin::Controller::ActiveCallsign; @@ -25,7 +21,9 @@ using UKControllerPlugin::Controller::ActiveCallsignCollection; using UKControllerPlugin::Controller::ControllerPosition; using UKControllerPlugin::Flightplan::StoredFlightplan; using UKControllerPlugin::Flightplan::StoredFlightplanCollection; -using UKControllerPlugin::Ownership::AirfieldOwnershipManager; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; using UKControllerPlugin::Squawk::ApiSquawkAllocation; using UKControllerPlugin::Squawk::ApiSquawkAllocationHandler; using UKControllerPlugin::Squawk::SquawkAssignment; @@ -50,8 +48,7 @@ namespace UKControllerPluginTest { this->mockFlightplan = std::make_shared>(); this->mockRadarTarget = std::make_shared>(); this->mockSelfController = std::make_shared>(); - this->airfieldOwnership = - std::make_unique(this->airfields, this->activeCallsigns); + this->airfieldOwnership = std::make_unique(); this->assignmentRules = std::make_unique( this->flightplans, this->pluginLoopback, *this->airfieldOwnership, this->activeCallsigns); this->generator = std::make_unique( @@ -65,8 +62,13 @@ namespace UKControllerPluginTest { this->controller = std::unique_ptr( new ControllerPosition(1, "EGKK_APP", 126.820, {"EGKK"}, true, false)); this->activeCallsigns.AddUserCallsign( - ActiveCallsign("EGKK_APP", "Testy McTestface", *this->controller)); - this->airfieldOwnership->RefreshOwner("EGKK"); + ActiveCallsign("EGKK_APP", "Testy McTestface", *this->controller, true)); + this->airfieldOwnership->SetProvidersForAirfield( + "EGKK", + std::vector>{std::make_shared( + ServiceType::Delivery, + std::make_shared( + this->activeCallsigns.GetUserCallsign()))}); ON_CALL(*this->mockSelfController, IsVatsimRecognisedController()).WillByDefault(Return(true)); } @@ -82,10 +84,9 @@ namespace UKControllerPluginTest { StoredFlightplanCollection flightplans; ActiveCallsignCollection activeCallsigns; std::unique_ptr assignmentRules; - std::unique_ptr airfieldOwnership; + std::unique_ptr airfieldOwnership; std::unique_ptr controller; std::shared_ptr squawkAllocationHandler; - AirfieldCollection airfields; }; TEST_F(SquawkGeneratorTest, GeneralSquawkReturnsFalseOnNoAction) @@ -489,7 +490,8 @@ namespace UKControllerPluginTest { ON_CALL(*this->mockFlightplan, IsTrackedByUser()).WillByDefault(Return(true)); - this->activeCallsigns.RemoveCallsign(ActiveCallsign("EGKK_APP", "Testy McTestface", *this->controller)); + this->activeCallsigns.RemoveCallsign( + ActiveCallsign("EGKK_APP", "Testy McTestface", *this->controller, true)); EXPECT_FALSE(this->generator->ForceLocalSquawkForAircraft(*this->mockFlightplan, *this->mockRadarTarget)); } diff --git a/test/testingutils/CMakeLists.txt b/test/testingutils/CMakeLists.txt index 85db4dd93..bca5d6af3 100644 --- a/test/testingutils/CMakeLists.txt +++ b/test/testingutils/CMakeLists.txt @@ -20,6 +20,7 @@ set(mock "mock/MockApiInterface.h" "mock/MockCurlApi.h" "mock/MockDialogProvider.h" + "mock/MockTaskRunnerInterface.h" "mock/MockWinApi.h" ) source_group("mock" FILES ${mock}) diff --git a/test/testingutils/helper/TestEnvironment.h b/test/testingutils/helper/TestEnvironment.h index f7d20357a..4eb36a42e 100644 --- a/test/testingutils/helper/TestEnvironment.h +++ b/test/testingutils/helper/TestEnvironment.h @@ -1,33 +1,36 @@ #pragma once #include "pch/pch.h" #include "log/LoggerFunctions.h" +#include "mock/MockTaskRunnerInterface.h" +#include "task/RunAsyncTask.h" +using UKControllerPluginTest::TaskManager::MockTaskRunnerInterface; class TestEnvironment : public ::testing::Environment { public: + virtual ~TestEnvironment() + { + } - virtual ~TestEnvironment() {} + virtual void SetUp() + { + // Put in a null logger + SetLoggerInstance( + std::make_shared("null_logger", std::make_shared())); - virtual void SetUp() - { - // Put in a null logger - SetLoggerInstance( - std::make_shared( - "null_logger", - std::make_shared() - ) - ); + // Put a task runner that just runs everything non-async + SetTaskRunner(std::make_shared()); - // Start up GDI so we can use its functions, even though not drawing to screen. - Gdiplus::GdiplusStartupInput gdiStartup; - GdiplusStartup(&this->gdiPlusToken, &gdiStartup, NULL); - } + // Start up GDI so we can use its functions, even though not drawing to screen. + Gdiplus::GdiplusStartupInput gdiStartup; + GdiplusStartup(&this->gdiPlusToken, &gdiStartup, NULL); + } - virtual void TearDown() - { - Gdiplus::GdiplusShutdown(this->gdiPlusToken); - } + virtual void TearDown() + { + Gdiplus::GdiplusShutdown(this->gdiPlusToken); + } - ULONG_PTR gdiPlusToken; + ULONG_PTR gdiPlusToken; }; diff --git a/test/testingutils/mock/MockApiInterface.h b/test/testingutils/mock/MockApiInterface.h index e0b099544..7eb3d49eb 100644 --- a/test/testingutils/mock/MockApiInterface.h +++ b/test/testingutils/mock/MockApiInterface.h @@ -26,6 +26,7 @@ namespace UKControllerPluginTest::Api { (const, override)); MOCK_METHOD(void, AcknowledgePrenoteMessage, (int, int), (const, override)); MOCK_METHOD(void, DeletePrenoteMessage, (int), (const, override)); + MOCK_METHOD(nlohmann::json, CreateMissedApproach, (const std::string&), (const, override)); MOCK_CONST_METHOD0(GetHoldDependency, nlohmann::json(void)); MOCK_CONST_METHOD0(GetAssignedHolds, nlohmann::json(void)); MOCK_CONST_METHOD2(AssignAircraftToHold, void(std::string, std::string)); diff --git a/test/plugin/mock/MockTaskRunnerInterface.h b/test/testingutils/mock/MockTaskRunnerInterface.h similarity index 100% rename from test/plugin/mock/MockTaskRunnerInterface.h rename to test/testingutils/mock/MockTaskRunnerInterface.h diff --git a/test/utils/api/ApiHelperTest.cpp b/test/utils/api/ApiHelperTest.cpp index 4d690f14e..777eb0bc9 100644 --- a/test/utils/api/ApiHelperTest.cpp +++ b/test/utils/api/ApiHelperTest.cpp @@ -802,4 +802,20 @@ namespace UKControllerPluginUtilsTest::Api { this->helper.DeletePrenoteMessage(55); } + + TEST_F(ApiHelperTest, CreateMissedApproachMakesRequest) + { + nlohmann::json responseData; + responseData["bla"] = "bla"; + CurlResponse response(responseData.dump(), false, 200); + + nlohmann::json expectedData; + expectedData["callsign"] = "BAW123"; + + CurlRequest expectedRequest(GetApiCurlRequest("/missed-approaches", CurlRequest::METHOD_POST, expectedData)); + + EXPECT_CALL(this->mockCurlApi, MakeCurlRequest(expectedRequest)).Times(1).WillOnce(Return(response)); + + EXPECT_EQ(responseData, this->helper.CreateMissedApproach("BAW123")); + } } // namespace UKControllerPluginUtilsTest::Api diff --git a/test/utils/api/ApiRequestBuilderTest.cpp b/test/utils/api/ApiRequestBuilderTest.cpp index 904a609f2..a0cbe880f 100644 --- a/test/utils/api/ApiRequestBuilderTest.cpp +++ b/test/utils/api/ApiRequestBuilderTest.cpp @@ -64,15 +64,6 @@ namespace UKControllerPluginUtilsTest::Api { EXPECT_TRUE(expectedRequest == this->builder.BuildRemoteFileRequest("http://testurl.com/files/test1.json")); } - TEST_F(ApiRequestBuilderTest, ItBuildsVersionCheckRequests) - { - CurlRequest expectedRequest("http://testurl.com/version/1.0.0/status", CurlRequest::METHOD_GET); - expectedRequest.AddHeader("Authorization", "Bearer apikey"); - expectedRequest.AddHeader("Accept", "application/json"); - expectedRequest.AddHeader("Content-Type", "application/json"); - EXPECT_TRUE(expectedRequest == this->builder.BuildVersionCheckRequest("1.0.0")); - } - TEST_F(ApiRequestBuilderTest, ItBuildsSquawkAssignmentDeletionRequests) { CurlRequest expectedRequest("http://testurl.com/squawk-assignment/BAW123", CurlRequest::METHOD_DELETE); @@ -553,4 +544,18 @@ namespace UKControllerPluginUtilsTest::Api { EXPECT_TRUE(expectedRequest == this->builder.BuildDeletePrenoteMessageRequest(55)); } + + TEST_F(ApiRequestBuilderTest, ItBuildsMissedApproachMessage) + { + CurlRequest expectedRequest("http://testurl.com/missed-approaches", CurlRequest::METHOD_POST); + + expectedRequest.AddHeader("Authorization", "Bearer apikey"); + expectedRequest.AddHeader("Accept", "application/json"); + expectedRequest.AddHeader("Content-Type", "application/json"); + + nlohmann::json expectedData = {{"callsign", "BAW123"}}; + expectedRequest.SetBody(expectedData.dump()); + + EXPECT_TRUE(expectedRequest == this->builder.BuildMissedApproachMessage("BAW123")); + } } // namespace UKControllerPluginUtilsTest::Api