From af3896e6bc15dc2f287561578616874957d9212b Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 23 Jul 2026 23:21:11 -0700 Subject: [PATCH 1/2] SwSh Egg program refactor: helpers, routines, detectors --- .../PokemonSwSh_Commands_EggRoutines.cpp | 14 ++ .../PokemonSwSh_Commands_EggRoutines.h | 2 + .../Inference/PokemonSwSh_BoxEggDetector.cpp | 113 ++++++++++++++++ .../Inference/PokemonSwSh_BoxEggDetector.h | 45 +++++++ .../PokemonSwSh_BoxEmptySlotDetector.cpp | 69 ++++++++++ .../PokemonSwSh_BoxEmptySlotDetector.h | 41 ++++++ .../PokemonSwSh_DialogBoxDetector.cpp | 18 +++ .../Inference/PokemonSwSh_DialogBoxDetector.h | 22 +++ .../Programs/PokemonSwSh_BoxHelpers.cpp | 127 ++++++++++++++++++ .../Programs/PokemonSwSh_BoxHelpers.h | 20 +++ .../Programs/PokemonSwSh_MenuNavigation.cpp | 76 +++++++++++ .../Programs/PokemonSwSh_MenuNavigation.h | 13 +- SerialPrograms/cmake/SourceFiles.cmake | 5 + 13 files changed, 564 insertions(+), 1 deletion(-) create mode 100644 SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.cpp create mode 100644 SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h create mode 100644 SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.cpp create mode 100644 SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.h create mode 100644 SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.cpp diff --git a/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.cpp b/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.cpp index dc347a45b5..850e3e5bb5 100644 --- a/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.cpp @@ -67,6 +67,20 @@ void travel_back_to_lady(ProControllerContext& context){ ssf_press_left_joystick(context, {+1, +1}, 960ms, 960ms); } +void travel_to_spin_location2(ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, -0.126}, 800ms, 2000ms); + ssf_press_left_joystick(context, {+1, -1}, 400ms, 400ms); +} +void travel_back_to_lady2(ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 240ms, 240ms); + ssf_press_left_joystick(context, {+1, -0.126}, 2080ms, 2080ms); + ssf_press_left_joystick(context, {-1, 0}, 400ms, 3200ms); + ssf_mash1_button(context, BUTTON_R, 800ms); // bike boost + ssf_do_nothing(context, 2400ms); + ssf_press_left_joystick(context, {+0.5, +1}, 960ms, 960ms); + ssf_press_left_joystick(context, {+1, +1}, 960ms, 960ms); +} + } diff --git a/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.h b/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.h index 89716e5e5d..6857a25e3b 100644 --- a/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.h +++ b/SerialPrograms/Source/PokemonSwSh/Commands/PokemonSwSh_Commands_EggRoutines.h @@ -20,6 +20,8 @@ void move_while_mashing_B (ProControllerContext& context, Milliseconds duratio void spin_and_mash_A (ProControllerContext& context, Milliseconds duration); void travel_to_spin_location(ProControllerContext& context); void travel_back_to_lady (ProControllerContext& context); +void travel_to_spin_location2(ProControllerContext& context); +void travel_back_to_lady2 (ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.cpp b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.cpp new file mode 100644 index 0000000000..452952a479 --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.cpp @@ -0,0 +1,113 @@ +/* Box Egg Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/ImageMatch/ImageCropper.h" +#include "CommonTools/ImageMatch/ExactImageMatcher.h" +#include "PokemonSwSh/Resources/PokemonSwSh_PokemonSprites.h" +#include "PokemonSwSh_BoxEggDetector.h" + +// #include +// using std::cout; +// using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ + + + +const ImageMatch::ExactImageMatcher& EGG_MATCHER(){ + static ImageMatch::ExactImageMatcher matcher(RESOURCE_PATH() + "PokemonSwSh/EggTemplate.png"); + return matcher; +} + +BoxEggDetector::BoxEggDetector(SlotLocation side, uint8_t row, double min_euclidean_distance, Color color) +: m_min_euclidean_distance_squared(min_euclidean_distance * min_euclidean_distance) +, m_color(color){ + if (side == SlotLocation::PARTY){ + if (row > 5){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "INVALID party row for BoxEggDetector"); + } + m_box = ImageFloatBox(0.045414, 0.1255 * row + 0.2401, 0.023882, 0.043856); + }else if (side == SlotLocation::BOX){ + if (row > 4){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "INVALID box row for BoxEggDetector"); + } + m_box = ImageFloatBox(0.270241, 0.1255 * row + 0.2401, 0.023882, 0.049); + }else{ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "INVALID SlotLocation for BoxEggDetector"); + } +} + +void BoxEggDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_box); +} + +bool BoxEggDetector::detect(const ImageViewRGB32& screen){ + + std::vector crops = get_crop_candidates(extract_box_reference(screen, m_box)); + + // int i = 0; + double rmsd_threshold = 80; + for (const ImageViewRGB32& crop : crops){ + // crop.save("image" + std::to_string(i) + ".png"); + // i++; + double rmsd = EGG_MATCHER().rmsd(crop); + // cout << "RMSD: " << std::to_string(rmsd) << endl; + if (rmsd < rmsd_threshold){ + return true; + } + } + + return false; +} + +auto BoxEggDetector::get_crop_candidates(const ImageViewRGB32& image) const -> std::vector{ + ImageStats border = image_border_stats(image); +// cout << border.average << border.stddev << endl; +// image.save("image1.png"); + ImagePixelBox box = ImageMatch::enclosing_rectangle_with_pixel_filter( + image, + [&](Color pixel){ +// if (qAlpha(pixel) == 0){ +// return false; +// } +// FloatPixel p(pixel); +// cout << p << endl; + double r = (double)pixel.red() - border.average.r; + double g = (double)pixel.green() - border.average.g; + double b = (double)pixel.blue() - border.average.b; + bool stop = r*r + g*g + b*b >= m_min_euclidean_distance_squared; + if (stop){ +// FloatPixel p(pixel); +// cout << p << " : " << r << " " << g << " " << b << endl; + } + return stop; + } + ); + std::vector ret; + ret.emplace_back(extract_box_reference(image, box)); + return ret; +} + + + + + + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h new file mode 100644 index 0000000000..c7e8bc2a85 --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h @@ -0,0 +1,45 @@ +/* Box Egg Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonSwSh_BoxEggDetector_H +#define PokemonAutomation_PokemonSwSh_BoxEggDetector_H + +#include +#include "Common/Cpp/Color.h" +#include "CommonTools/VisualDetector.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ + + +enum class SlotLocation{ + PARTY, // player's party + BOX, // one of the 5 x 6 slots in the box +}; + +class BoxEggDetector : public StaticScreenDetector{ +public: + BoxEggDetector(SlotLocation side, uint8_t row, double min_euclidean_distance = 100, Color color = COLOR_BLUE); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + + std::vector get_crop_candidates(const ImageViewRGB32& image) const; + +private: + Color m_color; + ImageFloatBox m_box; + double m_min_euclidean_distance_squared; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.cpp b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.cpp new file mode 100644 index 0000000000..7dbc766417 --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.cpp @@ -0,0 +1,69 @@ +/* Box Empty Slot Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/ImageMatch/ImageCropper.h" +#include "CommonTools/ImageMatch/ExactImageMatcher.h" +#include "PokemonSwSh/Resources/PokemonSwSh_PokemonSprites.h" +#include "PokemonSwSh_BoxEmptySlotDetector.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ + + + + +BoxEmptySlotDetector::BoxEmptySlotDetector(SlotLocation side, uint8_t row, uint8_t column, Color color) +: m_color(color){ + if (side == SlotLocation::PARTY){ + if (row > 5){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "INVALID party row for BoxEmptySlotDetector"); + } + m_box = ImageFloatBox(0.087527, 0.1255 * row + 0.192607, 0.079869, 0.089494); + }else if (side == SlotLocation::BOX){ + if (row > 4 || column > 5){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "INVALID box row/column for BoxEmptySlotDetector"); + } + m_box = ImageFloatBox(0.0705 * column + 0.265, 0.1255 * row + 0.260700, 0.043, 0.023346); + }else{ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "INVALID SlotLocation for BoxEmptySlotDetector"); + } +} + +void BoxEmptySlotDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_box); +} + +bool BoxEmptySlotDetector::detect(const ImageViewRGB32& screen){ + ImageStats stats = image_stats(extract_box_reference(screen, m_box)); + + const double max_stddev_sum = 50; + const double stddev = stats.stddev.sum(); + // cout << "BoxEmptySlotDetector::detect: stddev sum " << stddev << " vs max " << max_stddev_sum << endl; + + // if the stddev.sum() is low, it's likely an empty slot + return stddev <= max_stddev_sum; +} + + + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.h b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.h new file mode 100644 index 0000000000..d497d514c3 --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.h @@ -0,0 +1,41 @@ +/* Box Empty Slot Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonSwSh_BoxEmptySlotDetector_H +#define PokemonAutomation_PokemonSwSh_BoxEmptySlotDetector_H + +#include "Common/Cpp/Color.h" +#include "CommonTools/VisualDetector.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "PokemonSwSh_BoxEggDetector.h" +// #include "CommonTools/ImageMatch/ExactImageDictionaryMatcher.h" +// #include "CommonTools/ImageMatch/CroppedImageDictionaryMatcher.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ + +// Determine if given slot is empty + +class BoxEmptySlotDetector : public StaticScreenDetector{ +public: + BoxEmptySlotDetector(SlotLocation side, uint8_t row, uint8_t column, Color color = COLOR_BLUE); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + + +private: + Color m_color; + ImageFloatBox m_box; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp index a16fdb66dd..fb260f8137 100644 --- a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.cpp @@ -62,6 +62,24 @@ bool BlackDialogBoxDetector::process_frame(const ImageViewRGB32& frame, WallCloc return detected && m_stop_on_detected; } +BlackDialogBoxDetector2::BlackDialogBoxDetector2(Color color) + : m_color(color) + , m_bottom(0.184009, 0.945419, 0.554217, 0.021442) + , m_left(0.175246, 0.807018, 0.019715, 0.064327) + , m_right(0.808324, 0.810916, 0.014239, 0.146199) +{} +void BlackDialogBoxDetector2::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_bottom); + items.add(m_color, m_left); + items.add(m_color, m_right); +} + +bool BlackDialogBoxDetector2::detect(const ImageViewRGB32& screen){ + return is_grey(extract_box_reference(screen, m_bottom), 50, 300) + && is_black(extract_box_reference(screen, m_left), 200, 50) + && is_black(extract_box_reference(screen, m_right), 200, 50); +} + diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h index 4f970abdec..ef6c1b6ecf 100644 --- a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h @@ -37,6 +37,28 @@ class BlackDialogBoxDetector : public VisualInferenceCallback{ std::atomic m_detected; }; +// Detect the bigger black dialog box. It is used in places such as egg hatching detection +class BlackDialogBoxDetector2 : public StaticScreenDetector{ +public: + BlackDialogBoxDetector2(Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + + +protected: + Color m_color; + ImageFloatBox m_bottom; + ImageFloatBox m_left; + ImageFloatBox m_right; +}; +class BlackDialogBoxWatcher2 : public DetectorToFinder{ +public: + BlackDialogBoxWatcher2(Color color = COLOR_RED) + : DetectorToFinder("BlackDialogBoxWatcher2", std::chrono::milliseconds(250), color) + {} +}; + class WhiteDialogBoxDetector : public StaticScreenDetector{ public: diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.cpp new file mode 100644 index 0000000000..a0321a629d --- /dev/null +++ b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.cpp @@ -0,0 +1,127 @@ +/* Box Helpers + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonSwSh/Inference/PokemonSwSh_IvJudgeReader.h" +#include "PokemonSwSh_BoxHelpers.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSwSh{ + + + +bool change_view_to_stats_or_judge( + VideoStream& stream, ProControllerContext& context, + bool throw_exception +){ + ImageFloatBox name_bar(0.69, 0.03, 0.30, 0.05); + OverlayBoxScope name_bar_overlay(stream.overlay(), name_bar); + for (size_t attempts = 0;; attempts++){ + if (throw_exception){ + if (attempts == 10){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Unable to change Pokemon view after 10 tries.", + stream + ); + } + }else{ + if (attempts == 3){ + return false; + } + } + + context.wait_for_all_requests(); + VideoSnapshot screen = stream.video().snapshot(); + ImageStats stats = image_stats(extract_box_reference(screen, name_bar)); +// cout << stats.stddev << endl; + if (stats.stddev.sum() > 50){ + break; + } + + stream.log("Unable to detect stats menu. Attempting to correct.", COLOR_RED); + +// // Alternate one and two + presses. If IV checker is enabled, we should +// // land on the IV checker. Otherwise, it will land us back to nothing. +// // Then the next press will be a single which will put us on the stats +// // with no IV checker. + pbf_press_button(context, BUTTON_PLUS, 160ms, 840ms); +// if (attempts % 2 == 0){ +// pbf_press_button(context, BUTTON_PLUS, 160ms, 1840ms); +// } + } + return true; +} + + +void change_view_to_judge( + VideoStream& stream, ProControllerContext& context, + Language language +){ + if (language == Language::None){ + throw InternalProgramError( + &stream.logger(), PA_CURRENT_FUNCTION, + "change_view_to_judge() called with no language." + ); + } + + ImageFloatBox name_bar(0.69, 0.03, 0.30, 0.05); + IvJudgeReaderScope iv_checker(stream.overlay(), language); + OverlayBoxScope name_bar_overlay(stream.overlay(), name_bar); + for (size_t attempts = 0;; attempts++){ + if (attempts == 10){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Unable to change Pokemon view to judge after 10 tries. Have you unlocked it?", + stream + ); + } + + context.wait_for_all_requests(); + VideoSnapshot screen = stream.video().snapshot(); + ImageStats stats = image_stats(extract_box_reference(screen, name_bar)); +// cout << stats.stddev << endl; + + // Check if we're even on a stats screen. + if (stats.stddev.sum() < 50){ + stream.log("Unable to detect stats menu. Attempting to correct.", COLOR_RED); + pbf_press_button(context, BUTTON_PLUS, 160ms, 840ms); + continue; + } + + // See if we're on the judge screen. + IvJudgeReader::Results ivs = iv_checker.read(stream.logger(), screen); + + size_t detected = 0; + if (ivs.hp != IvJudgeValue::UnableToDetect) detected++; + if (ivs.attack != IvJudgeValue::UnableToDetect) detected++; + if (ivs.defense != IvJudgeValue::UnableToDetect) detected++; + if (ivs.spatk != IvJudgeValue::UnableToDetect) detected++; + if (ivs.spdef != IvJudgeValue::UnableToDetect) detected++; + if (ivs.speed != IvJudgeValue::UnableToDetect) detected++; + + // If less than 4 of the IVs are read, assume we're not on the judge screen. + if (detected < 4){ + pbf_press_button(context, BUTTON_PLUS, 160ms, 1840ms); + }else{ + break; + } + } +} + + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.h b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.h index 86ca1d2fea..67eaa943dd 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.h @@ -7,9 +7,11 @@ #ifndef PokemonAutomation_PokemonSwSh_BoxHelpers_H #define PokemonAutomation_PokemonSwSh_BoxHelpers_H +#include "CommonFramework/Language.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" namespace PokemonAutomation{ +class VideoStream; namespace NintendoSwitch{ namespace PokemonSwSh{ @@ -18,6 +20,24 @@ static inline void box_scroll(ProControllerContext& context, DpadPosition direct ssf_press_dpad_ptv(context, direction, 200ms, 104ms); } +// Assuming the current slot in box system is a pokemon, not egg or empty space, +// try to change the view to the judge. However, it may land on the stats instead. +// If it can't land on either stats or judge and `throw_exception` is true, it will throw an exception. +// Return true if it successfully changed view to judge (or stats if judge is not found) +bool change_view_to_stats_or_judge( + VideoStream& stream, ProControllerContext& context, + bool throw_exception = true +); + + +// Assuming the current slot in box system is a pokemon, not egg or empty space, +// change the view to the judge. If it fails, it will OperationFailedException::fire. +void change_view_to_judge( + VideoStream& stream, ProControllerContext& context, + Language language +); + + } diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.cpp index 71b4f06f80..142ad7a049 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.cpp @@ -165,8 +165,84 @@ void menus_to_boxsystem(VideoStream& stream, ProControllerContext& context){ } +void save_game(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + stream.log("Save game."); + stream.overlay().add_log("Save game", COLOR_WHITE); + + WallClock deadline = current_time() + std::chrono::minutes(2); + bool seen_save_menu = false; + do{ + YCommIconWatcher overworld; + MainMenuWatcher main_menu; + SelectionArrowFinder save_arrow(stream.overlay(), {0.475, 0.688, 0.060, 0.089}); + context.wait_for_all_requests(); + + int ret = wait_until( + stream, context, + std::chrono::seconds(30), + { + overworld, + main_menu, + save_arrow, + } + ); + switch (ret){ + case 0: + stream.log("Detected Overworld...", COLOR_BLUE); + if (seen_save_menu){ + stream.log("Detected Overworld. Successfully saved.", COLOR_BLUE); + return; + } + + pbf_press_button(context, BUTTON_X, 160ms, 40ms); + continue; + case 1: + stream.log("Detected Main Menu...", COLOR_BLUE); + pbf_press_button(context, BUTTON_R, 80ms, 2000ms); + continue; + case 2: + stream.log("Detected Save Menu...", COLOR_BLUE); + seen_save_menu = true; + pbf_mash_button(context, BUTTON_A, 500ms); + continue; + default: + stream.log("save_game(): No recognized state after 30 seconds.", COLOR_RED); + pbf_mash_button(context, BUTTON_B, 5000ms); + } + }while (current_time() < deadline); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Unable to save game after 2 minutes.", + stream + ); + +} +void mash_B_until_y_comm_icon( + VideoStream& stream, + ProControllerContext& context, + const std::string& error_msg +){ + context.wait_for_all_requests(); + const bool y_comm_visible = true; + YCommIconWatcher y_comm_detector(COLOR_RED, y_comm_visible); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 10s); + }, + {y_comm_detector} + ); + if (ret != 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + error_msg + " No Y-Comm mark found.", + stream + ); + } +} diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.h b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.h index 5397ddb349..756c339018 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/PokemonSwSh_MenuNavigation.h @@ -36,11 +36,22 @@ void navigate_to_menu_app( size_t app_index ); - +// Move to Main Menu, starting from Overworld, Party menu, or Box menu void menus_to_mainmenu(VideoStream& stream, ProControllerContext& context); void menus_to_boxsystem(VideoStream& stream, ProControllerContext& context); +void save_game(VideoStream& stream, ProControllerContext& context); + +// Used to wait until Y-Comm icon shows up. +// Throw error if it does not find it after 10 sec. +void mash_B_until_y_comm_icon( + VideoStream& stream, + ProControllerContext& context, + const std::string& error_msg +); + + } } diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 5182ad3bdb..2cf83f1d01 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -2529,6 +2529,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonSwSh/Inference/Dens/PokemonSwSh_RaidCatchDetector.h Source/PokemonSwSh/Inference/Dens/PokemonSwSh_RaidLobbyReader.cpp Source/PokemonSwSh/Inference/Dens/PokemonSwSh_RaidLobbyReader.h + Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.cpp + Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h + Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.cpp + Source/PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.h Source/PokemonSwSh/Inference/PokemonSwSh_BoxGenderDetector.cpp Source/PokemonSwSh/Inference/PokemonSwSh_BoxGenderDetector.h Source/PokemonSwSh/Inference/PokemonSwSh_BoxNatureDetector.cpp @@ -2804,6 +2808,7 @@ file(GLOB LIBRARY_SOURCES Source/PokemonSwSh/Programs/OverworldBot/PokemonSwSh_ShinyHuntAutonomous-Overworld.h Source/PokemonSwSh/Programs/PokemonSwSh_BasicCatcher.cpp Source/PokemonSwSh/Programs/PokemonSwSh_BasicCatcher.h + Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.cpp Source/PokemonSwSh/Programs/PokemonSwSh_BoxHelpers.h Source/PokemonSwSh/Programs/PokemonSwSh_EncounterDetection.cpp Source/PokemonSwSh/Programs/PokemonSwSh_EncounterDetection.h From b3723b4fe14b249c2cf829c53cd3f58d4f195d1a Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 23 Jul 2026 23:28:59 -0700 Subject: [PATCH 2/2] fix initialization --- .../Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h index c7e8bc2a85..f8c4299503 100644 --- a/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h +++ b/SerialPrograms/Source/PokemonSwSh/Inference/PokemonSwSh_BoxEggDetector.h @@ -32,9 +32,9 @@ class BoxEggDetector : public StaticScreenDetector{ std::vector get_crop_candidates(const ImageViewRGB32& image) const; private: + double m_min_euclidean_distance_squared; Color m_color; ImageFloatBox m_box; - double m_min_euclidean_distance_squared; };