diff --git a/docs/UserGuide/Features/MissedApproaches.md b/docs/UserGuide/Features/MissedApproaches.md index d59c635c5..baff169fb 100644 --- a/docs/UserGuide/Features/MissedApproaches.md +++ b/docs/UserGuide/Features/MissedApproaches.md @@ -76,11 +76,17 @@ 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, +service, e.g. Final Approach. You will be alerted to a missed approach at any airfield where you +are providing the given type of service. ### Airfields -Only airfields selected from the list will have their missed approaches alerted to you. +In some instances, it may be prudent to be warned of missed approaches at airfields that you don't directly control. +For example, Heathrow Director may want to know about missed approaches at Northolt and Farnborough, as this has a +direct impact on their arrival stream. + +You may select any airfields recognised by the plugin. If a missed approach occurs at any of these airfields, +you will be alerted. ## ASR-specific settings diff --git a/resource/UKControllerPlugin.rc b/resource/UKControllerPlugin.rc index 0b064109e..d8f25d0a6 100644 --- a/resource/UKControllerPlugin.rc +++ b/resource/UKControllerPlugin.rc @@ -312,7 +312,7 @@ BEGIN "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 + LTEXT "Always alert me to missed approaches at these airfields:",MISSED_APPROACH_AIRFIELDS,7,133,181,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 diff --git a/src/plugin/missedapproach/MissedApproachAudioAlert.cpp b/src/plugin/missedapproach/MissedApproachAudioAlert.cpp index db4afd8b7..5cfa17958 100644 --- a/src/plugin/missedapproach/MissedApproachAudioAlert.cpp +++ b/src/plugin/missedapproach/MissedApproachAudioAlert.cpp @@ -35,7 +35,9 @@ namespace UKControllerPlugin::MissedApproach { * 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. + * 3a. The destination airfield is an "Always Alert" airfield. + * OR + * 3b. The user is providing a specified service at the aircrafts destination airfield. * @param missedApproach * @return */ @@ -54,9 +56,26 @@ namespace UKControllerPlugin::MissedApproach { return false; } - const auto airfields = - this->airfieldServiceProvisions.GetAirfieldsWhereUserProvidingServices(this->options->ServiceProvisions()); + return this->DestinationIsAlwaysAlert(*flightplan) || this->UserProvidingServicesAtDestination(*flightplan); + } - return std::find(airfields.cbegin(), airfields.cend(), flightplan->GetDestination()) != airfields.cend(); + auto + MissedApproachAudioAlert::DestinationIsAlwaysAlert(const Euroscope::EuroScopeCFlightPlanInterface& flightplan) const + -> bool + { + const auto alwaysAlertAirfields = this->options->Airfields(); + return std::find(alwaysAlertAirfields.begin(), alwaysAlertAirfields.end(), flightplan.GetDestination()) != + alwaysAlertAirfields.cend(); + } + + auto MissedApproachAudioAlert::UserProvidingServicesAtDestination( + const Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> bool + { + const auto airfieldsWhereProvidingService = + this->airfieldServiceProvisions.GetAirfieldsWhereUserProvidingServices(this->options->ServiceProvisions()); + return std::find( + airfieldsWhereProvidingService.cbegin(), + airfieldsWhereProvidingService.cend(), + flightplan.GetDestination()) != airfieldsWhereProvidingService.cend(); } } // namespace UKControllerPlugin::MissedApproach diff --git a/src/plugin/missedapproach/MissedApproachAudioAlert.h b/src/plugin/missedapproach/MissedApproachAudioAlert.h index e46d67eba..475444f86 100644 --- a/src/plugin/missedapproach/MissedApproachAudioAlert.h +++ b/src/plugin/missedapproach/MissedApproachAudioAlert.h @@ -2,6 +2,7 @@ namespace UKControllerPlugin { namespace Euroscope { + class EuroScopeCFlightPlanInterface; class EuroscopePluginLoopbackInterface; } // namespace Euroscope namespace Ownership { @@ -31,6 +32,10 @@ namespace UKControllerPlugin::MissedApproach { void Play(const std::shared_ptr& missedApproach) const; private: + [[nodiscard]] auto DestinationIsAlwaysAlert(const Euroscope::EuroScopeCFlightPlanInterface& flightplan) const + -> bool; + [[nodiscard]] auto + UserProvidingServicesAtDestination(const Euroscope::EuroScopeCFlightPlanInterface& flightplan) const -> bool; [[nodiscard]] auto ShouldPlay(const std::shared_ptr& missedApproach) const -> bool; // Options for missed approaches diff --git a/src/plugin/missedapproach/MissedApproachRenderer.cpp b/src/plugin/missedapproach/MissedApproachRenderer.cpp index a6aa6dafb..7e667c311 100644 --- a/src/plugin/missedapproach/MissedApproachRenderer.cpp +++ b/src/plugin/missedapproach/MissedApproachRenderer.cpp @@ -40,21 +40,19 @@ namespace UKControllerPlugin::MissedApproach { { const auto airfieldsProvidingServices = this->serviceProviders.GetAirfieldsWhereUserProvidingServices(this->options->ServiceProvisions()); - const auto& renderFor = this->options->Airfields(); + const auto& alwaysRenderFor = 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()) { + if (airfieldsProvidingServices.empty() && alwaysRenderFor.empty()) { return; } + // Combine the "always alerts" and the "service provision" alerts to get all relevant airfields + std::vector relevantAirfields; + relevantAirfields.reserve(airfieldsProvidingServices.size() + alwaysRenderFor.size()); + relevantAirfields.insert( + relevantAirfields.end(), airfieldsProvidingServices.begin(), airfieldsProvidingServices.end()); + relevantAirfields.insert(relevantAirfields.end(), alwaysRenderFor.begin(), alwaysRenderFor.end()); + this->missedApproaches->ForEach([this, &relevantAirfields, &radarScreen, &graphics]( const std::shared_ptr& missed) { // Only display for certain duration diff --git a/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp b/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp index be32de63d..209d2e116 100644 --- a/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp +++ b/test/plugin/missedapproach/MissedApproachAudioAlertTest.cpp @@ -74,6 +74,15 @@ namespace UKControllerPluginTest::MissedApproach { alert.Play(Create(true)); } + TEST_F(MissedApproachAudioAlertTest, ItPlaysAlertIfDestinationIsSetToAlwaysAlert) + { + this->options->SetServiceProvisions(ServiceType::Ground); + this->options->SetAirfields({"EGKK"}); + EXPECT_CALL(mockWindows, PlayWave(MAKEINTRESOURCE(WAVE_MISSED_APPROACH))).Times(1); + + alert.Play(Create(false)); + } + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfTurnedOff) { this->options->SetAudioAlert(false); @@ -101,6 +110,16 @@ namespace UKControllerPluginTest::MissedApproach { alert.Play(Create(false)); } + TEST_F(MissedApproachAudioAlertTest, ItDoesntPlayAudioAlertIfIncorrectAirfieldIsOnAlwaysALert) + { + this->options->SetServiceProvisions(ServiceType::Ground); + this->options->SetAirfields({"EGLL"}); + + 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));