From 0b7983a49f4bd7af61213beccf78572c7074f3b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=86=E2=9C=A0Sa=CD=A5b=CD=A3e=CD=ABr=F0=9F=91=91?= =?UTF-8?q?=E2=B0=80?= Date: Mon, 3 Nov 2025 21:56:33 +0800 Subject: [PATCH 1/2] LZA: add program for hunting static alpha Whirlipede --- .../Source/PokemonLZA/PokemonLZA_Panels.cpp | 3 + .../PokemonLZA_SewerWhirlipedeRunner.cpp | 185 ++++++++++++++++++ .../PokemonLZA_SewerWhirlipedeRunner.h | 48 +++++ SerialPrograms/SourceFiles.cmake | 2 + 4 files changed, 238 insertions(+) create mode 100644 SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp create mode 100644 SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp index b32b7bd5a7..5cc14259ef 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Panels.cpp @@ -28,6 +28,7 @@ #include "Programs/ShinyHunting/PokemonLZA_BeldumHunter.h" #include "Programs/ShinyHunting/PokemonLZA_AutoFossil.h" #include "Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h" +#include "Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h" // Developer #include "Programs/TestPrograms/PokemonLZA_OverworldWatcher.h" @@ -68,8 +69,10 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); 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){ diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp new file mode 100644 index 0000000000..42a07abfb3 --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp @@ -0,0 +1,185 @@ +/* Shiny Hunt - Sewer Whirlipede Runner + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonLA/Inference/Sounds/PokemonLA_ShinySoundDetector.h" +#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" +#include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" +#include "PokemonLZA_SewerWhirlipedeRunner.h" + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonLZA { + +using namespace Pokemon; + + +ShinyHunt_SewerWhirlipedeRunner_Descriptor::ShinyHunt_SewerWhirlipedeRunner_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonLZA:ShinyHunt-SewerWhirlipedeRunner", STRING_POKEMON + " LZA", + "Shiny Hunt - Sewer Whirlipede Runner", + "Programs/PokemonLZA/ShinyHunt-SewerWhirlipedeRunner.html", + "Shiny hunt by repeatedly respawning statis alpha Whirlipede, running just outside of the Lumiose Sewers.", + ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS, {} + ) +{} +class ShinyHunt_SewerWhirlipedeRunner_Descriptor::Stats : public StatsTracker{ +public: + Stats() + : resets(m_stats["Whirlipede Respawning"]) + , day_changes(m_stats["Day/Night Changes"]) + , shinies(m_stats["Shiny Sounds"]) + , errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Whirlipede Respawning"); + m_display_order.emplace_back("Day/Night Changes"); + m_display_order.emplace_back("Shiny Sounds"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + + std::atomic& resets; + std::atomic& day_changes; + std::atomic& shinies; + std::atomic& errors; +}; +std::unique_ptr ShinyHunt_SewerWhirlipedeRunner_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + + +ShinyHunt_SewerWhirlipedeRunner::ShinyHunt_SewerWhirlipedeRunner() + : SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::NOTIFY_ON_FIRST_ONLY) + , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_STATUS, + &SHINY_DETECTED.NOTIFICATIONS, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_RECOVERABLE, + &NOTIFICATION_ERROR_FATAL, + }) +{ + PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); + PA_ADD_OPTION(SHINY_DETECTED); + PA_ADD_OPTION(NOTIFICATIONS); +} + + +void run_one_way(ConsoleHandle& console, ProControllerContext& context){ + ButtonWatcher buttonA(COLOR_RED, ButtonType::ButtonA, {0.3, 0.2, 0.4, 0.7}, &console.overlay()); + run_until( + console, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, 128, 255, 100ms, 100ms); + pbf_press_button(context, BUTTON_L, 100ms, 100ms); + ssf_press_button(context, BUTTON_B, 0ms, 500ms, 0ms); + pbf_move_left_joystick(context, 165, 0, 3000ms, 0ms); + }, + {{buttonA}} + ); +} + +bool fly_to_lumiose_sewers(ConsoleHandle& console, ProControllerContext& context){ + context.wait_for_all_requests(); + pbf_move_left_joystick(context, 192, 192, 50ms, 100ms); // TODO: inference + return fly_from_map(console, context); +} + +void run_at_lumiose_sewers_entrance( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + ShinyHunt_SewerWhirlipedeRunner_Descriptor::Stats& stats = env.current_stats(); + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen(COLOR_BLUE); + int ret = run_until( + env.console, context, + [&](ProControllerContext& context){ + run_one_way(env.console, context); + }, + {{black_screen}} + ); + if (ret == 0){ + env.console.log("[SewerWhirlipedeRunner] Detected day/night change after entering."); + stats.day_changes++; + context.wait_for(std::chrono::milliseconds(2000)); // TODO: wait for ending + } + open_map(env.console, context); + fly_to_lumiose_sewers(env.console, context); +} + +void ShinyHunt_SewerWhirlipedeRunner::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + ShinyHunt_SewerWhirlipedeRunner_Descriptor::Stats& stats = + env.current_stats(); + + if (SHINY_DETECTED.ACTION == ShinySoundDetectedAction::NOTIFY_ON_ALL){ + throw UserSetupError( + env.console, + "Shiny would be detected/notified at most once. Choose one of the other 2 options." + ); + } + + while (true){ + float shiny_coefficient = 1.0; + 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(); + shiny_coefficient = error_coefficient; + return true; + }); + + int ret = run_until( + env.console, context, + [&](ProControllerContext& context){ + while (true){ + send_program_status_notification(env, NOTIFICATION_STATUS); + stats.resets++; + run_at_lumiose_sewers_entrance(env, context); + env.update_stats(); + } + }, + {{shiny_detector}} + ); + + // This should never happen. + if (ret != 0){ + continue; + } + + bool exit = SHINY_DETECTED.on_shiny_sound( + env, env.console, context, + stats.shinies, + shiny_coefficient + ); + + open_map(env.console, context); + if (!fly_to_lumiose_sewers(env.console, context)){ + pbf_mash_button(context, BUTTON_B, 5000ms); + } + + if (exit){ + break; + } + } + + 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_SewerWhirlipedeRunner.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h new file mode 100644 index 0000000000..7d0bc5cc2c --- /dev/null +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h @@ -0,0 +1,48 @@ +/* Shiny Hunt - Sewer Whirlipede Runner + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonLZA_Sewer_Whirlipede_Runner_H +#define PokemonAutomation_PokemonLZA_Sewer_Whirlipede_Runner_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_SewerWhirlipedeRunner_Descriptor : public SingleSwitchProgramDescriptor { +public: + ShinyHunt_SewerWhirlipedeRunner_Descriptor(); + + class Stats; + virtual std::unique_ptr make_stats() const override; +}; + + +class ShinyHunt_SewerWhirlipedeRunner : public SingleSwitchProgramInstance { +public: + ShinyHunt_SewerWhirlipedeRunner(); + + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; + +private: + PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; + + ShinySoundDetectedActionOption SHINY_DETECTED; + + EventNotificationOption NOTIFICATION_STATUS; + EventNotificationsOption NOTIFICATIONS; +}; + + +} // namespace PokemonLZA +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif diff --git a/SerialPrograms/SourceFiles.cmake b/SerialPrograms/SourceFiles.cmake index 9a9f760816..d4a0299b60 100644 --- a/SerialPrograms/SourceFiles.cmake +++ b/SerialPrograms/SourceFiles.cmake @@ -1600,6 +1600,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_ShinyHunt_OverworldReset.h Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.cpp Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_WildZoneEntrance.h + Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp + Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h Source/PokemonLZA/Programs/TestPrograms/PokemonLZA_MoveBoxArrow.cpp Source/PokemonLZA/Programs/TestPrograms/PokemonLZA_MoveBoxArrow.h Source/PokemonLZA/Programs/TestPrograms/PokemonLZA_OverworldWatcher.cpp From 5fbc63687efd5086c9467d0a48526e14dff22ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=86=E2=9C=A0Sa=CD=A5b=CD=A3e=CD=ABr=F0=9F=91=91?= =?UTF-8?q?=E2=B0=80?= Date: Tue, 4 Nov 2025 10:00:13 +0800 Subject: [PATCH 2/2] remove shiny sound detection; add duration --- .../PokemonLZA_SewerWhirlipedeRunner.cpp | 81 ++++--------------- .../PokemonLZA_SewerWhirlipedeRunner.h | 8 +- 2 files changed, 18 insertions(+), 71 deletions(-) diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp index 42a07abfb3..2bded54e85 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.cpp @@ -13,7 +13,6 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "Pokemon/Pokemon_Strings.h" -#include "PokemonLA/Inference/Sounds/PokemonLA_ShinySoundDetector.h" #include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" #include "PokemonLZA/Programs/PokemonLZA_BasicNavigation.h" #include "PokemonLZA_SewerWhirlipedeRunner.h" @@ -30,7 +29,7 @@ ShinyHunt_SewerWhirlipedeRunner_Descriptor::ShinyHunt_SewerWhirlipedeRunner_Desc "PokemonLZA:ShinyHunt-SewerWhirlipedeRunner", STRING_POKEMON + " LZA", "Shiny Hunt - Sewer Whirlipede Runner", "Programs/PokemonLZA/ShinyHunt-SewerWhirlipedeRunner.html", - "Shiny hunt by repeatedly respawning statis alpha Whirlipede, running just outside of the Lumiose Sewers.", + "Shiny hunt by repeatedly respawning static alpha Whirlipede, running just outside of the Lumiose Sewers.", ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, AllowCommandsWhenRunning::DISABLE_COMMANDS, {} ) @@ -40,18 +39,15 @@ class ShinyHunt_SewerWhirlipedeRunner_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Whirlipede Respawning"]) , day_changes(m_stats["Day/Night Changes"]) - , shinies(m_stats["Shiny Sounds"]) , errors(m_stats["Errors"]) { m_display_order.emplace_back("Whirlipede Respawning"); m_display_order.emplace_back("Day/Night Changes"); - m_display_order.emplace_back("Shiny Sounds"); m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& day_changes; - std::atomic& shinies; std::atomic& errors; }; std::unique_ptr ShinyHunt_SewerWhirlipedeRunner_Descriptor::make_stats() const{ @@ -60,18 +56,16 @@ std::unique_ptr ShinyHunt_SewerWhirlipedeRunner_Descriptor::make_s ShinyHunt_SewerWhirlipedeRunner::ShinyHunt_SewerWhirlipedeRunner() - : SHINY_DETECTED("Shiny Detected", "", "2000 ms", ShinySoundDetectedAction::NOTIFY_ON_FIRST_ONLY) + : DURATION("duration:
Run the program this long.", LockMode::UNLOCK_WHILE_RUNNING, "1 h") , NOTIFICATION_STATUS("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATIONS({ &NOTIFICATION_STATUS, - &SHINY_DETECTED.NOTIFICATIONS, &NOTIFICATION_PROGRAM_FINISH, &NOTIFICATION_ERROR_RECOVERABLE, &NOTIFICATION_ERROR_FATAL, }) { - PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); - PA_ADD_OPTION(SHINY_DETECTED); + PA_ADD_OPTION(DURATION); PA_ADD_OPTION(NOTIFICATIONS); } @@ -90,10 +84,11 @@ void run_one_way(ConsoleHandle& console, ProControllerContext& context){ ); } -bool fly_to_lumiose_sewers(ConsoleHandle& console, ProControllerContext& context){ +void fly_to_lumiose_sewers(ConsoleHandle& console, ProControllerContext& context){ context.wait_for_all_requests(); - pbf_move_left_joystick(context, 192, 192, 50ms, 100ms); // TODO: inference - return fly_from_map(console, context); + pbf_move_left_joystick(context, 192, 192, 50ms, 200ms); // TODO: inference + fly_from_map(console, context); + context.wait_for(std::chrono::milliseconds(1000)); } void run_at_lumiose_sewers_entrance( @@ -112,9 +107,9 @@ void run_at_lumiose_sewers_entrance( {{black_screen}} ); if (ret == 0){ - env.console.log("[SewerWhirlipedeRunner] Detected day/night change after entering."); + env.console.log("[SewerWhirlipedeRunner] Detected day/night change"); stats.day_changes++; - context.wait_for(std::chrono::milliseconds(2000)); // TODO: wait for ending + context.wait_for(std::chrono::milliseconds(2000)); } open_map(env.console, context); fly_to_lumiose_sewers(env.console, context); @@ -123,57 +118,13 @@ void run_at_lumiose_sewers_entrance( void ShinyHunt_SewerWhirlipedeRunner::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ ShinyHunt_SewerWhirlipedeRunner_Descriptor::Stats& stats = env.current_stats(); - - if (SHINY_DETECTED.ACTION == ShinySoundDetectedAction::NOTIFY_ON_ALL){ - throw UserSetupError( - env.console, - "Shiny would be detected/notified at most once. Choose one of the other 2 options." - ); - } - - while (true){ - float shiny_coefficient = 1.0; - 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(); - shiny_coefficient = error_coefficient; - return true; - }); - - int ret = run_until( - env.console, context, - [&](ProControllerContext& context){ - while (true){ - send_program_status_notification(env, NOTIFICATION_STATUS); - stats.resets++; - run_at_lumiose_sewers_entrance(env, context); - env.update_stats(); - } - }, - {{shiny_detector}} - ); - - // This should never happen. - if (ret != 0){ - continue; - } - - bool exit = SHINY_DETECTED.on_shiny_sound( - env, env.console, context, - stats.shinies, - shiny_coefficient - ); - - open_map(env.console, context); - if (!fly_to_lumiose_sewers(env.console, context)){ - pbf_mash_button(context, BUTTON_B, 5000ms); - } - - if (exit){ - break; - } - } + WallClock deadline = current_time() + DURATION.get(); + do{ + send_program_status_notification(env, NOTIFICATION_STATUS); + stats.resets++; + run_at_lumiose_sewers_entrance(env, context); + env.update_stats(); + }while (current_time() < deadline); go_home(env.console, context); send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); diff --git a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h index 7d0bc5cc2c..0524ae248e 100644 --- a/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h +++ b/SerialPrograms/Source/PokemonLZA/Programs/ShinyHunting/PokemonLZA_SewerWhirlipedeRunner.h @@ -7,10 +7,9 @@ #ifndef PokemonAutomation_PokemonLZA_Sewer_Whirlipede_Runner_H #define PokemonAutomation_PokemonLZA_Sewer_Whirlipede_Runner_H +#include "Common/Cpp/Options/TimeDurationOption.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 { @@ -33,10 +32,7 @@ class ShinyHunt_SewerWhirlipedeRunner : public SingleSwitchProgramInstance { virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; private: - PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; - - ShinySoundDetectedActionOption SHINY_DETECTED; - + MillisecondsOption DURATION; EventNotificationOption NOTIFICATION_STATUS; EventNotificationsOption NOTIFICATIONS; };