From 5a8e1f5ce006cb37033d8255330938b900490f47 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Sun, 12 Jul 2026 10:11:24 -0500 Subject: [PATCH] Move auto catch logic to navigation. Use optimal actions for safari encounters. --- .../PokemonFRLG_DexRegistrationDetector.cpp | 68 ----- .../PokemonFRLG_DexRegistrationDetector.h | 51 ---- .../PokemonFRLG_PokedexRegisteredDetector.cpp | 42 ++- .../PokemonFRLG_PokedexRegisteredDetector.h | 12 +- .../PokemonFRLG/PokemonFRLG_Navigation.cpp | 277 +++++++++++++++++- .../PokemonFRLG/PokemonFRLG_Navigation.h | 21 ++ .../PokemonFRLG_RngNavigation.cpp | 141 +-------- .../PokemonFRLG_RngNavigation.h | 7 - .../RngManipulation/PokemonFRLG_WildRng.cpp | 31 +- SerialPrograms/cmake/SourceFiles.cmake | 2 - 10 files changed, 375 insertions(+), 277 deletions(-) delete mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.cpp delete mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.h diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.cpp deleted file mode 100644 index f231030e2b..0000000000 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* Dex Registration Detector - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "Common/Cpp/Exceptions.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/ImageTypes/ImageRGB32.h" -#include "CommonFramework/ImageTools/ImageStats.h" -#include "CommonFramework/ImageTypes/ImageViewRGB32.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonTools/Images/SolidColorTest.h" -#include "CommonTools/Images/ImageFilter.h" -#include "CommonTools/Images/WaterfillUtilities.h" -#include "PokemonFRLG_DexRegistrationDetector.h" - - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonFRLG{ - - -DexRegistrationDetector::DexRegistrationDetector(Color color) - : m_top_box(0.940865, 0.013942, 0.049519, 0.071394) - , m_header_box(0.037019, 0.147355, 0.928365, 0.015865) - , m_divider_box(0.016827, 0.560577, 0.961827, 0.002885) - , m_body_box(0.016827, 0.582577, 0.961827, 0.002885) - , m_bottom_box(0.007692, 0.912500, 0.688942, 0.075721) -{} -void DexRegistrationDetector::make_overlays(VideoOverlaySet& items) const{ - const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_top_box)); // Green RGB(189, 205, 0) - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_header_box)); // White - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_divider_box)); // Brown RGB(136, 100, 38) - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_body_box)); // Beige RGB(255, 245, 204) - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_bottom_box)); // Green - -} -bool DexRegistrationDetector::detect(const ImageViewRGB32& screen){ - ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); - - ImageViewRGB32 top_image = extract_box_reference(game_screen, m_top_box); - ImageViewRGB32 header_image = extract_box_reference(game_screen, m_header_box); - ImageViewRGB32 divider_image = extract_box_reference(game_screen, m_divider_box); - ImageViewRGB32 body_image = extract_box_reference(game_screen, m_body_box); - ImageViewRGB32 bottom_image = extract_box_reference(game_screen, m_bottom_box); - - - if ( is_solid(top_image, { 0.480, 0.520, 0.000 }, 0.25, 20) - && is_white(header_image) - && is_solid(divider_image, { 0.496, 0.365, 0.139 }, 0.25, 20) - && is_solid(body_image, { 0.362, 0.348, 0.290 }, 0.25, 20) - && is_solid(bottom_image, { 0.480, 0.520, 0.000 }, 0.25, 20) - && !is_white(top_image) - && !is_white(divider_image) - && !is_white(bottom_image) - ){ - return true; - } - return false; -} - - - -} -} -} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.h deleted file mode 100644 index f2e36e3fc7..0000000000 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Dex Registration Detector - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonFRLG_DexRegistrationDetector_H -#define PokemonAutomation_PokemonFRLG_DexRegistrationDetector_H - -#include -#include "Common/Cpp/Color.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonTools/VisualDetector.h" -#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" -#include "PokemonFRLG/PokemonFRLG_Settings.h" - -namespace PokemonAutomation{ - class CancellableScope; - class VideoFeed; -namespace NintendoSwitch{ -namespace PokemonFRLG{ - -class DexRegistrationDetector : public StaticScreenDetector{ -public: - DexRegistrationDetector(Color color); - - virtual void make_overlays(VideoOverlaySet& items) const override; - virtual bool detect(const ImageViewRGB32& screen) override; - -private: - ImageFloatBox m_top_box; - ImageFloatBox m_header_box; - ImageFloatBox m_divider_box; - ImageFloatBox m_body_box; - ImageFloatBox m_bottom_box; -}; -class DexRegistrationWatcher : public DetectorToFinder{ -public: - DexRegistrationWatcher(Color color) - : DetectorToFinder("DexRegistrationWatcher", std::chrono::milliseconds(50), color) - {} -}; - - - -} -} -} - -#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp index ea4e741cf2..205e5f088f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp @@ -6,6 +6,7 @@ #include "Kernels/Waterfill/Kernels_Waterfill_Types.h" #include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/SolidColorTest.h" #include "CommonTools/Images/WaterfillUtilities.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" #include "PokemonFRLG_PokedexRegisteredDetector.h" @@ -38,17 +39,26 @@ class PokedexRegisteredMatcher : public ImageMatch::WaterfillTemplateMatcher{ PokedexRegisteredDetector::PokedexRegisteredDetector( Color color, - VideoOverlay* overlay, - const ImageFloatBox& box + VideoOverlay* overlay ) : m_color(color) , m_overlay(overlay) - , m_box(box) + , m_box(0.85, 0.923, 0.082, 0.070) + , m_top_box(0.940865, 0.013942, 0.049519, 0.071394) + , m_header_box(0.037019, 0.147355, 0.928365, 0.015865) + , m_divider_box(0.016827, 0.560577, 0.961827, 0.002885) + , m_body_box(0.016827, 0.582577, 0.961827, 0.002885) + , m_bottom_box(0.007692, 0.912500, 0.688942, 0.075721) {} void PokedexRegisteredDetector::make_overlays(VideoOverlaySet& items) const{ const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; - items.add(m_color, GAME_BOX.inner_to_outer(m_box)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box)); // White "A" button prompt + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_top_box)); // Green RGB(189, 205, 0) + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_header_box)); // White + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_divider_box)); // Brown RGB(136, 100, 38) + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_body_box)); // Beige RGB(255, 245, 204) + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_bottom_box)); // Green } bool PokedexRegisteredDetector::detect(const ImageViewRGB32& screen){ @@ -86,6 +96,30 @@ bool PokedexRegisteredDetector::detect(const ImageViewRGB32& screen){ } ); + if (!found){ + m_last_detected_box.reset(); + + ImageViewRGB32 top_image = extract_box_reference(game_screen, m_top_box); + ImageViewRGB32 header_image = extract_box_reference(game_screen, m_header_box); + ImageViewRGB32 divider_image = extract_box_reference(game_screen, m_divider_box); + ImageViewRGB32 body_image = extract_box_reference(game_screen, m_body_box); + ImageViewRGB32 bottom_image = extract_box_reference(game_screen, m_bottom_box); + + if (is_solid(top_image, { 0.480, 0.520, 0.000 }, 0.25, 20) + && is_white(header_image) + && is_solid(divider_image, { 0.496, 0.365, 0.139 }, 0.25, 20) + && is_solid(body_image, { 0.362, 0.348, 0.290 }, 0.25, 20) + && is_solid(bottom_image, { 0.480, 0.520, 0.000 }, 0.25, 20) + && !is_white(top_image) + && !is_white(divider_image) + && !is_white(bottom_image) + ){ + return true; + } + + return false; + } + if (m_overlay){ if (found){ m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN); diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h index 1c0a8dfe30..4f9c37d221 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h @@ -18,12 +18,12 @@ namespace PokemonFRLG{ // Detects the white "A" button prompt that appears in the bottom right of the // screen when a Pokémon is registered in the Pokédex. +// If that fails, detect the various colors of the Pokédex registration screen. class PokedexRegisteredDetector : public StaticScreenDetector{ public: PokedexRegisteredDetector( Color color, - VideoOverlay* overlay, - const ImageFloatBox& box = ImageFloatBox(0.85, 0.923, 0.082, 0.070) + VideoOverlay* overlay ); virtual void make_overlays(VideoOverlaySet& items) const override; @@ -35,6 +35,11 @@ class PokedexRegisteredDetector : public StaticScreenDetector{ Color m_color; VideoOverlay* m_overlay; const ImageFloatBox m_box; + const ImageFloatBox m_top_box; + const ImageFloatBox m_header_box; + const ImageFloatBox m_divider_box; + const ImageFloatBox m_body_box; + const ImageFloatBox m_bottom_box; ImageFloatBox m_last_detected; std::optional m_last_detected_box; @@ -45,10 +50,9 @@ class PokedexRegisteredWatcher : public DetectorToFinder= 10){ + console.log("auto_catch(): failed to detect battle menu"); + return -1; + } + count++; + + BattleMenuWatcher battle_menu(COLOR_RED); + PartyMenuWatcher party_menu(COLOR_RED); + PokedexRegisteredWatcher dex_registration(COLOR_RED, &console.overlay()); + BlackScreenWatcher black_screen(COLOR_RED); + CatchFanfareDetector catch_detector(console.logger(), [&](float error_coefficient) -> bool{ + catch_coefficient = error_coefficient; + return true; + }); + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + for (int i = 0; i < 60; i++){ + pbf_press_button(context, BUTTON_B, 200ms, 300ms); + } + }, + { battle_menu, party_menu, dex_registration, black_screen, catch_detector }, + 10ms + ); + + int start_ret; + switch (ret){ + case 0: + console.log("Battle menu detected"); + break; + case 1: + console.log("Party menu detected. Attempting to send out next Pokemon"); + pbf_move_left_joystick(context, { 0, -1 }, 200ms, 300ms); + pbf_mash_button(context, BUTTON_A, 1000ms); + continue; + case 2: + console.log("Dex registration detected. Exiting battle..."); + pbf_mash_button(context, BUTTON_B, 5000ms); + return catch_detected ? static_cast(i) : 0; + case 3: + console.log("Black screen detected. Battle exited."); + pbf_mash_button(context, BUTTON_B, 2500ms); + return catch_detected ? static_cast(i) : 0; + case 4: + console.log("Catch detected!", COLOR_BLUE); + catch_detected = true; + pbf_wait(context, 2000ms); + continue; + default: + console.log("No recognized state. Try checking if in the overworld..."); + StartMenuWatcher start_menu; + context.wait_for_all_requests(); + start_ret = run_until( + console, context, + [](ProControllerContext& context) { + for (int i = 0; i < 3; i++){ + pbf_press_button(context, BUTTON_PLUS, 200ms, 2800ms); + pbf_mash_button(context, BUTTON_B, 500ms); + } + }, + { start_menu } + ); + if (start_ret < 0){ + console.log("auto_catch(): no recognized state after 30 seconds."); + return true; + } + console.log("Overworld detected."); + pbf_mash_button(context, BUTTON_B, 500ms); + context.wait_for_all_requests(); + return catch_detected ? static_cast(i) : 0; + } + + break; + } + + if (i == max_ball_throws){ + break; + } + + // select BAG (selection arrow does not wrap around) + pbf_move_left_joystick(context, { +1, 0 }, 100ms, 150ms); + pbf_move_left_joystick(context, { 0, +1 }, 100ms, 150ms); + pbf_move_left_joystick(context, { +1, 0 }, 100ms, 150ms); + pbf_move_left_joystick(context, { 0, +1 }, 100ms, 150ms); + + BagWatcher bag_open(COLOR_RED); + int ret2 = run_until( + console, context, + [](ProControllerContext& context) { + for (int i = 0; i < 5; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + } + }, + { bag_open } + ); + if (ret2 < 0){ + console.log("auto_catch(): failed to open bag."); + return -1; + } + + if (i == 0){ + // go to balls pocket (pockets do not wrap around, topmost item will already be selected) + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 800ms); + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 800ms); + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 800ms); + } + + // use ball + pbf_mash_button(context, BUTTON_A, 5s); + } + + console.log("auto_catch(): ran out of balls."); + return 0; +} + +int auto_catch_safari( + ConsoleHandle& console, + ProControllerContext& context, + Language game_language, + int& safari_balls_remaining, + std::string encounter_name +){ + float catch_coefficient = 1.0; + bool battle_detected = false; + + SafariOptimalAction safari_optimal_action(game_language); + auto actions = safari_optimal_action.get_optimal_actions( + console, + encounter_name, + safari_balls_remaining + ); + int action_count = 0; + + std::vector action_list; + if (actions.has_value()) + action_list = actions->get(); + + AdvanceBattleDialogWatcher advance_battle_dialog(COLOR_RED); + WhiteDialogWatcher white_dialog(COLOR_RED); + BattleMenuWatcher battle_menu(COLOR_RED); + PartyMenuWatcher party_menu(COLOR_RED); + PokedexRegisteredWatcher dex_registration(COLOR_RED, &console.overlay()); + BlackScreenWatcher battle_end(COLOR_RED); + CatchFanfareDetector catch_detector(console.logger(), [&](float error_coefficient) -> bool{ + catch_coefficient = error_coefficient; + return true; + }); + BattleSelectionArrowWatcher nickname_question_arrow( + COLOR_RED, + &console.overlay(), + BattleConfirmationOption::YES + ); + + WallClock last_detected_time = current_time(); + while (true){ + if (current_time() - last_detected_time > std::chrono::seconds(20)){ + console.log("auto_catch_safari(): No battle activity detected for 20 seconds."); + + if (!battle_detected){ + console.log("Unable to detect battle activity."); + return -1; + } + + StartMenuWatcher start_menu; + context.wait_for_all_requests(); + int start_ret = run_until( + console, context, + [](ProControllerContext& context) { + for (int i = 0; i < 3; i++){ + pbf_press_button(context, BUTTON_PLUS, 200ms, 2800ms); + pbf_mash_button(context, BUTTON_B, 500ms); + } + }, + { start_menu } + ); + if (start_ret < 0){ + console.log("auto_catch_safari(): no recognized state after 30 seconds."); + return -1; + } + console.log("Overworld detected."); + pbf_mash_button(context, BUTTON_B, 500ms); + context.wait_for_all_requests(); + return 0; + } + + int ret = wait_until( + console, context, + std::chrono::milliseconds(2000), + { battle_menu, battle_end, advance_battle_dialog, nickname_question_arrow, catch_detector } + ); + + switch (ret){ + case 0: { // Battle Menu + battle_detected = true; + last_detected_time = current_time(); + SafariBattleMenuOption action = SafariBattleMenuOption::BALL; + + if (action_count < action_list.size()){ + action = action_list[action_count]; + action_count++; + } + + if (!move_cursor_to_option(console, context, action)){ + console.log("Failed to move cursor to option.", COLOR_RED); + if (action_count > 0){ + action_count--; + } + break; + } + + pbf_press_button(context, BUTTON_A, 200ms, 200ms); + context.wait_for_all_requests(); + + if (action == SafariBattleMenuOption::BALL){ + safari_balls_remaining--; + } + + break; + } + case 1: { // Battle End + console.log("Failed to catch pokemon."); + return 0; + } + case 2: // Various catch indicators + case 3: + case 4: { + console.log("Pokemon Caught!"); + + bool exiting_menus = true; + while (exiting_menus){ + int ret2 = wait_until( + console, context, + std::chrono::milliseconds(5000), + { nickname_question_arrow, white_dialog, advance_battle_dialog, dex_registration } + ); + + switch (ret2){ + case 0: + case 1: + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + exiting_menus = false; + break; + case 2: + case 3: + pbf_press_button(context, BUTTON_B, 200ms, 0ms); + context.wait_for_all_requests(); + } + } + + pbf_mash_button(context, BUTTON_B, 1500ms); + context.wait_for_all_requests(); + return 1; + } + } + } + + return 0; +} + int grass_spin(ConsoleHandle& console, ProControllerContext& context, bool leftright, Seconds timeout){ BlackScreenWatcher battle_triggered(COLOR_RED); BattleDialogWatcher battle_entered(COLOR_RED); diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index 2914425481..4f4456d5e2 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -9,6 +9,7 @@ #ifndef PokemonAutomation_PokemonFRLG_Navigation_H #define PokemonAutomation_PokemonFRLG_Navigation_H +#include "CommonFramework/Language.h" #include "CommonFramework/Tools/VideoStream.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartySlot.h" @@ -121,6 +122,26 @@ int grass_spin(ConsoleHandle& console, ProControllerContext& context, bool leftr // returns -1 if no encounter is triggered, 0 if a non-shiny is encounter, and 1 if a shiny is encountered int fish_encounter(ConsoleHandle& console, ProControllerContext& context, Seconds timeout = 300s); +// Automatically catch a Pokemon. +// Returns -1 if there are detection issues, 0 if the Pokemon was not caught, and 1 if it was caught. +int auto_catch( + ConsoleHandle& console, + ProControllerContext& context, + const uint64_t& max_ball_throws +); + +// Automatically catch a Pokemon encountered in the Safari Zone. +// uses optimal Safari Zone catching strategy. +// safari_balls_remaining is updated to reflect the number of balls remaining after the encounter. +// Returns -1 if there are detection issues, 0 if the Pokemon was not caught, and 1 if it was caught. +int auto_catch_safari( + ConsoleHandle& console, + ProControllerContext& context, + Language game_language, + int& safari_balls_remaining, + std::string encounter_name +); + // Go to home to check that scaling is 100%. Then resume game. void home_black_border_check(ConsoleHandle& console, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp index 4e3745f9be..00e917ef74 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -1,12 +1,15 @@ +/* Rng Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" -#include "PokemonFRLG/Inference/Sounds/PokemonFRLG_CatchFanfareDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" @@ -15,13 +18,10 @@ #include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" #include "PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h" #include "PokemonFRLG/Inference/PokemonFRLG_PokemonSpriteReader.h" #include "PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h" -#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_BlindNavigation.h" #include "PokemonFRLG_RngCalibration.h" @@ -154,135 +154,6 @@ AdvObservedPokemon read_summary( return pokemon; } - -int auto_catch( - ConsoleHandle& console, - ProControllerContext& context, - const uint64_t& max_ball_throws, - bool safari_zone -){ - float catch_coefficient = 1.0; - bool catch_detected = false; - - for (uint64_t i=0; i<=max_ball_throws; i++){ - int count = 0; - while(true){ - if (count >= 10){ - console.log("auto_catch(): failed to detect battle menu"); - return -1; - } - count++; - - BattleMenuWatcher battle_menu(COLOR_RED); - PartyMenuWatcher party_menu(COLOR_RED); - DexRegistrationWatcher dex_registration(COLOR_RED); - BlackScreenWatcher black_screen(COLOR_RED); - CatchFanfareDetector catch_detector(console.logger(), [&](float error_coefficient) -> bool{ - catch_coefficient = error_coefficient; - return true; - }); - context.wait_for_all_requests(); - int ret = run_until( - console, context, - [](ProControllerContext& context) { - for (int i=0; i<60; i++){ - pbf_press_button(context, BUTTON_B, 200ms, 300ms); - } - }, - { battle_menu, party_menu, dex_registration, black_screen, catch_detector}, - 10ms - ); - - int start_ret; - switch (ret){ - case 0: - console.log("Battle menu detected"); - break; - case 1: - console.log("Party menu detected. Attempting to send out next Pokemon"); - pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms); - pbf_mash_button(context, BUTTON_A, 1000ms); - continue; - case 2: - console.log("Dex registration detected. Exiting battle..."); - pbf_mash_button(context, BUTTON_B, 5000ms); - return catch_detected ? static_cast(i) : 0; - case 3: - console.log("Black screen detected. Battle exited."); - pbf_mash_button(context, BUTTON_B, 2500ms); - return catch_detected ? static_cast(i) : 0; - case 4: - console.log("Catch detected!", COLOR_BLUE); - catch_detected = true; - pbf_wait(context, 2000ms); - continue; - default: - console.log("No recognized state. Try checking if in the overworld..."); - StartMenuWatcher start_menu; - context.wait_for_all_requests(); - start_ret = run_until( - console, context, - [](ProControllerContext& context) { - for (int i=0; i<3; i++){ - pbf_press_button(context, BUTTON_PLUS, 200ms, 2800ms); - pbf_mash_button(context, BUTTON_B, 500ms); - } - }, - { start_menu } - ); - if (start_ret < 0){ - console.log("auto_catch(): no recognized state after 30 seconds."); - return true; - } - console.log("Overworld detected."); - pbf_mash_button(context, BUTTON_B, 500ms); - context.wait_for_all_requests(); - return catch_detected ? static_cast(i) : 0; - } - - break; - } - - if (i == max_ball_throws) { break; } - - if (!safari_zone){ - // select BAG (selection arrow does not wrap around) - pbf_move_left_joystick(context, {+1, 0}, 100ms, 150ms); - pbf_move_left_joystick(context, {0, +1}, 100ms, 150ms); - pbf_move_left_joystick(context, {+1, 0}, 100ms, 150ms); - pbf_move_left_joystick(context, {0, +1}, 100ms, 150ms); - - BagWatcher bag_open(COLOR_RED); - int ret2 = run_until( - console, context, - [](ProControllerContext& context) { - for (int i=0; i<5; i++){ - pbf_press_button(context, BUTTON_A, 200ms, 1800ms); - } - }, - { bag_open } - ); - if (ret2 < 0){ - console.log("auto_catch(): failed to open bag."); - return -1; - } - - if (i == 0){ - // go to balls pocket (pockets do not wrap around, topmost item will already be selected) - pbf_move_left_joystick(context, {+1, 0}, 200ms, 800ms); - pbf_move_left_joystick(context, {+1, 0}, 200ms, 800ms); - pbf_move_left_joystick(context, {+1, 0}, 200ms, 800ms); - } - } - - // use ball - pbf_mash_button(context, BUTTON_A, 5s); - } - - console.log("auto_catch(): ran out of balls."); - return 0; -} - bool use_rare_candy( ConsoleHandle& console, ProControllerContext& context, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h index 29c647a25b..90d1db32ce 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h @@ -45,13 +45,6 @@ AdvObservedPokemon read_summary( std::set species = {} ); -int auto_catch( - ConsoleHandle& console, - ProControllerContext& context, - const uint64_t& max_ball_throws, - bool safari_zone = false -); - bool use_rare_candy( ConsoleHandle& console, ProControllerContext& context, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp index e4c542813f..8045ff6cfc 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp @@ -6,14 +6,18 @@ #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/Language.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/StartupChecks/StartProgramChecks.h" #include "Pokemon/Pokemon_Strings.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" +#include "PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h" +#include "PokemonFRLG/Programs/PokemonFRLG_SafariOptimalAction.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" @@ -520,15 +524,32 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& break; } - int balls_thrown = auto_catch(env.console, context, MAX_BALL_THROWS, safari_zone); - if (balls_thrown < 0){ + int catch_result = 0; + if (safari_zone){ + int safari_balls_remaining = 30; + + pbf_press_button(context, BUTTON_B, 100ms, 500ms); + context.wait_for_all_requests(); + + WildEncounterReader reader(COLOR_RED); + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + VideoSnapshot screen = env.console.video().snapshot(); + PokemonFRLG_WildEncounter encounter = reader.read_encounter(env.logger(), LANGUAGE, screen, SAFARI_ZONE_POKEMON_SUBSET); + + catch_result = auto_catch_safari(env.console, context, LANGUAGE, safari_balls_remaining, encounter.name); + } else{ + catch_result = auto_catch(env.console, context, MAX_BALL_THROWS); + } + + if (catch_result < 0){ stats.errors++; send_program_recoverable_error_notification( env, NOTIFICATION_ERROR_RECOVERABLE, - "auto_catch() encountered an error." - ); + safari_zone ? "auto_catch_safari() encountered an error." : "auto_catch() encountered an error." + ); continue; - }else if(balls_thrown == 0){ + } else if (catch_result == 0){ env.log("Failed catch."); continue; } diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index a8c4776d66..9f67dd21cc 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1495,8 +1495,6 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartySlot.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.cpp Source/PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h - Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.cpp - Source/PokemonFRLG/Inference/Menus/PokemonFRLG_DexRegistrationDetector.h Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_CatchFanfareDetector.cpp