From 0729d15e3c354eef25e711c1f5a9605eea3f5d26 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 25 Dec 2025 14:50:08 -0800 Subject: [PATCH 01/11] Add Terrakion Shiny Hunt Add LZA program to reset and detect shiny Terrakion --- .../Source/PokemonLZA/PokemonLZA_Panels.cpp | 2 + .../PokemonLZA_ShinyHunt_Terrakion.cpp | 291 ++++++++++++++++++ .../PokemonLZA_ShinyHunt_Terrakion.h | 48 +++ SerialPrograms/cmake/SourceFiles.cmake | 2 + 4 files changed, 343 insertions(+) create mode 100644 SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp create mode 100644 SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp index 30da3d4796..01dcd32678 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp @@ -41,6 +41,7 @@ #include "Programs/ShinyHunting/PokemonLZA_ShuttleRun.h" #include "Programs/ShinyHunting/PokemonLZA_SewerHunter.h" #include "Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.h" +#include "Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h" // Non-Shiny Hunting #include "Programs/NonShinyHunting/PokemonLZA_StatsReset.h" @@ -100,6 +101,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); if (IS_BETA_VERSION){ ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); } if (PreloadSettings::instance().DEVELOPER_MODE){ ret.emplace_back(make_single_switch_program()); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp new file mode 100644 index 0000000000..92c9f87a3b --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp @@ -0,0 +1,291 @@ +/* Shiny Hunt - Terrakion + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "Common/Cpp/PrettyPrint.h" +#include "CommonFramework/VideoPipeline/VideoOverlay.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" +#include "PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h" +#include "PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h" +#include "PokemonLA/Inference/Sounds/PokemonLA_ShinySoundDetector.h" +#include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" +#include "PokemonLZA/Programs/PokemonLZA_GameEntry.h" +#include "PokemonLZA_ShinyHunt_Terrakion.h" + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonLZA { + +using namespace Pokemon; + + +ShinyHunt_Terrakion_Descriptor::ShinyHunt_Terrakion_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonLZA:ShinyHunt-Terrakion", + STRING_POKEMON + " LZA", + "Shiny Hunt - Terrakion", + "Programs/PokemonLZA/ShinyHunt-Terrakion.html", + "Shiny hunt Terrakion in Hyperspace Lumiose", + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS, + {} + ) +{} + + +class ShinyHunt_Terrakion_Descriptor::Stats : public StatsTracker{ +public: + Stats() + : shinies(m_stats["Shinies"]) + , resets(m_stats["Resets"]) + , checks(m_stats["Checks"]) + , errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Resets"); + m_display_order.emplace_back("Checks"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + + std::atomic& shinies; + std::atomic& resets; + std::atomic& checks; + std::atomic& errors; +}; +std::unique_ptr ShinyHunt_Terrakion_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + + +ShinyHunt_Terrakion::ShinyHunt_Terrakion() + : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 400, 200, 4800) + , SHINY_DETECTED("Shiny Detected", "", "1000 ms", ShinySoundDetectedAction::STOP_PROGRAM) + , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_STATUS, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_RECOVERABLE, + &NOTIFICATION_ERROR_FATAL, + }) +{ + PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); + PA_ADD_OPTION(THRESHOLD); + PA_ADD_OPTION(SHINY_DETECTED); + PA_ADD_OPTION(NOTIFICATIONS); +} + + +bool are_calories_under_threshold(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + ShinyHunt_Terrakion_Descriptor::Stats& stats, SimpleIntegerOption& THRESHOLD) { + + OverworldPartySelectionWatcher overworld(COLOR_WHITE, &env.console.overlay()); + int ret = wait_until( + env.console, context, 10s, + {overworld} + ); + if (ret < 0) { + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "route_terrakion_reset(): Cannot detect overworld after warp pad.", + env.console + ); + } + std::shared_ptr overworld_screen = overworld.last_detected_frame(); + if (overworld_screen == nullptr){ + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "route_terrakion_reset(): Failed to get overworld screen!", + env.console + ); + } + HyperspaceCalorieDetector hyperspace_calorie_detector(env.logger()); + if (!hyperspace_calorie_detector.detect(*overworld_screen)){ + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "route_hyperspace_wild_zone(): Cannot read Calorie number on screen.", + env.console + ); + } + const uint16_t calorie_number = hyperspace_calorie_detector.calorie_number(); + const uint16_t min_calorie = THRESHOLD; + const std::string log_msg = std::format("Calories: {}/{}", calorie_number, min_calorie); + env.add_overlay_log(log_msg); + env.log(log_msg); + if (calorie_number <= min_calorie){ + env.log("Calorie threshold reached. Stopping resets to check Terrakion.", COLOR_PURPLE); + return true; + } + return false; +} + + +void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + ShinyHunt_Terrakion_Descriptor::Stats& stats) { + + ButtonWatcher ButtonA( + COLOR_RED, + ButtonType::ButtonA, + {0.4, 0.1, 0.2, 0.8}, + &env.console.overlay() + ); + + int ret = wait_until( + env.console, context, 10s, + {ButtonA} + ); + if (ret < 0) { + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "route_terrakion_reset(): Cannot detect warp pad after 10 seconds", + env.console + ); + } else { + env.log("Detected warp pad."); + // env.console.overlay().add_log("Warp Pad Detected"); + } +} + + +// void wait_until_overworld(SingleSwitchProgramEnvironment& env, ProControllerContext& context, +// ShinyHunt_Terrakion_Descriptor::Stats& stats) { + +// OverworldPartySelectionOverWatcher overworldWatcher( +// COLOR_GREEN, +// &env.console.overlay() +// ); +// int ret = wait_until( +// env.console, context, 10s, +// {overworldWatcher} +// ); +// if (ret < 0){ +// stats.errors++; +// env.update_stats(); +// OperationFailedException::fire( +// ErrorReport::SEND_ERROR_REPORT, +// "wait_until_overworld(): Cannot detect overworld after 10 seconds", +// env.console +// ); +// } else { +// env.log("Detected overworld."); +// env.console.overlay().add_log("Overworld Detected"); +// } +// } + + +void route_reset_terrakion(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + ShinyHunt_Terrakion_Descriptor::Stats& stats, bool do_rolls=true) { + + context.wait_for_all_requests(); + + // Warp away from Terrakion to despawn + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_A, 160ms, 80ms); + + // Warp towards Terrakion + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_A, 160ms, 80ms); + + // Roll and roll back on Terrakion's roof to respawn + detect_warp_pad(env, context, stats); + if (do_rolls) { + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {0, -1}, 80ms, 160ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + } + context.wait_for_all_requests(); + env.add_overlay_log("Respawned Terrakion"); + env.log("Respawned Terrakion."); + stats.resets++; + env.update_stats(); +} + + +void route_check_terrakion(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + ShinyHunt_Terrakion_Descriptor::Stats& stats) { + + context.wait_for_all_requests(); + + // Roll to Terrakion to trigger potential shiny sound + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {-1, 1}, 80ms, 160ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {0, 1}, 80ms, 500ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {1, 1}, 80ms, 160ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + + env.add_overlay_log("Checking for Shiny"); + env.log("Checking shiny status of Terrakion."); + context.wait_for_all_requests(); + stats.checks++; + env.update_stats(); +} + +void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) { + ShinyHunt_Terrakion_Descriptor::Stats& stats = env.current_stats(); + ShinySoundHandler shiny_sound_handler(SHINY_DETECTED); + PokemonLA::ShinySoundDetector shiny_detector(env.console, [&](float error_coefficient) -> bool { + // Warning: This callback will be run from a different thread than this function. + stats.shinies++; + env.update_stats(); + env.console.overlay().add_log("Shiny Sound Detected!", COLOR_YELLOW); + return shiny_sound_handler.on_shiny_sound( + env, env.console, + stats.shinies, + error_coefficient + ); + }); + + run_until( + env.console, context, + [&](ProControllerContext& context){ + while (true){ + context.wait_for_all_requests(); + shiny_sound_handler.process_pending(context); + + bool stop_resets = are_calories_under_threshold(env, context, stats, THRESHOLD); + if (stop_resets) { + // Reset one additional time without rolling to reset position + route_reset_terrakion(env, context, stats, false); + route_check_terrakion(env, context, stats); + reset_game_from_home(env, env.console, context); + } else { + route_reset_terrakion(env, context, stats); + } + env.update_stats(); + if (stats.resets.load(std::memory_order_relaxed) % 10 == 0){ + send_program_status_notification(env, NOTIFICATION_STATUS); + } + } + }, + {{shiny_detector}} + ); + + shiny_sound_handler.process_pending(context); + go_home(env.console, context); + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); +} + + +} // namespace PokemonLZA +} // namespace NintendoSwitch +} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h new file mode 100644 index 0000000000..a082996eb4 --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h @@ -0,0 +1,48 @@ +/* Shiny Hunt - Terrakion + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonLZA_ShinyHunt_Terrakion_H +#define PokemonAutomation_PokemonLZA_ShinyHunt_Terrakion_H + +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" +#include "PokemonLZA/Options/PokemonLZA_ShinyDetectedAction.h" + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonLZA { + + +class ShinyHunt_Terrakion_Descriptor : public SingleSwitchProgramDescriptor { +public: + ShinyHunt_Terrakion_Descriptor(); + + class Stats; + virtual std::unique_ptr make_stats() const override; +}; + + +class ShinyHunt_Terrakion : public SingleSwitchProgramInstance { +public: + ShinyHunt_Terrakion(); + + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; + +private: + PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; + SimpleIntegerOption THRESHOLD; + ShinySoundDetectedActionOption SHINY_DETECTED; + EventNotificationOption NOTIFICATION_STATUS; + EventNotificationsOption NOTIFICATIONS; +}; + + +} // namespace PokemonLZA +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 64ccd08568..d9b8630811 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1719,6 +1719,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_OverworldReset.cpp Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_OverworldReset.h + Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp + Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShuttleRun.cpp Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShuttleRun.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.cpp From 8860382e282d9bd89ff69897bf6b007823a03a32 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 25 Dec 2025 15:10:43 -0800 Subject: [PATCH 02/11] Add Save on Start Added option to save on program start for Terrakion Hunter --- .../ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp | 7 +++++++ .../Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h | 1 + 2 files changed, 8 insertions(+) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp index 92c9f87a3b..8a7cf9827f 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp @@ -69,6 +69,7 @@ std::unique_ptr ShinyHunt_Terrakion_Descriptor::make_stats() const ShinyHunt_Terrakion::ShinyHunt_Terrakion() : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 400, 200, 4800) + , SAVE_ON_START("Save on Start:
Save the game when starting the program. Each cycle will start at the calorie count at the time of the save", LockMode::LOCK_WHILE_RUNNING, true) , SHINY_DETECTED("Shiny Detected", "", "1000 ms", ShinySoundDetectedAction::STOP_PROGRAM) , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATIONS({ @@ -80,6 +81,7 @@ ShinyHunt_Terrakion::ShinyHunt_Terrakion() { PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); PA_ADD_OPTION(THRESHOLD); + PA_ADD_OPTION(SAVE_ON_START); PA_ADD_OPTION(SHINY_DETECTED); PA_ADD_OPTION(NOTIFICATIONS); } @@ -255,6 +257,11 @@ void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProContro ); }); + if (SAVE_ON_START) { + save_game_to_menu(env.console, context); + pbf_mash_button(context, BUTTON_B, 2000ms); + } + run_until( env.console, context, [&](ProControllerContext& context){ diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h index a082996eb4..3433cedd33 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h @@ -36,6 +36,7 @@ class ShinyHunt_Terrakion : public SingleSwitchProgramInstance { private: PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; SimpleIntegerOption THRESHOLD; + BooleanCheckBoxOption SAVE_ON_START; ShinySoundDetectedActionOption SHINY_DETECTED; EventNotificationOption NOTIFICATION_STATUS; EventNotificationsOption NOTIFICATIONS; From 1b8220651479624459f6556d864dd3e9f5ace607 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 25 Dec 2025 16:03:12 -0800 Subject: [PATCH 03/11] Increased Default Calorie Threshold Increased calorie threshold for Terrakion --- .../Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp index 8a7cf9827f..d26818e0c6 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp @@ -68,7 +68,7 @@ std::unique_ptr ShinyHunt_Terrakion_Descriptor::make_stats() const ShinyHunt_Terrakion::ShinyHunt_Terrakion() - : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 400, 200, 4800) + : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 800, 200, 4800) , SAVE_ON_START("Save on Start:
Save the game when starting the program. Each cycle will start at the calorie count at the time of the save", LockMode::LOCK_WHILE_RUNNING, true) , SHINY_DETECTED("Shiny Detected", "", "1000 ms", ShinySoundDetectedAction::STOP_PROGRAM) , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) @@ -279,7 +279,7 @@ void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProContro route_reset_terrakion(env, context, stats); } env.update_stats(); - if (stats.resets.load(std::memory_order_relaxed) % 10 == 0){ + if (stats.resets.load(std::memory_order_relaxed) % 100 == 0){ send_program_status_notification(env, NOTIFICATION_STATUS); } } From 9ac801cad61133a8b328c3eb2e6eb4bf8bb80dbd Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Thu, 25 Dec 2025 22:40:27 -0800 Subject: [PATCH 04/11] Added Extra Rolls to Sequence Added extra rolls since shiny sound was not triggered --- .../Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp index d26818e0c6..456ba4e573 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp @@ -234,6 +234,8 @@ void route_check_terrakion(SingleSwitchProgramEnvironment& env, ProControllerCon pbf_press_button(context, BUTTON_Y, 100ms, 900ms); pbf_move_left_joystick(context, {1, 1}, 80ms, 160ms); pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); env.add_overlay_log("Checking for Shiny"); env.log("Checking shiny status of Terrakion."); From ad94ffe3ac420792e9b2331a7501ed159e4c978a Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Dec 2025 14:10:49 -0800 Subject: [PATCH 05/11] Catch and Retry Added game reset when failure is detected --- .../PokemonLZA_ShinyHunt_Terrakion.cpp | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp index 456ba4e573..8d572e2e44 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp @@ -5,6 +5,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/GlobalSettingsPanel.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "Common/Cpp/PrettyPrint.h" @@ -68,7 +69,7 @@ std::unique_ptr ShinyHunt_Terrakion_Descriptor::make_stats() const ShinyHunt_Terrakion::ShinyHunt_Terrakion() - : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 800, 200, 4800) + : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 1500, 200, 4800) , SAVE_ON_START("Save on Start:
Save the game when starting the program. Each cycle will start at the calorie count at the time of the save", LockMode::LOCK_WHILE_RUNNING, true) , SHINY_DETECTED("Shiny Detected", "", "1000 ms", ShinySoundDetectedAction::STOP_PROGRAM) , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) @@ -88,14 +89,14 @@ ShinyHunt_Terrakion::ShinyHunt_Terrakion() bool are_calories_under_threshold(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats, SimpleIntegerOption& THRESHOLD) { + ShinyHunt_Terrakion_Descriptor::Stats& stats, SimpleIntegerOption& THRESHOLD){ OverworldPartySelectionWatcher overworld(COLOR_WHITE, &env.console.overlay()); int ret = wait_until( env.console, context, 10s, {overworld} ); - if (ret < 0) { + if (ret < 0){ stats.errors++; env.update_stats(); OperationFailedException::fire( @@ -138,7 +139,7 @@ bool are_calories_under_threshold(SingleSwitchProgramEnvironment& env, ProContro void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats) { + ShinyHunt_Terrakion_Descriptor::Stats& stats){ ButtonWatcher ButtonA( COLOR_RED, @@ -151,7 +152,7 @@ void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& env.console, context, 10s, {ButtonA} ); - if (ret < 0) { + if (ret < 0){ stats.errors++; env.update_stats(); OperationFailedException::fire( @@ -159,7 +160,7 @@ void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& "route_terrakion_reset(): Cannot detect warp pad after 10 seconds", env.console ); - } else { + }else{ env.log("Detected warp pad."); // env.console.overlay().add_log("Warp Pad Detected"); } @@ -193,7 +194,7 @@ void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& void route_reset_terrakion(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats, bool do_rolls=true) { + ShinyHunt_Terrakion_Descriptor::Stats& stats, bool do_rolls=true){ context.wait_for_all_requests(); @@ -207,7 +208,7 @@ void route_reset_terrakion(SingleSwitchProgramEnvironment& env, ProControllerCon // Roll and roll back on Terrakion's roof to respawn detect_warp_pad(env, context, stats); - if (do_rolls) { + if (do_rolls){ pbf_press_button(context, BUTTON_Y, 100ms, 900ms); pbf_move_left_joystick(context, {0, -1}, 80ms, 160ms); pbf_press_button(context, BUTTON_Y, 100ms, 900ms); @@ -221,7 +222,7 @@ void route_reset_terrakion(SingleSwitchProgramEnvironment& env, ProControllerCon void route_check_terrakion(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats) { + ShinyHunt_Terrakion_Descriptor::Stats& stats){ context.wait_for_all_requests(); @@ -244,10 +245,10 @@ void route_check_terrakion(SingleSwitchProgramEnvironment& env, ProControllerCon env.update_stats(); } -void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) { +void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ ShinyHunt_Terrakion_Descriptor::Stats& stats = env.current_stats(); ShinySoundHandler shiny_sound_handler(SHINY_DETECTED); - PokemonLA::ShinySoundDetector shiny_detector(env.console, [&](float error_coefficient) -> bool { + PokemonLA::ShinySoundDetector shiny_detector(env.console, [&](float error_coefficient) -> bool{ // Warning: This callback will be run from a different thread than this function. stats.shinies++; env.update_stats(); @@ -259,11 +260,12 @@ void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProContro ); }); - if (SAVE_ON_START) { + if (SAVE_ON_START){ save_game_to_menu(env.console, context); pbf_mash_button(context, BUTTON_B, 2000ms); } + int consecutive_failures = 0; run_until( env.console, context, [&](ProControllerContext& context){ @@ -271,14 +273,36 @@ void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProContro context.wait_for_all_requests(); shiny_sound_handler.process_pending(context); - bool stop_resets = are_calories_under_threshold(env, context, stats, THRESHOLD); - if (stop_resets) { - // Reset one additional time without rolling to reset position - route_reset_terrakion(env, context, stats, false); - route_check_terrakion(env, context, stats); + try{ + bool stop_resets = are_calories_under_threshold(env, context, stats, THRESHOLD); + if (stop_resets){ + // Reset one additional time without rolling to reset position + route_reset_terrakion(env, context, stats, false); + route_check_terrakion(env, context, stats); + go_home(env.console, context); + reset_game_from_home(env, env.console, context); + }else{ + route_reset_terrakion(env, context, stats); + } + }catch (OperationFailedException& e){ + consecutive_failures++; + env.log("Consecutive failures: " + std::to_string(consecutive_failures), COLOR_RED); + if (consecutive_failures >= 3){ + if (PreloadSettings::instance().DEVELOPER_MODE && GlobalSettings::instance().SAVE_DEBUG_VIDEOS_ON_SWITCH){ + env.log("Saving debug video on Switch..."); + env.console.overlay().add_log("Save Debug Video on Switch"); + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + context.wait_for_all_requests(); + } + go_home(env.console, context); // go Home to preserve game state for debugging + throw; + } + env.log("Error encountered. Resetting...", COLOR_RED); + stats.errors++; + env.update_stats(); + env.console.overlay().add_log("Error Found. Reset Game", COLOR_RED); + go_home(env.console, context); reset_game_from_home(env, env.console, context); - } else { - route_reset_terrakion(env, context, stats); } env.update_stats(); if (stats.resets.load(std::memory_order_relaxed) % 100 == 0){ From b14ac8d375644137321faeba21afe777cdb9ca79 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Fri, 26 Dec 2025 17:12:51 -0800 Subject: [PATCH 06/11] Fix Build --- .../Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp index 8d572e2e44..5874959964 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp @@ -284,7 +284,7 @@ void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProContro }else{ route_reset_terrakion(env, context, stats); } - }catch (OperationFailedException& e){ + }catch (OperationFailedException&){ consecutive_failures++; env.log("Consecutive failures: " + std::to_string(consecutive_failures), COLOR_RED); if (consecutive_failures >= 3){ From 12e6f806aa6543572a84244164c47b10806cca85 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Sun, 28 Dec 2025 01:03:55 -0800 Subject: [PATCH 07/11] Merged Hyperspace Shiny Legendary Programs Refactored ShinyHunt HyperspaceLegendary to include Terrakion and add flexibility for more --- ...kemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 296 +++++++++++++----- ...PokemonLZA_ShinyHunt_HyperspaceLegendary.h | 5 + 2 files changed, 223 insertions(+), 78 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index f3b1a19725..4b37aa927b 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -5,6 +5,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/GlobalSettingsPanel.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoOverlay.h" @@ -15,9 +16,11 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "Pokemon/Pokemon_Strings.h" +#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" #include "PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h" #include "PokemonLA/Inference/Sounds/PokemonLA_ShinySoundDetector.h" #include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" +#include "PokemonLZA/Programs/PokemonLZA_GameEntry.h" #include "PokemonLZA_ShinyHunt_HyperspaceLegendary.h" namespace PokemonAutomation { @@ -42,15 +45,18 @@ class ShinyHunt_HyperspaceLegendary_Descriptor::Stats : public StatsTracker{ public: Stats() : resets(m_stats["Resets"]) + , rounds(m_stats["Rounds"]) , shinies(m_stats["Shiny Sounds"]) , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); + m_display_order.emplace_back("Rounds"); m_display_order.emplace_back("Shiny Sounds"); m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; + std::atomic& rounds; std::atomic& shinies; std::atomic& errors; }; @@ -61,9 +67,13 @@ std::unique_ptr ShinyHunt_HyperspaceLegendary_Descriptor::make_sta ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() - : SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::NOTIFY_ON_FIRST_ONLY) + : SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::STOP_PROGRAM) , LEGENDARY("Hunt Route:", { + // {Legendary::LATIAS, "latias", "Latias"}, + // {Legendary::LATIOS, "latios", "Latios"}, + // {Legendary::COBALION, "cobalion", "Cobalion"}, + {Legendary::TERRAKION, "terrakion", "Terrakion"}, {Legendary::VIRIZION, "virizion", "Virizion"}, }, LockMode::LOCK_WHILE_RUNNING, @@ -83,6 +93,10 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() LockMode::UNLOCK_WHILE_RUNNING, 600, 0, 9999 // default, min, max ) + , SAVE_ON_START( + "Save on Start:
Save the game when starting the program. Each cycle will start at the calorie count at the time of the save", + LockMode::LOCK_WHILE_RUNNING, + true) // default , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATIONS({ &NOTIFICATION_STATUS, @@ -96,85 +110,181 @@ ShinyHunt_HyperspaceLegendary::ShinyHunt_HyperspaceLegendary() PA_ADD_OPTION(LEGENDARY); PA_ADD_OPTION(MAX_ROUNDS); PA_ADD_OPTION(MIN_CALORIE_REMAINING); + PA_ADD_OPTION(SAVE_ON_START); PA_ADD_OPTION(SHINY_DETECTED); PA_ADD_OPTION(NOTIFICATIONS); } namespace { -// Return if the loop should stop -typedef std::function route_func; - -void route_default( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats){ - // Open map - bool can_fast_travel = open_map(env.console, context); - if (!can_fast_travel){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "route_default(): Cannot open map for fast travel.", - env.console +class LegendaryRoute { +public: + virtual ~LegendaryRoute() = default; + + // The route used to reset and respawn the legendary + virtual void reset( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats, + SimpleIntegerOption& MIN_CALORIE_REMAINING){ + + const uint16_t min_calorie = MIN_CALORIE_REMAINING; + + HyperspaceCalorieLimitWatcher calorie_watcher(env.logger(), min_calorie); + const int ret = run_until( + env.console, context, + [&](ProControllerContext& context){ + while (true){ + reset_route(env, context, stats); + + stats.resets++; + env.update_stats(); + + uint16_t calorie_number = calorie_watcher.calorie_number(); + const std::string log_msg = std::format("Calorie: {}/{}", calorie_number, min_calorie); + env.add_overlay_log(log_msg); + env.log(log_msg); + } + }, + {{calorie_watcher}} ); + if (ret < 0){ + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + std::string(typeid(*this).name()) + " reset(): Error during reset loop.", + env.console + ); + } + env.log("min calorie reached"); } - // Move map cursor upwards a little bit - pbf_move_left_joystick_old(context, 128, 64, 100ms, 200ms); + // Unique route used to reset each respective legendary, to be implemented by subclasses + virtual void reset_route( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats){ - // Fly from map to reset spawns - FastTravelState travel_status = fly_from_map(env.console, context); - if (travel_status != FastTravelState::SUCCESS){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "route_default(): Cannot fast travel after moving map cursor.", - env.console - ); + return; } -} -bool route_virizion( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats, - SimpleIntegerOption& MIN_CALORIE_REMAINING, - uint8_t& ready_to_stop_counter){ + // The route used to check if the legendary is shiny after resetting + virtual bool check( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats){ - const uint16_t min_calorie = MIN_CALORIE_REMAINING; + return false; // For legendaries that do not have checking implemented + } - // running forward - Milliseconds duration(4400); + void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats){ - HyperspaceCalorieLimitWatcher calorie_watcher(env.logger(), min_calorie); - const int ret = run_until( - env.console, context, - [&](ProControllerContext& context){ - // running forward - ssf_press_button(context, BUTTON_B, 0ms, 2*duration, 0ms); - // Add 30 ms to avoid any drift using the balustrade - pbf_move_left_joystick(context, {0, +1}, duration + 30ms, 0ms); - // run back - pbf_move_left_joystick(context, {0, -1}, duration, 0ms); - pbf_wait(context, 100ms); - }, - {{calorie_watcher}} - ); - uint16_t calorie_number = calorie_watcher.calorie_number(); - const std::string log_msg = std::format("Calorie: {}/{}", calorie_number, min_calorie); - env.add_overlay_log(log_msg); - env.log(log_msg); - if (ret == 0){ - env.log("min calorie reached"); - return true; + ButtonWatcher ButtonA( + COLOR_RED, + ButtonType::ButtonA, + {0.4, 0.1, 0.2, 0.8}, + &env.console.overlay() + ); + + int ret = wait_until( + env.console, context, 10s, + {ButtonA} + ); + if (ret < 0){ + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "route_terrakion_reset(): Cannot detect warp pad after 10 seconds", + env.console + ); + }else{ + env.log("Detected warp pad."); + // env.console.overlay().add_log("Warp Pad Detected"); + } } +}; - return false; -} +class TerrakionRoute : public LegendaryRoute { +public: + void reset_route( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats + ) override { + context.wait_for_all_requests(); + + // Warp away from Terrakion to despawn + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_A, 160ms, 80ms); + + // Warp towards Terrakion + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_A, 160ms, 80ms); + + // Roll and roll back on Terrakion's roof to respawn + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {0, -1}, 80ms, 160ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + } + bool check( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats + ) override { + context.wait_for_all_requests(); + + // Use warp pads to reset position + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_A, 160ms, 80ms); + detect_warp_pad(env, context, stats); + pbf_press_button(context, BUTTON_A, 160ms, 80ms); + detect_warp_pad(env, context, stats); + + // Roll to Terrakion to trigger potential shiny sound + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {-1, 1}, 80ms, 160ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {0, 1}, 80ms, 500ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_move_left_joystick(context, {1, 1}, 80ms, 160ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + pbf_press_button(context, BUTTON_Y, 100ms, 900ms); + + env.add_overlay_log("Checking for Shiny"); + env.log("Checking shiny status of Terrakion."); + context.wait_for_all_requests(); + + go_home(env.console, context); + reset_game_from_home(env, env.console, context); + + return false; + } +}; + +class VirizionRoute : public LegendaryRoute { +public: + void reset_route( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats + ) override { + context.wait_for_all_requests(); + // running forward + ssf_press_button(context, BUTTON_B, 0ms, 8800ms, 0ms); + // Add 30 ms to avoid any drift using the balustrade + pbf_move_left_joystick(context, {0, +1}, 4430ms, 0ms); + // run back + pbf_move_left_joystick(context, {0, -1}, 4400ms, 0ms); + pbf_wait(context, 100ms); + } +}; } // namespace @@ -201,8 +311,23 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, ); }); - uint64_t num_resets = 0; - uint8_t ready_to_stop_counter = 0; + std::unique_ptr legendary_route; + switch(LEGENDARY) { + case Legendary::TERRAKION: + legendary_route = std::make_unique(); + break; + case Legendary::VIRIZION: + legendary_route = std::make_unique(); + break; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "legendary route not implemented", + env.console + ); + } + + int consecutive_failures = 0; run_until( env.console, context, [&](ProControllerContext& context){ @@ -211,27 +336,42 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, shiny_sound_handler.process_pending(context); bool should_stop = false; - if (LEGENDARY == Legendary::VIRIZION){ - should_stop = route_virizion(env, context, stats, MIN_CALORIE_REMAINING, ready_to_stop_counter); - } else{ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "legendary hunt not implemented", - env.console - ); + try{ + legendary_route->reset(env, context, stats, MIN_CALORIE_REMAINING); + context.wait_for_all_requests(); + + should_stop = legendary_route->check(env, context, stats); + context.wait_for_all_requests(); + + stats.rounds++; + }catch (OperationFailedException&){ + consecutive_failures++; + env.log("Consecutive failures: " + std::to_string(consecutive_failures), COLOR_RED); + if (consecutive_failures >= 3){ + if (PreloadSettings::instance().DEVELOPER_MODE && GlobalSettings::instance().SAVE_DEBUG_VIDEOS_ON_SWITCH){ + env.log("Saving debug video on Switch..."); + env.console.overlay().add_log("Save Debug Video on Switch"); + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + context.wait_for_all_requests(); + } + go_home(env.console, context); // go Home to preserve game state for debugging + throw; + } + env.log("Error encountered. Resetting...", COLOR_RED); + stats.errors++; + env.console.overlay().add_log("Error Found. Reset Game", COLOR_RED); + go_home(env.console, context); + reset_game_from_home(env, env.console, context); } - num_resets++; - stats.resets++; env.update_stats(); - if (stats.resets.load(std::memory_order_relaxed) % 10 == 0){ + if (stats.rounds.load(std::memory_order_relaxed) % 10 == 0){ send_program_status_notification(env, NOTIFICATION_STATUS); } - if (should_stop){ break; } - if (MAX_ROUNDS > 0 && num_resets >= MAX_ROUNDS){ - env.log(std::format("Reached reset limit {}", static_cast(MAX_ROUNDS))); + if (MAX_ROUNDS > 0 && stats.rounds >= MAX_ROUNDS){ + env.log(std::format("Reached round limit {}", static_cast(MAX_ROUNDS))); break; } diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h index 97d194046a..e8363e59bc 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.h @@ -35,6 +35,10 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{ virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; enum class Legendary{ + // LATIAS, + // LATIOS, + // COBALION, + TERRAKION, VIRIZION }; @@ -44,6 +48,7 @@ class ShinyHunt_HyperspaceLegendary : public SingleSwitchProgramInstance{ EnumDropdownOption LEGENDARY; SimpleIntegerOption MAX_ROUNDS; SimpleIntegerOption MIN_CALORIE_REMAINING; + BooleanCheckBoxOption SAVE_ON_START; EventNotificationOption NOTIFICATION_STATUS; EventNotificationsOption NOTIFICATIONS; From 72ad60fb8af4e77103e055207b65f8e835f83a50 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Sun, 28 Dec 2025 01:09:55 -0800 Subject: [PATCH 08/11] Removed Terrakion Hunter Standalone --- .../Source/PokemonLZA/PokemonLZA_Panels.cpp | 1 - .../PokemonLZA_ShinyHunt_Terrakion.cpp | 324 ------------------ .../PokemonLZA_ShinyHunt_Terrakion.h | 49 --- SerialPrograms/cmake/SourceFiles.cmake | 2 - 4 files changed, 376 deletions(-) delete mode 100644 SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp delete mode 100644 SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp index 01dcd32678..8502ecd399 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp @@ -101,7 +101,6 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); if (IS_BETA_VERSION){ ret.emplace_back(make_single_switch_program()); - ret.emplace_back(make_single_switch_program()); } if (PreloadSettings::instance().DEVELOPER_MODE){ ret.emplace_back(make_single_switch_program()); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp deleted file mode 100644 index 5874959964..0000000000 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp +++ /dev/null @@ -1,324 +0,0 @@ -/* Shiny Hunt - Terrakion - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/GlobalSettingsPanel.h" -#include "CommonFramework/ProgramStats/StatsTracking.h" -#include "CommonFramework/Notifications/ProgramNotifications.h" -#include "Common/Cpp/PrettyPrint.h" -#include "CommonFramework/VideoPipeline/VideoOverlay.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "Pokemon/Pokemon_Strings.h" -#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h" -#include "PokemonLA/Inference/Sounds/PokemonLA_ShinySoundDetector.h" -#include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" -#include "PokemonLZA/Programs/PokemonLZA_GameEntry.h" -#include "PokemonLZA_ShinyHunt_Terrakion.h" - -namespace PokemonAutomation { -namespace NintendoSwitch { -namespace PokemonLZA { - -using namespace Pokemon; - - -ShinyHunt_Terrakion_Descriptor::ShinyHunt_Terrakion_Descriptor() - : SingleSwitchProgramDescriptor( - "PokemonLZA:ShinyHunt-Terrakion", - STRING_POKEMON + " LZA", - "Shiny Hunt - Terrakion", - "Programs/PokemonLZA/ShinyHunt-Terrakion.html", - "Shiny hunt Terrakion in Hyperspace Lumiose", - ProgramControllerClass::StandardController_NoRestrictions, - FeedbackType::REQUIRED, - AllowCommandsWhenRunning::DISABLE_COMMANDS, - {} - ) -{} - - -class ShinyHunt_Terrakion_Descriptor::Stats : public StatsTracker{ -public: - Stats() - : shinies(m_stats["Shinies"]) - , resets(m_stats["Resets"]) - , checks(m_stats["Checks"]) - , errors(m_stats["Errors"]) - { - m_display_order.emplace_back("Shinies"); - m_display_order.emplace_back("Resets"); - m_display_order.emplace_back("Checks"); - m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); - } - - std::atomic& shinies; - std::atomic& resets; - std::atomic& checks; - std::atomic& errors; -}; -std::unique_ptr ShinyHunt_Terrakion_Descriptor::make_stats() const{ - return std::unique_ptr(new Stats()); -} - - -ShinyHunt_Terrakion::ShinyHunt_Terrakion() - : THRESHOLD("Calorie Threshold:
Stop resetting and check Terrakion when this many calories remain", LockMode::UNLOCK_WHILE_RUNNING, 1500, 200, 4800) - , SAVE_ON_START("Save on Start:
Save the game when starting the program. Each cycle will start at the calorie count at the time of the save", LockMode::LOCK_WHILE_RUNNING, true) - , SHINY_DETECTED("Shiny Detected", "", "1000 ms", ShinySoundDetectedAction::STOP_PROGRAM) - , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) - , NOTIFICATIONS({ - &NOTIFICATION_STATUS, - &NOTIFICATION_PROGRAM_FINISH, - &NOTIFICATION_ERROR_RECOVERABLE, - &NOTIFICATION_ERROR_FATAL, - }) -{ - PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); - PA_ADD_OPTION(THRESHOLD); - PA_ADD_OPTION(SAVE_ON_START); - PA_ADD_OPTION(SHINY_DETECTED); - PA_ADD_OPTION(NOTIFICATIONS); -} - - -bool are_calories_under_threshold(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats, SimpleIntegerOption& THRESHOLD){ - - OverworldPartySelectionWatcher overworld(COLOR_WHITE, &env.console.overlay()); - int ret = wait_until( - env.console, context, 10s, - {overworld} - ); - if (ret < 0){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "route_terrakion_reset(): Cannot detect overworld after warp pad.", - env.console - ); - } - std::shared_ptr overworld_screen = overworld.last_detected_frame(); - if (overworld_screen == nullptr){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "route_terrakion_reset(): Failed to get overworld screen!", - env.console - ); - } - HyperspaceCalorieDetector hyperspace_calorie_detector(env.logger()); - if (!hyperspace_calorie_detector.detect(*overworld_screen)){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "route_hyperspace_wild_zone(): Cannot read Calorie number on screen.", - env.console - ); - } - const uint16_t calorie_number = hyperspace_calorie_detector.calorie_number(); - const uint16_t min_calorie = THRESHOLD; - const std::string log_msg = std::format("Calories: {}/{}", calorie_number, min_calorie); - env.add_overlay_log(log_msg); - env.log(log_msg); - if (calorie_number <= min_calorie){ - env.log("Calorie threshold reached. Stopping resets to check Terrakion.", COLOR_PURPLE); - return true; - } - return false; -} - - -void detect_warp_pad(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats){ - - ButtonWatcher ButtonA( - COLOR_RED, - ButtonType::ButtonA, - {0.4, 0.1, 0.2, 0.8}, - &env.console.overlay() - ); - - int ret = wait_until( - env.console, context, 10s, - {ButtonA} - ); - if (ret < 0){ - stats.errors++; - env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "route_terrakion_reset(): Cannot detect warp pad after 10 seconds", - env.console - ); - }else{ - env.log("Detected warp pad."); - // env.console.overlay().add_log("Warp Pad Detected"); - } -} - - -// void wait_until_overworld(SingleSwitchProgramEnvironment& env, ProControllerContext& context, -// ShinyHunt_Terrakion_Descriptor::Stats& stats) { - -// OverworldPartySelectionOverWatcher overworldWatcher( -// COLOR_GREEN, -// &env.console.overlay() -// ); -// int ret = wait_until( -// env.console, context, 10s, -// {overworldWatcher} -// ); -// if (ret < 0){ -// stats.errors++; -// env.update_stats(); -// OperationFailedException::fire( -// ErrorReport::SEND_ERROR_REPORT, -// "wait_until_overworld(): Cannot detect overworld after 10 seconds", -// env.console -// ); -// } else { -// env.log("Detected overworld."); -// env.console.overlay().add_log("Overworld Detected"); -// } -// } - - -void route_reset_terrakion(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats, bool do_rolls=true){ - - context.wait_for_all_requests(); - - // Warp away from Terrakion to despawn - detect_warp_pad(env, context, stats); - pbf_press_button(context, BUTTON_A, 160ms, 80ms); - - // Warp towards Terrakion - detect_warp_pad(env, context, stats); - pbf_press_button(context, BUTTON_A, 160ms, 80ms); - - // Roll and roll back on Terrakion's roof to respawn - detect_warp_pad(env, context, stats); - if (do_rolls){ - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_move_left_joystick(context, {0, -1}, 80ms, 160ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - } - context.wait_for_all_requests(); - env.add_overlay_log("Respawned Terrakion"); - env.log("Respawned Terrakion."); - stats.resets++; - env.update_stats(); -} - - -void route_check_terrakion(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - ShinyHunt_Terrakion_Descriptor::Stats& stats){ - - context.wait_for_all_requests(); - - // Roll to Terrakion to trigger potential shiny sound - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_move_left_joystick(context, {-1, 1}, 80ms, 160ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_move_left_joystick(context, {0, 1}, 80ms, 500ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_move_left_joystick(context, {1, 1}, 80ms, 160ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - pbf_press_button(context, BUTTON_Y, 100ms, 900ms); - - env.add_overlay_log("Checking for Shiny"); - env.log("Checking shiny status of Terrakion."); - context.wait_for_all_requests(); - stats.checks++; - env.update_stats(); -} - -void ShinyHunt_Terrakion::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - ShinyHunt_Terrakion_Descriptor::Stats& stats = env.current_stats(); - ShinySoundHandler shiny_sound_handler(SHINY_DETECTED); - PokemonLA::ShinySoundDetector shiny_detector(env.console, [&](float error_coefficient) -> bool{ - // Warning: This callback will be run from a different thread than this function. - stats.shinies++; - env.update_stats(); - env.console.overlay().add_log("Shiny Sound Detected!", COLOR_YELLOW); - return shiny_sound_handler.on_shiny_sound( - env, env.console, - stats.shinies, - error_coefficient - ); - }); - - if (SAVE_ON_START){ - save_game_to_menu(env.console, context); - pbf_mash_button(context, BUTTON_B, 2000ms); - } - - int consecutive_failures = 0; - run_until( - env.console, context, - [&](ProControllerContext& context){ - while (true){ - context.wait_for_all_requests(); - shiny_sound_handler.process_pending(context); - - try{ - bool stop_resets = are_calories_under_threshold(env, context, stats, THRESHOLD); - if (stop_resets){ - // Reset one additional time without rolling to reset position - route_reset_terrakion(env, context, stats, false); - route_check_terrakion(env, context, stats); - go_home(env.console, context); - reset_game_from_home(env, env.console, context); - }else{ - route_reset_terrakion(env, context, stats); - } - }catch (OperationFailedException&){ - consecutive_failures++; - env.log("Consecutive failures: " + std::to_string(consecutive_failures), COLOR_RED); - if (consecutive_failures >= 3){ - if (PreloadSettings::instance().DEVELOPER_MODE && GlobalSettings::instance().SAVE_DEBUG_VIDEOS_ON_SWITCH){ - env.log("Saving debug video on Switch..."); - env.console.overlay().add_log("Save Debug Video on Switch"); - pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); - context.wait_for_all_requests(); - } - go_home(env.console, context); // go Home to preserve game state for debugging - throw; - } - env.log("Error encountered. Resetting...", COLOR_RED); - stats.errors++; - env.update_stats(); - env.console.overlay().add_log("Error Found. Reset Game", COLOR_RED); - go_home(env.console, context); - reset_game_from_home(env, env.console, context); - } - env.update_stats(); - if (stats.resets.load(std::memory_order_relaxed) % 100 == 0){ - send_program_status_notification(env, NOTIFICATION_STATUS); - } - } - }, - {{shiny_detector}} - ); - - shiny_sound_handler.process_pending(context); - go_home(env.console, context); - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); -} - - -} // namespace PokemonLZA -} // namespace NintendoSwitch -} // namespace PokemonAutomation diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h deleted file mode 100644 index 3433cedd33..0000000000 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h +++ /dev/null @@ -1,49 +0,0 @@ -/* Shiny Hunt - Terrakion - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonLZA_ShinyHunt_Terrakion_H -#define PokemonAutomation_PokemonLZA_ShinyHunt_Terrakion_H - -#include "Common/Cpp/Options/SimpleIntegerOption.h" -#include "CommonFramework/Notifications/EventNotificationsTable.h" -#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" -#include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" -#include "PokemonLZA/Options/PokemonLZA_ShinyDetectedAction.h" - -namespace PokemonAutomation { -namespace NintendoSwitch { -namespace PokemonLZA { - - -class ShinyHunt_Terrakion_Descriptor : public SingleSwitchProgramDescriptor { -public: - ShinyHunt_Terrakion_Descriptor(); - - class Stats; - virtual std::unique_ptr make_stats() const override; -}; - - -class ShinyHunt_Terrakion : public SingleSwitchProgramInstance { -public: - ShinyHunt_Terrakion(); - - virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; - -private: - PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; - SimpleIntegerOption THRESHOLD; - BooleanCheckBoxOption SAVE_ON_START; - ShinySoundDetectedActionOption SHINY_DETECTED; - EventNotificationOption NOTIFICATION_STATUS; - EventNotificationsOption NOTIFICATIONS; -}; - - -} // namespace PokemonLZA -} // namespace NintendoSwitch -} // namespace PokemonAutomation -#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index d9b8630811..64ccd08568 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1719,8 +1719,6 @@ file(GLOB LIBRARY_SOURCES Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_OverworldReset.cpp Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_OverworldReset.h - Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.cpp - Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShuttleRun.cpp Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShuttleRun.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneCafe.cpp From 0be3e4586beefb30101e998c1a41dbf7cb99b80c Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Sun, 28 Dec 2025 01:11:21 -0800 Subject: [PATCH 09/11] Missed Header Removal --- SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp index 8502ecd399..30da3d4796 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp @@ -41,7 +41,6 @@ #include "Programs/ShinyHunting/PokemonLZA_ShuttleRun.h" #include "Programs/ShinyHunting/PokemonLZA_SewerHunter.h" #include "Programs/ShinyHunting/PokemonLZA_ShinyHunt_HelioptileHunter.h" -#include "Programs/ShinyHunting/PokemonLZA_ShinyHunt_Terrakion.h" // Non-Shiny Hunting #include "Programs/NonShinyHunting/PokemonLZA_StatsReset.h" From ce3e1a01f65ae2a25cff8d576c25840ea29898a0 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Sun, 28 Dec 2025 01:14:29 -0800 Subject: [PATCH 10/11] Added Missing Save Option --- .../PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 4b37aa927b..58d5c047ed 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -327,6 +327,11 @@ void ShinyHunt_HyperspaceLegendary::program(SingleSwitchProgramEnvironment& env, ); } + if (SAVE_ON_START){ + save_game_to_menu(env.console, context); + pbf_mash_button(context, BUTTON_B, 2000ms); + } + int consecutive_failures = 0; run_until( env.console, context, From fc15b96fbcb4d43671a01ba8685c91a81ebfb308 Mon Sep 17 00:00:00 2001 From: Eric Zhang Date: Tue, 30 Dec 2025 00:20:16 -0800 Subject: [PATCH 11/11] Modified Reset Stop Condition Fixed on-the-fly min calorie changing. Check calories only after fully executing route to maintain consistent positioning --- ...kemonLZA_ShinyHunt_HyperspaceLegendary.cpp | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp index 58d5c047ed..de1265ccd7 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_HyperspaceLegendary.cpp @@ -9,6 +9,7 @@ #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoOverlay.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/StartupChecks/VideoResolutionCheck.h" @@ -121,43 +122,43 @@ class LegendaryRoute { public: virtual ~LegendaryRoute() = default; - // The route used to reset and respawn the legendary + // A wrapper function to repeatedly run the reset route and exit when min calorie is reached virtual void reset( SingleSwitchProgramEnvironment& env, ProControllerContext& context, ShinyHunt_HyperspaceLegendary_Descriptor::Stats& stats, SimpleIntegerOption& MIN_CALORIE_REMAINING){ - const uint16_t min_calorie = MIN_CALORIE_REMAINING; + while (true){ + const uint16_t min_calorie = MIN_CALORIE_REMAINING; + HyperspaceCalorieDetector calorie_detector(env.logger()); - HyperspaceCalorieLimitWatcher calorie_watcher(env.logger(), min_calorie); - const int ret = run_until( - env.console, context, - [&](ProControllerContext& context){ - while (true){ - reset_route(env, context, stats); - - stats.resets++; - env.update_stats(); - - uint16_t calorie_number = calorie_watcher.calorie_number(); - const std::string log_msg = std::format("Calorie: {}/{}", calorie_number, min_calorie); - env.add_overlay_log(log_msg); - env.log(log_msg); - } - }, - {{calorie_watcher}} - ); - if (ret < 0){ - stats.errors++; + reset_route(env, context, stats); + context.wait_for_all_requests(); + stats.resets++; env.update_stats(); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - std::string(typeid(*this).name()) + " reset(): Error during reset loop.", - env.console - ); + + VideoSnapshot screen = env.console.video().snapshot(); + bool valid_calorie_count = calorie_detector.detect(screen); + if (!valid_calorie_count){ + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + std::string(typeid(*this).name()) + " reset(): Error detecting calorie count.", + env.console + ); + } + uint16_t calorie_number = calorie_detector.calorie_number(); + + const std::string log_msg = std::format("Calorie: {}/{}", calorie_number, min_calorie); + env.add_overlay_log(log_msg); + env.log(log_msg); + if (calorie_number <= min_calorie){ + env.log("min calorie reached"); + break; + } } - env.log("min calorie reached"); } // Unique route used to reset each respective legendary, to be implemented by subclasses