From 85175180cd4e5aaecb4181ea054b7f8c2bf530c0 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 10 Mar 2026 18:44:29 -0500 Subject: [PATCH 1/5] FRLG add selection arrow detection and start menu navigation --- .../PokemonFRLG_SelectionArrowDetector.cpp | 122 ++++++++++ .../PokemonFRLG_SelectionArrowDetector.h | 88 +++++++ .../PokemonFRLG_NuggetBridgeFarmer.cpp | 53 ++--- .../Farming/PokemonFRLG_NuggetBridgeFarmer.h | 2 +- .../PokemonFRLG_StartMenuNavigation.cpp | 217 ++++++++++++++++++ .../PokemonFRLG_StartMenuNavigation.h | 33 +++ SerialPrograms/cmake/SourceFiles.cmake | 4 + 7 files changed, 478 insertions(+), 41 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp new file mode 100644 index 0000000000..88b65e6294 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp @@ -0,0 +1,122 @@ +/* Selection Arrow Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "PokemonFRLG_SelectionArrowDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPosition position){ + switch (position){ + case SelectionArrowPosition::START_MENU_POKEDEX: + return ImageFloatBox(0.685, 0.055, 0.03, 0.075); + case SelectionArrowPosition::START_MENU_POKEMON: + return ImageFloatBox(0.685, 0.145, 0.03, 0.075); + case SelectionArrowPosition::START_MENU_BAG: + return ImageFloatBox(0.685, 0.235, 0.03, 0.075); + case SelectionArrowPosition::START_MENU_TRAINER: + return ImageFloatBox(0.685, 0.330, 0.03, 0.075); + case SelectionArrowPosition::START_MENU_SAVE: + return ImageFloatBox(0.685, 0.415, 0.03, 0.075); + case SelectionArrowPosition::START_MENU_OPTION: + return ImageFloatBox(0.685, 0.510, 0.03, 0.075); + case SelectionArrowPosition::START_MENU_EXIT: + return ImageFloatBox(0.685, 0.6, 0.03, 0.075); + case SelectionArrowPosition::CHOICE_MENU_YES: + return ImageFloatBox(0.660, 0.450, 0.03, 0.075); + case SelectionArrowPosition::CHOICE_MENU_NO: + return ImageFloatBox(0.660, 0.535, 0.03, 0.075); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Selection Arrow Position"); +} + +class SelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + SelectionArrowMatcher(const char* path) + : WaterfillTemplateMatcher( + path, + Color(50, 50, 50), Color(115, 115, 115), 70 + ) + { + m_aspect_ratio_lower = 0.8; + m_aspect_ratio_upper = 1.2; + m_area_ratio_lower = 0.8; + m_area_ratio_upper = 1.2; + } + + static const SelectionArrowMatcher& matcher(){ + static SelectionArrowMatcher matcher("PokemonFRLG/SelectionArrow.png"); + return matcher; + } +}; + +SelectionArrowDetector::SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(box) +{} +SelectionArrowDetector::SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowPosition position +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(arrow_box_for_position(position)) +{} +void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_arrow_box); +} +bool SelectionArrowDetector::detect(const ImageViewRGB32& screen){ + double screen_rel_size = (screen.height() / 1080.0); + double screen_rel_size_2 = screen_rel_size * screen_rel_size; + + double min_area_1080p = 700; + double rmsd_threshold = 80; + size_t min_area = size_t(screen_rel_size_2 * min_area_1080p); + + const std::vector> FILTERS = { + {0xff464646, 0xff787878} + }; + + bool found = match_template_by_waterfill( + screen.size(), + extract_box_reference(screen, m_arrow_box), + SelectionArrowMatcher::matcher(), + FILTERS, + {min_area, SIZE_MAX}, + rmsd_threshold, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + m_last_detected = translate_to_parent(screen, m_arrow_box, object); + return true; + } + ); + + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, m_last_detected, COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + + return found; +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h new file mode 100644 index 0000000000..84fe4162fa --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h @@ -0,0 +1,88 @@ +/* Selection Arrow Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_SelectionArrowDetector_H +#define PokemonAutomation_PokemonFRLG_SelectionArrowDetector_H + +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +enum class SelectionArrowPosition{ + START_MENU_POKEDEX, + START_MENU_POKEMON, + START_MENU_BAG, + START_MENU_TRAINER, + START_MENU_SAVE, + START_MENU_OPTION, + START_MENU_EXIT, + CHOICE_MENU_YES, + CHOICE_MENU_NO +}; + +class SelectionArrowDetector : public StaticScreenDetector{ +public: + SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box + ); + + SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowPosition position + ); + + static ImageFloatBox arrow_box_for_position(SelectionArrowPosition position); + + const ImageFloatBox& last_detected() const { return m_last_detected; } + + virtual void make_overlays(VideoOverlaySet& items) const override; + + // This is not const so that detectors can save/cache state. + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + friend class SelectionArrowWatcher; + + const Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_arrow_box; + + ImageFloatBox m_last_detected; + std::optional m_last_detected_box; +}; +class SelectionArrowWatcher : public DetectorToFinder{ +public: + SelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, box) + {} + SelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + SelectionArrowPosition position, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, arrow_box_for_position(position)) + { + } +}; + + +} +} +} + +#endif \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp index 06e233521b..75dc127fed 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp @@ -14,6 +14,8 @@ #include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_NuggetBridgeFarmer.h" +#include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" +#include namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -59,13 +61,13 @@ NuggetBridgeFarmer::NuggetBridgeFarmer() 0 ) , GO_HOME_WHEN_DONE(false) - /*, PERIODIC_SAVE( + , PERIODIC_SAVE( "Periodically Save:
" "Save the game every this many nuggets. This reduces the loss to game crashes. Set to zero to disable.", LockMode::UNLOCK_WHILE_RUNNING, 10, 0 - )*/ + ) , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATIONS({ &NOTIFICATION_STATUS_UPDATE, @@ -77,7 +79,7 @@ NuggetBridgeFarmer::NuggetBridgeFarmer() PA_ADD_OPTION(NUM_NUGGETS); PA_ADD_OPTION(GO_HOME_WHEN_DONE); - //PA_ADD_OPTION(PERIODIC_SAVE); + PA_ADD_OPTION(PERIODIC_SAVE); PA_ADD_OPTION(NOTIFICATIONS); } @@ -89,13 +91,19 @@ void NuggetBridgeFarmer::program(SingleSwitchProgramEnvironment& env, ProControl NuggetBridgeFarmer_Descriptor::Stats& stats = env.current_stats(); DeferredStopButtonOption::ResetOnExit reset_on_exit(STOP_AFTER_CURRENT); - //for (uint32_t nuggets_since_last_save = 0;; nuggets_since_last_save++) { - while (true) { + for (uint32_t nuggets_since_last_save = 0;; nuggets_since_last_save++) { send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); if (NUM_NUGGETS != 0 && stats.nuggets >= NUM_NUGGETS) { break; } + if (PERIODIC_SAVE != 0 && nuggets_since_last_save >= PERIODIC_SAVE) { + env.console.log("Saving game..."); + + save_game_to_overworld(env.console, context); + nuggets_since_last_save = 0; + } + env.console.log("Exiting Pokemon Center..."); while (true){ BlackScreenWatcher pokemon_ceter_exit(COLOR_RED); @@ -210,41 +218,6 @@ void NuggetBridgeFarmer::program(SingleSwitchProgramEnvironment& env, ProControl } } - //TODO: Implement periodic saving. The Save Menu keeps the last cursor position. Need to implement arrow dectection on the correct option - /*if (PERIODIC_SAVE != 0 && nuggets_since_last_save >= PERIODIC_SAVE) { - StartMenuWatcher start_menu = StartMenuWatcher(COLOR_RED); - - env.console.log("Saving game..."); - - while (true) - { - int ret = run_until( - env.console, context, - [](ProControllerContext& context) { - pbf_press_button(context, BUTTON_PLUS, 320ms, 640ms); - pbf_wait(context, 100ms); - context.wait_for_all_requests(); - }, - { start_menu } - ); - - if (ret == 0) { - break; - } - } - - pbf_press_dpad(context, DPAD_DOWN, 320ms, 320ms); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 320ms); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 320ms); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 320ms); - - pbf_press_button(context, BUTTON_A, 320ms, 320ms); - pbf_press_button(context, BUTTON_A, 320ms, 320ms); - - pbf_mash_button(context, BUTTON_B, 2000ms); - nuggets_since_last_save = 0; - }*/ - stats.nuggets++; env.update_stats(); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h index e3b9a25c2e..66da2669ef 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h @@ -41,7 +41,7 @@ class NuggetBridgeFarmer : public SingleSwitchProgramInstance { SimpleIntegerOption NUM_NUGGETS; GoHomeWhenDoneOption GO_HOME_WHEN_DONE; - //SimpleIntegerOption PERIODIC_SAVE; + SimpleIntegerOption PERIODIC_SAVE; EventNotificationOption NOTIFICATION_STATUS_UPDATE; EventNotificationsOption NOTIFICATIONS; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp new file mode 100644 index 0000000000..92c1400c7a --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp @@ -0,0 +1,217 @@ +/* Start Menu Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Time.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" +#include "PokemonFRLG_StartMenuNavigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG { + +void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context){ + + bool seen_start_menu = false; + + WallClock start = current_time(); + while (true){ + context.wait_for_all_requests(); + + if (current_time() - start > std::chrono::seconds(120)) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "save_game_to_overworld(): Unable to save game after 2 minutes.", + console + ); + } + + StartMenuWatcher start_menu = StartMenuWatcher(COLOR_RED); + + int ret = wait_until( + console, context, + std::chrono::seconds(1), + { + start_menu + } + ); + + if (ret == 0) { + seen_start_menu = true; + } + + while (!seen_start_menu) + { + int ret2 = run_until( + console, context, + [](ProControllerContext& context) { + pbf_press_button(context, BUTTON_PLUS, 320ms, 200ms); + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + }, + { start_menu } + ); + + if (ret2 == 0) { + seen_start_menu = true; + } + } + + SelectionArrowWatcher pokedex_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_POKEDEX + ); + + SelectionArrowWatcher pokemon_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_POKEMON + ); + + SelectionArrowWatcher bag_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_BAG + ); + + SelectionArrowWatcher trainer_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_TRAINER + ); + + SelectionArrowWatcher save_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_SAVE + ); + + SelectionArrowWatcher option_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_OPTION + ); + + SelectionArrowWatcher exit_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::START_MENU_EXIT + ); + + int ret3 = wait_until( + console, context, + std::chrono::seconds(2), + { + pokedex_arrow, + pokemon_arrow, + bag_arrow, + trainer_arrow, + save_arrow, + option_arrow, + exit_arrow + } + ); + + switch (ret3){ + case 0: + console.log("Detected Pokedex Arrow. Naviating to 'SAVE'."); + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + context.wait_for_all_requests(); + break; + case 1: + console.log("Detected Pokemon Arrow. Naviating to 'SAVE'."); + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + context.wait_for_all_requests(); + break; + case 2: + console.log("Detected Bag Arrow. Naviating to 'SAVE'."); + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + context.wait_for_all_requests(); + break; + case 3: + console.log("Detected Trainer Arrow. Naviating to 'SAVE'."); + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + context.wait_for_all_requests(); + break; + case 4: + console.log("Detected Save Arrow."); + break; + case 5: + console.log("Detected Option Arrow. Naviating to 'SAVE'."); + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + context.wait_for_all_requests(); + break; + case 6: + console.log("Detected Exit Arrow. Naviating to 'SAVE'."); + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + context.wait_for_all_requests(); + break; + default: + continue; + } + + pbf_press_button(context, BUTTON_A, 320ms, 400ms); + + bool save_confirmed = false; + // There can be one or two confirmation dialogs depending on whether the game was saved prior to this. + while (true){ + if (current_time() - start > std::chrono::seconds(120)) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "save_game_to_overworld(): Unable to save game after 2 minutes.", + console + ); + } + + SelectionArrowWatcher save_confirm_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPosition::CHOICE_MENU_YES + ); + + int ret4 = wait_until( + console, context, + std::chrono::seconds(10), + { + save_confirm_arrow + } + ); + + if (ret4 == 0) { + console.log("Detected Save Confirmation Arrow. Saving game."); + pbf_press_button(context, BUTTON_A, 320ms, 200ms); + save_confirmed = true; + } + + if (ret4 != 0 && save_confirmed) { + break; + } + + context.wait_for_all_requests(); + } + + context.wait_for_all_requests(); + + return; + } + +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h new file mode 100644 index 0000000000..2f76ed937e --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h @@ -0,0 +1,33 @@ +/* Start Menu Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_StartMenuNavigation_H +#define PokemonAutomation_PokemonFRLG_StartMenuNavigation_H + +namespace PokemonAutomation{ + +class ImageRGB32; + +template class ControllerContext; +struct ImageFloatBox; + +namespace NintendoSwitch{ + +class ConsoleHandle; +class ProController; +using ProControllerContext = ControllerContext; + +namespace PokemonFRLG { + +// Starting from either the overworld or the main menu, save the game. +// This function returns in the overworld. +void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context); + +} +} +} + +#endif \ No newline at end of file diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 867e36916d..7f50baea66 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1414,6 +1414,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h + Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp + Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -1424,6 +1426,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/PokemonFRLG_Settings.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h + Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp + Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp From ac4252427384a736752e67970dd7e288888ff018 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 10 Mar 2026 20:04:14 -0500 Subject: [PATCH 2/5] attempt to make opening start menu more reliable --- .../PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp index 92c1400c7a..1068d902f0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp @@ -53,7 +53,7 @@ void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& contex int ret2 = run_until( console, context, [](ProControllerContext& context) { - pbf_press_button(context, BUTTON_PLUS, 320ms, 200ms); + pbf_press_button(context, BUTTON_PLUS, 320ms, 320ms); pbf_wait(context, 100ms); context.wait_for_all_requests(); }, From b9cebb4421029886dc1a52643f73abc27ac1d1e6 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 10 Mar 2026 20:54:43 -0500 Subject: [PATCH 3/5] Seperate menu enums --- .../PokemonFRLG_SelectionArrowDetector.cpp | 40 +++++++++++----- .../PokemonFRLG_SelectionArrowDetector.h | 48 ++++++++++++++----- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp index 88b65e6294..52e40624a8 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp @@ -13,25 +13,33 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPosition position){ +ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionStartMenu position){ switch (position){ - case SelectionArrowPosition::START_MENU_POKEDEX: + case SelectionArrowPositionStartMenu::POKEDEX: return ImageFloatBox(0.685, 0.055, 0.03, 0.075); - case SelectionArrowPosition::START_MENU_POKEMON: + case SelectionArrowPositionStartMenu::POKEMON: return ImageFloatBox(0.685, 0.145, 0.03, 0.075); - case SelectionArrowPosition::START_MENU_BAG: + case SelectionArrowPositionStartMenu::BAG: return ImageFloatBox(0.685, 0.235, 0.03, 0.075); - case SelectionArrowPosition::START_MENU_TRAINER: + case SelectionArrowPositionStartMenu::TRAINER: return ImageFloatBox(0.685, 0.330, 0.03, 0.075); - case SelectionArrowPosition::START_MENU_SAVE: + case SelectionArrowPositionStartMenu::SAVE: return ImageFloatBox(0.685, 0.415, 0.03, 0.075); - case SelectionArrowPosition::START_MENU_OPTION: + case SelectionArrowPositionStartMenu::OPTION: return ImageFloatBox(0.685, 0.510, 0.03, 0.075); - case SelectionArrowPosition::START_MENU_EXIT: + case SelectionArrowPositionStartMenu::EXIT: return ImageFloatBox(0.685, 0.6, 0.03, 0.075); - case SelectionArrowPosition::CHOICE_MENU_YES: + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Selection Arrow Position"); +} + +ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionConfirmationMenu position) { + switch (position) { + case SelectionArrowPositionConfirmationMenu::YES: return ImageFloatBox(0.660, 0.450, 0.03, 0.075); - case SelectionArrowPosition::CHOICE_MENU_NO: + case SelectionArrowPositionConfirmationMenu::NO: return ImageFloatBox(0.660, 0.535, 0.03, 0.075); default: break; @@ -71,12 +79,22 @@ SelectionArrowDetector::SelectionArrowDetector( SelectionArrowDetector::SelectionArrowDetector( Color color, VideoOverlay* overlay, - SelectionArrowPosition position + SelectionArrowPositionStartMenu position ) : m_color(color) , m_overlay(overlay) , m_arrow_box(arrow_box_for_position(position)) {} +SelectionArrowDetector::SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowPositionConfirmationMenu position +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(arrow_box_for_position(position)) +{ +} void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{ items.add(m_color, m_arrow_box); } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h index 84fe4162fa..d9f32c682e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h @@ -14,16 +14,21 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -enum class SelectionArrowPosition{ - START_MENU_POKEDEX, - START_MENU_POKEMON, - START_MENU_BAG, - START_MENU_TRAINER, - START_MENU_SAVE, - START_MENU_OPTION, - START_MENU_EXIT, - CHOICE_MENU_YES, - CHOICE_MENU_NO +const int START_MENU_OPTION_COUNT = 7; +// The order of these enums should be the same as the order of options in the game menu, from top to bottom, for ease of use with loops. +enum class SelectionArrowPositionStartMenu{ + POKEDEX, + POKEMON, + BAG, + TRAINER, + SAVE, + OPTION, + EXIT +}; + +enum class SelectionArrowPositionConfirmationMenu { + YES, + NO }; class SelectionArrowDetector : public StaticScreenDetector{ @@ -37,10 +42,18 @@ class SelectionArrowDetector : public StaticScreenDetector{ SelectionArrowDetector( Color color, VideoOverlay* overlay, - SelectionArrowPosition position + SelectionArrowPositionStartMenu position + ); + + SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowPositionConfirmationMenu position ); - static ImageFloatBox arrow_box_for_position(SelectionArrowPosition position); + static ImageFloatBox arrow_box_for_position(SelectionArrowPositionStartMenu position); + + static ImageFloatBox arrow_box_for_position(SelectionArrowPositionConfirmationMenu position); const ImageFloatBox& last_detected() const { return m_last_detected; } @@ -72,7 +85,16 @@ class SelectionArrowWatcher : public DetectorToFinder{ SelectionArrowWatcher( Color color, VideoOverlay* overlay, - SelectionArrowPosition position, + SelectionArrowPositionStartMenu position, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, arrow_box_for_position(position)) + { + } + SelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + SelectionArrowPositionConfirmationMenu position, std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) ) : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, arrow_box_for_position(position)) From 656237b25740efb73af2a4ac00a3f36a6ade48aa Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 10 Mar 2026 20:55:33 -0500 Subject: [PATCH 4/5] add move_cursor_to_position --- .../PokemonFRLG_StartMenuNavigation.cpp | 193 ++++++++---------- .../PokemonFRLG_StartMenuNavigation.h | 7 + 2 files changed, 97 insertions(+), 103 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp index 1068d902f0..ca11b61401 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp @@ -18,6 +18,89 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG { +bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionStartMenu destination){ + SelectionArrowWatcher pokedex_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::POKEDEX + ); + + SelectionArrowWatcher pokemon_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::POKEMON + ); + + SelectionArrowWatcher bag_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::BAG + ); + + SelectionArrowWatcher trainer_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::TRAINER + ); + + SelectionArrowWatcher save_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::SAVE + ); + + SelectionArrowWatcher option_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::OPTION + ); + + SelectionArrowWatcher exit_arrow = SelectionArrowWatcher( + COLOR_RED, + &console.overlay(), + SelectionArrowPositionStartMenu::EXIT + ); + + // The order of these watchers needs to match the order of the SelectionArrowPositionStartMenu enum for the math below to work. + int ret = wait_until( + console, context, + std::chrono::seconds(2), + { + pokedex_arrow, + pokemon_arrow, + bag_arrow, + trainer_arrow, + save_arrow, + option_arrow, + exit_arrow + } + ); + + if (ret < 0) { + console.log("Unable to detect selection arrow. Not moving cursor.", COLOR_RED); + return false; + } + + int destination_index = static_cast(destination); + int forward = (destination_index - ret + START_MENU_OPTION_COUNT) % START_MENU_OPTION_COUNT; + int backward = (ret - destination_index + START_MENU_OPTION_COUNT) % START_MENU_OPTION_COUNT; + if (forward <= backward) { + for (int i = 0; i < forward; i++) { + pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); + } + context.wait_for_all_requests(); + } + else { + for (int i = 0; i < backward; i++) + { + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + } + context.wait_for_all_requests(); + } + + return true; +} + void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context){ bool seen_start_menu = false; @@ -65,106 +148,10 @@ void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& contex } } - SelectionArrowWatcher pokedex_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_POKEDEX - ); - - SelectionArrowWatcher pokemon_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_POKEMON - ); - - SelectionArrowWatcher bag_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_BAG - ); - - SelectionArrowWatcher trainer_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_TRAINER - ); - - SelectionArrowWatcher save_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_SAVE - ); - - SelectionArrowWatcher option_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_OPTION - ); - - SelectionArrowWatcher exit_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPosition::START_MENU_EXIT - ); - - int ret3 = wait_until( - console, context, - std::chrono::seconds(2), - { - pokedex_arrow, - pokemon_arrow, - bag_arrow, - trainer_arrow, - save_arrow, - option_arrow, - exit_arrow - } - ); - - switch (ret3){ - case 0: - console.log("Detected Pokedex Arrow. Naviating to 'SAVE'."); - pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); - pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); - pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); - context.wait_for_all_requests(); - break; - case 1: - console.log("Detected Pokemon Arrow. Naviating to 'SAVE'."); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); - context.wait_for_all_requests(); - break; - case 2: - console.log("Detected Bag Arrow. Naviating to 'SAVE'."); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); - context.wait_for_all_requests(); - break; - case 3: - console.log("Detected Trainer Arrow. Naviating to 'SAVE'."); - pbf_press_dpad(context, DPAD_DOWN, 320ms, 400ms); - context.wait_for_all_requests(); - break; - case 4: - console.log("Detected Save Arrow."); - break; - case 5: - console.log("Detected Option Arrow. Naviating to 'SAVE'."); - pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); - context.wait_for_all_requests(); - break; - case 6: - console.log("Detected Exit Arrow. Naviating to 'SAVE'."); - pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); - pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); - context.wait_for_all_requests(); - break; - default: + if (!move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::SAVE)) { continue; } - + pbf_press_button(context, BUTTON_A, 320ms, 400ms); bool save_confirmed = false; @@ -181,12 +168,12 @@ void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& contex SelectionArrowWatcher save_confirm_arrow = SelectionArrowWatcher( COLOR_RED, &console.overlay(), - SelectionArrowPosition::CHOICE_MENU_YES + SelectionArrowPositionConfirmationMenu::YES ); int ret4 = wait_until( console, context, - std::chrono::seconds(10), + std::chrono::seconds(1), { save_confirm_arrow } @@ -205,10 +192,10 @@ void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& contex context.wait_for_all_requests(); } - context.wait_for_all_requests(); + context.wait_for_all_requests(); - return; - } + return; +} } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h index 2f76ed937e..f09b5650f1 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h @@ -22,6 +22,13 @@ using ProControllerContext = ControllerContext; namespace PokemonFRLG { +// Starting from the start menu, move the selection arrow to the specified position. +// Return true if successful, false otherwise (e.g. if selection arrow is not detected). +bool move_cursor_to_position( + ConsoleHandle& console, ProControllerContext& context, + SelectionArrowPositionStartMenu destination +); + // Starting from either the overworld or the main menu, save the game. // This function returns in the overworld. void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context); From ec6a2fc365117788124a07b171bda03b8a04501f Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 10 Mar 2026 21:24:46 -0500 Subject: [PATCH 5/5] Update defaults --- .../Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp index 75dc127fed..2793b6f069 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp @@ -57,7 +57,7 @@ NuggetBridgeFarmer::NuggetBridgeFarmer() "Number of Nuggets:
" "Zero will run until 'Stop after Current Nugget' is pressed or the program is manually stopped.", LockMode::UNLOCK_WHILE_RUNNING, - 120, // About 2 hours of farming. + 100, // About 2 hours of farming. 0 ) , GO_HOME_WHEN_DONE(false) @@ -65,7 +65,7 @@ NuggetBridgeFarmer::NuggetBridgeFarmer() "Periodically Save:
" "Save the game every this many nuggets. This reduces the loss to game crashes. Set to zero to disable.", LockMode::UNLOCK_WHILE_RUNNING, - 10, + 50, 0 ) , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600))