diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp index 78961198d..ac5bbb3e1 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp @@ -120,6 +120,100 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ return false; } +BattleDialogDetector::BattleDialogDetector(Color color) + : m_dialog_top_box(0.152, 0.721, 0.694, 0.008) + , m_dialog_right_box(0.842, 0.730, 0.004, 0.171) +{} +void BattleDialogDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(COLOR_RED, m_dialog_top_box); + items.add(COLOR_RED, m_dialog_right_box); +} +bool BattleDialogDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 dialog_top_image = extract_box_reference(screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(screen, m_dialog_right_box); + + if (is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }) + && is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }) + ){ + return true; + } + return false; +} + + +BattleMenuDetector::BattleMenuDetector(Color color) + : m_menu_top_box(0.522, 0.716, 0.331, 0.011) + , m_menu_right_box(0.845, 0.727, 0.008, 0.175) + , m_dialog_top_box(0.153, 0.720, 0.343, 0.014) //top of the white dialog box + , m_dialog_right_box(0.486, 0.722, 0.009, 0.177) //right side, closest to the menu +{} +void BattleMenuDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(COLOR_RED, m_menu_top_box); + items.add(COLOR_RED, m_menu_right_box); + items.add(COLOR_RED, m_dialog_top_box); + items.add(COLOR_RED, m_dialog_right_box); +} +bool BattleMenuDetector::detect(const ImageViewRGB32& screen){ + //Menu is white + ImageViewRGB32 menu_top_image = extract_box_reference(screen, m_menu_top_box); + ImageViewRGB32 menu_right_image = extract_box_reference(screen, m_menu_right_box); + + //Background dialog is teal + ImageViewRGB32 dialog_top_image = extract_box_reference(screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(screen, m_dialog_right_box); + + if (is_solid(menu_top_image, { 0.335, 0.332, 0.335 }) //253, 251, 254 white + && is_solid(menu_right_image, { 0.335, 0.332, 0.335 }) + && is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }) //40, 81, 106 teal + && is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }) + ){ + return true; + } + return false; +} + + +AdvanceBattleDialogDetector::AdvanceBattleDialogDetector(Color color) + : m_dialog_box(0.152, 0.721, 0.694, 0.177) + , m_dialog_top_box(0.152, 0.721, 0.694, 0.008) + , m_dialog_right_box(0.842, 0.730, 0.004, 0.171) +{} +void AdvanceBattleDialogDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(COLOR_RED, m_dialog_box); + items.add(COLOR_RED, m_dialog_top_box); + items.add(COLOR_RED, m_dialog_right_box); +} +bool AdvanceBattleDialogDetector::detect(const ImageViewRGB32& screen){ + 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 + ); + ImageStats stats = image_stats(filtered_region); + + /* + filtered_region.save("./filtered_only.png"); + cout << stats.average.r << endl; + cout << stats.average.g << endl; + cout << stats.average.b << endl; + */ + + ImageViewRGB32 dialog_top_image = extract_box_reference(screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(screen, m_dialog_right_box); + + if (is_solid(dialog_top_image, { 0.176, 0.357, 0.467 }) + && is_solid(dialog_right_image, { 0.176, 0.357, 0.467 }) + && (stats.average.r > stats.average.b + 200) + && (stats.average.r > stats.average.g + 200) + ) + { + return true; + } + return false; +} + } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h index a77b39381..ffaf3f020 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h @@ -95,6 +95,70 @@ class SelectionDialogWatcher : public DetectorToFinder{ {} }; +// Battle dialog boxes are teal, similar to r/s/e +class BattleDialogDetector : public StaticScreenDetector{ +public: + BattleDialogDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; +}; +class BattleDialogWatcher : public DetectorToFinder{ +public: + BattleDialogWatcher(Color color) + : DetectorToFinder("BattleDialogWatcher", std::chrono::milliseconds(250), color) + {} +}; + + +// Battle menu is a white box on the right with FIGHT/POKEMON/BAG/RUN +// The dialog box with "what will POKEMON do?" is dark teal/navy +class BattleMenuDetector : public StaticScreenDetector{ +public: + BattleMenuDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_menu_top_box; + ImageFloatBox m_menu_right_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; +}; +class BattleMenuWatcher : public DetectorToFinder{ +public: + BattleMenuWatcher(Color color) + : DetectorToFinder("BattleMenuWatcher", std::chrono::milliseconds(250), color) + {} +}; + + +// Detect the red advancement arrow by filtering for red. +// No red colored text in battle? +// reuse the battledialog checks for the top and right +class AdvanceBattleDialogDetector : public StaticScreenDetector{ +public: + AdvanceBattleDialogDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_dialog_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; +}; +class AdvanceBattleDialogWatcher : public DetectorToFinder{ +public: + AdvanceBattleDialogWatcher(Color color) + : DetectorToFinder("AdvanceBattleDialogWatcher", std::chrono::milliseconds(250), color) + {} +}; diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index c446dd02d..061b9c807 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -136,67 +136,88 @@ void open_slot_six(ConsoleHandle& console, ProControllerContext& context){ context.wait_for_all_requests(); } -/* -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, - std::chrono::seconds(30), + console, context, + std::chrono::seconds(30), //More than enough time for shiny sound {{legendary_appeared}} ); if (ret == 0) { - stream.log("Advance arrow detected."); + console.log("Battle Advance arrow detected."); } else { OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "handle_encounter(): Did not detect battle start.", - stream + "handle_encounter(): Did not detect battle advance arrow.", + console ); } pbf_wait(context, 1000ms); context.wait_for_all_requests(); + + /* + //Send out shiny lead to test detection + BattleMenuWatcher battle_menu(COLOR_RED); + console.log("Sending out lead Pokemon."); + pbf_press_button(context, BUTTON_A, 320ms, 320ms); + + int ret2 = wait_until( + console, context, + std::chrono::seconds(15), + { {battle_menu} } + ); + if (ret2 == 0) { + console.log("Battle menu detecteed!"); + } + else { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "handle_encounter(): Did not detect battle menu.", + console + ); + } + pbf_wait(context, 100000ms); //extreme audio delay on my cheap test device + context.wait_for_all_requests(); + */ + }, {{shiny_detector}} ); 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("No shiny detected."); if (send_out_lead) { - //Send out lead, no shiny detection needed. + //Send out lead, no shiny detection needed. (Or wanted.) 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); @@ -205,7 +226,47 @@ bool handle_encounter(VideoStream& stream, ProControllerContext& context, bool s return false; } -*/ + +void flee_battle(ConsoleHandle& console, ProControllerContext& context) { + console.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); + + AdvanceBattleDialogWatcher ran_away(COLOR_YELLOW); + int ret2 = wait_until( + console, context, + std::chrono::seconds(5), + {{ran_away}} + ); + if (ret2 == 0) { + console.log("Running away..."); + } else { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "flee_battle(): Unable to navigate to flee button.", + console + ); + } + + 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( + console, context, + std::chrono::seconds(5), + {{battle_over}} + ); + if (ret3 == 0) { + console.log("Successfully fled the battle."); + } else { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "flee_battle(): Unable to flee from battle.", + console + ); + } +} + } } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index 46389e4b8..29f1fcf50 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -27,10 +27,12 @@ void soft_reset(const ProgramInfo& info, VideoStream& stream, ProControllerConte void open_slot_six(ConsoleHandle& console, 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); +// Set send_out_lead to true and then use flee_battle() after if for run away resets +// For soft resets, send_out_lead as false and then soft_reset() to save time. +bool handle_encounter(ConsoleHandle& console, ProControllerContext& context, bool send_out_lead); +// Run from battle. Cursor must start on the FIGHT button. Assumes fleeing will always work. (Smoke Ball) +void flee_battle(ConsoleHandle& console, ProControllerContext& context); } } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 0a13424a5..e2ed54faa 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -11,6 +11,8 @@ #include "PokemonFRLG_Settings.h" #include "Programs/ShinyHunting/PokemonFRLG_GiftReset.h" +#include "Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h" +#include "Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h" #include "Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" @@ -34,6 +36,8 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back("---- Shiny Hunting ----"); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp index b10b44464..d977d99ab 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp @@ -69,7 +69,7 @@ GiftReset::GiftReset() ) , GO_HOME_WHEN_DONE(true) , NOTIFICATION_SHINY( - "Shiny", + "Shiny found", true, true, ImageAttachmentMode::JPG, {"Notifs", "Showcase"} ) @@ -255,7 +255,7 @@ void GiftReset::open_summary(SingleSwitchProgramEnvironment& env, ProControllerC if (ret1 == 0){ env.log("Entered party menu."); }else{ - env.log("Timed out waiting to enter game.", COLOR_RED); + env.log("Unable to enter party menu.", COLOR_RED); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "open_summary(): Unable to enter Party menu.", @@ -283,7 +283,7 @@ void GiftReset::open_summary(SingleSwitchProgramEnvironment& env, ProControllerC if (ret2 == 0){ env.log("Entered summary."); }else{ - env.log("Timed out waiting to enter game.", COLOR_RED); + env.log("Unable to enter summary.", COLOR_RED); OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "open_summary(): Unable to enter summary.", diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp new file mode 100644 index 000000000..293ebec96 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp @@ -0,0 +1,158 @@ +/* Legendary Reset + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#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/StartupChecks/StartProgramChecks.h" +#include "Pokemon/Pokemon_Strings.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" +#include "PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_LegendaryReset.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +LegendaryReset_Descriptor::LegendaryReset_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:LegendaryReset", + Pokemon::STRING_POKEMON + " FRLG", "Legendary Reset", + "Programs/PokemonFRLG/LegendaryReset.html", + "Shiny hunt legendary Pokemon using soft resets.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct LegendaryReset_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 LegendaryReset_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +LegendaryReset::LegendaryReset() + : WALK_UP( + "Walk Up (Ho-Oh only):
Walk up to trigger encounter.", + LockMode::LOCK_WHILE_RUNNING, + false + ) + , GO_HOME_WHEN_DONE(true) + , NOTIFICATION_SHINY( + "Shiny found", + true, true, ImageAttachmentMode::JPG, + {"Notifs", "Showcase"} + ) + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_SHINY, + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + }) +{ + PA_ADD_OPTION(WALK_UP); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +void LegendaryReset::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryReset_Descriptor::Stats& stats = env.current_stats(); + + /* + * Settings: Text Speed fast. Default borders. Audio required. + * Setup: Stand in front of target. Save the game. + * Lead can be shiny, reset is before lead is even sent out. + * This is the same as RSE resets. + * For deoxys solve the puzzle first. + */ + + while (true) { + if (WALK_UP) { + //Step forward to start the encounter. + pbf_press_dpad(context, DPAD_UP, 320ms, 400ms); + } + + //Talk to target + pbf_press_button(context, BUTTON_A, 320ms, 320ms); + + //Mash B until black screen detected but not over (entered battle) + BlackScreenWatcher battle_entered(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + //Long mash + wait for cases like Deoxys + pbf_mash_button(context, BUTTON_B, 10000ms); + pbf_wait(context, 10000ms); + context.wait_for_all_requests(); + }, + {battle_entered} + ); + context.wait_for_all_requests(); + if (ret != 0){ + stats.errors++; + env.update_stats(); + env.log("Failed to enter battle.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to enter battle.", + env.console + ); + } + else { + env.log("Battle started."); + } + + //handle_encounter will wait for "POKEMON appeared!" + bool legendary_shiny = handle_encounter(env.console, context, false); + if (legendary_shiny) { + stats.shinies++; + env.update_stats(); + send_program_notification(env, NOTIFICATION_SHINY, COLOR_YELLOW, "Shiny found!", {}, "", env.console.video().snapshot(), true); + break; + } + + //No shiny found + env.log("Soft resetting."); + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Soft resetting." + ); + + soft_reset(env.program_info(), env.console, context); + stats.resets++; + env.update_stats(); + context.wait_for_all_requests(); + } + + 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/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h new file mode 100644 index 000000000..624f6c822 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h @@ -0,0 +1,52 @@ +/* Legendary Reset + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_LegendaryReset_H +#define PokemonAutomation_PokemonFRLG_LegendaryReset_H + +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "Common/Cpp/Options/SimpleIntegerOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class LegendaryReset_Descriptor : public SingleSwitchProgramDescriptor{ +public: + LegendaryReset_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class LegendaryReset : public SingleSwitchProgramInstance{ +public: + LegendaryReset(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + BooleanCheckBoxOption WALK_UP; + + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} +#endif + + diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp new file mode 100644 index 000000000..602139759 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp @@ -0,0 +1,306 @@ +/* Legendary Run Away + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#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/StartupChecks/StartProgramChecks.h" +#include "Pokemon/Pokemon_Strings.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" +#include "PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_LegendaryRunAway.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +LegendaryRunAway_Descriptor::LegendaryRunAway_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:LegendaryRunAway", + Pokemon::STRING_POKEMON + " FRLG", "Legendary Run Away", + "Programs/PokemonFRLG/LegendaryRunAway.html", + "Shiny hunt legendary Pokemon using the run away method.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct LegendaryRunAway_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 LegendaryRunAway_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +LegendaryRunAway::LegendaryRunAway() + : TARGET( + "Target:
", + { + {Target::hooh, "hooh", "Ho-Oh"}, + {Target::lugia, "lugia", "Lugia"}, + }, + LockMode::LOCK_WHILE_RUNNING, + Target::hooh + ) + , GO_HOME_WHEN_DONE(true) + , NOTIFICATION_SHINY( + "Shiny found", + true, true, ImageAttachmentMode::JPG, + {"Notifs", "Showcase"} + ) + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_SHINY, + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + }) + , m_advanced_options( + "Advanced Options: You should not need to touch anything below here." + ) + , HOOH_UP_DOWN( + "Ho-Oh up/down time:
Time it takes to run up to Ho-Oh or down away to reset.", + LockMode::LOCK_WHILE_RUNNING, + "1000 ms" + ) + , HOOH_LEFT_RIGHT( + "Ho-Oh left/right time:
Time it takes when facing the same direction to take one step left or right.", + LockMode::LOCK_WHILE_RUNNING, + "240 ms" + ) + , LUGIA_UP_DOWN( + "Lugia up/down:
Time it takes to run up to Lugia or down away to reset.", + LockMode::LOCK_WHILE_RUNNING, + "600 ms" + ) + , LUGIA_LEFT_RIGHT( + "Lugia left time:
Time it takes walk three steps left after entering Lugia's room.", + LockMode::LOCK_WHILE_RUNNING, + "550 ms" + ) +{ + PA_ADD_OPTION(TARGET); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); + PA_ADD_STATIC(m_advanced_options); + PA_ADD_OPTION(HOOH_UP_DOWN); + PA_ADD_OPTION(HOOH_LEFT_RIGHT); + PA_ADD_OPTION(LUGIA_UP_DOWN); + PA_ADD_OPTION(LUGIA_LEFT_RIGHT); +} + +void LegendaryRunAway::reset_hooh(SingleSwitchProgramEnvironment& env, ProControllerContext& context) { + 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, HOOH_UP_DOWN); + pbf_press_dpad(context, DPAD_DOWN, HOOH_UP_DOWN, 160ms); + + //Turn right, take 1 step. Wait for black screen over. + int ret = run_until( + env.console, context, + [&](ProControllerContext& context){ + pbf_press_dpad(context, DPAD_RIGHT, HOOH_LEFT_RIGHT, 160ms); + pbf_wait(context, 2400ms); + }, + {exit_area} + ); + context.wait_for_all_requests(); + if (ret != 0){ + env.log("Failed to exit area.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to exit area.", + env.console + ); + } + else { + env.log("Left area."); + } + + BlackScreenOverWatcher enter_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + //turn left, take one step. now turn back right and take a step. wait for black screen over. + int ret2 = run_until( + env.console, context, + [&](ProControllerContext& context){ + pbf_press_dpad(context, DPAD_LEFT, HOOH_LEFT_RIGHT, 160ms); + + ssf_press_button(context, BUTTON_B, 0ms, 500ms); + pbf_press_dpad(context, DPAD_RIGHT, 500ms, 160ms); + pbf_wait(context, 2400ms); + }, + {enter_area} + ); + context.wait_for_all_requests(); + if (ret2 != 0){ + env.log("Failed to enter area.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to enter area.", + env.console + ); + } + else { + env.log("Entered area."); + } + + //reverse above steps, but only take 9 steps up + //doesn't really matter since we want to trigger the encounter anyway + pbf_press_dpad(context, DPAD_LEFT, HOOH_LEFT_RIGHT, 160ms); + + ssf_press_button(context, BUTTON_B, 0ms, HOOH_UP_DOWN); + pbf_press_dpad(context, DPAD_UP, HOOH_UP_DOWN, 160ms); + + context.wait_for_all_requests(); +} + +void LegendaryRunAway::reset_lugia(SingleSwitchProgramEnvironment& env, ProControllerContext& context) { + 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, LUGIA_UP_DOWN); + pbf_press_dpad(context, DPAD_DOWN, LUGIA_UP_DOWN, 160ms); + + //Turn right, 3 steps right. Wait for black screen over. + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + ssf_press_button(context, BUTTON_B, 0ms, 720ms); + pbf_press_dpad(context, DPAD_RIGHT, 720ms, 160ms); + pbf_wait(context, 2400ms); + }, + {exit_area} + ); + context.wait_for_all_requests(); + if (ret != 0){ + env.log("Failed to exit area.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to exit area.", + env.console + ); + } + else { + env.log("Left area."); + } + + BlackScreenOverWatcher enter_area(COLOR_RED, {0.282, 0.064, 0.448, 0.871}); + //turn up, take one step. then turn back down and take a step. wait for black screen over. + int ret2 = run_until( + env.console, context, + [](ProControllerContext& context){ + ssf_press_button(context, BUTTON_B, 0ms, 500ms); + pbf_press_dpad(context, DPAD_UP, 500ms, 160ms); + context.wait_for_all_requests(); + ssf_press_button(context, BUTTON_B, 0ms, 500ms); + pbf_press_dpad(context, DPAD_DOWN, 500ms, 160ms); + pbf_wait(context, 2400ms); + }, + {enter_area} + ); + context.wait_for_all_requests(); + if (ret2 != 0){ + env.log("Failed to enter area.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to enter area.", + env.console + ); + } + else { + env.log("Entered area."); + } + + //reverse above steps + ssf_press_button(context, BUTTON_B, 0ms, LUGIA_LEFT_RIGHT); + pbf_press_dpad(context, DPAD_LEFT, LUGIA_LEFT_RIGHT, 160ms); + context.wait_for_all_requests(); + + ssf_press_button(context, BUTTON_B, 0ms, LUGIA_UP_DOWN); + pbf_press_dpad(context, DPAD_UP, LUGIA_UP_DOWN, 160ms); + context.wait_for_all_requests(); +} + +void LegendaryRunAway::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + LegendaryRunAway_Descriptor::Stats& stats = env.current_stats(); + + /* + * Settings: Text Speed fast. Default borders. Battle animations off. Audio required. + * Smoke Ball or fast pokemon required. + * Setup: Stand in front of target. Save the game. + * Lead can be shiny, reset is before lead is even sent out. + * This is the same as RSE resets. + */ + + while (true) { + if (TARGET == Target::hooh) { + //Step forward to start the encounter. + pbf_press_dpad(context, DPAD_UP, 160ms, 400ms); + } else { + //Press A to start the encounter. + pbf_press_button(context, BUTTON_A, 320ms, 320ms); + } + + 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); + break; + } + env.log("No shiny found."); + flee_battle(env.console, context); + + //Close out dialog box + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + + //Exit and re-enter the room + switch (TARGET) { + case Target::hooh: + reset_hooh(env, context); + break; + case Target::lugia: + reset_lugia(env, context); + break; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Invalid target!", + env.console + ); + break; + } + 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/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h new file mode 100644 index 000000000..5f54b6266 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h @@ -0,0 +1,67 @@ +/* Legendary Run Away + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_LegendaryRunAway_H +#define PokemonAutomation_PokemonFRLG_LegendaryRunAway_H + +#include "Common/Cpp/Options/StaticTextOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "Common/Cpp/Options/TimeDurationOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class LegendaryRunAway_Descriptor : public SingleSwitchProgramDescriptor{ +public: + LegendaryRunAway_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class LegendaryRunAway : public SingleSwitchProgramInstance{ +public: + LegendaryRunAway(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + enum class Target{ + hooh, + lugia, + }; + EnumDropdownOption TARGET; + + void reset_hooh(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + void reset_lugia(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; + + SectionDividerOption m_advanced_options; + MillisecondsOption HOOH_UP_DOWN; + MillisecondsOption HOOH_LEFT_RIGHT; + MillisecondsOption LUGIA_UP_DOWN; + MillisecondsOption LUGIA_LEFT_RIGHT; +}; + +} +} +} +#endif + + diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.cpp index 092a5614c..097d5c3c5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.cpp @@ -70,7 +70,7 @@ PrizeCornerReset::PrizeCornerReset() ) , GO_HOME_WHEN_DONE(true) , NOTIFICATION_SHINY( - "Shiny", + "Shiny found", true, true, ImageAttachmentMode::JPG, {"Notifs", "Showcase"} ) diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 73970aacb..a87fc8919 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1417,6 +1417,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/PokemonFRLG_Settings.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.h + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp