From 9b64f15fdd745e0d7fac3398e3b7a0bfbbd7dd77 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Sun, 29 Mar 2026 15:39:06 -0500 Subject: [PATCH 1/3] Add FGLR Lucky Egg Farmer --- .../PokemonFRLG_PartyHeldItemDetector.cpp | 124 +++++ .../Menus/PokemonFRLG_PartyHeldItemDetector.h | 83 +++ ...kemonFRLG_BattleSelectionArrowDetector.cpp | 171 ++++++ ...PokemonFRLG_BattleSelectionArrowDetector.h | 135 +++++ .../PokemonFRLG_PokedexRegisteredDetector.cpp | 103 ++++ .../PokemonFRLG_PokedexRegisteredDetector.h | 59 ++ .../PokemonFRLG_SelectionArrowDetector.cpp | 34 ++ .../PokemonFRLG_SelectionArrowDetector.h | 34 +- .../PokemonFRLG/PokemonFRLG_Navigation.cpp | 10 +- .../PokemonFRLG/PokemonFRLG_Navigation.h | 6 +- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 + .../Farming/PokemonFRLG_LuckyEggFarmer.cpp | 511 ++++++++++++++++++ .../Farming/PokemonFRLG_LuckyEggFarmer.h | 67 +++ .../PokemonFRLG_BattleMenuNavigation.cpp | 115 ++++ .../PokemonFRLG_BattleMenuNavigation.h | 43 ++ .../PokemonFRLG_StartMenuNavigation.cpp | 110 ++-- .../PokemonFRLG_StartMenuNavigation.h | 7 + SerialPrograms/cmake/SourceFiles.cmake | 10 + 18 files changed, 1560 insertions(+), 64 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp new file mode 100644 index 000000000..b3a0584d9 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp @@ -0,0 +1,124 @@ +/* Party Held Item Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_PartyHeldItemDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +class PartyHeldItemMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + PartyHeldItemMatcher(const char* path) + : WaterfillTemplateMatcher( + path, + Color(210, 210, 100), + Color(255, 255, 190), + 20 + ) + { + m_aspect_ratio_lower = 0.5; + m_aspect_ratio_upper = 2.0; + m_area_ratio_lower = 0.2; + m_area_ratio_upper = 4.0; + } + + static const PartyHeldItemMatcher& matcher(){ + static PartyHeldItemMatcher matcher("PokemonFRLG/PartyHeldItem.png"); + return matcher; + } +}; + +ImageFloatBox PartyHeldItemDetector::box_for_slot(PartyHeldItemSlot slot){ + switch (slot){ + case PartyHeldItemSlot::SLOT_0: + return ImageFloatBox(0.069, 0.285, 0.025, 0.050); + case PartyHeldItemSlot::SLOT_1: + return ImageFloatBox(0.432, 0.150, 0.025, 0.050); + case PartyHeldItemSlot::SLOT_2: + return ImageFloatBox(0.432, 0.3, 0.025, 0.050); + case PartyHeldItemSlot::SLOT_3: + return ImageFloatBox(0.432, 0.45, 0.025, 0.050); + case PartyHeldItemSlot::SLOT_4: + return ImageFloatBox(0.432, 0.6, 0.025, 0.050); + case PartyHeldItemSlot::SLOT_5: + return ImageFloatBox(0.432, 0.725, 0.025, 0.050); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Party Held Item Slot"); +} + +PartyHeldItemDetector::PartyHeldItemDetector( + Color color, + VideoOverlay* overlay, + PartyHeldItemSlot slot +) + : m_color(color) + , m_overlay(overlay) + , m_box(box_for_slot(slot)) +{} +PartyHeldItemDetector::PartyHeldItemDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box +) + : m_color(color) + , m_overlay(overlay) + , m_box(box) +{} +void PartyHeldItemDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_box)); +} +bool PartyHeldItemDetector::detect(const ImageViewRGB32& screen){ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX); + + double screen_rel_size = (game_screen.height() / 1080.0); + double screen_rel_size_2 = screen_rel_size * screen_rel_size; + + double min_area_1080p = 100; + double rmsd_threshold = 220; + size_t min_area = size_t(screen_rel_size_2 * min_area_1080p); + + const std::vector> FILTERS = { + {0xffd2d264, 0xffffffbe} + }; + + bool found = match_template_by_waterfill( + game_screen.size(), + extract_box_reference(game_screen, m_box), + PartyHeldItemMatcher::matcher(), + FILTERS, + {min_area, SIZE_MAX}, + rmsd_threshold, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + m_last_detected = translate_to_parent(game_screen, m_box, object); + return true; + } + ); + + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + + return found; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h new file mode 100644 index 000000000..7dae06b1c --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h @@ -0,0 +1,83 @@ +/* Party Held Item Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_PartyHeldItemDetector_H +#define PokemonAutomation_PokemonFRLG_PartyHeldItemDetector_H + +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +enum class PartyHeldItemSlot{ + SLOT_1, + SLOT_2, + SLOT_3, + SLOT_4, + SLOT_5, + SLOT_6 +}; + +class PartyHeldItemDetector : public StaticScreenDetector{ +public: + PartyHeldItemDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box + ); + PartyHeldItemDetector( + Color color, + VideoOverlay* overlay, + PartyHeldItemSlot slot + ); + + static ImageFloatBox box_for_slot(PartyHeldItemSlot slot); + + 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 PartyHeldItemWatcher; + + const Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_box; + + ImageFloatBox m_last_detected; + std::optional m_last_detected_box; +}; +class PartyHeldItemWatcher : public DetectorToFinder{ +public: + PartyHeldItemWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("PartyHeldItemWatcher", hold_duration, color, overlay, box) + {} + PartyHeldItemWatcher( + Color color, + VideoOverlay* overlay, + PartyHeldItemSlot slot, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("PartyHeldItemWatcher", hold_duration, color, overlay, box_for_slot(slot)) + {} +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp new file mode 100644 index 000000000..8dc1cbfb6 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp @@ -0,0 +1,171 @@ +/* Battle 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/PokemonFRLG_Settings.h" +#include "PokemonFRLG_BattleSelectionArrowDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +class BattleSelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + BattleSelectionArrowMatcher(const char* path) + : WaterfillTemplateMatcher( + path, + Color(35, 40, 40), Color(100, 100, 100), 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 BattleSelectionArrowMatcher& matcher(){ + static BattleSelectionArrowMatcher matcher("PokemonFRLG/BattleSelectionArrow.png"); + return matcher; + } +}; + +ImageFloatBox BattleSelectionArrowDetector::box_for_option(BattleMenuOption option){ + switch (option){ + case BattleMenuOption::FIGHT: + return ImageFloatBox(0.533, 0.767, 0.024, 0.067); + case BattleMenuOption::BAG: + return ImageFloatBox(0.768, 0.767, 0.024, 0.067); + case BattleMenuOption::POKEMON: + return ImageFloatBox(0.533, 0.868, 0.024, 0.067); + case BattleMenuOption::RUN: + return ImageFloatBox(0.768, 0.868, 0.024, 0.067); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Battle Menu Option"); +} +ImageFloatBox BattleSelectionArrowDetector::box_for_option(SafariBattleMenuOption option){ + switch (option){ + case SafariBattleMenuOption::BALL: + return ImageFloatBox(0.533, 0.767, 0.024, 0.067); + case SafariBattleMenuOption::BAIT: + return ImageFloatBox(0.768, 0.767, 0.024, 0.067); + case SafariBattleMenuOption::ROCK: + return ImageFloatBox(0.533, 0.868, 0.024, 0.067); + case SafariBattleMenuOption::RUN: + return ImageFloatBox(0.768, 0.868, 0.024, 0.067); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Safari Battle Menu Option"); +} +ImageFloatBox BattleSelectionArrowDetector::box_for_option(BattleConfirmationOption option){ + switch (option){ + case BattleConfirmationOption::YES: + return ImageFloatBox(0.795, 0.465, 0.030, 0.071); + case BattleConfirmationOption::NO: + return ImageFloatBox(0.795, 0.569, 0.030, 0.071); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Battle Confirmation Option"); +} + +BattleSelectionArrowDetector::BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(box) +{} +BattleSelectionArrowDetector::BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + BattleMenuOption option +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(box_for_option(option)) +{} +BattleSelectionArrowDetector::BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SafariBattleMenuOption option +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(box_for_option(option)) +{} +BattleSelectionArrowDetector::BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(ImageFloatBox(0.768, 0.868, 0.212, 0.159)) +{ +} +BattleSelectionArrowDetector::BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + BattleConfirmationOption option +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(box_for_option(option)) +{} +void BattleSelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_arrow_box)); +} +bool BattleSelectionArrowDetector::detect(const ImageViewRGB32& screen){ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX); + + double screen_rel_size = (game_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 = { + {0xff232828, 0xff646464} + }; + + bool found = match_template_by_waterfill( + game_screen.size(), + extract_box_reference(game_screen, m_arrow_box), + BattleSelectionArrowMatcher::matcher(), + FILTERS, + {min_area, SIZE_MAX}, + rmsd_threshold, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + m_last_detected = translate_to_parent(game_screen, m_arrow_box, object); + return true; + } + ); + + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + + return found; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h new file mode 100644 index 000000000..4557facee --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h @@ -0,0 +1,135 @@ +/* Battle Selection Arrow Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_BattleSelectionArrowDetector_H +#define PokemonAutomation_PokemonFRLG_BattleSelectionArrowDetector_H + +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// The order of these enums matches the in-game layout (row-major, top-left to bottom-right). +const int BATTLE_MENU_OPTION_COUNT = 4; +enum class BattleMenuOption{ + FIGHT, + BAG, + POKEMON, + RUN, +}; + +const int SAFARI_BATTLE_MENU_OPTION_COUNT = 4; +enum class SafariBattleMenuOption{ + BALL, + BAIT, + ROCK, + RUN, +}; + +enum class BattleConfirmationOption{ + YES, + NO, +}; + +class BattleSelectionArrowDetector : public StaticScreenDetector{ +public: + BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box + ); + BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + BattleMenuOption option + ); + BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SafariBattleMenuOption option + ); + BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay + ); + BattleSelectionArrowDetector( + Color color, + VideoOverlay* overlay, + BattleConfirmationOption option + ); + + static ImageFloatBox box_for_option(BattleMenuOption option); + static ImageFloatBox box_for_option(SafariBattleMenuOption option); + static ImageFloatBox box_for_option(BattleConfirmationOption option); + + 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 BattleSelectionArrowWatcher; + + const Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_arrow_box; + + ImageFloatBox m_last_detected; + std::optional m_last_detected_box; +}; +class BattleSelectionArrowWatcher : public DetectorToFinder{ +public: + BattleSelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box) + {} + BattleSelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + BattleMenuOption option, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box_for_option(option)) + {} + BattleSelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + SafariBattleMenuOption option, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box_for_option(option)) + {} + BattleSelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + BattleConfirmationOption option, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, box_for_option(option)) + {} + BattleSelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("BattleSelectionArrowWatcher", hold_duration, color, overlay, ImageFloatBox(0.768, 0.868, 0.212, 0.159)) + {} +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp new file mode 100644 index 000000000..ea4e741cf --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp @@ -0,0 +1,103 @@ +/* Pokedex Registered Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_PokedexRegisteredDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +class PokedexRegisteredMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + PokedexRegisteredMatcher() + : WaterfillTemplateMatcher( + "PokemonFRLG/PokedexButtonA.png", + Color(128, 128, 128), Color(255, 255, 255), 100 + ) + { + m_aspect_ratio_lower = 0.5; + m_aspect_ratio_upper = 2.0; + m_area_ratio_lower = 0.5; + m_area_ratio_upper = 2.0; + } + + static const PokedexRegisteredMatcher& matcher(){ + static PokedexRegisteredMatcher matcher; + return matcher; + } +}; + + +PokedexRegisteredDetector::PokedexRegisteredDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box +) + : m_color(color) + , m_overlay(overlay) + , m_box(box) +{} + +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)); +} + +bool PokedexRegisteredDetector::detect(const ImageViewRGB32& screen){ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + ImageViewRGB32 game_screen = extract_box_reference(screen, GAME_BOX); + + double screen_rel_size = (game_screen.height() / 1080.0); + double screen_rel_size_2 = screen_rel_size * screen_rel_size; + + double min_area_1080p = 700; + double rmsd_threshold = 70; + size_t min_area = size_t(screen_rel_size_2 * min_area_1080p); + + const std::vector> FILTERS = { + {0xff808080, 0xffffffff}, + {0xff909090, 0xffffffff}, + {0xffa0a0a0, 0xffffffff}, + {0xffb0b0b0, 0xffffffff}, + {0xffc0c0c0, 0xffffffff}, + {0xffd0d0d0, 0xffffffff}, + {0xffe0e0e0, 0xffffffff}, + {0xfff0f0f0, 0xffffffff}, + }; + + bool found = match_template_by_waterfill( + game_screen.size(), + extract_box_reference(game_screen, m_box), + PokedexRegisteredMatcher::matcher(), + FILTERS, + {min_area, SIZE_MAX}, + rmsd_threshold, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + m_last_detected = translate_to_parent(game_screen, m_box, object); + return true; + } + ); + + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, GAME_BOX.inner_to_outer(m_last_detected), COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + + return found; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h new file mode 100644 index 000000000..1c0a8dfe3 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h @@ -0,0 +1,59 @@ +/* Pokedex Registered Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_PokedexRegisteredDetector_H +#define PokemonAutomation_PokemonFRLG_PokedexRegisteredDetector_H + +#include "optional" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +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. +class PokedexRegisteredDetector : public StaticScreenDetector{ +public: + PokedexRegisteredDetector( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box = ImageFloatBox(0.85, 0.923, 0.082, 0.070) + ); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + + virtual void reset_state() override { m_last_detected_box.reset(); } + +private: + Color m_color; + VideoOverlay* m_overlay; + const ImageFloatBox m_box; + + ImageFloatBox m_last_detected; + std::optional m_last_detected_box; +}; + +class PokedexRegisteredWatcher : public DetectorToFinder{ +public: + PokedexRegisteredWatcher( + Color color, + VideoOverlay* overlay, + const ImageFloatBox& box = ImageFloatBox(0.85, 0.923, 0.082, 0.070), + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("PokedexRegisteredWatcher", hold_duration, color, overlay, box) + {} +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp index 29c662fbb..23f1e1842 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp @@ -36,6 +36,30 @@ ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPosit throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Selection Arrow Position"); } +ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionSafariMenu position){ + // Safari Zone menu has the same 7 slots as the overworld menu; RETIRE occupies slot 0, + // shifting POKEDEX..EXIT each down one relative to the overworld enum. + switch (position){ + case SelectionArrowPositionSafariMenu::RETIRE: + return ImageFloatBox(0.727692, 0.0523077, 0.0369231, 0.0778846); + case SelectionArrowPositionSafariMenu::POKEDEX: + return ImageFloatBox(0.727692, 0.1457692, 0.0369231, 0.0778846); + case SelectionArrowPositionSafariMenu::POKEMON: + return ImageFloatBox(0.727692, 0.2392307, 0.0369231, 0.0778846); + case SelectionArrowPositionSafariMenu::BAG: + return ImageFloatBox(0.727692, 0.3378846, 0.0369231, 0.0778846); + case SelectionArrowPositionSafariMenu::TRAINER: + return ImageFloatBox(0.727692, 0.4261538, 0.0369231, 0.0778846); + case SelectionArrowPositionSafariMenu::OPTION: + return ImageFloatBox(0.727692, 0.5248076, 0.0369231, 0.0778846); + case SelectionArrowPositionSafariMenu::EXIT: + return ImageFloatBox(0.727692, 0.6182692, 0.0369231, 0.0778846); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Safari Selection Arrow Position"); +} + ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionConfirmationMenu position){ switch (position){ case SelectionArrowPositionConfirmationMenu::YES: @@ -96,6 +120,16 @@ SelectionArrowDetector::SelectionArrowDetector( , m_arrow_box(arrow_box_for_position(position)) { } +SelectionArrowDetector::SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowPositionSafariMenu position +) + : m_color(color) + , m_overlay(overlay) + , m_arrow_box(arrow_box_for_position(position)) +{ +} void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{ const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; items.add(m_color, GAME_BOX.inner_to_outer(m_arrow_box)); diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h index d9f32c682..49564d77b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h @@ -14,8 +14,19 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -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. +const int SAFARI_START_MENU_OPTION_COUNT = 7; +enum class SelectionArrowPositionSafariMenu { + RETIRE, + POKEDEX, + POKEMON, + BAG, + TRAINER, + OPTION, + EXIT +}; + +const int START_MENU_OPTION_COUNT = 7; enum class SelectionArrowPositionStartMenu{ POKEDEX, POKEMON, @@ -45,6 +56,12 @@ class SelectionArrowDetector : public StaticScreenDetector{ SelectionArrowPositionStartMenu position ); + SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowPositionSafariMenu position + ); + SelectionArrowDetector( Color color, VideoOverlay* overlay, @@ -52,14 +69,16 @@ class SelectionArrowDetector : public StaticScreenDetector{ ); static ImageFloatBox arrow_box_for_position(SelectionArrowPositionStartMenu position); - + + static ImageFloatBox arrow_box_for_position(SelectionArrowPositionSafariMenu position); + static ImageFloatBox arrow_box_for_position(SelectionArrowPositionConfirmationMenu 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. + // This is not const so that detectors can save/cache state. virtual bool detect(const ImageViewRGB32& screen) override; private: @@ -91,6 +110,15 @@ class SelectionArrowWatcher : public DetectorToFinder{ : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, arrow_box_for_position(position)) { } + SelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + SelectionArrowPositionSafariMenu 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, diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index 05b19d501..97174a230 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -592,7 +592,7 @@ void heal_at_pokecenter(ConsoleHandle& console, ProControllerContext& context){ } } -void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context){ +void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context){ uint16_t errors = 0; bool start_menu_is_open = false; while (true){ @@ -620,7 +620,12 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext switch (ret){ case 0: - ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::POKEMON); + if (menu_context == StartMenuContext::SAFARI_ZONE){ + ret = move_cursor_to_position(console, context, SelectionArrowPositionSafariMenu::POKEMON); + } else { + ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::POKEMON); + } + if (ret < 0){ console.log("Failed to navigate to POKEMON on the start menu."); errors++; @@ -632,6 +637,7 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext context.wait_for_all_requests(); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); } + continue; case 1: console.log("Party menu opened."); diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index d593bffd9..b035c87c6 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -55,7 +55,11 @@ void leave_pokecenter(ConsoleHandle& console, ProControllerContext& context); void heal_at_pokecenter(ConsoleHandle& console, ProControllerContext& context); // Starting from the start menu, a sub-screen of the start menu, or the overworld, navigate to the party screen -void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context); +enum class StartMenuContext { + STANDARD, + SAFARI_ZONE +}; +void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD); // 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/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 2f5f1be50..f34fbf0dc 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -10,6 +10,7 @@ #include "PokemonFRLG_Settings.h" +#include "Programs/Farming/PokemonFRLG_LuckyEggFarmer.h" #include "Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h" #include "Programs/Farming/PokemonFRLG_PickupFarmer.h" #include "Programs/ShinyHunting/PokemonFRLG_GiftReset.h" @@ -39,6 +40,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back("---- Farming ----"); ret.emplace_back(make_single_switch_program()); if (PreloadSettings::instance().DEVELOPER_MODE){ + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp new file mode 100644 index 000000000..79041bfb0 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp @@ -0,0 +1,511 @@ +/* Lucky Egg Farmer + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Options/ButtonOption.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_LuckyEggFarmer.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +LuckyEggFarmer_Descriptor::LuckyEggFarmer_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:LuckyEggFarmer", + Pokemon::STRING_POKEMON + " FRLG", "Lucky Egg Farmer", + "Programs/PokemonFRLG/LuckyEggFarmer.html", + "Farm the Lucky Egg from Chansey.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct LuckyEggFarmer_Descriptor::Stats : public StatsTracker { + public: + Stats() + : chanseys(m_stats["Chanseys Caught"]) + , eggs(m_stats["Lucky Eggs Found"]) + , shinies(m_stats["Shinies"]) + , errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Chanseys Caught"); + m_display_order.emplace_back("Lucky Eggs Found"); + m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + + std::atomic& chanseys; + std::atomic& eggs; + std::atomic& shinies; + std::atomic& errors; +}; + +std::unique_ptr LuckyEggFarmer_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +LuckyEggFarmer::LuckyEggFarmer() + : LANGUAGE( + "Game Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + Language::Korean, + Language::ChineseSimplified, + Language::ChineseTraditional, + }, + LockMode::LOCK_WHILE_RUNNING, + true + ) + , STOP_AFTER_CURRENT("Reset") + , TAKE_VIDEO("Take Video:
Record a video when the shiny is found.", LockMode::UNLOCK_WHILE_RUNNING, true) + , GO_HOME_WHEN_DONE(false) + , NOTIFICATION_SHINY( + "Shiny found", + true, true, ImageAttachmentMode::JPG, + { "Notifs", "Showcase" } + ) + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_SHINY, + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_FATAL, + }) +{ + PA_ADD_OPTION(STOP_AFTER_CURRENT); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + + PA_ADD_OPTION(NOTIFICATIONS); +} + +bool LuckyEggFarmer::navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context){ + BlackScreenWatcher zone_exit(COLOR_RED); + + int ret = run_until( + console, context, + [](ProControllerContext& context) { + ssf_press_button(context, BUTTON_B, 0ms, 4045ms); + pbf_press_dpad(context, DPAD_UP, 100ms, 0ms); + pbf_press_dpad(context, DPAD_RIGHT, 1966ms, 0ms); + pbf_press_dpad(context, DPAD_UP, 1800ms, 0ms); + pbf_press_dpad(context, DPAD_RIGHT, 450ms, 0ms); + pbf_wait(context, 1000ms); + }, + { zone_exit } + ); + + context.wait_for_all_requests(); + + if (ret != 0){ + console.log("Failed to detect zone transition after initial navigation."); + return false; + } + + BlackScreenOverWatcher overworld_entered(COLOR_RED); + + ret = wait_until( + console, context, + std::chrono::milliseconds(2000), + { overworld_entered } + ); + + if (ret != 0){ + console.log("Failed to detect overworld after first zone transition."); + return false; + } + + console.log("Exiting first zone..."); + + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + + ret = run_until( + console, context, + [](ProControllerContext& context) { + ssf_press_button(context, BUTTON_B, 0ms, 143005ms); + pbf_press_dpad(context, DPAD_RIGHT, 1600ms, 0ms); + pbf_press_dpad(context, DPAD_DOWN, 100ms, 0ms); + pbf_press_dpad(context, DPAD_RIGHT, 2650ms, 0ms); + pbf_press_dpad(context, DPAD_UP, 600ms, 0ms); + pbf_press_dpad(context, DPAD_LEFT, 920ms, 0ms); + pbf_press_dpad(context, DPAD_DOWN, 325ms, 0ms); + pbf_press_dpad(context, DPAD_LEFT, 800ms, 0ms); + pbf_press_dpad(context, DPAD_UP, 2000ms, 0ms); + pbf_press_dpad(context, DPAD_RIGHT, 975ms, 0ms); + pbf_press_dpad(context, DPAD_DOWN, 300ms, 0ms); + pbf_press_dpad(context, DPAD_RIGHT, 550ms, 0ms); + pbf_press_dpad(context, DPAD_UP, 675ms, 0ms); + pbf_press_dpad(context, DPAD_LEFT, 2000ms, 0ms); + pbf_press_dpad(context, DPAD_DOWN, 130ms, 0ms); + pbf_press_dpad(context, DPAD_LEFT, 3000ms, 0ms); + }, + { zone_exit } + ); + + if (ret != 0){ + console.log("Failed to detect zone transition while exiting second zone."); + return false; + } + + context.wait_for_all_requests(); + + ret = wait_until( + console, context, + std::chrono::milliseconds(2000), + { overworld_entered } + ); + + if (ret != 0){ + console.log("Failed to detect overworld after second zone transition."); + return false; + } + + console.log("Exiting second zone..."); + + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + + ssf_press_button(context, BUTTON_B, 0ms, 2800ms); + pbf_press_dpad(context, DPAD_LEFT, 2000ms, 0ms); + pbf_press_dpad(context, DPAD_UP, 800ms, 0ms); + + context.wait_for_all_requests(); + + return true; +} + +void LuckyEggFarmer::swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context){ + open_party_menu_from_overworld(console, context, StartMenuContext::SAFARI_ZONE); + pbf_press_button(context, BUTTON_A, 250ms, 100ms); + pbf_press_dpad(context, DPAD_UP, 250ms, 100ms); + pbf_press_dpad(context, DPAD_UP, 250ms, 100ms); + pbf_press_dpad(context, DPAD_UP, 250ms, 100ms); + pbf_press_button(context, BUTTON_A, 250ms, 100ms); + pbf_press_dpad(context, DPAD_RIGHT, 250ms, 100ms); + pbf_press_button(context, BUTTON_A, 250ms, 100ms); + pbf_wait(context, 1000ms); + pbf_mash_button(context, BUTTON_B, 1500ms); + context.wait_for_all_requests(); +} + +bool LuckyEggFarmer::find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context) { + ssf_press_button(context, BUTTON_B, 0ms, 400ms); + pbf_press_dpad(context, DPAD_RIGHT, 400ms, 0ms); + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + + BlackScreenWatcher battle_entered(COLOR_RED); + + // This could be removed if spin in place stops drifting. + AdvanceWhiteDialogWatcher out_of_steps(COLOR_RED); + + while (true) { + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_move_left_joystick(context, { +1, 0 }, 50ms, 150ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { -1, 0 }, 33ms, 150ms); + context.wait_for_all_requests(); + }, + { battle_entered, out_of_steps } + ); + + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + + if (ret == 0) { + env.log("Battle entered."); + return true; + } + else if (ret == 1) { + env.log("Out of steps dialog detected. Resetting..."); + return false; + } + } + + return false; +} + +bool LuckyEggFarmer::is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + std::set subset = { "nidoran-f", "nidoran-m", "nidorino", "nidorina", "exeggcute", "rhyhorn", "venomoth", "chansey", "tauros" }; + + WildEncounterReader reader(COLOR_RED); + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + env.log("Reading name..."); + VideoSnapshot screen = env.console.video().snapshot(); + PokemonFRLG_WildEncounter encounter; + reader.read_encounter(env.logger(), LANGUAGE, screen, subset, encounter); + env.log("Name: " + encounter.name); + + if (encounter.name != "chansey") { + env.log("Not a Chansey. Fleeing..."); + flee_battle(env.console, context); + context.wait_for_all_requests(); + return false; + } + else { + env.log("Chansey found!"); + return true; + } +} + +bool LuckyEggFarmer::attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left) { + //TODO: Optimal bait/ball throwing + + while (true) + { + BattleSelectionArrowWatcher nickname_question_arrow( + COLOR_RED, + &env.console.overlay(), + BattleConfirmationOption::YES + ); + + BattleSelectionArrowWatcher battle_arrow( + COLOR_RED, + &env.console.overlay(), + SafariBattleMenuOption::BALL + ); + + BlackScreenWatcher battle_end(COLOR_RED); + + AdvanceBattleDialogWatcher advance_battle_dialog(COLOR_RED); + + PokedexRegisteredWatcher pokedex_registered(COLOR_RED, &env.console.overlay()); + + WallClock start = current_time(); + while (true) + { + if (current_time() - start > std::chrono::seconds(20)) { + env.log("No battle activity detected for 20 seconds. Assuming battle ended and in the overworld."); + + //Check for safari zone building dialog? + + return false; + } + + int ret = wait_until( + env.console, context, + std::chrono::milliseconds(2000), + { nickname_question_arrow, battle_arrow, battle_end, advance_battle_dialog } + ); + + context.wait_for_all_requests(); + + if (ret == 0 || ret == 3) { + env.log("Caught a Chansey!"); + + while (true) + { + int ret2 = wait_until( + env.console, context, + std::chrono::milliseconds(2000), + { nickname_question_arrow, advance_battle_dialog, pokedex_registered } + ); + + if (ret2 == 0) { + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + break; + } + else if (ret2 == 1 || ret2 == 2) { + 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 true; + } + else if (ret == 1) { + balls_left--; + env.log("Detected battle arrow. Balls left: " + std::to_string(balls_left)); + pbf_press_button(context, BUTTON_A, 200ms, 200ms); + context.wait_for_all_requests(); + break; + } + else if (ret == 2) { + env.log("Failed to catch Chansey."); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + return false; + } + } + } +} + +bool LuckyEggFarmer::check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context) { + open_party_menu_from_overworld(console, context, StartMenuContext::SAFARI_ZONE); + PartyHeldItemDetector held_item_detector(COLOR_RED, &console.overlay(), ImageFloatBox(0.432, 0.3, 0.030, 0.485)); + if (held_item_detector.detect(console.video().snapshot())) { + return true; + } + + return false; +} + +void LuckyEggFarmer::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + + home_black_border_check(env.console, context); + + LuckyEggFarmer_Descriptor::Stats& stats = env.current_stats(); + DeferredStopButtonOption::ResetOnExit reset_on_exit(STOP_AFTER_CURRENT); + + while (true){ + send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); + + pbf_press_dpad(context, DPAD_UP, 200ms, 0ms); + + while (true) + { + BlackScreenWatcher safari_zone_exit(COLOR_RED); + + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_press_button(context, BUTTON_A, 200ms, 200ms); + }, + { safari_zone_exit } + ); + + if (ret == 0) { + break; + } + } + + while (true) { + BlackScreenOverWatcher overworld_entered(COLOR_RED); + + int ret = wait_until( + env.console, context, + std::chrono::milliseconds(2000), + { overworld_entered } + ); + + if (ret == 0) { + break; + } + } + + // There is a small delay from seeing the overworld to being able to actually move. + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + + if (!navigate_to_chansey(env.console, context)){ + env.console.log("Navigation failed. Resetting..."); + soft_reset(env.console, context); + continue; + } + + swap_lead_pokemon(env.console, context); + + int chancy_count = 0; + int balls_left = 30; + + while (chancy_count < 4) { + if (!find_encounter(env, context)) { + break; + } + + bool encounter_shiny = handle_encounter(env.console, context, true); + if (encounter_shiny) { + stats.shinies++; + env.update_stats(); + send_program_notification( + env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); + if (TAKE_VIDEO) { + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } + return; + } + + if (!is_chansey(env, context)) { + continue; + } + + bool caught = attempt_catch(env, context, balls_left); + + if (caught){ + stats.chanseys++; + env.update_stats(); + chancy_count++; + } + + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + WhiteDialogDetector dialog(COLOR_RED); + bool in_safari_zone_building = dialog.detect(env.console.video().snapshot()); + + if (in_safari_zone_building && !caught) { + break; + } + + if (in_safari_zone_building) { + pbf_mash_button(context, BUTTON_B, 500ms); + context.wait_for_all_requests(); + } + + if (caught) { + if (check_for_lucky_egg(env.console, context)) { + env.log("Lucky Egg found!"); + stats.eggs++; + env.update_stats(); + return; + } + env.log("Lucky Egg not found. Continuing to farm..."); + pbf_mash_button(context, BUTTON_B, 1500ms); + context.wait_for_all_requests(); + } + } + + soft_reset(env.console, context); + + if (STOP_AFTER_CURRENT.should_stop()){ + break; + } + } + + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + GO_HOME_WHEN_DONE.run_end_of_program(context); +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h new file mode 100644 index 000000000..5bd399ccd --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h @@ -0,0 +1,67 @@ +/* Lucky Egg Farmer + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_LuckyEggFarmer_H +#define PokemonAutomation_PokemonFRLG_LuckyEggFarmer_H + +#include "Common/Cpp/Options/ButtonOption.h" +#include "CommonTools/Options/LanguageOCROption.h" +#include "CommonFramework/Notifications/EventNotificationOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class LuckyEggFarmer_Descriptor : public SingleSwitchProgramDescriptor{ +public: + LuckyEggFarmer_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class LuckyEggFarmer : public SingleSwitchProgramInstance { +public: + LuckyEggFarmer(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override { + } + +private: + + bool navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context); + void swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context); + bool is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left); + bool check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context); + + OCR::LanguageOCROption LANGUAGE; + + DeferredStopButtonOption STOP_AFTER_CURRENT; + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; + +}; + +} +} +} + + + + +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp new file mode 100644 index 000000000..7e23883d6 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp @@ -0,0 +1,115 @@ +/* Battle Menu Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "cmath" +#include "limits" +#include "functional" +#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/PokemonFRLG_BattleSelectionArrowDetector.h" +#include "PokemonFRLG_BattleMenuNavigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +namespace{ + +const ImageFloatBox BATTLE_MENU_ARROW_BOX(0.768, 0.868, 0.212, 0.159); + +const int BATTLE_MENU_COLS = 2; + +bool move_cursor_2d_impl( + ConsoleHandle& console, + ProControllerContext& context, + const ImageFloatBox& search_box, + int option_count, + int cols, + std::function box_for_index, + int destination_index +){ + BattleSelectionArrowWatcher arrow(COLOR_RED, &console.overlay(), search_box); + + int ret = wait_until( + console, context, + std::chrono::seconds(2), + { arrow } + ); + + if (ret < 0){ + console.log("Unable to detect battle selection arrow. Not moving cursor.", COLOR_RED); + return false; + } + + double detected_cx = arrow.last_detected().x + arrow.last_detected().width / 2.0; + double detected_cy = arrow.last_detected().y + arrow.last_detected().height / 2.0; + + int current_index = 0; + double closest_dist = std::numeric_limits::max(); + for (int i = 0; i < option_count; i++){ + ImageFloatBox pos_box = box_for_index(i); + double cx = pos_box.x + pos_box.width / 2.0; + double cy = pos_box.y + pos_box.height / 2.0; + double dx = detected_cx - cx; + double dy = detected_cy - cy; + double dist = dx * dx + dy * dy; + if (dist < closest_dist){ + closest_dist = dist; + current_index = i; + } + } + + int current_row = current_index / cols; + int current_col = current_index % cols; + int dest_row = destination_index / cols; + int dest_col = destination_index % cols; + + int row_diff = dest_row - current_row; + for (int i = 0; i < std::abs(row_diff); i++){ + pbf_press_dpad(context, row_diff > 0 ? DPAD_DOWN : DPAD_UP, 320ms, 400ms); + } + + int col_diff = dest_col - current_col; + for (int i = 0; i < std::abs(col_diff); i++){ + pbf_press_dpad(context, col_diff > 0 ? DPAD_RIGHT : DPAD_LEFT, 320ms, 400ms); + } + + context.wait_for_all_requests(); + return true; +} + +} // anonymous namespace + + +bool move_cursor_to_option(ConsoleHandle& console, ProControllerContext& context, BattleMenuOption destination){ + return move_cursor_2d_impl( + console, context, + BATTLE_MENU_ARROW_BOX, + BATTLE_MENU_OPTION_COUNT, + BATTLE_MENU_COLS, + [](int i){ return BattleSelectionArrowDetector::box_for_option(static_cast(i)); }, + static_cast(destination) + ); +} + +bool move_cursor_to_option(ConsoleHandle& console, ProControllerContext& context, SafariBattleMenuOption destination){ + return move_cursor_2d_impl( + console, context, + BATTLE_MENU_ARROW_BOX, + SAFARI_BATTLE_MENU_OPTION_COUNT, + BATTLE_MENU_COLS, + [](int i){ return BattleSelectionArrowDetector::box_for_option(static_cast(i)); }, + static_cast(destination) + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h new file mode 100644 index 000000000..4d74cd592 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h @@ -0,0 +1,43 @@ +/* Battle Menu Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_BattleMenuNavigation_H +#define PokemonAutomation_PokemonFRLG_BattleMenuNavigation_H + +#include "PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h" + +namespace PokemonAutomation{ + +template class ControllerContext; + +namespace NintendoSwitch{ + +class ConsoleHandle; +class ProController; +using ProControllerContext = ControllerContext; + +namespace PokemonFRLG{ + + +// Starting from the normal battle command menu, move the selection arrow to the specified option. +// Returns true if successful, false otherwise (e.g. if selection arrow is not detected). +bool move_cursor_to_option( + ConsoleHandle& console, ProControllerContext& context, + BattleMenuOption destination +); + +// Starting from the Safari Zone battle command menu, move the selection arrow to the specified option. +// Returns true if successful, false otherwise (e.g. if selection arrow is not detected). +bool move_cursor_to_option( + ConsoleHandle& console, ProControllerContext& context, + SafariBattleMenuOption destination +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp index 2b2f5cac6..911e184f6 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp @@ -60,62 +60,24 @@ void close_start_menu(ConsoleHandle& console, ProControllerContext& context){ console.log("Start menu closed."); } -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 - ); +namespace { - SelectionArrowWatcher save_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPositionStartMenu::SAVE - ); +const ImageFloatBox MENU_ARROW_BOX(0.727692, 0.0523077, 0.0369231, 0.6438461); - SelectionArrowWatcher option_arrow = SelectionArrowWatcher( - COLOR_RED, - &console.overlay(), - SelectionArrowPositionStartMenu::OPTION - ); +bool move_cursor_impl( + ConsoleHandle& console, + ProControllerContext& context, + const ImageFloatBox& search_box, + int option_count, + std::function box_for_index, + int destination_index +){ + SelectionArrowWatcher arrow(COLOR_RED, &console.overlay(), search_box); - 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 - } + { arrow } ); if (ret < 0){ @@ -123,26 +85,58 @@ bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& conte 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; + double detected_center_y = arrow.last_detected().y + arrow.last_detected().height / 2; + int current_index = 0; + double closest_dist = std::numeric_limits::max(); + for (int i = 0; i < option_count; i++){ + ImageFloatBox pos_box = box_for_index(i); + double pos_center_y = pos_box.y + pos_box.height / 2; + double dist = std::abs(detected_center_y - pos_center_y); + if (dist < closest_dist){ + closest_dist = dist; + current_index = i; + } + } + + int forward = (destination_index - current_index + option_count) % option_count; + int backward = (current_index - destination_index + option_count) % 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++) - { + else{ + for (int i = 0; i < backward; i++){ pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); } - context.wait_for_all_requests(); } + context.wait_for_all_requests(); return true; } +} // anonymous namespace + +bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionStartMenu destination){ + return move_cursor_impl( + console, context, + MENU_ARROW_BOX, + START_MENU_OPTION_COUNT, + [](int i){ return SelectionArrowDetector::arrow_box_for_position(static_cast(i)); }, + static_cast(destination) + ); +} + +bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionSafariMenu destination){ + return move_cursor_impl( + console, context, + MENU_ARROW_BOX, + SAFARI_START_MENU_OPTION_COUNT, + [](int i){ return SelectionArrowDetector::arrow_box_for_position(static_cast(i)); }, + static_cast(destination) + ); +} + void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context){ WallClock start = current_time(); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h index ba8f3560f..581763a3f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h @@ -37,6 +37,13 @@ bool move_cursor_to_position( SelectionArrowPositionStartMenu destination ); +// Starting from the Safari Zone 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, + SelectionArrowPositionSafariMenu 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); diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index cc082e016..274f93698 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1439,10 +1439,14 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.cpp Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h + Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp + Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.cpp Source/PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h + Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.cpp + Source/PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp @@ -1451,16 +1455,22 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/PokemonFRLG_DigitReader.h Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h + Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.cpp + Source/PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h Source/PokemonFRLG/PokemonFRLG_Navigation.cpp Source/PokemonFRLG/PokemonFRLG_Navigation.h Source/PokemonFRLG/PokemonFRLG_Panels.cpp Source/PokemonFRLG/PokemonFRLG_Panels.h Source/PokemonFRLG/PokemonFRLG_Settings.cpp Source/PokemonFRLG/PokemonFRLG_Settings.h + Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp + Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.h + Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.cpp + Source/PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp From 215ae9774551bb7bc3a9727f36e7ffd2a6aa09c4 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Sun, 29 Mar 2026 15:58:24 -0500 Subject: [PATCH 2/3] remove tabs --- .../Programs/Farming/PokemonFRLG_LuckyEggFarmer.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h index 5bd399ccd..dc426391c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h @@ -39,10 +39,10 @@ class LuckyEggFarmer : public SingleSwitchProgramInstance { private: bool navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context); - void swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context); - bool is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - bool find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - bool attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left); + void swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context); + bool is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left); bool check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context); OCR::LanguageOCROption LANGUAGE; From 58f26a723940178cf185fb81c8ad0a86fbc8c9a4 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Sun, 29 Mar 2026 18:54:53 -0500 Subject: [PATCH 3/3] Fix enum --- .../Menus/PokemonFRLG_PartyHeldItemDetector.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp index b3a0584d9..7f944b0f7 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp @@ -39,17 +39,17 @@ class PartyHeldItemMatcher : public ImageMatch::WaterfillTemplateMatcher{ ImageFloatBox PartyHeldItemDetector::box_for_slot(PartyHeldItemSlot slot){ switch (slot){ - case PartyHeldItemSlot::SLOT_0: - return ImageFloatBox(0.069, 0.285, 0.025, 0.050); case PartyHeldItemSlot::SLOT_1: - return ImageFloatBox(0.432, 0.150, 0.025, 0.050); + return ImageFloatBox(0.069, 0.285, 0.025, 0.050); case PartyHeldItemSlot::SLOT_2: - return ImageFloatBox(0.432, 0.3, 0.025, 0.050); + return ImageFloatBox(0.432, 0.150, 0.025, 0.050); case PartyHeldItemSlot::SLOT_3: - return ImageFloatBox(0.432, 0.45, 0.025, 0.050); + return ImageFloatBox(0.432, 0.3, 0.025, 0.050); case PartyHeldItemSlot::SLOT_4: - return ImageFloatBox(0.432, 0.6, 0.025, 0.050); + return ImageFloatBox(0.432, 0.45, 0.025, 0.050); case PartyHeldItemSlot::SLOT_5: + return ImageFloatBox(0.432, 0.6, 0.025, 0.050); + case PartyHeldItemSlot::SLOT_6: return ImageFloatBox(0.432, 0.725, 0.025, 0.050); default: break;