From df23af9c2238aceed46e92090e8716525ed627b1 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sat, 7 Mar 2026 11:20:10 -0600 Subject: [PATCH 1/9] add preliminary RNG manip program --- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 + .../ShinyHunting/PokemonFRLG_StarterRNG.cpp | 417 ++++++++++++++++++ .../ShinyHunting/PokemonFRLG_StarterRNG.h | 77 ++++ SerialPrograms/cmake/SourceFiles.cmake | 2 + 4 files changed, 498 insertions(+) create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index a3edb13d21..17b9d9e24a 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -11,6 +11,7 @@ #include "PokemonFRLG_Settings.h" #include "Programs/ShinyHunting/PokemonFRLG_GiftReset.h" +#include "Programs/ShinyHunting/PokemonFRLG_StarterRNG.h" #include "Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h" #include "Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h" #include "Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.h" @@ -38,6 +39,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); if (PreloadSettings::instance().DEVELOPER_MODE){ + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp new file mode 100644 index 0000000000..e016d62646 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp @@ -0,0 +1,417 @@ +/* Starter RNG + * + * 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/Menus/PokemonFRLG_StartMenuDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_StarterRNG.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +StarterRNG_Descriptor::StarterRNG_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:StarterRNG", + Pokemon::STRING_POKEMON + " FRLG", "Starter RNG", + "Programs/PokemonFRLG/StarterRNG.html", + "Soft reset with specific timings for hitting a target Seed and Frame for RNG manipulation.", + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct StarterRNG_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 StarterRNG_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +StarterRNG::StarterRNG() + : TARGET( + "Target:
", + { + {Target::starters, "starters", "Bulbasaur / Squirtle / Charmander"}, + {Target::hitmon, "hitmon", "Magikarp, Hitmonlee / Hitmonchan"}, + {Target::eevee, "eevee", "Eevee"}, + {Target::lapras, "lapras", "Lapras"}, + {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, + {Target::sweetscent, "sweetscent", "Sweet Scent for wild encounters"}, + {Target::wildwalk, "wildwalk", "Hit seed and advance when walking in grass"}, + {Target::fishing, "fishing", "Hit seed and advance when fishing"} + }, + LockMode::LOCK_WHILE_RUNNING, + Target::starters + ) + , NUM_RESETS( + "Max Resets:
", + LockMode::UNLOCK_WHILE_RUNNING, + 100, 0 // default, min + ) + , RESET_TYPE( + "Reset Type:
\"Hard\" resets return to the home menu and close the game.
\"Soft\" resets don't require returning to the home menu.", + { + {ResetType::hard, "hard", "Hard Reset"}, + {ResetType::soft, "soft", "Soft Reset"} + }, + LockMode::UNLOCK_WHILE_RUNNING, + ResetType::hard + ) + , SEED_DELAY( + "Title Screen Delay Time (ms):
The delay between starting the game and advancing past the title screen.", + LockMode::UNLOCK_WHILE_RUNNING, + 35000, 34000 // default, min + ) + , LOAD_ADVANCES( + "Load Screen Advances:
The number of frames to advance before loading the game.
These pass at the \"normal\" rate compared to other consoles.", + LockMode::UNLOCK_WHILE_RUNNING, + 1000, 200 // default, min + ) + , DOUBLE_ADVANCES( + "In-Game Advances:
The number of frames to advance before finalizing the gift.
These pass at double the rate compared to other consoles, where every 2nd frame is skipped.", + LockMode::UNLOCK_WHILE_RUNNING, + 1000, 900 // default, min + ) + , TAKE_PICTURES("Take Pictures of Stats:
Take pictures of the first two pages of the summary screen.
Only applies to gifts.", LockMode::UNLOCK_WHILE_RUNNING, true) + , 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(TARGET); + PA_ADD_OPTION(NUM_RESETS); + PA_ADD_OPTION(RESET_TYPE); + PA_ADD_OPTION(SEED_DELAY); + PA_ADD_OPTION(LOAD_ADVANCES); + PA_ADD_OPTION(DOUBLE_ADVANCES); + PA_ADD_OPTION(TAKE_PICTURES); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +namespace{ + +// reset_to_starter(SingleSwitchProgramEnvironment& env, ProControllerContext& context, EnumDropdownOption& RESET_TYPE, SimpleIntegerOption& SEED_DELAY, SimpleIntegerOption& LOAD_ADVANCES, SimpleIntegerOption& DOUBLE_ADVANCES, BooleanCheckBoxOption& TAKE_PICTURES){ +// double FRAMERATE = 59.7275; // valid for GBA, but not sure for Switch +// uint64_t LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); +// uint64_t DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); +// env.log("Load screen delay: " + std::to_string(LOAD_DELAY)); +// env.log("In-game delay: " + std::to_string(DOUBLE_DELAY)); +// if (RESET_TYPE == ResetType::hard){ +// // close the game +// pbf_press_button(context, BUTTON_HOME, 200ms, 1300ms); +// pbf_press_button(context, BUTTON_Y, 200ms, 1300ms); +// pbf_press_button(context, BUTTON_A, 200ms, 2800ms); +// // press A to select game +// pbf_press_button(context, BUTTON_A, 200ms, 2300ms); +// // press A to select profile and immediately go back to the home screen +// pbf_press_button(context, BUTTON_A, 100ms, 100ms); +// pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); +// pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(SEED_DELAY - 200)); +// }else{ +// // perform soft reset and +// pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_X | BUTTON_Y, 200ms, std::chrono::milliseconds(SEED_DELAY - 200)); +// } +// // Advance to the load game screen and wait +// pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(LOAD_DELAY - 200)); +// // Load the game (sets the Initial Seed) +// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); +// // Skip through the recap +// pbf_press_button(context, BUTTON_B, 200ms, 2300ms); +// // Advance through starter dialogue and wait on "really quite energetic!" +// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); +// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); +// pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 7200)); +// // Finish dialogue (hits the target advance) +// pbf_press_button(context, BUTTON_A, 200ms, 5800ms); +// // Decline nickname +// pbf_press_button(context, BUTTON_B, 200ms, 2300ms); +// // Advance through rival choiec +// pbf_press_button(context, BUTTON_B, 200ms, 4800ms); +// // Navigate to summary +// pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); +// pbf_press_button(context, BUTTON_A, 200ms, 1000ms); +// pbf_press_button(context, BUTTON_A, 200ms, 1000ms); +// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); +// if (TAKE_PICTURES){ +// // Capture both summary screens +// pbf_wait(context, 1000ms); +// pbf_press_button(context, BUTTON_CAPTURE, 200ms, 1900ms); +// pbf_move_left_joystick(context, {+1, 0}, 200ms, 1300ms); +// pbf_press_button(context, BUTTON_CAPTURE, 200ms, 1800ms); +// }else{ +// // view both summary screens +// pbf_wait(context, 2000ms); +// pbf_move_left_joystick(context, {+1, 0}, 200ms, 1300ms); +// pbf_wait(context, 2500ms); +// } + +// context.wait_for_all_requests(); +// env.log("Encounter reached."); +// } + +void hard_reset(ProControllerContext& context){ + // close the game + pbf_press_button(context, BUTTON_HOME, 200ms, 1300ms); + pbf_press_button(context, BUTTON_Y, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + // press A to select game + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); + // press A to select profile and immediately go back to the home screen + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + pbf_press_button(context, BUTTON_A, 200ms, 0ms); // std::chrono::milliseconds(SEED_DELAY - 200)); +} + +void soft_reset(ProControllerContext& context){ + pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_X | BUTTON_Y, 200ms, 0ms); // std::chrono::milliseconds(SEED_DELAY - 200)); +} + +void set_seed_after_delay(ProControllerContext& context, SimpleIntegerOption& SEED_DELAY){ + // wait on title screen for the specified delay + pbf_wait(context, std::chrono::milliseconds(SEED_DELAY - 200)); // subtract 200ms (duration of button push during reset) + pbf_press_button(context, BUTTON_A, 200ms, 0ms); +} + +void load_game_after_delay(ProControllerContext& context, uint64_t& LOAD_DELAY){ + pbf_wait(context, std::chrono::milliseconds(LOAD_DELAY - 200)); // subtract 200ms (duration of button push during reset) + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + // skip recap + pbf_press_button(context, BUTTON_B, 200ms, 2300ms); + // need to later subtract 4000ms from delay to hit desired number of advances +} + +void collect_starter_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ + // Advance through starter dialogue and wait on "really quite energetic!" + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 7200)); // 4000ms + 3000ms + 200ms + // Finish dialogue (hits the target advance) + pbf_press_button(context, BUTTON_A, 200ms, 5800ms); + // Decline nickname + pbf_press_button(context, BUTTON_B, 200ms, 2300ms); + // Advance through rival choiec + pbf_press_button(context, BUTTON_B, 200ms, 4800ms); + // Navigate to summary + pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); +} + +void go_to_starter_summary(ProControllerContext& context){ + // Navigate to summary (1st party slot) + pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); +} + +void go_to_summary(ProControllerContext& context){ + // Navigate to summary (last party slot) +} + +void take_summary_pictures(ProControllerContext& context){ + // Capture both summary screens + pbf_wait(context, 2000ms); + pbf_press_button(context, BUTTON_CAPTURE, 200ms, 2300ms); + pbf_move_left_joystick(context, {+1, 0}, 200ms, 1300ms); + pbf_press_button(context, BUTTON_CAPTURE, 200ms, 2300ms); +} + +void grass_walk_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ + pbf_wait(context, std::chrono::milliseconds(DOUBLE_DELAY - 4000)); // 4000ms from the load menu + // "walk" without moving by tapping the joystick to change directions + // this is enough to trigger encounters + pbf_move_left_joystick(context, {+1, 0}, 33ms, 66ms); + pbf_move_left_joystick(context, {0, +1}, 33ms, 66ms); + pbf_move_left_joystick(context, {-1, 0}, 33ms, 66ms); + pbf_move_left_joystick(context, {0, -1}, 33ms, 66ms); + pbf_move_left_joystick(context, {+1, 0}, 33ms, 66ms); + pbf_move_left_joystick(context, {0, +1}, 33ms, 66ms); + pbf_move_left_joystick(context, {-1, 0}, 33ms, 66ms); + pbf_move_left_joystick(context, {0, -1}, 33ms, 66ms); + pbf_move_left_joystick(context, {+1, 0}, 33ms, 66ms); + pbf_move_left_joystick(context, {0, +1}, 33ms, 66ms); + pbf_move_left_joystick(context, {-1, 0}, 33ms, 66ms); + pbf_move_left_joystick(context, {0, -1}, 33ms, 66ms); + // wait for encounter to load + pbf_wait(context, 4000ms); +} + +} // namespace + + +void StarterRNG::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + /* + * Settings: Text Speed fast + */ + + StarterRNG_Descriptor::Stats& stats = env.current_stats(); + + bool shiny_found = false; + uint64_t num_resets = 0; + + VideoSnapshot screen; + + double FRAMERATE = 59.7275; // valid for GBA, but not sure for Switch + uint64_t LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); + uint64_t DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); + env.log("Load screen delay: " + std::to_string(LOAD_DELAY)); + env.log("In-game delay: " + std::to_string(DOUBLE_DELAY)); + + while (!shiny_found){ + if (RESET_TYPE == ResetType::hard){ + hard_reset(context); + }else if (RESET_TYPE == ResetType::soft){ + soft_reset(context); + }else{ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Invalid reset type", + env.console + ); + } + + set_seed_after_delay(context, SEED_DELAY); + load_game_after_delay(context, LOAD_DELAY); + + if (TARGET == Target::starters){ + collect_starter_after_delay(context, DOUBLE_DELAY); + go_to_starter_summary(context); + if (TAKE_PICTURES){ + take_summary_pictures(context); + } + context.wait_for_all_requests(); + env.log("Starter collected."); + + screen = env.console.video().snapshot(); + + ShinySymbolDetector shiny_checker(COLOR_YELLOW); + shiny_found = shiny_checker.read(env.console.logger(), screen); + }else if (TARGET == Target::hitmon){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Hitmonchan/Hitmonlee hunt not implemented", + env.console + ); + }else if (TARGET == Target::eevee){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Eevee hunt not implemented", + env.console + ); + }else if (TARGET == Target::lapras){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Lapras hunt not implemented", + env.console + ); + }else if (TARGET == Target::fossils){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Fossil hunt not implemented", + env.console + ); + }else if (TARGET == Target::sweetscent){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Sweet Scent hunt not implemented", + env.console + ); + }else if (TARGET == Target::wildwalk){ + grass_walk_after_delay(context, DOUBLE_DELAY); + context.wait_for_all_requests(); + }else if (TARGET == Target::fishing){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Fishing hunt not implemented", + env.console + ); + }else{ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Invalid RNG option", + env.console + ); + } + + num_resets++; + + if (shiny_found){ + env.log("Shiny found!"); + stats.shinies++; + send_program_notification( + env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + screen, + true + ); + break; + }else if (num_resets >= NUM_RESETS){ + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Maximum resets reached." + ); + break; + }else{ + env.log("Pokemon is not shiny."); + env.log("Resetting."); + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Resetting." + ); + 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_StarterRNG.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h new file mode 100644 index 0000000000..139dba4715 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h @@ -0,0 +1,77 @@ +/* Gift Reset + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_StarterRNG_H +#define PokemonAutomation_PokemonFRLG_StarterRNG_H + +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class StarterRNG_Descriptor : public SingleSwitchProgramDescriptor{ +public: + StarterRNG_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class StarterRNG : public SingleSwitchProgramInstance{ +public: + StarterRNG(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + enum class ResetType{ + hard, + soft, + }; + enum class Target{ + starters, + hitmon, + eevee, + lapras, + fossils, + sweetscent, + wildwalk, + fishing, + }; + + EnumDropdownOption TARGET; + + SimpleIntegerOption NUM_RESETS; + + EnumDropdownOption RESET_TYPE; + + SimpleIntegerOption SEED_DELAY; + SimpleIntegerOption LOAD_ADVANCES; + SimpleIntegerOption DOUBLE_ADVANCES; + + BooleanCheckBoxOption TAKE_PICTURES; + + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} +#endif + + + diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index e084f5b64e..1f6ff57557 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1422,6 +1422,8 @@ 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_StarterRNG.cpp + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp From a0fd6b27d8ce542f7d140060790655cd78d743cc Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sat, 7 Mar 2026 20:02:27 -0600 Subject: [PATCH 2/9] reorganize and rename stuff --- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 4 +- ...RNG.cpp => PokemonFRLG_RNGManipulator.cpp} | 126 +++++++++++------- ...rterRNG.h => PokemonFRLG_RNGManipulator.h} | 13 +- 3 files changed, 88 insertions(+), 55 deletions(-) rename SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/{PokemonFRLG_StarterRNG.cpp => PokemonFRLG_RNGManipulator.cpp} (78%) rename SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/{PokemonFRLG_StarterRNG.h => PokemonFRLG_RNGManipulator.h} (80%) diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 175c1b8745..9c5ea3b599 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -11,7 +11,7 @@ #include "PokemonFRLG_Settings.h" #include "Programs/ShinyHunting/PokemonFRLG_GiftReset.h" -#include "Programs/ShinyHunting/PokemonFRLG_StarterRNG.h" +#include "Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h" #include "Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h" #include "Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h" #include "Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.h" @@ -41,7 +41,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); if (PreloadSettings::instance().DEVELOPER_MODE){ - ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp similarity index 78% rename from SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp rename to SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index e016d62646..fabcdfecb5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -1,4 +1,4 @@ -/* Starter RNG +/* RNG Manipulator * * From: https://github.com/PokemonAutomation/ * @@ -18,17 +18,17 @@ #include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" -#include "PokemonFRLG_StarterRNG.h" +#include "PokemonFRLG_RNGManipulator.h" namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -StarterRNG_Descriptor::StarterRNG_Descriptor() +RNGManipulator_Descriptor::RNGManipulator_Descriptor() : SingleSwitchProgramDescriptor( - "PokemonFRLG:StarterRNG", - Pokemon::STRING_POKEMON + " FRLG", "Starter RNG", - "Programs/PokemonFRLG/StarterRNG.html", + "PokemonFRLG:RNGManipulator", + Pokemon::STRING_POKEMON + " FRLG", "RNG Manipulator", + "Programs/PokemonFRLG/RNGManipulator.html", "Soft reset with specific timings for hitting a target Seed and Frame for RNG manipulation.", ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, @@ -36,7 +36,7 @@ StarterRNG_Descriptor::StarterRNG_Descriptor() ) {} -struct StarterRNG_Descriptor::Stats : public StatsTracker{ +struct RNGManipulator_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Resets"]) , shinies(m_stats["Shinies"]) @@ -50,16 +50,17 @@ struct StarterRNG_Descriptor::Stats : public StatsTracker{ std::atomic& shinies; std::atomic& errors; }; -std::unique_ptr StarterRNG_Descriptor::make_stats() const{ +std::unique_ptr RNGManipulator_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); } -StarterRNG::StarterRNG() +RNGManipulator::RNGManipulator() : TARGET( "Target:
", { {Target::starters, "starters", "Bulbasaur / Squirtle / Charmander"}, - {Target::hitmon, "hitmon", "Magikarp, Hitmonlee / Hitmonchan"}, + {Target::magikarp, "magikarp", "Magikarp"}, + {Target::hitmon, "hitmon", "Hitmonlee / Hitmonchan"}, {Target::eevee, "eevee", "Eevee"}, {Target::lapras, "lapras", "Lapras"}, {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, @@ -76,7 +77,7 @@ StarterRNG::StarterRNG() 100, 0 // default, min ) , RESET_TYPE( - "Reset Type:
\"Hard\" resets return to the home menu and close the game.
\"Soft\" resets don't require returning to the home menu.", + "Reset Type:
\"Hard\" resets return to the home menu and close the game.
\"Soft\" resets don't require returning to the home menu, but may be less consistent.", { {ResetType::hard, "hard", "Hard Reset"}, {ResetType::soft, "soft", "Soft Reset"} @@ -87,7 +88,7 @@ StarterRNG::StarterRNG() , SEED_DELAY( "Title Screen Delay Time (ms):
The delay between starting the game and advancing past the title screen.", LockMode::UNLOCK_WHILE_RUNNING, - 35000, 34000 // default, min + 35000, 30000 // default, min ) , LOAD_ADVANCES( "Load Screen Advances:
The number of frames to advance before loading the game.
These pass at the \"normal\" rate compared to other consoles.", @@ -195,21 +196,21 @@ void hard_reset(ProControllerContext& context){ // press A to select profile and immediately go back to the home screen pbf_press_button(context, BUTTON_A, 100ms, 100ms); pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); - pbf_press_button(context, BUTTON_A, 200ms, 0ms); // std::chrono::milliseconds(SEED_DELAY - 200)); + pbf_press_button(context, BUTTON_A, 200ms, 0ms); } void soft_reset(ProControllerContext& context){ - pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_X | BUTTON_Y, 200ms, 0ms); // std::chrono::milliseconds(SEED_DELAY - 200)); + pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_X | BUTTON_Y, 200ms, 0ms); } void set_seed_after_delay(ProControllerContext& context, SimpleIntegerOption& SEED_DELAY){ // wait on title screen for the specified delay - pbf_wait(context, std::chrono::milliseconds(SEED_DELAY - 200)); // subtract 200ms (duration of button push during reset) + pbf_wait(context, std::chrono::milliseconds(SEED_DELAY - 200)); pbf_press_button(context, BUTTON_A, 200ms, 0ms); } void load_game_after_delay(ProControllerContext& context, uint64_t& LOAD_DELAY){ - pbf_wait(context, std::chrono::milliseconds(LOAD_DELAY - 200)); // subtract 200ms (duration of button push during reset) + pbf_wait(context, std::chrono::milliseconds(LOAD_DELAY - 200)); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); // skip recap pbf_press_button(context, BUTTON_B, 200ms, 2300ms); @@ -225,13 +226,19 @@ void collect_starter_after_delay(ProControllerContext& context, uint64_t& DOUBLE pbf_press_button(context, BUTTON_A, 200ms, 5800ms); // Decline nickname pbf_press_button(context, BUTTON_B, 200ms, 2300ms); - // Advance through rival choiec + // Advance through rival choice pbf_press_button(context, BUTTON_B, 200ms, 4800ms); - // Navigate to summary - pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); +} + +void collect_magikarp_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ + // Advance through starter dialogue and wait on YES/NO pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 7200)); // 4000ms + 3000ms + 200ms + // Finish dialogue (hits the target advance) + pbf_press_button(context, BUTTON_A, 200ms, 3800ms); + // Decline nickname + pbf_press_button(context, BUTTON_B, 200ms, 1300ms); } void go_to_starter_summary(ProControllerContext& context){ @@ -239,11 +246,18 @@ void go_to_starter_summary(ProControllerContext& context){ pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); pbf_press_button(context, BUTTON_A, 200ms, 1000ms); pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); } void go_to_summary(ProControllerContext& context){ // Navigate to summary (last party slot) + pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); + pbf_move_left_joystick(context, {0, -1}, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); } void take_summary_pictures(ProControllerContext& context){ @@ -254,48 +268,53 @@ void take_summary_pictures(ProControllerContext& context){ pbf_press_button(context, BUTTON_CAPTURE, 200ms, 2300ms); } -void grass_walk_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ +bool grass_walk_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ pbf_wait(context, std::chrono::milliseconds(DOUBLE_DELAY - 4000)); // 4000ms from the load menu + context.wait_for_all_requests(); // "walk" without moving by tapping the joystick to change directions - // this is enough to trigger encounters - pbf_move_left_joystick(context, {+1, 0}, 33ms, 66ms); - pbf_move_left_joystick(context, {0, +1}, 33ms, 66ms); - pbf_move_left_joystick(context, {-1, 0}, 33ms, 66ms); - pbf_move_left_joystick(context, {0, -1}, 33ms, 66ms); - pbf_move_left_joystick(context, {+1, 0}, 33ms, 66ms); - pbf_move_left_joystick(context, {0, +1}, 33ms, 66ms); - pbf_move_left_joystick(context, {-1, 0}, 33ms, 66ms); - pbf_move_left_joystick(context, {0, -1}, 33ms, 66ms); - pbf_move_left_joystick(context, {+1, 0}, 33ms, 66ms); - pbf_move_left_joystick(context, {0, +1}, 33ms, 66ms); - pbf_move_left_joystick(context, {-1, 0}, 33ms, 66ms); - pbf_move_left_joystick(context, {0, -1}, 33ms, 66ms); - // wait for encounter to load - pbf_wait(context, 4000ms); + BlackScreenWatcher battle_entered(COLOR_RED); + run_until( + env.console, context, + [](ProControllerContext& context) { + while (true){ + // "walk" without moving by tapping the joystick to change directions + // this is enough to trigger encounters + pbf_move_left_joystick(context, {+1, 0}, 33ms, 150ms); + pbf_move_left_joystick(context, {0, +1}, 33ms, 150ms); + pbf_move_left_joystick(context, {-1, 0}, 33ms, 150ms); + pbf_move_left_joystick(context, {0, -1}, 33ms, 150ms); + } + }, + { battle_entered } + ); + bool encounter_shiny = handle_encounter(env.console, context, false); + return encounter_shiny; } } // namespace -void StarterRNG::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast */ - StarterRNG_Descriptor::Stats& stats = env.current_stats(); + RNGManipulator_Descriptor::Stats& stats = env.current_stats(); bool shiny_found = false; uint64_t num_resets = 0; - VideoSnapshot screen; - double FRAMERATE = 59.7275; // valid for GBA, but not sure for Switch - uint64_t LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); - uint64_t DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); - env.log("Load screen delay: " + std::to_string(LOAD_DELAY)); - env.log("In-game delay: " + std::to_string(DOUBLE_DELAY)); + uint64_t LOAD_DELAY; + uint64_t DOUBLE_DELAY; + + VideoSnapshot screen; while (!shiny_found){ + LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); + DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); + env.log("Load screen delay: " + std::to_string(LOAD_DELAY)); + env.log("In-game delay: " + std::to_string(DOUBLE_DELAY)); if (RESET_TYPE == ResetType::hard){ hard_reset(context); }else if (RESET_TYPE == ResetType::soft){ @@ -322,6 +341,19 @@ void StarterRNG::program(SingleSwitchProgramEnvironment& env, ProControllerConte screen = env.console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); + shiny_found = shiny_checker.read(env.console.logger(), screen); + }else if (TARGET == Target::magikarp){ + collect_magikarp_after_delay(context, DOUBLE_DELAY); + go_to_summary(context); + if (TAKE_PICTURES){ + take_summary_pictures(context); + } + context.wait_for_all_requests(); + env.log("Magikarp collected."); + + screen = env.console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); }else if (TARGET == Target::hitmon){ @@ -355,7 +387,7 @@ void StarterRNG::program(SingleSwitchProgramEnvironment& env, ProControllerConte env.console ); }else if (TARGET == Target::wildwalk){ - grass_walk_after_delay(context, DOUBLE_DELAY); + shiny_found = grass_walk_after_delay(env, context, DOUBLE_DELAY); context.wait_for_all_requests(); }else if (TARGET == Target::fishing){ OperationFailedException::fire( diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h similarity index 80% rename from SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h rename to SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h index 139dba4715..64accd4b10 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h @@ -4,8 +4,8 @@ * */ -#ifndef PokemonAutomation_PokemonFRLG_StarterRNG_H -#define PokemonAutomation_PokemonFRLG_StarterRNG_H +#ifndef PokemonAutomation_PokemonFRLG_RNGManipulator_H +#define PokemonAutomation_PokemonFRLG_RNGManipulator_H #include "Common/Cpp/Options/SimpleIntegerOption.h" #include "Common/Cpp/Options/BooleanCheckBoxOption.h" @@ -17,16 +17,16 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -class StarterRNG_Descriptor : public SingleSwitchProgramDescriptor{ +class RNGManipulator_Descriptor : public SingleSwitchProgramDescriptor{ public: - StarterRNG_Descriptor(); + RNGManipulator_Descriptor(); struct Stats; virtual std::unique_ptr make_stats() const override; }; -class StarterRNG : public SingleSwitchProgramInstance{ +class RNGManipulator : public SingleSwitchProgramInstance{ public: - StarterRNG(); + RNGManipulator(); virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; virtual void start_program_border_check( VideoStream& stream, @@ -40,6 +40,7 @@ class StarterRNG : public SingleSwitchProgramInstance{ }; enum class Target{ starters, + magikarp, hitmon, eevee, lapras, From c70a96f3b86b8d39d6885ddb3935dab6b163f048 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sat, 7 Mar 2026 21:35:26 -0600 Subject: [PATCH 3/9] fix cmake --- SerialPrograms/cmake/SourceFiles.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 841241ec56..2c57526a41 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1424,8 +1424,8 @@ 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_StarterRNG.cpp - Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_StarterRNG.h + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp From 3257be6e45087355bca7755f2f5df845b0ea1a48 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 9 Mar 2026 16:08:07 -0500 Subject: [PATCH 4/9] add sweet scent --- .../PokemonFRLG_RNGManipulator.cpp | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index fabcdfecb5..629d424500 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -256,10 +256,38 @@ void go_to_summary(ProControllerContext& context){ pbf_press_button(context, BUTTON_A, 200ms, 1000ms); pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); + // open summary pbf_press_button(context, BUTTON_A, 200ms, 1000ms); pbf_press_button(context, BUTTON_A, 200ms, 2300ms); } +bool use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ + // Navigate to last party slot + pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); + pbf_move_left_joystick(context, {0, -1}, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + // hover over Sweet Scent (2nd option, maybe HMs change this) + pbf_move_left_joystick(context, {0, -1}, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 8400)); + // 8000ms + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + context.wait_for_all_requests(); + BlackScreenWatcher battle_entered(COLOR_RED); + run_until( + env.console, context, + [](ProControllerContext& context) { + while (true){ + pbf_wait(context, 100ms); + } + }, + { battle_entered } + ); + bool encounter_shiny = handle_encounter(env.console, context, false); + return encounter_shiny; +} + void take_summary_pictures(ProControllerContext& context){ // Capture both summary screens pbf_wait(context, 2000ms); @@ -302,7 +330,6 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC RNGManipulator_Descriptor::Stats& stats = env.current_stats(); bool shiny_found = false; - uint64_t num_resets = 0; double FRAMERATE = 59.7275; // valid for GBA, but not sure for Switch uint64_t LOAD_DELAY; @@ -381,11 +408,8 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC env.console ); }else if (TARGET == Target::sweetscent){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Sweet Scent hunt not implemented", - env.console - ); + shiny_found = use_sweet_scent(env, context, DOUBLE_DELAY); + context.wait_for_all_requests(); }else if (TARGET == Target::wildwalk){ shiny_found = grass_walk_after_delay(env, context, DOUBLE_DELAY); context.wait_for_all_requests(); @@ -403,8 +427,7 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC ); } - num_resets++; - + stats.resets++; if (shiny_found){ env.log("Shiny found!"); stats.shinies++; @@ -418,7 +441,7 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC true ); break; - }else if (num_resets >= NUM_RESETS){ + }else if (stats.resets >= NUM_RESETS){ send_program_status_notification( env, NOTIFICATION_STATUS_UPDATE, "Maximum resets reached." @@ -431,7 +454,6 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC env, NOTIFICATION_STATUS_UPDATE, "Resetting." ); - stats.resets++; env.update_stats(); context.wait_for_all_requests(); } From a93d1bbad4ac94df438a41648eedb7fd03db85b1 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 9 Mar 2026 17:17:19 -0500 Subject: [PATCH 5/9] tidy up --- .../PokemonFRLG_RNGManipulator.cpp | 131 ++++-------------- .../ShinyHunting/PokemonFRLG_RNGManipulator.h | 16 ++- 2 files changed, 38 insertions(+), 109 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index 629d424500..832bd8af34 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -60,13 +60,15 @@ RNGManipulator::RNGManipulator() { {Target::starters, "starters", "Bulbasaur / Squirtle / Charmander"}, {Target::magikarp, "magikarp", "Magikarp"}, - {Target::hitmon, "hitmon", "Hitmonlee / Hitmonchan"}, - {Target::eevee, "eevee", "Eevee"}, - {Target::lapras, "lapras", "Lapras"}, - {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, + // {Target::hitmon, "hitmon", "Hitmonlee / Hitmonchan"}, + // {Target::eevee, "eevee", "Eevee"}, + // {Target::lapras, "lapras", "Lapras"}, + // {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, {Target::sweetscent, "sweetscent", "Sweet Scent for wild encounters"}, - {Target::wildwalk, "wildwalk", "Hit seed and advance when walking in grass"}, - {Target::fishing, "fishing", "Hit seed and advance when fishing"} + {Target::grasswalk, "grasswalk", "Walk to trigger wild encounters (inaccurate)."}, + // {Target::fishing, "fishing", "Fishing"}, + // {Target::static, "static", "Static overworld encounters (including Legendaries)"}, + // {Target::roaming, "roaming", "Roaming Legendaries"} }, LockMode::LOCK_WHILE_RUNNING, Target::starters @@ -100,7 +102,16 @@ RNGManipulator::RNGManipulator() LockMode::UNLOCK_WHILE_RUNNING, 1000, 900 // default, min ) - , TAKE_PICTURES("Take Pictures of Stats:
Take pictures of the first two pages of the summary screen.
Only applies to gifts.", LockMode::UNLOCK_WHILE_RUNNING, true) + , TAKE_PICTURES( + "Take Pictures of Stats:
Take pictures of the first two pages of the summary screen.
Only applies to gifts. Useful for calibrating your seed and advances.", + LockMode::UNLOCK_WHILE_RUNNING, + false // default + ) + , TAKE_VIDEO( + "Take Video:
Record a video when the shiny is found.", + LockMode::UNLOCK_WHILE_RUNNING, + true // default + ) , GO_HOME_WHEN_DONE(true) , NOTIFICATION_SHINY( "Shiny found", @@ -121,71 +132,13 @@ RNGManipulator::RNGManipulator() PA_ADD_OPTION(LOAD_ADVANCES); PA_ADD_OPTION(DOUBLE_ADVANCES); PA_ADD_OPTION(TAKE_PICTURES); + PA_ADD_OPTION(TAKE_VIDEO); PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(NOTIFICATIONS); } namespace{ -// reset_to_starter(SingleSwitchProgramEnvironment& env, ProControllerContext& context, EnumDropdownOption& RESET_TYPE, SimpleIntegerOption& SEED_DELAY, SimpleIntegerOption& LOAD_ADVANCES, SimpleIntegerOption& DOUBLE_ADVANCES, BooleanCheckBoxOption& TAKE_PICTURES){ -// double FRAMERATE = 59.7275; // valid for GBA, but not sure for Switch -// uint64_t LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); -// uint64_t DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); -// env.log("Load screen delay: " + std::to_string(LOAD_DELAY)); -// env.log("In-game delay: " + std::to_string(DOUBLE_DELAY)); -// if (RESET_TYPE == ResetType::hard){ -// // close the game -// pbf_press_button(context, BUTTON_HOME, 200ms, 1300ms); -// pbf_press_button(context, BUTTON_Y, 200ms, 1300ms); -// pbf_press_button(context, BUTTON_A, 200ms, 2800ms); -// // press A to select game -// pbf_press_button(context, BUTTON_A, 200ms, 2300ms); -// // press A to select profile and immediately go back to the home screen -// pbf_press_button(context, BUTTON_A, 100ms, 100ms); -// pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); -// pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(SEED_DELAY - 200)); -// }else{ -// // perform soft reset and -// pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_X | BUTTON_Y, 200ms, std::chrono::milliseconds(SEED_DELAY - 200)); -// } -// // Advance to the load game screen and wait -// pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(LOAD_DELAY - 200)); -// // Load the game (sets the Initial Seed) -// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); -// // Skip through the recap -// pbf_press_button(context, BUTTON_B, 200ms, 2300ms); -// // Advance through starter dialogue and wait on "really quite energetic!" -// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); -// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); -// pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 7200)); -// // Finish dialogue (hits the target advance) -// pbf_press_button(context, BUTTON_A, 200ms, 5800ms); -// // Decline nickname -// pbf_press_button(context, BUTTON_B, 200ms, 2300ms); -// // Advance through rival choiec -// pbf_press_button(context, BUTTON_B, 200ms, 4800ms); -// // Navigate to summary -// pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); -// pbf_press_button(context, BUTTON_A, 200ms, 1000ms); -// pbf_press_button(context, BUTTON_A, 200ms, 1000ms); -// pbf_press_button(context, BUTTON_A, 200ms, 1300ms); -// if (TAKE_PICTURES){ -// // Capture both summary screens -// pbf_wait(context, 1000ms); -// pbf_press_button(context, BUTTON_CAPTURE, 200ms, 1900ms); -// pbf_move_left_joystick(context, {+1, 0}, 200ms, 1300ms); -// pbf_press_button(context, BUTTON_CAPTURE, 200ms, 1800ms); -// }else{ -// // view both summary screens -// pbf_wait(context, 2000ms); -// pbf_move_left_joystick(context, {+1, 0}, 200ms, 1300ms); -// pbf_wait(context, 2500ms); -// } - -// context.wait_for_all_requests(); -// env.log("Encounter reached."); -// } - void hard_reset(ProControllerContext& context){ // close the game pbf_press_button(context, BUTTON_HOME, 200ms, 1300ms); @@ -250,7 +203,7 @@ void go_to_starter_summary(ProControllerContext& context){ } void go_to_summary(ProControllerContext& context){ - // Navigate to summary (last party slot) + // navigate to summary (last party slot) pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); pbf_move_left_joystick(context, {0, -1}, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1000ms); @@ -262,16 +215,15 @@ void go_to_summary(ProControllerContext& context){ } bool use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ - // Navigate to last party slot + // navigate to last party slot pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); pbf_move_left_joystick(context, {0, -1}, 200ms, 1800ms); pbf_press_button(context, BUTTON_A, 200ms, 1000ms); pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 800ms); - // hover over Sweet Scent (2nd option, maybe HMs change this) + // hover over Sweet Scent (2nd option, but maybe HMs could change this) pbf_move_left_joystick(context, {0, -1}, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 8400)); - // 8000ms pbf_press_button(context, BUTTON_A, 200ms, 800ms); context.wait_for_all_requests(); BlackScreenWatcher battle_entered(COLOR_RED); @@ -383,46 +335,18 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); - }else if (TARGET == Target::hitmon){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Hitmonchan/Hitmonlee hunt not implemented", - env.console - ); - }else if (TARGET == Target::eevee){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Eevee hunt not implemented", - env.console - ); - }else if (TARGET == Target::lapras){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Lapras hunt not implemented", - env.console - ); - }else if (TARGET == Target::fossils){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Fossil hunt not implemented", - env.console - ); }else if (TARGET == Target::sweetscent){ shiny_found = use_sweet_scent(env, context, DOUBLE_DELAY); context.wait_for_all_requests(); - }else if (TARGET == Target::wildwalk){ + env.log("Wild encounter started."); + }else if (TARGET == Target::grasswalk){ shiny_found = grass_walk_after_delay(env, context, DOUBLE_DELAY); context.wait_for_all_requests(); - }else if (TARGET == Target::fishing){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Fishing hunt not implemented", - env.console - ); + env.log("Wild encounter started."); }else{ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "Invalid RNG option", + "Option not yet implemented.", env.console ); } @@ -440,6 +364,9 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC screen, true ); + if (TAKE_VIDEO){ + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } break; }else if (stats.resets >= NUM_RESETS){ send_program_status_notification( diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h index 64accd4b10..aaa703a583 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h @@ -41,13 +41,15 @@ class RNGManipulator : public SingleSwitchProgramInstance{ enum class Target{ starters, magikarp, - hitmon, - eevee, - lapras, - fossils, + // hitmon, + // eevee, + // lapras, + // fossils, sweetscent, - wildwalk, - fishing, + grasswalk, + // fishing, + // static, + // roaming }; EnumDropdownOption TARGET; @@ -62,8 +64,8 @@ class RNGManipulator : public SingleSwitchProgramInstance{ BooleanCheckBoxOption TAKE_PICTURES; + BooleanCheckBoxOption TAKE_VIDEO; GoHomeWhenDoneOption GO_HOME_WHEN_DONE; - EventNotificationOption NOTIFICATION_SHINY; EventNotificationOption NOTIFICATION_STATUS_UPDATE; EventNotificationsOption NOTIFICATIONS; From d58f4b7d7df5bd7fa77fefa4b288f6ff71fce200 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Tue, 10 Mar 2026 13:27:43 -0500 Subject: [PATCH 6/9] added black screen detection during reset --- .../PokemonFRLG_RNGManipulator.cpp | 89 ++++++++++++++++--- .../ShinyHunting/PokemonFRLG_RNGManipulator.h | 2 +- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index 832bd8af34..788824f039 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -4,7 +4,7 @@ * */ -#include "CommonFramework/Exceptions/OperationFailedException.h" + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" @@ -61,7 +61,7 @@ RNGManipulator::RNGManipulator() {Target::starters, "starters", "Bulbasaur / Squirtle / Charmander"}, {Target::magikarp, "magikarp", "Magikarp"}, // {Target::hitmon, "hitmon", "Hitmonlee / Hitmonchan"}, - // {Target::eevee, "eevee", "Eevee"}, + {Target::eevee, "eevee", "Eevee"}, // {Target::lapras, "lapras", "Lapras"}, // {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, {Target::sweetscent, "sweetscent", "Sweet Scent for wild encounters"}, @@ -147,8 +147,8 @@ void hard_reset(ProControllerContext& context){ // press A to select game pbf_press_button(context, BUTTON_A, 200ms, 2300ms); // press A to select profile and immediately go back to the home screen - pbf_press_button(context, BUTTON_A, 100ms, 100ms); - pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + // pbf_press_button(context, BUTTON_A, 100ms, 100ms); + // pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); pbf_press_button(context, BUTTON_A, 200ms, 0ms); } @@ -156,6 +156,39 @@ void soft_reset(ProControllerContext& context){ pbf_press_button(context, BUTTON_B | BUTTON_A | BUTTON_X | BUTTON_Y, 200ms, 0ms); } +uint64_t wait_for_copyright_text(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // wait for copyright text to appear + BlackScreenWatcher black_screen(COLOR_RED); + context.wait_for_all_requests(); + int black_ret = wait_until( + env.console, context, 2000ms, + {black_screen} + ); + if (black_ret != 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Black screen not detected after starting game.", + env.console + ); + } + BlackScreenOverWatcher copyright_detected(COLOR_RED); + context.wait_for_all_requests(); + WallClock start_time = current_time(); + int ret = wait_until( + env.console, context, 2000ms, + {copyright_detected} + ); + if (ret != 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Black screen detected for more than 2 seconds.", + env.console + ); + } + auto elapsed = current_time() - start_time; + return std::chrono::duration_cast(elapsed).count(); +} + void set_seed_after_delay(ProControllerContext& context, SimpleIntegerOption& SEED_DELAY){ // wait on title screen for the specified delay pbf_wait(context, std::chrono::milliseconds(SEED_DELAY - 200)); @@ -194,6 +227,16 @@ void collect_magikarp_after_delay(ProControllerContext& context, uint64_t& DOUBL pbf_press_button(context, BUTTON_B, 200ms, 1300ms); } + +void collect_eevee_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ + // No dialogue to advance through -- just wait + pbf_wait(context, std::chrono::milliseconds(DOUBLE_DELAY - 4000)); + // Interact with the pokeball + pbf_press_button(context, BUTTON_A, 200ms, 3800ms); + // Decline nickname + pbf_press_button(context, BUTTON_B, 200ms, 1300ms); +} + void go_to_starter_summary(ProControllerContext& context){ // Navigate to summary (1st party slot) pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); @@ -227,15 +270,17 @@ bool use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& pbf_press_button(context, BUTTON_A, 200ms, 800ms); context.wait_for_all_requests(); BlackScreenWatcher battle_entered(COLOR_RED); - run_until( - env.console, context, - [](ProControllerContext& context) { - while (true){ - pbf_wait(context, 100ms); - } - }, - { battle_entered } + int ret = wait_until( + env.console, context, 10000ms, + {battle_entered} ); + if (ret != 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Sweet Scent failed to initiate encounter.", + env.console + ); + } bool encounter_shiny = handle_encounter(env.console, context, false); return encounter_shiny; } @@ -292,8 +337,8 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC while (!shiny_found){ LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); - env.log("Load screen delay: " + std::to_string(LOAD_DELAY)); - env.log("In-game delay: " + std::to_string(DOUBLE_DELAY)); + env.log("Load screen delay: " + std::to_string(LOAD_DELAY) + "ms"); + env.log("In-game delay: " + std::to_string(DOUBLE_DELAY) + "ms"); if (RESET_TYPE == ResetType::hard){ hard_reset(context); }else if (RESET_TYPE == ResetType::soft){ @@ -306,6 +351,9 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC ); } + uint64_t STARTUP_DELAY = wait_for_copyright_text(env, context); + env.log("Sstartup delay: " + std::to_string(STARTUP_DELAY) + "ms"); + set_seed_after_delay(context, SEED_DELAY); load_game_after_delay(context, LOAD_DELAY); @@ -333,6 +381,19 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC screen = env.console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); + shiny_found = shiny_checker.read(env.console.logger(), screen); + }else if (TARGET == Target::eevee) { + collect_eevee_after_delay(context, DOUBLE_DELAY); + go_to_summary(context); + if (TAKE_PICTURES){ + take_summary_pictures(context); + } + context.wait_for_all_requests(); + env.log("Eevee collected."); + + screen = env.console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); }else if (TARGET == Target::sweetscent){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h index aaa703a583..b0a7fb9710 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h @@ -42,7 +42,7 @@ class RNGManipulator : public SingleSwitchProgramInstance{ starters, magikarp, // hitmon, - // eevee, + eevee, // lapras, // fossils, sweetscent, From b52a3a912e7959c7551184dfdec09d570d8ffbbf Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Tue, 10 Mar 2026 13:30:56 -0500 Subject: [PATCH 7/9] oops --- .../Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index 788824f039..2f2d1892e2 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -4,7 +4,7 @@ * */ - #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" @@ -146,9 +146,7 @@ void hard_reset(ProControllerContext& context){ pbf_press_button(context, BUTTON_A, 200ms, 2800ms); // press A to select game pbf_press_button(context, BUTTON_A, 200ms, 2300ms); - // press A to select profile and immediately go back to the home screen - // pbf_press_button(context, BUTTON_A, 100ms, 100ms); - // pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + // press A to select profile and launch the game pbf_press_button(context, BUTTON_A, 200ms, 0ms); } From 1bbc7c3984882b954b446f104cd7d2f027e7f61d Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Thu, 12 Mar 2026 08:23:20 -0500 Subject: [PATCH 8/9] add snorlax --- .../PokemonFRLG_RNGManipulator.cpp | 62 ++++++++++++------- .../ShinyHunting/PokemonFRLG_RNGManipulator.h | 1 + 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index 2f2d1892e2..538f9befd4 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -64,6 +64,7 @@ RNGManipulator::RNGManipulator() {Target::eevee, "eevee", "Eevee"}, // {Target::lapras, "lapras", "Lapras"}, // {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, + {Target::snorlax, "snorlax", "Snorlax"}, {Target::sweetscent, "sweetscent", "Sweet Scent for wild encounters"}, {Target::grasswalk, "grasswalk", "Walk to trigger wild encounters (inaccurate)."}, // {Target::fishing, "fishing", "Fishing"}, @@ -98,7 +99,7 @@ RNGManipulator::RNGManipulator() 1000, 200 // default, min ) , DOUBLE_ADVANCES( - "In-Game Advances:
The number of frames to advance before finalizing the gift.
These pass at double the rate compared to other consoles, where every 2nd frame is skipped.", + "In-Game Advances:
The number of frames to advance before triggering the gift/encounter.
These pass at double the rate compared to other consoles, where every 2nd frame is skipped.", LockMode::UNLOCK_WHILE_RUNNING, 1000, 900 // default, min ) @@ -159,7 +160,7 @@ uint64_t wait_for_copyright_text(SingleSwitchProgramEnvironment& env, ProControl BlackScreenWatcher black_screen(COLOR_RED); context.wait_for_all_requests(); int black_ret = wait_until( - env.console, context, 2000ms, + env.console, context, 5000ms, {black_screen} ); if (black_ret != 0){ @@ -173,13 +174,13 @@ uint64_t wait_for_copyright_text(SingleSwitchProgramEnvironment& env, ProControl context.wait_for_all_requests(); WallClock start_time = current_time(); int ret = wait_until( - env.console, context, 2000ms, + env.console, context, 5000ms, {copyright_detected} ); if (ret != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "Black screen detected for more than 2 seconds.", + "Black screen detected for more than 5 seconds.", env.console ); } @@ -201,6 +202,24 @@ void load_game_after_delay(ProControllerContext& context, uint64_t& LOAD_DELAY){ // need to later subtract 4000ms from delay to hit desired number of advances } +bool watch_for_shiny_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + BlackScreenWatcher battle_entered(COLOR_RED); + context.wait_for_all_requests(); + int ret = wait_until( + env.console, context, 10000ms, + {battle_entered} + ); + if (ret != 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to initiate encounter.", + env.console + ); + } + bool encounter_shiny = handle_encounter(env.console, context, false); + return encounter_shiny; +} + void collect_starter_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // Advance through starter dialogue and wait on "really quite energetic!" pbf_press_button(context, BUTTON_A, 200ms, 1300ms); @@ -225,7 +244,6 @@ void collect_magikarp_after_delay(ProControllerContext& context, uint64_t& DOUBL pbf_press_button(context, BUTTON_B, 200ms, 1300ms); } - void collect_eevee_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // No dialogue to advance through -- just wait pbf_wait(context, std::chrono::milliseconds(DOUBLE_DELAY - 4000)); @@ -235,6 +253,15 @@ void collect_eevee_after_delay(ProControllerContext& context, uint64_t& DOUBLE_D pbf_press_button(context, BUTTON_B, 200ms, 1300ms); } +void encounter_snorlax_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ + // Interact with Snorlax, YES to PokeFlute, wait on "woke up!" + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 9800ms); // PokeFlute tune + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 15700)); // 4000ms + 1500ms + 10000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, 200ms); + +} + void go_to_starter_summary(ProControllerContext& context){ // Navigate to summary (1st party slot) pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); @@ -255,7 +282,7 @@ void go_to_summary(ProControllerContext& context){ pbf_press_button(context, BUTTON_A, 200ms, 2300ms); } -bool use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ +void use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // navigate to last party slot pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); pbf_move_left_joystick(context, {0, -1}, 200ms, 1800ms); @@ -266,21 +293,6 @@ bool use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& // hover over Sweet Scent (2nd option, but maybe HMs could change this) pbf_move_left_joystick(context, {0, -1}, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 8400)); pbf_press_button(context, BUTTON_A, 200ms, 800ms); - context.wait_for_all_requests(); - BlackScreenWatcher battle_entered(COLOR_RED); - int ret = wait_until( - env.console, context, 10000ms, - {battle_entered} - ); - if (ret != 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Sweet Scent failed to initiate encounter.", - env.console - ); - } - bool encounter_shiny = handle_encounter(env.console, context, false); - return encounter_shiny; } void take_summary_pictures(ProControllerContext& context){ @@ -394,8 +406,14 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); + }else if (TARGET == Target::snorlax){ + encounter_snorlax_after_delay(env, context, DOUBLE_DELAY); + shiny_found = watch_for_shiny_encounter(env, context); + context.wait_for_all_requests(); + env.log("Wild encounter started."); }else if (TARGET == Target::sweetscent){ - shiny_found = use_sweet_scent(env, context, DOUBLE_DELAY); + use_sweet_scent(env, context, DOUBLE_DELAY); + shiny_found = watch_for_shiny_encounter(env, context); context.wait_for_all_requests(); env.log("Wild encounter started."); }else if (TARGET == Target::grasswalk){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h index b0a7fb9710..d16b1454d5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h @@ -45,6 +45,7 @@ class RNGManipulator : public SingleSwitchProgramInstance{ eevee, // lapras, // fossils, + snorlax, sweetscent, grasswalk, // fishing, From e91da51f3b1baedd1e516478f5a28b169da3e748 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Fri, 13 Mar 2026 12:56:17 -0500 Subject: [PATCH 9/9] cleanup, add offsets, fix framerate --- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 +- .../PokemonFRLG_RNGManipulator.cpp | 167 +++++++++--------- .../ShinyHunting/PokemonFRLG_RNGManipulator.h | 1 - 3 files changed, 81 insertions(+), 89 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index be29bbe61f..dee0644420 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -48,8 +48,8 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); if (PreloadSettings::instance().DEVELOPER_MODE){ - ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp index 538f9befd4..ce0a5cd4ca 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.cpp @@ -9,13 +9,14 @@ #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/ImageTools/ImageBoxes.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/Menus/PokemonFRLG_StartMenuDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" +#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" #include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_RNGManipulator.h" @@ -66,7 +67,6 @@ RNGManipulator::RNGManipulator() // {Target::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, {Target::snorlax, "snorlax", "Snorlax"}, {Target::sweetscent, "sweetscent", "Sweet Scent for wild encounters"}, - {Target::grasswalk, "grasswalk", "Walk to trigger wild encounters (inaccurate)."}, // {Target::fishing, "fishing", "Fishing"}, // {Target::static, "static", "Static overworld encounters (including Legendaries)"}, // {Target::roaming, "roaming", "Roaming Legendaries"} @@ -91,7 +91,7 @@ RNGManipulator::RNGManipulator() , SEED_DELAY( "Title Screen Delay Time (ms):
The delay between starting the game and advancing past the title screen.", LockMode::UNLOCK_WHILE_RUNNING, - 35000, 30000 // default, min + 32000, 30000 // default, min ) , LOAD_ADVANCES( "Load Screen Advances:
The number of frames to advance before loading the game.
These pass at the \"normal\" rate compared to other consoles.", @@ -101,7 +101,7 @@ RNGManipulator::RNGManipulator() , DOUBLE_ADVANCES( "In-Game Advances:
The number of frames to advance before triggering the gift/encounter.
These pass at double the rate compared to other consoles, where every 2nd frame is skipped.", LockMode::UNLOCK_WHILE_RUNNING, - 1000, 900 // default, min + 1000, 700 // default, min ) , TAKE_PICTURES( "Take Pictures of Stats:
Take pictures of the first two pages of the summary screen.
Only applies to gifts. Useful for calibrating your seed and advances.", @@ -157,30 +157,32 @@ void soft_reset(ProControllerContext& context){ uint64_t wait_for_copyright_text(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // wait for copyright text to appear - BlackScreenWatcher black_screen(COLOR_RED); + BlackScreenWatcher black_screen(COLOR_RED, ImageFloatBox {0.25, 0.25, 0.55, 0.55}); context.wait_for_all_requests(); int black_ret = wait_until( - env.console, context, 5000ms, - {black_screen} + env.console, context, 10000ms, + {black_screen}, + 1ms ); if (black_ret != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "Black screen not detected after starting game.", + "Black screen not detected within 10 seconds of starting game.", env.console ); } - BlackScreenOverWatcher copyright_detected(COLOR_RED); + BlackScreenOverWatcher copyright_detected(COLOR_RED, ImageFloatBox {0.25, 0.25, 0.55, 0.55}); context.wait_for_all_requests(); WallClock start_time = current_time(); int ret = wait_until( - env.console, context, 5000ms, - {copyright_detected} + env.console, context, 10000ms, + {copyright_detected }, + 1ms ); if (ret != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "Black screen detected for more than 5 seconds.", + "Black screen detected for more than 10 seconds after starting game.", env.console ); } @@ -188,23 +190,25 @@ uint64_t wait_for_copyright_text(SingleSwitchProgramEnvironment& env, ProControl return std::chrono::duration_cast(elapsed).count(); } -void set_seed_after_delay(ProControllerContext& context, SimpleIntegerOption& SEED_DELAY){ +void set_seed_after_delay(ProControllerContext& context, SimpleIntegerOption& SEED_DELAY, int64_t& SEED_OFFSET){ // wait on title screen for the specified delay - pbf_wait(context, std::chrono::milliseconds(SEED_DELAY - 200)); - pbf_press_button(context, BUTTON_A, 200ms, 0ms); + pbf_wait(context, std::chrono::milliseconds(SEED_DELAY + SEED_OFFSET)); + // hold A for a few seconds through the transition to the load screen + pbf_press_button(context, BUTTON_A, 3000ms, 0ms); } void load_game_after_delay(ProControllerContext& context, uint64_t& LOAD_DELAY){ - pbf_wait(context, std::chrono::milliseconds(LOAD_DELAY - 200)); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_wait(context, std::chrono::milliseconds(LOAD_DELAY - 3000)); + pbf_press_button(context, BUTTON_A, 33ms, 1467ms); // skip recap - pbf_press_button(context, BUTTON_B, 200ms, 2300ms); + pbf_press_button(context, BUTTON_B, 33ms, 2467ms); // need to later subtract 4000ms from delay to hit desired number of advances } bool watch_for_shiny_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ BlackScreenWatcher battle_entered(COLOR_RED); context.wait_for_all_requests(); + env.log("Wild encounter started."); int ret = wait_until( env.console, context, 10000ms, {battle_entered} @@ -220,7 +224,7 @@ bool watch_for_shiny_encounter(SingleSwitchProgramEnvironment& env, ProControlle return encounter_shiny; } -void collect_starter_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ +void collect_starter_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // Advance through starter dialogue and wait on "really quite energetic!" pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); @@ -231,9 +235,11 @@ void collect_starter_after_delay(ProControllerContext& context, uint64_t& DOUBLE pbf_press_button(context, BUTTON_B, 200ms, 2300ms); // Advance through rival choice pbf_press_button(context, BUTTON_B, 200ms, 4800ms); + context.wait_for_all_requests(); + env.log("Starter collected."); } -void collect_magikarp_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ +void collect_magikarp_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // Advance through starter dialogue and wait on YES/NO pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); @@ -242,15 +248,19 @@ void collect_magikarp_after_delay(ProControllerContext& context, uint64_t& DOUBL pbf_press_button(context, BUTTON_A, 200ms, 3800ms); // Decline nickname pbf_press_button(context, BUTTON_B, 200ms, 1300ms); + context.wait_for_all_requests(); + env.log("Magikarp collected."); } -void collect_eevee_after_delay(ProControllerContext& context, uint64_t& DOUBLE_DELAY){ +void collect_eevee_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // No dialogue to advance through -- just wait pbf_wait(context, std::chrono::milliseconds(DOUBLE_DELAY - 4000)); // Interact with the pokeball pbf_press_button(context, BUTTON_A, 200ms, 3800ms); // Decline nickname pbf_press_button(context, BUTTON_B, 200ms, 1300ms); + context.wait_for_all_requests(); + env.log("Eevee collected."); } void encounter_snorlax_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ @@ -259,24 +269,36 @@ void encounter_snorlax_after_delay(SingleSwitchProgramEnvironment& env, ProContr pbf_press_button(context, BUTTON_A, 200ms, 9800ms); // PokeFlute tune pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 15700)); // 4000ms + 1500ms + 10000ms + 200ms pbf_press_button(context, BUTTON_A, 200ms, 200ms); + context.wait_for_all_requests(); + env.log("Snorlax encounter started."); +} +void open_party_menu_from_overworld(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); + int ret = move_cursor_to_position(env.console, context, SelectionArrowPositionStartMenu::POKEMON); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to navigate to POKEMON on the Start menu.", + env.console + ); + } + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + context.wait_for_all_requests(); } -void go_to_starter_summary(ProControllerContext& context){ +void go_to_starter_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // Navigate to summary (1st party slot) - pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + open_party_menu_from_overworld(env, context); pbf_press_button(context, BUTTON_A, 200ms, 1000ms); pbf_press_button(context, BUTTON_A, 200ms, 2300ms); } -void go_to_summary(ProControllerContext& context){ +void go_to_sixth_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // navigate to summary (last party slot) - pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); - pbf_move_left_joystick(context, {0, -1}, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); - pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); + open_party_menu_from_overworld(env, context); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); // open summary pbf_press_button(context, BUTTON_A, 200ms, 1000ms); pbf_press_button(context, BUTTON_A, 200ms, 2300ms); @@ -284,48 +306,30 @@ void go_to_summary(ProControllerContext& context){ void use_sweet_scent(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ // navigate to last party slot - pbf_press_button(context, BUTTON_PLUS, 200ms, 800ms); - pbf_move_left_joystick(context, {0, -1}, 200ms, 1800ms); + pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); + pbf_move_left_joystick(context, {0, -1}, 200ms, 800ms); pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); - pbf_move_left_joystick(context, {0, +1}, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 800ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_press_button(context, BUTTON_A, 200ms, 300ms); // hover over Sweet Scent (2nd option, but maybe HMs could change this) pbf_move_left_joystick(context, {0, -1}, 200ms, std::chrono::milliseconds(DOUBLE_DELAY - 8400)); pbf_press_button(context, BUTTON_A, 200ms, 800ms); + context.wait_for_all_requests(); + env.log("Sweet Scent used."); } -void take_summary_pictures(ProControllerContext& context){ +void take_summary_pictures(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // Capture both summary screens pbf_wait(context, 2000ms); pbf_press_button(context, BUTTON_CAPTURE, 200ms, 2300ms); pbf_move_left_joystick(context, {+1, 0}, 200ms, 1300ms); pbf_press_button(context, BUTTON_CAPTURE, 200ms, 2300ms); -} - -bool grass_walk_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& DOUBLE_DELAY){ - pbf_wait(context, std::chrono::milliseconds(DOUBLE_DELAY - 4000)); // 4000ms from the load menu context.wait_for_all_requests(); - // "walk" without moving by tapping the joystick to change directions - BlackScreenWatcher battle_entered(COLOR_RED); - run_until( - env.console, context, - [](ProControllerContext& context) { - while (true){ - // "walk" without moving by tapping the joystick to change directions - // this is enough to trigger encounters - pbf_move_left_joystick(context, {+1, 0}, 33ms, 150ms); - pbf_move_left_joystick(context, {0, +1}, 33ms, 150ms); - pbf_move_left_joystick(context, {-1, 0}, 33ms, 150ms); - pbf_move_left_joystick(context, {0, -1}, 33ms, 150ms); - } - }, - { battle_entered } - ); - bool encounter_shiny = handle_encounter(env.console, context, false); - return encounter_shiny; + env.log("Pictures take of Summary pages 1 and 2."); } + } // namespace @@ -338,7 +342,11 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC bool shiny_found = false; - double FRAMERATE = 59.7275; // valid for GBA, but not sure for Switch + double FRAMERATE = 60.0; // FPS. tested on Switch 1 + + int64_t SEED_OFFSET = -2015; // milliseconds. tested on Switch 1 against ten-lines seeds + int64_t ADVANCES_OFFSET = 162; // frames. test on Switch 1 + uint64_t LOAD_DELAY; uint64_t DOUBLE_DELAY; @@ -346,7 +354,7 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC while (!shiny_found){ LOAD_DELAY = uint64_t((LOAD_ADVANCES)/ FRAMERATE * 1000); - DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES)/ FRAMERATE * 500); + DOUBLE_DELAY = uint64_t((DOUBLE_ADVANCES + ADVANCES_OFFSET)/ FRAMERATE * 500); env.log("Load screen delay: " + std::to_string(LOAD_DELAY) + "ms"); env.log("In-game delay: " + std::to_string(DOUBLE_DELAY) + "ms"); if (RESET_TYPE == ResetType::hard){ @@ -362,64 +370,49 @@ void RNGManipulator::program(SingleSwitchProgramEnvironment& env, ProControllerC } uint64_t STARTUP_DELAY = wait_for_copyright_text(env, context); - env.log("Sstartup delay: " + std::to_string(STARTUP_DELAY) + "ms"); + env.log("Startup delay: " + std::to_string(STARTUP_DELAY) + "ms"); - set_seed_after_delay(context, SEED_DELAY); + set_seed_after_delay(context, SEED_DELAY, SEED_OFFSET); load_game_after_delay(context, LOAD_DELAY); if (TARGET == Target::starters){ - collect_starter_after_delay(context, DOUBLE_DELAY); - go_to_starter_summary(context); + collect_starter_after_delay(env, context, DOUBLE_DELAY); + go_to_starter_summary(env, context); if (TAKE_PICTURES){ - take_summary_pictures(context); + take_summary_pictures(env, context); } context.wait_for_all_requests(); - env.log("Starter collected."); - screen = env.console.video().snapshot(); - ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); }else if (TARGET == Target::magikarp){ - collect_magikarp_after_delay(context, DOUBLE_DELAY); - go_to_summary(context); + collect_magikarp_after_delay(env, context, DOUBLE_DELAY); + go_to_sixth_summary(env, context); if (TAKE_PICTURES){ - take_summary_pictures(context); + take_summary_pictures(env, context); } context.wait_for_all_requests(); - env.log("Magikarp collected."); - screen = env.console.video().snapshot(); - ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); }else if (TARGET == Target::eevee) { - collect_eevee_after_delay(context, DOUBLE_DELAY); - go_to_summary(context); + collect_eevee_after_delay(env, context, DOUBLE_DELAY); + go_to_sixth_summary(env, context); if (TAKE_PICTURES){ - take_summary_pictures(context); + take_summary_pictures(env, context); } context.wait_for_all_requests(); - env.log("Eevee collected."); - screen = env.console.video().snapshot(); - ShinySymbolDetector shiny_checker(COLOR_YELLOW); shiny_found = shiny_checker.read(env.console.logger(), screen); }else if (TARGET == Target::snorlax){ encounter_snorlax_after_delay(env, context, DOUBLE_DELAY); shiny_found = watch_for_shiny_encounter(env, context); context.wait_for_all_requests(); - env.log("Wild encounter started."); }else if (TARGET == Target::sweetscent){ use_sweet_scent(env, context, DOUBLE_DELAY); shiny_found = watch_for_shiny_encounter(env, context); context.wait_for_all_requests(); - env.log("Wild encounter started."); - }else if (TARGET == Target::grasswalk){ - shiny_found = grass_walk_after_delay(env, context, DOUBLE_DELAY); - context.wait_for_all_requests(); - env.log("Wild encounter started."); }else{ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h index d16b1454d5..05d77d7966 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RNGManipulator.h @@ -47,7 +47,6 @@ class RNGManipulator : public SingleSwitchProgramInstance{ // fossils, snorlax, sweetscent, - grasswalk, // fishing, // static, // roaming