diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Settings.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Settings.cpp index 05e3ff5f1e..d1541adf1a 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Settings.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Settings.cpp @@ -83,6 +83,7 @@ GameSettings::GameSettings() PA_ADD_STATIC(m_soft_reset_timings); PA_ADD_OPTION(SELECT_BUTTON_MASH0); PA_ADD_OPTION(ENTER_GAME_WAIT0); + PA_ADD_OPTION(ENTER_GAME_MASH0); PA_ADD_STATIC(m_shiny_audio_settings); PA_ADD_OPTION(SHINY_SOUND_THRESHOLD); PA_ADD_OPTION(SHINY_SOUND_LOW_FREQUENCY); diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp index fe4113ecc7..42d8035415 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp @@ -11,65 +11,99 @@ #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "PokemonRSE/PokemonRSE_Settings.h" #include "PokemonRSE_DialogDetector.h" -#include -using std::cout; -using std::endl; +//#include +//using std::cout; +//using std::endl; namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonRSE{ /* -DialogDetector::DialogDetector(Color color) - : m_left_box(0.155, 0.727, 0.015, 0.168) - , m_right_box(0.837, 0.729, 0.008, 0.161) -{} -void DialogDetector::make_overlays(VideoOverlaySet& items) const{ - items.add(COLOR_RED, m_left_box); - items.add(COLOR_RED, m_right_box); -} -bool DialogDetector::detect(const ImageViewRGB32& screen) const{ - ImageViewRGB32 left_image = extract_box_reference(screen, m_left_box); - ImageViewRGB32 right_image = extract_box_reference(screen, m_right_box); - if (is_solid(left_image, { 0.335, 0.331, 0.332 }) && is_solid(right_image, { 0.335, 0.331, 0.332 })){ - return true; - } - return false; -} -*/ - BattleDialogDetector::BattleDialogDetector(Color color) - : m_left_box(0.158, 0.725, 0.011, 0.176) - , m_right_box(0.827, 0.722, 0.013, 0.178) + : m_dialog_top_box(0.049224, 0.738530, 0.901359, 0.008023) + , m_dialog_right_box(0.943471, 0.746553, 0.007111, 0.212602) + , m_dialog_top_jpn_box(0.068780, 0.738530, 0.856913, 0.012034) + , m_dialog_right_jpn_box(0.918582, 0.747890, 0.007111, 0.208590) {} void BattleDialogDetector::make_overlays(VideoOverlaySet& items) const{ - items.add(COLOR_RED, m_left_box); - items.add(COLOR_RED, m_right_box); + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_jpn_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_jpn_box)); } bool BattleDialogDetector::detect(const ImageViewRGB32& screen){ - ImageViewRGB32 left_image = extract_box_reference(screen, m_left_box); - ImageViewRGB32 right_image = extract_box_reference(screen, m_right_box); - if (is_solid(left_image, { 0.335, 0.331, 0.332 }) && is_solid(right_image, { 0.335, 0.331, 0.332 })){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + ImageViewRGB32 dialog_top_jpn_image = extract_box_reference(game_screen, m_dialog_top_jpn_box); + ImageViewRGB32 dialog_right_jpn_image = extract_box_reference(game_screen, m_dialog_right_jpn_box); + + if ((is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }, 0.25, 20)) + || + (is_solid(dialog_top_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && is_solid(dialog_right_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20)) + ){ return true; } return false; } - +*/ BattleMenuDetector::BattleMenuDetector(Color color) - : m_left_box(0.439, 0.717, 0.021, 0.192) - , m_right_box(0.821, 0.725, 0.030, 0.181) + : m_menu_top_box(0.599462, 0.738530, 0.366233, 0.004011) //top of the white box + , m_menu_bottom_box(0.602128, 0.947120, 0.368010, 0.006686) + , m_dialog_top_box(0.068780, 0.739867, 0.488903, 0.008023) //teal boxes + , m_dialog_right_box(0.553238, 0.739867, 0.006222, 0.215276) + + , m_menu_top_eme_box(0.534571, 0.746553, 0.434679, 0.008023) + , m_menu_right_eme_box(0.962138, 0.747890, 0.007111, 0.205916) + , m_dialog_top_eme_box(0.046557, 0.741204, 0.442679, 0.008023) + , m_dialog_right_eme_box(0.487458, 0.742541, 0.004445, 0.216613) {} void BattleMenuDetector::make_overlays(VideoOverlaySet& items) const{ - items.add(COLOR_RED, m_left_box); - items.add(COLOR_RED, m_right_box); + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_bottom_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); + + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_top_eme_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_right_eme_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_eme_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_eme_box)); } bool BattleMenuDetector::detect(const ImageViewRGB32& screen){ - ImageViewRGB32 left_image = extract_box_reference(screen, m_left_box); - ImageViewRGB32 right_image = extract_box_reference(screen, m_right_box); - if (is_solid(left_image, { 0.335, 0.331, 0.332 }) && is_solid(right_image, { 0.25, 0.38, 0.369 })){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + //Menu is white + ImageViewRGB32 menu_top_image = extract_box_reference(game_screen, m_menu_top_box); + ImageViewRGB32 menu_bottom_image = extract_box_reference(game_screen, m_menu_bottom_box); + ImageViewRGB32 menu_top_jpn_image = extract_box_reference(game_screen, m_menu_top_eme_box); + ImageViewRGB32 menu_right_jpn_image = extract_box_reference(game_screen, m_menu_right_eme_box); + + //Background dialog is teal + ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + ImageViewRGB32 dialog_top_jpn_image = extract_box_reference(game_screen, m_dialog_top_eme_box); + ImageViewRGB32 dialog_right_jpn_image = extract_box_reference(game_screen, m_dialog_right_eme_box); + + if ((is_white(menu_top_image) //Ruby/Sapphire, all languages. Emerald Japan. + && is_white(menu_bottom_image) + && is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }, 0.25, 20)) + || + (is_white(menu_top_jpn_image) //Emerald, all languages except Japan. + && is_white(menu_right_jpn_image) + && is_solid(dialog_top_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && is_solid(dialog_right_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20)) + ){ return true; } return false; @@ -77,22 +111,31 @@ bool BattleMenuDetector::detect(const ImageViewRGB32& screen){ AdvanceBattleDialogDetector::AdvanceBattleDialogDetector(Color color) - : m_dialog_box(0.156, 0.715, 0.686, 0.193) - , m_left_box(0.156, 0.724, 0.010, 0.176) - , m_right_box(0.836, 0.717, 0.008, 0.189) + : m_dialog_box(0.043890, 0.737193, 0.913804, 0.227310) + , m_dialog_top_box(0.049224, 0.738530, 0.901359, 0.008023) + , m_dialog_right_box(0.943471, 0.746553, 0.007111, 0.212602) + , m_dialog_jpn_box(0.059891, 0.742541, 0.872914, 0.216613) + , m_dialog_top_jpn_box(0.068780, 0.738530, 0.856913, 0.012034) + , m_dialog_right_jpn_box(0.918582, 0.747890, 0.007111, 0.208590) {} void AdvanceBattleDialogDetector::make_overlays(VideoOverlaySet& items) const{ - items.add(COLOR_RED, m_dialog_box); - items.add(COLOR_RED, m_left_box); - items.add(COLOR_RED, m_right_box); + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_jpn_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_jpn_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_jpn_box)); } bool AdvanceBattleDialogDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + const bool replace_color_within_range = false; //Filter out background ImageRGB32 filtered_region = filter_rgb32_range( - extract_box_reference(screen, m_dialog_box), - combine_rgb(185, 0, 1), combine_rgb(255, 32, 33), Color(0), replace_color_within_range + extract_box_reference(game_screen, m_dialog_box), + combine_rgb(164, 0, 0), combine_rgb(255, 114, 87), Color(0), replace_color_within_range ); ImageStats stats = image_stats(filtered_region); @@ -103,15 +146,28 @@ bool AdvanceBattleDialogDetector::detect(const ImageViewRGB32& screen){ cout << stats.average.b << endl; */ - ImageViewRGB32 left_image = extract_box_reference(screen, m_left_box); - ImageViewRGB32 right_image = extract_box_reference(screen, m_right_box); - - if (is_solid(left_image, { 0.335, 0.331, 0.332 }) - && is_solid(right_image, { 0.335, 0.331, 0.332 }) - && (stats.average.r > stats.average.b + 200) - && (stats.average.r > stats.average.g + 200) - ) - { + //japanese + ImageRGB32 filtered_region_jpn = filter_rgb32_range( + extract_box_reference(game_screen, m_dialog_jpn_box), + combine_rgb(164, 0, 0), combine_rgb(255, 114, 87), Color(0), replace_color_within_range + ); + ImageStats stats2 = image_stats(filtered_region_jpn); + + ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + ImageViewRGB32 dialog_top_jpn_image = extract_box_reference(game_screen, m_dialog_top_jpn_box); + ImageViewRGB32 dialog_right_jpn_image = extract_box_reference(game_screen, m_dialog_right_jpn_box); + + if ((is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && (stats.average.r > stats.average.b + 180) + && (stats.average.r > stats.average.g + 180)) + || + (is_solid(dialog_top_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && is_solid(dialog_right_jpn_image, { 0.176, 0.357, 0.467 }, 0.25, 20) + && (stats2.average.r > stats2.average.b + 180) + && (stats2.average.r > stats2.average.g + 180)) + ){ return true; } return false; diff --git a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h index c059e368b8..94cd6625e2 100644 --- a/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h +++ b/SerialPrograms/Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h @@ -21,28 +21,9 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonRSE{ -/* -// TODO: Detect that a dialog box is on screen by looking for the white of the box -class DialogDetector : public StaticScreenDetector{ -public: - DialogDetector(Color color); - - virtual void make_overlays(VideoOverlaySet& items) const override; - virtual bool detect(const ImageViewRGB32& screen) const override; - -private: - ImageFloatBox m_left_box; - ImageFloatBox m_right_box; -}; -class DialogWatcher : public DetectorToFinder{ -public: - DialogWatcher(Color color) - : DetectorToFinder("DialogWatcher", std::chrono::milliseconds(250), color) - {} -}; -*/ - // Battle dialog boxes are teal +// This is unused right now so isn't tested well +/* class BattleDialogDetector : public StaticScreenDetector{ public: BattleDialogDetector(Color color); @@ -51,8 +32,11 @@ class BattleDialogDetector : public StaticScreenDetector{ virtual bool detect(const ImageViewRGB32& screen) override; private: - ImageFloatBox m_left_box; - ImageFloatBox m_right_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; + + ImageFloatBox m_dialog_top_jpn_box; + ImageFloatBox m_dialog_right_jpn_box; }; class BattleDialogWatcher : public DetectorToFinder{ public: @@ -60,10 +44,12 @@ class BattleDialogWatcher : public DetectorToFinder{ : DetectorToFinder("BattleDialogWatcher", std::chrono::milliseconds(250), color) {} }; +*/ - -// Battle menu is up when it is white on the right and teal on the left -// For emerald the text is more flush to the left, so we target the right of the battle menu box +// Battle menu is a white box on the right +// Teal dialog box remains on the left +// For R/S, the selection arrow is only in JPN ver, other languages use a box +// Positions slightly different on non-JPN Emerald but all langs use an arrow class BattleMenuDetector : public StaticScreenDetector{ public: BattleMenuDetector(Color color); @@ -72,8 +58,17 @@ class BattleMenuDetector : public StaticScreenDetector{ virtual bool detect(const ImageViewRGB32& screen) override; private: - ImageFloatBox m_left_box; - ImageFloatBox m_right_box; + //R/S both JPN and ENG, E for JPN + ImageFloatBox m_menu_top_box; + ImageFloatBox m_menu_bottom_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; + + //Emerald for non-JPN + ImageFloatBox m_menu_top_eme_box; + ImageFloatBox m_menu_right_eme_box; + ImageFloatBox m_dialog_top_eme_box; + ImageFloatBox m_dialog_right_eme_box; }; class BattleMenuWatcher : public DetectorToFinder{ public: @@ -85,8 +80,7 @@ class BattleMenuWatcher : public DetectorToFinder{ // Detect the red advancement arrow by filtering for red. -// This works for now, I don't think there's colored text? -// TODO: Change this to detect that the dialog arrow is in the dialog box by filtering for the red arrow +// This is the same as BattleDialogDetector apart from the arrow class AdvanceBattleDialogDetector : public StaticScreenDetector{ public: AdvanceBattleDialogDetector(Color color); @@ -96,8 +90,12 @@ class AdvanceBattleDialogDetector : public StaticScreenDetector{ private: ImageFloatBox m_dialog_box; - ImageFloatBox m_left_box; - ImageFloatBox m_right_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; + + ImageFloatBox m_dialog_jpn_box; + ImageFloatBox m_dialog_top_jpn_box; + ImageFloatBox m_dialog_right_jpn_box; }; class AdvanceBattleDialogWatcher : public DetectorToFinder{ public: @@ -107,7 +105,6 @@ class AdvanceBattleDialogWatcher : public DetectorToFinder ShinySoundDetector::build_spectrogram_matcher(size_t sample_rate){ return std::make_unique( "Shiny Sound", - AudioTemplateCache::instance().get_throw("PokemonRSE/ShinySound", sample_rate), + AudioTemplateCache::instance().get_throw("PokemonFRLG/ShinySound", sample_rate), SpectrogramMatcher::Mode::SPIKE_CONV, sample_rate, GameSettings::instance().SHINY_SOUND_LOW_FREQUENCY ); diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.cpp index eee77a539e..49dd2f3441 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.cpp +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.cpp @@ -7,10 +7,17 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonTools/Random.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/StartupChecks/StartProgramChecks.h" +#include "CommonTools/StartupChecks/VideoResolutionCheck.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "Pokemon/Pokemon_Strings.h" #include "PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h" #include "PokemonRSE/Inference/Sounds/PokemonRSE_ShinySoundDetector.h" #include "PokemonRSE/PokemonRSE_Settings.h" @@ -21,47 +28,92 @@ namespace NintendoSwitch{ namespace PokemonRSE{ -void soft_reset(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context){ +bool try_soft_reset(ConsoleHandle& console, ProControllerContext& context){ // A + B + Select + Start - pbf_press_button(context, BUTTON_B | BUTTON_Y | BUTTON_MINUS | BUTTON_PLUS, 80ms, 1440ms); + pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_MINUS | BUTTON_PLUS, 360ms, 1440ms); - pbf_mash_button(context, BUTTON_PLUS, GameSettings::instance().START_BUTTON_MASH0); + pbf_mash_button(context, BUTTON_MINUS, GameSettings::instance().SELECT_BUTTON_MASH0); context.wait_for_all_requests(); + //Wait for save file select screen + WhiteScreenOverWatcher whitescreen(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + + int ls = run_until( + console, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_PLUS, 1000ms); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + }, + { whitescreen } + ); + context.wait_for_all_requests(); + if (ls == 0){ + console.log("Entered load menu."); + }else{ + console.log("soft_reset(): Unable to enter load menu.", COLOR_RED); + return false; + } + //Let the animations finish + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + //Load game pbf_press_button(context, BUTTON_A, 160ms, 320ms); //Wait for game to load in - BlackScreenOverWatcher detector(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + BlackScreenOverWatcher detector2(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); int ret = wait_until( - stream, context, + console, context, GameSettings::instance().ENTER_GAME_WAIT0, - {detector} + {detector2} ); if (ret == 0){ - stream.log("Entered game!"); + console.log("Entered game!"); }else{ - stream.log("Timed out waiting to enter game.", COLOR_RED); - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "soft_reset(): Timed out waiting to enter game.", - stream - ); + console.log("soft_reset(): Timed out waiting to enter game.", COLOR_RED); + return false; } + + //Random wait + console.log("Randomly waiting to avoid duplicate hits."); + Milliseconds rng_wait2 = std::chrono::milliseconds(random_u32(0, 5000)); + pbf_wait(context, rng_wait2); context.wait_for_all_requests(); + + return true; } -void flee_battle(VideoStream& stream, ProControllerContext& context){ - stream.log("Navigate to Run."); - pbf_press_dpad(context, DPAD_RIGHT, 160ms, 160ms); - pbf_press_dpad(context, DPAD_DOWN, 160ms, 160ms); - pbf_press_button(context, BUTTON_A, 160ms, 320ms); +uint64_t soft_reset(ConsoleHandle& console, ProControllerContext& context){ + uint64_t errors = 0; + for (; errors < 5; errors++){ + if (try_soft_reset(console, context)){ + console.log("Soft reset completed."); + return errors; + } + } + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "soft_reset(): Failed to reset after 5 attempts.", + console + ); +} +void flee_battle(VideoStream& stream, ProControllerContext& context){ AdvanceBattleDialogWatcher ran_away(COLOR_YELLOW); - int ret2 = wait_until( + int ret2 = run_until( stream, context, - std::chrono::seconds(5), - {{ran_away}} + [&](ProControllerContext& context){ + stream.log("Navigate to Run."); + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 160ms); + pbf_press_dpad(context, DPAD_DOWN, 160ms, 160ms); + pbf_press_button(context, BUTTON_A, 160ms, 320ms); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + }, + { ran_away } ); + context.wait_for_all_requests(); if (ret2 == 0){ stream.log("Running away..."); }else{ @@ -72,15 +124,19 @@ void flee_battle(VideoStream& stream, ProControllerContext& context){ ); } - pbf_press_button(context, BUTTON_A, 320ms, 320ms); BlackScreenOverWatcher battle_over(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); - int ret3 = wait_until( + int ret3 = run_until( stream, context, - std::chrono::seconds(5), - {{battle_over}} + [&](ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 320ms, 320ms); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + }, + { battle_over } ); + context.wait_for_all_requests(); if (ret3 == 0){ - stream.log("Ran from battle."); + stream.log("Successfully ran from battle."); }else{ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, @@ -90,33 +146,29 @@ void flee_battle(VideoStream& stream, ProControllerContext& context){ } } -bool handle_encounter(VideoStream& stream, ProControllerContext& context, bool send_out_lead){ +bool handle_encounter(ConsoleHandle& console, ProControllerContext& context, bool send_out_lead){ float shiny_coefficient = 1.0; - ShinySoundDetector shiny_detector(stream.logger(), [&](float error_coefficient) -> bool{ + ShinySoundDetector shiny_detector(console.logger(), [&](float error_coefficient) -> bool{ shiny_coefficient = error_coefficient; return true; }); AdvanceBattleDialogWatcher legendary_appeared(COLOR_YELLOW); - stream.log("Starting battle."); - pbf_mash_button(context, BUTTON_A, 4320ms); - context.wait_for_all_requests(); - int res = run_until( - stream, context, + console, context, [&](ProControllerContext& context){ int ret = wait_until( - stream, context, + console, context, std::chrono::seconds(30), {{legendary_appeared}} ); if (ret == 0){ - stream.log("Advance arrow detected."); + console.log("Advance arrow detected."); }else{ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "handle_encounter(): Did not detect battle start.", - stream + console ); } pbf_wait(context, 1000ms); @@ -126,29 +178,29 @@ bool handle_encounter(VideoStream& stream, ProControllerContext& context, bool s ); shiny_detector.throw_if_no_sound(); if (res == 0){ - stream.log("Shiny detected!"); + console.log("Shiny detected!"); return true; } - stream.log("Shiny not found."); + console.log("Shiny not found."); if (send_out_lead){ //Send out lead, no shiny detection needed. BattleMenuWatcher battle_menu(COLOR_RED); - stream.log("Sending out lead Pokemon."); + console.log("Sending out lead Pokemon."); pbf_press_button(context, BUTTON_A, 320ms, 320ms); int ret = wait_until( - stream, context, + console, context, std::chrono::seconds(15), { {battle_menu} } ); if (ret == 0){ - stream.log("Battle menu detecteed!"); + console.log("Battle menu detecteed!"); }else{ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "handle_encounter(): Did not detect battle menu.", - stream + console ); } pbf_wait(context, 1000ms); @@ -158,6 +210,28 @@ bool handle_encounter(VideoStream& stream, ProControllerContext& context, bool s return false; } +void home_black_border_check(ConsoleHandle& console, ProControllerContext& context){ + if (GameSettings::instance().DEVICE == GameSettings::Device::switch_1_2){ + console.log("Switch 1 or 2 selected in Settings."); + + console.log("Checking for min 720p and 16:9."); + assert_16_9_720p_min(console, console); + + console.log("Going to home to check for black border."); + pbf_press_button(context, BUTTON_ZL, 120ms, 880ms); // Connect the controller. + pbf_press_button(context, BUTTON_HOME, 120ms, 880ms); + context.wait_for_all_requests(); + StartProgramChecks::check_border(console); + console.log("Returning to game."); + resume_game_from_home(console, context); + context.wait_for_all_requests(); + console.log("Entered game."); + }else{ + console.log("Non-Switch device selected in Settings."); + console.log("Skipping black border and aspect ratio checks.", COLOR_BLUE); + } +} + } } diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.h b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.h index c90f5df443..fff62173b9 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.h +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Navigation.h @@ -13,13 +13,17 @@ #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" namespace PokemonAutomation{ - struct ProgramInfo; namespace NintendoSwitch{ + class ConsoleHandle; + class ProController; + using ProControllerContext = ControllerContext; namespace PokemonRSE{ -// Press A+B+Select+Start at the same time to soft reset, then re-enters the game. -// For now this assumes no dry battery. -void soft_reset(const ProgramInfo& info, VideoStream& stream, ProControllerContext &context); +// Press A+B+Select+Start at the same time to soft reset, then re-enter the game. +// This is intended for R/S (E's RNG is broken) and assumes that there is no dry battery. +// For R/S, the initial seed is based on the real time clock and changes every minute +// A randomized wait is used after loading in to avoid hitting the same frame +uint64_t soft_reset(ConsoleHandle& console, ProControllerContext& context); // Run from battle. Cursor must start on the FIGHT button. Assumes fleeing will always work. (Smoke Ball) void flee_battle(VideoStream& stream, ProControllerContext& context); @@ -27,7 +31,11 @@ void flee_battle(VideoStream& stream, ProControllerContext& context); // After press A/walking up to enter a battle, run this handle the battle start and to check if opponent is shiny. // Set send_out_lead to true and then use flee_battle() after if game is Emerald. // For R/S, send_out_lead as false and then soft_reset() to save time. -bool handle_encounter(VideoStream& stream, ProControllerContext& context, bool send_out_lead); +bool handle_encounter(ConsoleHandle& console, ProControllerContext& context, bool send_out_lead); + +// Go to home to check that scaling is 100%. Then resume game. +// Skips checks if not Switch. +void home_black_border_check(ConsoleHandle& console, ProControllerContext& context); } diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp index 536ebbc6a4..20adb1d6d8 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.cpp @@ -20,13 +20,40 @@ GameSettings& GameSettings::instance(){ static GameSettings settings; return settings; } +GameSettings::~GameSettings(){ + DEVICE.remove_listener(*this); + GAME_BOX.remove_listener(*this); + GAME_BOX.X.remove_listener(*this); + GAME_BOX.Y.remove_listener(*this); + GAME_BOX.WIDTH.remove_listener(*this); + GAME_BOX.HEIGHT.remove_listener(*this); +} GameSettings::GameSettings() : BatchOption(LockMode::LOCK_WHILE_RUNNING) + , m_game_device_settings("Game Device settings:") + , DEVICE( + "Device:
Select the device the game is running on. " + "Refer to the documentation for specific setups.", + { + {Device::switch_1_2, "switch_1_2", "Nintendo Switch 1 and 2"}, + //{Device::rg35xx, "rg35xx", "RG35XX"}, + //{Device::custom, "custom", "Custom"}, + }, + LockMode::LOCK_WHILE_RUNNING, + Device::switch_1_2 + ) + , GAME_BOX( + "Game Box: The part of the screen containing the actual video feed.", + LockMode::LOCK_WHILE_RUNNING, + GroupOption::EnableMode::ALWAYS_ENABLED, + true, + {0.09375, 0.00462963, 0.8125, 0.962963} + ) , m_soft_reset_timings("Soft Reset Timings:") - , START_BUTTON_MASH0( - "Start Button Mash:
Mash Start for this long after a soft reset to get to the main menu.", + , SELECT_BUTTON_MASH0( + "Select Button Mash:
Mash select for this long after a soft reset to get to Press Start.", LockMode::LOCK_WHILE_RUNNING, - "5000 ms" + "3200 ms" ) , ENTER_GAME_WAIT0( "Enter Game Wait:
Wait this long for the game to load.", @@ -37,23 +64,61 @@ GameSettings::GameSettings() , SHINY_SOUND_THRESHOLD( "Shiny Sound Threshold:
Maximum error coefficient to trigger a shiny detection.", LockMode::LOCK_WHILE_RUNNING, - 0.97, 0, 1.0 + 0.80, 0, 1.0 ) , SHINY_SOUND_LOW_FREQUENCY( "Shiny Sound Low Frequency (Hz):
High pass filter frequency for shiny sound.", LockMode::LOCK_WHILE_RUNNING, - 5000, 0, 48000 + 3000, 0, 48000 ) { + PA_ADD_STATIC(m_game_device_settings); + PA_ADD_OPTION(DEVICE); + PA_ADD_STATIC(GAME_BOX); PA_ADD_STATIC(m_soft_reset_timings); - PA_ADD_OPTION(START_BUTTON_MASH0); + PA_ADD_OPTION(SELECT_BUTTON_MASH0); PA_ADD_OPTION(ENTER_GAME_WAIT0); PA_ADD_STATIC(m_shiny_audio_settings); PA_ADD_OPTION(SHINY_SOUND_THRESHOLD); PA_ADD_OPTION(SHINY_SOUND_LOW_FREQUENCY); -} + GameSettings::on_config_value_changed(this); + DEVICE.add_listener(*this); + GAME_BOX.add_listener(*this); + GAME_BOX.X.add_listener(*this); + GAME_BOX.Y.add_listener(*this); + GAME_BOX.WIDTH.add_listener(*this); + GAME_BOX.HEIGHT.add_listener(*this); +} +void GameSettings::on_config_value_changed(void* object){ + switch (DEVICE){ + case Device::switch_1_2: + GAME_BOX.X.set(0.09375); + GAME_BOX.Y.set(0.00462963); + GAME_BOX.WIDTH.set(0.8125); + GAME_BOX.HEIGHT.set(0.962963); + GAME_BOX.set_visibility(ConfigOptionState::DISABLED); + break; + case Device::rg35xx: + GAME_BOX.X.set(0.125); + GAME_BOX.Y.set(0.0564814814814815); + GAME_BOX.WIDTH.set(0.7494791666666667); + GAME_BOX.HEIGHT.set(0.8861111111111111); + GAME_BOX.set_visibility(ConfigOptionState::DISABLED); + break; + case Device::custom: + GAME_BOX.set_visibility(ConfigOptionState::ENABLED); + break; + default: + GAME_BOX.X.set(0.09375); + GAME_BOX.Y.set(0.00462963); + GAME_BOX.WIDTH.set(0.8125); + GAME_BOX.HEIGHT.set(0.962963); + GAME_BOX.set_visibility(ConfigOptionState::ENABLED); + break; + } +} diff --git a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h index 74d1a114f8..77b0884c97 100644 --- a/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h +++ b/SerialPrograms/Source/PokemonRSE/PokemonRSE_Settings.h @@ -7,9 +7,11 @@ #ifndef PokemonAutomation_PokemonRSE_Settings_H #define PokemonAutomation_PokemonRSE_Settings_H +#include "Common/Cpp/Options/EnumDropdownOption.h" #include "Common/Cpp/Options/StaticTextOption.h" #include "Common/Cpp/Options/FloatingPointOption.h" #include "Common/Cpp/Options/TimeDurationOption.h" +#include "CommonFramework/Options/BoxOption.h" #include "CommonFramework/Panels/SettingsPanel.h" namespace PokemonAutomation{ @@ -17,19 +19,33 @@ namespace NintendoSwitch{ namespace PokemonRSE{ -class GameSettings : public BatchOption{ +class GameSettings : public BatchOption, private ConfigOption::Listener{ + ~GameSettings(); GameSettings(); public: static GameSettings& instance(); + enum class Device{ + switch_1_2, + rg35xx, + custom, + }; + + SectionDividerOption m_game_device_settings; + EnumDropdownOption DEVICE; + + BoxOption GAME_BOX; + SectionDividerOption m_soft_reset_timings; - MillisecondsOption START_BUTTON_MASH0; + MillisecondsOption SELECT_BUTTON_MASH0; MillisecondsOption ENTER_GAME_WAIT0; SectionDividerOption m_shiny_audio_settings; FloatingPointOption SHINY_SOUND_THRESHOLD; FloatingPointOption SHINY_SOUND_LOW_FREQUENCY; +private: + virtual void on_config_value_changed(void* object) override; }; diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.cpp b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.cpp index 7f1fcd8188..42014384c2 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.cpp +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.cpp @@ -9,7 +9,7 @@ #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/StartupChecks/StartProgramChecks.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "Pokemon/Pokemon_Strings.h" #include "PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h" @@ -27,7 +27,7 @@ AudioStarterReset_Descriptor::AudioStarterReset_Descriptor() Pokemon::STRING_POKEMON + " RSE", "Starter Reset", "Programs/PokemonRSE/AudioStarterReset.html", "Soft reset for a shiny starter. Ruby and Sapphire only.", - ProgramControllerClass::StandardController_RequiresPrecision, + ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::VIDEO_AUDIO, AllowCommandsWhenRunning::DISABLE_COMMANDS ) @@ -38,14 +38,17 @@ struct AudioStarterReset_Descriptor::Stats : public StatsTracker{ : resets(m_stats["Resets"]) , poochyena(m_stats["Shiny Poochyena"]) , shinystarter(m_stats["Shiny Starter"]) + , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shiny Poochyena"); m_display_order.emplace_back("Shiny Starter"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& poochyena; std::atomic& shinystarter; + std::atomic& errors; }; std::unique_ptr AudioStarterReset_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); @@ -62,6 +65,8 @@ AudioStarterReset::AudioStarterReset() LockMode::LOCK_WHILE_RUNNING, Target::treecko ) + , TAKE_VIDEO("Take Video:
Record a video when the shiny starter is found.", LockMode::UNLOCK_WHILE_RUNNING, true) + , GO_HOME_WHEN_DONE(true) , NOTIFICATION_SHINY_POOCH( "Shiny Poochyena", false, false, ImageAttachmentMode::JPG, @@ -80,24 +85,24 @@ AudioStarterReset::AudioStarterReset() &NOTIFICATION_PROGRAM_FINISH, }) { + PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); PA_ADD_OPTION(TARGET); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(NOTIFICATIONS); } void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - StartProgramChecks::check_performance_class_wired_or_wireless(context); - AudioStarterReset_Descriptor::Stats& stats = env.current_stats(); + home_black_border_check(env.console, context); + /* * Settings: Text Speed fast. - * Full screen, no filter? The device I'm using to test has similar looking output, but I don't have switch online+. - * If on a retro handheld, make sure the screen matches that of NSO+. - * * Setup: Stand in front of the Professor's bag and save the game. * * Required to fight, so have to do the SR method instead of run away - * Soft reset programs are only for Ruby/Sapphire, as Emerald has the 0 seed issue. + * Soft reset programs are only for Ruby/Sapphire, as Emerald always has the same starting seed. * * This also assumes no dry battery. */ @@ -110,20 +115,51 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll return true; }); + //Pressing A opens the bag so the screen goes black env.log("Opening bag and selecting starter."); - pbf_press_button(context, BUTTON_A, 320ms, 1440ms); + BlackScreenOverWatcher bag_opened(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + for (int i = 0; i < 10; i++){ + pbf_press_button(context, BUTTON_A, 320ms, 640ms); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + } + }, + {bag_opened} + ); + context.wait_for_all_requests(); + if (ret != 0){ + env.log("Failed to open bag after 10 attempts.", COLOR_RED); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to open bag after 10 attempts.", + env.console + ); + }else{ + env.log("Opened professor's bag."); + } switch (TARGET){ case Target::treecko: + env.log("Treecko selected. Moving left."); pbf_press_dpad(context, DPAD_LEFT, 320ms, 800ms); break; case Target::torchic: //Default cursor position, do nothing. + env.log("Torchic selected. Do not move cursor."); break; case Target::mudkip: + env.log("Mudkip selected. Moving right."); pbf_press_dpad(context, DPAD_RIGHT, 320ms, 800ms); break; default: + env.log("Invalid target selected."); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "AudioStarterReset: Invalid target.", @@ -131,10 +167,36 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll ); break; } - pbf_mash_button(context, BUTTON_A, 4320ms); + + //Mash A to select starter. Stop once black screen is detected to start listening for shiny pooch. + BlackScreenWatcher starter_battle_start(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + int ret3 = run_until( + env.console, context, + [](ProControllerContext& context){ + for (int i = 0; i < 10; i++){ + pbf_mash_button(context, BUTTON_A, 5000ms); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + } + }, + {starter_battle_start} + ); + context.wait_for_all_requests(); + if (ret3 != 0){ + env.log("Failed to start battle after 10 attempts.", COLOR_RED); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to start battle after 10 attempts.", + env.console + ); + }else{ + env.log("Pooch battle started."); + } context.wait_for_all_requests(); - env.log("Starter selected. Checking for shiny Poochyena."); + env.log("Checking for shiny Poochyena."); AdvanceBattleDialogWatcher pooch_appeared(COLOR_YELLOW); int res = run_until( @@ -147,6 +209,15 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll ); if (ret == 0){ env.log("Advance arrow detected."); + } else { + env.log("Battle Advance arrow was not detected."); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Battle Advance arrow was not detected.", + env.console + ); } pbf_wait(context, 1000ms); context.wait_for_all_requests(); @@ -158,7 +229,14 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll env.log("Shiny Poochyena detected!"); stats.poochyena++; env.update_stats(); - send_program_notification(env, NOTIFICATION_SHINY_POOCH, COLOR_YELLOW, "Shiny Poochyena found", {}, "", env.console.video().snapshot(), true); + send_program_notification(env, + NOTIFICATION_SHINY_POOCH, + COLOR_YELLOW, + "Shiny Poochyena found", + {}, "", + env.console.video().snapshot(), + true + ); }else{ env.log("Poochyena is not shiny."); } @@ -184,6 +262,15 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll ); if (ret == 0){ env.log("Battle menu detecteed!"); + } else { + env.log("Battle menu was not detected."); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Battle menu was not detected.", + env.console + ); } pbf_wait(context, 1000ms); context.wait_for_all_requests(); @@ -206,6 +293,9 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll true ); shiny_starter = true; + if (TAKE_VIDEO){ + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } }else{ env.log("Starter is not shiny."); env.log("Soft resetting."); @@ -213,14 +303,16 @@ void AudioStarterReset::program(SingleSwitchProgramEnvironment& env, ProControll env, NOTIFICATION_STATUS_UPDATE, "Soft resetting." ); + stats.errors += soft_reset(env.console, context); stats.resets++; env.update_stats(); - soft_reset(env.program_info(), env.console, context); + context.wait_for_all_requests(); } } - //if system set to nintendo switch, have go home when done option? - + if (GO_HOME_WHEN_DONE){ + pbf_press_button(context, BUTTON_HOME, 200ms, 1000ms); + } send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); } diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.h b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.h index 9e890300dd..b52d258a12 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.h +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_AudioStarterReset.h @@ -7,8 +7,11 @@ #ifndef PokemonAutomation_PokemonRSE_AudioStarterReset_H #define PokemonAutomation_PokemonRSE_AudioStarterReset_H +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -32,6 +35,8 @@ class AudioStarterReset : public SingleSwitchProgramInstance{ ) override{} private: + PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; + enum class Target{ treecko, torchic, @@ -39,6 +44,9 @@ class AudioStarterReset : public SingleSwitchProgramInstance{ }; EnumDropdownOption TARGET; + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY_POOCH; EventNotificationOption NOTIFICATION_SHINY_STARTER; EventNotificationOption NOTIFICATION_STATUS_UPDATE; diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.cpp b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.cpp index 30bcb74b20..793dcca618 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.cpp +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.cpp @@ -29,7 +29,7 @@ LegendaryHuntEmerald_Descriptor::LegendaryHuntEmerald_Descriptor() Pokemon::STRING_POKEMON + " RSE", "Legendary Hunt (Emerald)", "Programs/PokemonRSE/LegendaryHuntEmerald.html", "Use the Run Away method to shiny hunt legendaries in Emerald.", - ProgramControllerClass::StandardController_RequiresPrecision, + ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::VIDEO_AUDIO, AllowCommandsWhenRunning::DISABLE_COMMANDS ) @@ -39,12 +39,15 @@ struct LegendaryHuntEmerald_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Resets"]) , shinies(m_stats["Shinies"]) + , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& shinies; + std::atomic& errors; }; std::unique_ptr LegendaryHuntEmerald_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); @@ -63,6 +66,8 @@ LegendaryHuntEmerald::LegendaryHuntEmerald() LockMode::LOCK_WHILE_RUNNING, Target::regis ) + , TAKE_VIDEO("Take Video:
Record a video when the shiny starter is found.", LockMode::UNLOCK_WHILE_RUNNING, true) + , GO_HOME_WHEN_DONE(true) , NOTIFICATION_SHINY( "Shiny Found", true, true, ImageAttachmentMode::JPG, @@ -75,11 +80,16 @@ LegendaryHuntEmerald::LegendaryHuntEmerald() &NOTIFICATION_PROGRAM_FINISH, }) { + PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); PA_ADD_OPTION(TARGET); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(NOTIFICATIONS); } void LegendaryHuntEmerald::reset_regi(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryHuntEmerald_Descriptor::Stats& stats = env.current_stats(); + //turn around, walk down 4/until black screen over BlackScreenOverWatcher exit_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); BlackScreenOverWatcher enter_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); @@ -95,6 +105,8 @@ void LegendaryHuntEmerald::reset_regi(SingleSwitchProgramEnvironment& env, ProCo context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to exit area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to exit area.", @@ -118,6 +130,8 @@ void LegendaryHuntEmerald::reset_regi(SingleSwitchProgramEnvironment& env, ProCo context.wait_for_all_requests(); if (ret2 != 0){ env.log("Failed to enter area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to enter area.", @@ -135,6 +149,8 @@ void LegendaryHuntEmerald::reset_regi(SingleSwitchProgramEnvironment& env, ProCo } void LegendaryHuntEmerald::reset_groudon(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryHuntEmerald_Descriptor::Stats& stats = env.current_stats(); + //Turn left. Take 10 steps. ssf_press_button(context, BUTTON_B, 0ms, 1440ms); pbf_press_dpad(context, DPAD_LEFT, 1440ms, 160ms); @@ -170,6 +186,8 @@ void LegendaryHuntEmerald::reset_groudon(SingleSwitchProgramEnvironment& env, Pr context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to exit area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to exit area.", @@ -192,6 +210,8 @@ void LegendaryHuntEmerald::reset_groudon(SingleSwitchProgramEnvironment& env, Pr ); context.wait_for_all_requests(); if (ret2 != 0){ + stats.errors++; + env.update_stats(); env.log("Failed to enter area.", COLOR_RED); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, @@ -220,6 +240,8 @@ void LegendaryHuntEmerald::reset_groudon(SingleSwitchProgramEnvironment& env, Pr } void LegendaryHuntEmerald::reset_kyogre(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryHuntEmerald_Descriptor::Stats& stats = env.current_stats(); + //Turn down. Take 1 step. ssf_press_button(context, BUTTON_B, 0ms, 160ms); pbf_press_dpad(context, DPAD_DOWN, 160ms, 160ms); @@ -258,6 +280,8 @@ void LegendaryHuntEmerald::reset_kyogre(SingleSwitchProgramEnvironment& env, Pro context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to exit area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to exit area.", @@ -280,6 +304,8 @@ void LegendaryHuntEmerald::reset_kyogre(SingleSwitchProgramEnvironment& env, Pro context.wait_for_all_requests(); if (ret2 != 0){ env.log("Failed to enter area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to enter area.", @@ -311,6 +337,8 @@ void LegendaryHuntEmerald::reset_kyogre(SingleSwitchProgramEnvironment& env, Pro } void LegendaryHuntEmerald::reset_hooh(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryHuntEmerald_Descriptor::Stats& stats = env.current_stats(); + BlackScreenOverWatcher exit_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); //Turn around, 10 steps down ssf_press_button(context, BUTTON_B, 0ms, 1440ms); @@ -329,6 +357,8 @@ void LegendaryHuntEmerald::reset_hooh(SingleSwitchProgramEnvironment& env, ProCo context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to exit area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to exit area.", @@ -355,6 +385,8 @@ void LegendaryHuntEmerald::reset_hooh(SingleSwitchProgramEnvironment& env, ProCo context.wait_for_all_requests(); if (ret2 != 0){ env.log("Failed to enter area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to enter area.", @@ -376,6 +408,8 @@ void LegendaryHuntEmerald::reset_hooh(SingleSwitchProgramEnvironment& env, ProCo } void LegendaryHuntEmerald::reset_lugia(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryHuntEmerald_Descriptor::Stats& stats = env.current_stats(); + BlackScreenOverWatcher exit_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); //Turn around, 5 steps down ssf_press_button(context, BUTTON_B, 0ms, 720ms); @@ -394,6 +428,8 @@ void LegendaryHuntEmerald::reset_lugia(SingleSwitchProgramEnvironment& env, ProC context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to exit area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to exit area.", @@ -420,6 +456,8 @@ void LegendaryHuntEmerald::reset_lugia(SingleSwitchProgramEnvironment& env, ProC context.wait_for_all_requests(); if (ret2 != 0){ env.log("Failed to enter area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to enter area.", @@ -440,10 +478,10 @@ void LegendaryHuntEmerald::reset_lugia(SingleSwitchProgramEnvironment& env, ProC } void LegendaryHuntEmerald::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - StartProgramChecks::check_performance_class_wired_or_wireless(context); - LegendaryHuntEmerald_Descriptor::Stats& stats = env.current_stats(); + home_black_border_check(env.console, context); + /* * Text speed fast, battle animations off * smoke ball or fast pokemon req. no entry effects. @@ -454,28 +492,68 @@ void LegendaryHuntEmerald::program(SingleSwitchProgramEnvironment& env, ProContr */ while (true){ - switch (TARGET){ - case Target::hooh: - case Target::kyogre: - case Target::groudon: - //Step forward to start the encounter. - pbf_press_dpad(context, DPAD_UP, 160ms, 400ms); - break; - //case Target::groudon: //Step up is easier. - // pbf_press_dpad(context, DPAD_RIGHT, 160ms, 400ms); - // break; - //case Target::kyogre: - // pbf_press_dpad(context, DPAD_LEFT, 160ms, 400ms); - // break; - default:; + BlackScreenWatcher legendary_battle_start(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + int ret3 = run_until( + env.console, context, + [&](ProControllerContext& context){ + for (int i = 0; i < 5; i++){ + switch (TARGET){ + case Target::hooh: + case Target::kyogre: + case Target::groudon: + //Step forward to start the encounter. + pbf_press_dpad(context, DPAD_UP, 160ms, 400ms); + break; + //case Target::groudon: //Step up is easier. + // pbf_press_dpad(context, DPAD_RIGHT, 160ms, 400ms); + // break; + //case Target::kyogre: + // pbf_press_dpad(context, DPAD_LEFT, 160ms, 400ms); + // break; + case Target::lugia: + case Target::regis: + pbf_mash_button(context, BUTTON_A, 5000ms); + context.wait_for_all_requests(); + default:; + } + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + } + }, + {legendary_battle_start} + ); + context.wait_for_all_requests(); + if (ret3 != 0){ + env.log("Failed to start battle after 5 attempts.", COLOR_RED); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to start battle after 5 attempts.", + env.console + ); + }else{ + env.log("Legendary battle started."); } - //handle_encounter presses A already for everything else + context.wait_for_all_requests(); bool legendary_shiny = handle_encounter(env.console, context, true); if (legendary_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); + } + + send_program_notification(env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); break; } env.log("No shiny found."); @@ -514,7 +592,9 @@ void LegendaryHuntEmerald::program(SingleSwitchProgramEnvironment& env, ProContr stats.resets++; env.update_stats(); } - + if (GO_HOME_WHEN_DONE){ + pbf_press_button(context, BUTTON_HOME, 200ms, 1000ms); + } send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); } diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.h b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.h index a0d7d574c6..09c1f7562c 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.h +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_LegendaryHunt-Emerald.h @@ -9,6 +9,9 @@ #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -32,6 +35,8 @@ class LegendaryHuntEmerald : public SingleSwitchProgramInstance{ ) override{} private: + PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; + enum class Target{ regis, groudon, @@ -41,6 +46,9 @@ class LegendaryHuntEmerald : public SingleSwitchProgramInstance{ }; EnumDropdownOption TARGET; + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY; EventNotificationOption NOTIFICATION_STATUS_UPDATE; EventNotificationsOption NOTIFICATIONS; diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.cpp b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.cpp index 16fd405abe..703da0fb53 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.cpp +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.cpp @@ -9,8 +9,8 @@ #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" -//#include "CommonTools/Async/InferenceRoutines.h" -//#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/StartupChecks/StartProgramChecks.h" #include "Pokemon/Pokemon_Strings.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" @@ -29,7 +29,7 @@ ShinyHuntDeoxys_Descriptor::ShinyHuntDeoxys_Descriptor() Pokemon::STRING_POKEMON + " RSE", "Shiny Hunt - Deoxys", "Programs/PokemonRSE/ShinyHuntDeoxys.html", "Use the Run Away method to shiny hunt Deoxys in Emerald.", - ProgramControllerClass::StandardController_RequiresPrecision, + ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::VIDEO_AUDIO, AllowCommandsWhenRunning::DISABLE_COMMANDS ) @@ -39,12 +39,15 @@ struct ShinyHuntDeoxys_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Resets"]) , shinies(m_stats["Shinies"]) + , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& shinies; + std::atomic& errors; }; std::unique_ptr ShinyHuntDeoxys_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); @@ -61,6 +64,8 @@ ShinyHuntDeoxys::ShinyHuntDeoxys() LockMode::LOCK_WHILE_RUNNING, StartPos::rock_unsolved ) + , TAKE_VIDEO("Take Video:
Record a video when the shiny starter is found.", LockMode::UNLOCK_WHILE_RUNNING, true) + , GO_HOME_WHEN_DONE(true) , WALK_UP_DOWN_TIME0( "Walk up/down time
Spend this long to run up to the triangle rock.", LockMode::LOCK_WHILE_RUNNING, @@ -78,7 +83,10 @@ ShinyHuntDeoxys::ShinyHuntDeoxys() &NOTIFICATION_PROGRAM_FINISH, }) { + PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); PA_ADD_OPTION(STARTPOS); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(WALK_UP_DOWN_TIME0); PA_ADD_OPTION(NOTIFICATIONS); } @@ -188,10 +196,10 @@ void ShinyHuntDeoxys::solve_puzzle(SingleSwitchProgramEnvironment& env, ProContr } void ShinyHuntDeoxys::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - StartProgramChecks::check_performance_class_wired_or_wireless(context); - ShinyHuntDeoxys_Descriptor::Stats& stats = env.current_stats(); + home_black_border_check(env.console, context); + /* * Settings: Text Speed fast. Turn off animations. * Full screen, no filter. @@ -228,6 +236,8 @@ void ShinyHuntDeoxys::program(SingleSwitchProgramEnvironment& env, ProController solve_puzzle(env, context); break; default: + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Invalid starting position selected.", @@ -239,11 +249,50 @@ void ShinyHuntDeoxys::program(SingleSwitchProgramEnvironment& env, ProController } //Start battle + BlackScreenWatcher legendary_battle_start(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + int ret3 = run_until( + env.console, context, + [&](ProControllerContext& context){ + for (int i = 0; i < 5; i++){ + pbf_mash_button(context, BUTTON_A, 3000ms); + pbf_wait(context, 10000ms); + context.wait_for_all_requests(); + } + }, + {legendary_battle_start} + ); + context.wait_for_all_requests(); + if (ret3 != 0){ + env.log("Failed to start battle after 5 attempts.", COLOR_RED); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to start battle after 5 attempts.", + env.console + ); + }else{ + env.log("Legendary battle started."); + } + context.wait_for_all_requests(); + bool legendary_shiny = handle_encounter(env.console, context, true); if (legendary_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); + } + + send_program_notification(env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); break; } env.log("No shiny found."); @@ -272,8 +321,9 @@ void ShinyHuntDeoxys::program(SingleSwitchProgramEnvironment& env, ProController env.update_stats(); } - //switch - go home when done - + if (GO_HOME_WHEN_DONE){ + pbf_press_button(context, BUTTON_HOME, 200ms, 1000ms); + } send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); } diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.h b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.h index 62b7454d0a..3a8d1c464d 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.h +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Deoxys.h @@ -10,6 +10,8 @@ #include "Common/Cpp/Options/TimeDurationOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -33,6 +35,8 @@ class ShinyHuntDeoxys : public SingleSwitchProgramInstance{ ) override{} private: + PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; + enum class StartPos{ boat, rock_unsolved, @@ -40,6 +44,9 @@ class ShinyHuntDeoxys : public SingleSwitchProgramInstance{ }; EnumDropdownOption STARTPOS; + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + MillisecondsOption WALK_UP_DOWN_TIME0; EventNotificationOption NOTIFICATION_SHINY; diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.cpp b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.cpp index ee989ef8b8..cbc4826891 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.cpp +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.cpp @@ -29,7 +29,7 @@ ShinyHuntMew_Descriptor::ShinyHuntMew_Descriptor() Pokemon::STRING_POKEMON + " RSE", "Shiny Hunt - Mew", "Programs/PokemonRSE/ShinyHuntMew.html", "Use the Run Away method to shiny hunt Mew in Emerald.", - ProgramControllerClass::StandardController_RequiresPrecision, + ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::VIDEO_AUDIO, AllowCommandsWhenRunning::DISABLE_COMMANDS ) @@ -39,19 +39,24 @@ struct ShinyHuntMew_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Resets"]) , shinies(m_stats["Shinies"]) + , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& shinies; + std::atomic& errors; }; std::unique_ptr ShinyHuntMew_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); } ShinyHuntMew::ShinyHuntMew() - : NOTIFICATION_SHINY( + : TAKE_VIDEO("Take Video:
Record a video when the shiny starter is found.", LockMode::UNLOCK_WHILE_RUNNING, true) + , GO_HOME_WHEN_DONE(true) + , NOTIFICATION_SHINY( "Shiny Found", true, true, ImageAttachmentMode::JPG, {"Notifs", "Showcase"} @@ -96,6 +101,9 @@ ShinyHuntMew::ShinyHuntMew() "150 ms" ) { + PA_ADD_STATIC(SHINY_REQUIRES_AUDIO); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(NOTIFICATIONS); PA_ADD_STATIC(m_advanced_options); PA_ADD_OPTION(MEW_WAIT_TIME); @@ -107,6 +115,8 @@ ShinyHuntMew::ShinyHuntMew() } void ShinyHuntMew::enter_mew(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + ShinyHuntMew_Descriptor::Stats& stats = env.current_stats(); + BlackScreenOverWatcher enter_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); int ret = run_until( env.console, context, @@ -119,6 +129,8 @@ void ShinyHuntMew::enter_mew(SingleSwitchProgramEnvironment& env, ProControllerC context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to enter area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to enter area.", @@ -149,13 +161,42 @@ void ShinyHuntMew::enter_mew(SingleSwitchProgramEnvironment& env, ProControllerC ssf_press_button(context, BUTTON_B, 0ms, RIGHT_GRASS_2_TIME); pbf_press_dpad(context, DPAD_RIGHT, RIGHT_GRASS_2_TIME, 0ms); - //Turn up. Start battle. + //Turn up. pbf_press_dpad(context, DPAD_UP, FACE_UP_TIME, 0ms); + context.wait_for_all_requests(); + //Start battle. + BlackScreenWatcher legendary_battle_start(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + int ret3 = run_until( + env.console, context, + [&](ProControllerContext& context){ + for (int i = 0; i < 5; i++){ + pbf_mash_button(context, BUTTON_A, 3000ms); + pbf_wait(context, 10000ms); + context.wait_for_all_requests(); + } + }, + {legendary_battle_start} + ); + context.wait_for_all_requests(); + if (ret3 != 0){ + env.log("Failed to start battle after 5 attempts.", COLOR_RED); + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to start battle after 5 attempts.", + env.console + ); + }else{ + env.log("Legendary battle started."); + } context.wait_for_all_requests(); } void ShinyHuntMew::exit_mew(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + ShinyHuntMew_Descriptor::Stats& stats = env.current_stats(); + ssf_press_button(context, BUTTON_B, 0ms, 400ms); pbf_press_dpad(context, DPAD_DOWN, 400ms, 160ms); @@ -177,6 +218,8 @@ void ShinyHuntMew::exit_mew(SingleSwitchProgramEnvironment& env, ProControllerCo context.wait_for_all_requests(); if (ret != 0){ env.log("Failed to exit area.", COLOR_RED); + stats.errors++; + env.update_stats(); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Failed to exit area.", @@ -188,10 +231,10 @@ void ShinyHuntMew::exit_mew(SingleSwitchProgramEnvironment& env, ProControllerCo } void ShinyHuntMew::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - StartProgramChecks::check_performance_class_wired_or_wireless(context); - ShinyHuntMew_Descriptor::Stats& stats = env.current_stats(); + home_black_border_check(env.console, context); + /* * Requires more precision to ensure a Mew encounter every time. * Movement is very configurable due to this. @@ -207,7 +250,19 @@ void ShinyHuntMew::program(SingleSwitchProgramEnvironment& env, ProControllerCon if (legendary_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); + } + + send_program_notification(env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); break; } env.log("No shiny found."); @@ -223,6 +278,9 @@ void ShinyHuntMew::program(SingleSwitchProgramEnvironment& env, ProControllerCon env.update_stats(); } + if (GO_HOME_WHEN_DONE){ + pbf_press_button(context, BUTTON_HOME, 200ms, 1000ms); + } send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); } diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.h b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.h index 6d8f81e086..a653a08ead 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.h +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_ShinyHunt-Mew.h @@ -11,6 +11,8 @@ #include "Common/Cpp/Options/TimeDurationOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "PokemonLA/Options/PokemonLA_ShinyDetectedAction.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -34,6 +36,10 @@ class ShinyHuntMew : public SingleSwitchProgramInstance{ ) override{} private: + PokemonLA::ShinyRequiresAudioText SHINY_REQUIRES_AUDIO; + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY; EventNotificationOption NOTIFICATION_STATUS_UPDATE; EventNotificationsOption NOTIFICATIONS; diff --git a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_StarterReset.cpp b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_StarterReset.cpp index c0691fc6c4..6e50b206bd 100644 --- a/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_StarterReset.cpp +++ b/SerialPrograms/Source/PokemonRSE/Programs/ShinyHunting/PokemonRSE_StarterReset.cpp @@ -39,12 +39,15 @@ struct StarterReset_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Resets"]) , shinystarter(m_stats["Shiny Starter"]) + , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shiny Starter"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& shinystarter; + std::atomic& errors; }; std::unique_ptr StarterReset_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); @@ -192,8 +195,10 @@ void StarterReset::program(SingleSwitchProgramEnvironment& env, ProControllerCon env, NOTIFICATION_STATUS_UPDATE, "Soft resetting." ); - soft_reset(env.program_info(), env.console, context); + stats.errors += soft_reset(env.console, context); stats.resets++; + env.update_stats(); + context.wait_for_all_requests(); } }