diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 2f5f1be50..7b12704d8 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -13,6 +13,7 @@ #include "Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h" #include "Programs/Farming/PokemonFRLG_PickupFarmer.h" #include "Programs/ShinyHunting/PokemonFRLG_GiftReset.h" +#include "Programs/ShinyHunting/PokemonFRLG_RngHelper.h" #include "Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h" #include "Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.h" #include "Programs/ShinyHunting/PokemonFRLG_PrizeCornerReset.h" @@ -51,6 +52,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); if (PreloadSettings::instance().DEVELOPER_MODE){ ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp new file mode 100644 index 000000000..8643c6e20 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp @@ -0,0 +1,1001 @@ +/* RNG Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "CommonTools/Random.h" +#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 "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/PokemonFRLG_SelectionArrowDetector.h" +#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" +#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_RngHelper.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +RngHelper_Descriptor::RngHelper_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:RngHelper", + Pokemon::STRING_POKEMON + " FRLG", "RNG Helper", + "Programs/PokemonFRLG/RngHelper.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 RngHelper_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 RngHelper_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +RngHelper::RngHelper() + : TARGET( + "Target:
", + { + {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::gamecornerabra, "gamecornerabra", "Game Corner Abra"}, + {Target::gamecornerclefairy, "gamecornerclefairy", "Game Corner Clefairy"}, + {Target::gamecornerdratini, "gamecornerdratini", "Game Corner Dratini"}, + {Target::gamecornerbug, "gamecornerbug", "Game Corner Bug (Scyther / Pinsir)"}, + {Target::gamecornerporygon, "gamecornerporygon", "Game Corner Porygon"}, + // {Target::togepi, "togepi", "Togepi"}, + {Target::staticencounter, "staticencounter", "Static Overworld Encounters"}, + {Target::snorlax, "snorlax", "Snorlax"}, + {Target::mewtwo, "mewtwo", "Mewtwo"}, + {Target::hooh, "hooh", "Ho-oh"}, + {Target::hypno, "berryforesthypno", "Berry Forest Hypno"}, + {Target::sweetscent, "sweetscent", "Sweet Scent"}, + {Target::fishing, "fishing", "Fishing"}, + {Target::safarizonecenter, "safarizonecenter", "Safari Zone Center (Sweet Scent)"}, + {Target::safarizoneeast, "safarizoneeast", "Safari Zone East (Sweet Scent)"}, + {Target::safarizonenorth, "safarizonenorth", "Safari Zone North (Sweet Scent)"}, + {Target::safarizonewest, "safarizonewest", "Safari Zone West (Sweet Scent)"}, + {Target::safarizonesurf, "safarizonesurf", "Safari Zone Surfing"}, + {Target::safarizonefish, "safarizonefish", "Safari Zone Fishing"}, + // {Target::roaming, "roaming", "Roaming Legendaries"} + }, + LockMode::LOCK_WHILE_RUNNING, + Target::starters + ) + , NUM_RESETS( + "Max Resets:
", + LockMode::UNLOCK_WHILE_RUNNING, + 1, 0 // default, min + ) + , SEED_DELAY( + "Seed Delay Time (ms):
The delay between starting the game and advancing past the title screen. Set this to match your target seed.", + LockMode::LOCK_WHILE_RUNNING, + 35000, 28000 // default, min + ) + , SEED_CALIBRATION( + "Seed Calibration (ms):
Modifies the seed delay time.", + LockMode::UNLOCK_WHILE_RUNNING, + 0 // default + ) + , LOAD_ADVANCES( + "Load Screen Advances (frames):
The number of frames to advance before loading the game.
These pass at the \"normal\" rate compared to other consoles.", + LockMode::LOCK_WHILE_RUNNING, + 1000, 200 // default, min + ) + , LOAD_CALIBRATION( + "Load Screen Advances Calibration (frames):
A \"fine adjustment\" that modifies the frame advances passed on the load screen.
", + LockMode::UNLOCK_WHILE_RUNNING, + 0 // default + ) + , INGAME_ADVANCES( + "In-Game Advances (frames):
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.
Warning: this needs to be long enough to accomodate all in-game button presses prior to the gift/encounter", + LockMode::LOCK_WHILE_RUNNING, + 12345, 700 // default, min + ) + , INGAME_CALIBRATION( + "In-Game Advances Calibration (frames):
A \"coarse adjustment\" that modifies the frame advances passed after loading the game.
", + LockMode::UNLOCK_WHILE_RUNNING, + 0 // default + ) + , USE_COPYRIGHT_TEXT( + "Detect Copyright Text:
Start the seed timer only after detecting the copyright text. Can be helpful for improving seed consistency.", + LockMode::LOCK_WHILE_RUNNING, + true // default + ) + , USE_TEACHY_TV( + "Use Teachy TV:
Opens the Teachy TV to quickly advance in-game frames at 313x speed.
Warning: can result in larger misses.", + LockMode::LOCK_WHILE_RUNNING, + false // default + ) + , TAKE_VIDEO( + "Take Video:
Record a video when the shiny is found.", + LockMode::LOCK_WHILE_RUNNING, + true // default + ) + , 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(SEED_DELAY); + PA_ADD_OPTION(SEED_CALIBRATION); + PA_ADD_OPTION(LOAD_ADVANCES); + PA_ADD_OPTION(LOAD_CALIBRATION); + PA_ADD_OPTION(INGAME_ADVANCES); + PA_ADD_OPTION(INGAME_CALIBRATION); + PA_ADD_OPTION(USE_COPYRIGHT_TEXT); + PA_ADD_OPTION(USE_TEACHY_TV); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +namespace{ + +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 launch the game + pbf_press_button(context, BUTTON_A, 50ms, 150ms); + // return to HOME menu and wait a moment + pbf_press_button(context, BUTTON_HOME, 125ms, 2000ms); + // open game + pbf_press_button(context, BUTTON_A, 125ms, 0ms); +} + + +uint64_t wait_for_copyright_text(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // wait for copyright text to appear + BlackScreenWatcher black_screen(COLOR_RED, ImageFloatBox {0.25, 0.25, 0.55, 0.55}); + context.wait_for_all_requests(); + WallClock start_time = current_time(); // immediately (more or less) after the button press to enter the game + int black_ret = wait_until( + env.console, context, 10000ms, + {black_screen}, + 1ms + ); + if (black_ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Black screen not detected within 10 seconds of starting game.", + env.console + ); + } + BlackScreenOverWatcher copyright_detected(COLOR_RED, ImageFloatBox {0.25, 0.25, 0.55, 0.55}); + int ret = wait_until( + env.console, context, 10000ms, + {copyright_detected }, + 1ms + ); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Black screen detected for more than 10 seconds after starting game.", + env.console + ); + } + auto elapsed = current_time() - start_time; // when the copyright text appears + return std::chrono::duration_cast(elapsed).count(); +} + +void set_seed_after_delay(ProControllerContext& context, SimpleIntegerOption& SEED_DELAY, SimpleIntegerOption& SEED_CALIBRATION, int64_t& FIXED_SEED_OFFSET){ + // wait on title screen for the specified delay + pbf_wait(context, std::chrono::milliseconds(SEED_DELAY + SEED_CALIBRATION + FIXED_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(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& LOAD_DELAY){ + if (LOAD_DELAY < 3200){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "The load screen delay cannot be less than 3200ms (192 frames). Check your load screen calibration.", + env.console + ); + } + 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, 33ms, 2467ms); + // need to later subtract 4000ms from delay to hit desired number of advances +} + +void wait_with_teachy_tv(ProControllerContext& context, uint64_t& TEACHY_DELAY){ + // open start menu -> bag -> key items -> Teachy TV -> use + pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); + 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, 2300ms); + pbf_move_left_joystick(context, {+1, 0}, 200ms, 2300ms); + pbf_press_button(context, BUTTON_A, 200ms, 300ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(TEACHY_DELAY)); + // close teachy tv -> close bag -> reset start menu cursor position - > close start menu + pbf_press_button(context, BUTTON_B, 200ms, 2300ms); + pbf_press_button(context, BUTTON_B, 200ms, 2300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_press_button(context, BUTTON_B, 200ms, 300ms); + // total non-teachy delay duration: 13700ms + // if used in the Safari Zone: 14200ms +} + +void collect_starter_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY){ + if (INGAME_DELAY < 7500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Starters: the in-game delay cannot be less than 7500ms (900 frames). Check your in-game advances and calibration.", + env.console + ); + } + // 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(INGAME_DELAY - 7200)); // 4000ms + 3000ms + 200ms + // Finish dialogue (hits the target advance) + pbf_press_button(context, BUTTON_A, 200ms, 5800ms); + // Decline nickname + pbf_mash_button(context, BUTTON_B, 2500ms); + // Advance through rival choice + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + env.log("Starter collected."); +} + +void collect_magikarp_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY){ + if (INGAME_DELAY < 7500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Magikarp: the in-game delay cannot be less than 7500ms (900 frames). Check your in-game advances and calibration.", + env.console + ); + } + // 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(INGAME_DELAY - 7200)); // 4000ms + 3000ms + 200ms + // Finish dialogue (hits the target advance) + pbf_press_button(context, BUTTON_A, 200ms, 3800ms); + // Decline nickname + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + env.log("Magikarp collected."); +} + +void collect_hitmon_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY){ + if (INGAME_DELAY < 4500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Hitmonchan/Hitmonlee: the in-game delay cannot be less than 4500ms (540 frames). Check your in-game advances and calibration.", + env.console + ); + } + // One dialog before accepting + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 4200)); // 4000ms + 200ms + // Confirm selection + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + // Decline nickname + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + env.log("Hitmonchan/Hitmonlee collected."); +} + +void collect_eevee_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY){ + if (INGAME_DELAY < 4000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Eevee: the in-game delay cannot be less than 4000ms (480 frames). Check your in-game advances and calibration.", + env.console + ); + } + // No dialogue to advance through -- just wait + pbf_wait(context, std::chrono::milliseconds(INGAME_DELAY - 4000)); + // Interact with the pokeball + pbf_press_button(context, BUTTON_A, 200ms, 3800ms); + // Decline nickname + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + env.log("Eevee collected."); +} + +void collect_lapras_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY){ + if (INGAME_DELAY < 7500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Lapras: the in-game delay cannot be less than 7500ms (900 frames). Check your in-game advances and calibration.", + env.console + ); + } + // 3 dialog presses + 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(INGAME_DELAY - 7200)); // 4000ms + 3000ms + 200ms + // Accept Lapras on target frame + pbf_press_button(context, BUTTON_A, 200ms, 3800ms); + // Decline nickname and exit dialog + pbf_mash_button(context, BUTTON_B, 7500ms); + context.wait_for_all_requests(); + env.log("Lapras collected."); +} + +void collect_fossil_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY){ + if (INGAME_DELAY < 6000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Fossils: the in-game delay cannot be less than 6000ms (720 frames). Check your in-game advances and calibration.", + env.console + ); + } + // 2 dialog presses + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 5700)); // 4000ms + 1500ms + 200ms + // Advance dialog on target frame + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + // Decline nickname + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + env.log("Fossil Pokemon collected."); +} + +void collect_gamecorner_after_delay(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint64_t& INGAME_DELAY, int SLOT){ + if (INGAME_DELAY < 8500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Game Corner: the in-game delay cannot be less than 8500ms (1020 frames). Check your in-game advances and calibration.", + env.console + ); + } + // 2 dialog presses + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + // navigate to desired option + for (int i=0; i(); + + home_black_border_check(env.console, context); + + bool shiny_found = false; + + double FRAMERATE = 59.999977; // FPS. from Dhruv (don't know original source) + double FRAME_DURATION = 1000 / FRAMERATE; + + int64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -1700 : -800; // milliseconds. approximate, might be console-specific (?) + int64_t FIXED_ADVANCES_OFFSET = 0; // frames + + uint64_t LOAD_DELAY; + uint64_t INGAME_DELAY; + uint64_t TEACHY_DELAY; + + while (!shiny_found){ + double MODIFIED_INGAME_ADVANCES = INGAME_ADVANCES + FIXED_ADVANCES_OFFSET + INGAME_CALIBRATION; + if (MODIFIED_INGAME_ADVANCES < 0) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "In-game advances cannot be negative. Check your in-game advances and calibration.", + env.console + ); + } + uint64_t TEACHY_ADVANCES = 0; + + bool SAFARI_ZONE = (TARGET == Target::safarizonecenter + || TARGET == Target::safarizoneeast + || TARGET == Target::safarizonenorth + || TARGET == Target::safarizonewest + || TARGET == Target::safarizonesurf + || TARGET == Target::safarizonefish + ); + + uint64_t TEACHY_TV_BUFFER = SAFARI_ZONE ? 12000 : 5000; // Safari zone targets need extra time to walk to the right position + + bool should_use_teachy_tv = USE_TEACHY_TV && MODIFIED_INGAME_ADVANCES > TEACHY_TV_BUFFER; // don't use Teacy TV for short in-game advance targets + if (should_use_teachy_tv) { + TEACHY_ADVANCES = uint64_t((int)std::floor((MODIFIED_INGAME_ADVANCES - TEACHY_TV_BUFFER) / 313) * 313); + } + + LOAD_DELAY = uint64_t((LOAD_ADVANCES + LOAD_CALIBRATION) * FRAME_DURATION); + TEACHY_DELAY = uint64_t(TEACHY_ADVANCES * FRAME_DURATION / 313); + INGAME_DELAY = uint64_t((MODIFIED_INGAME_ADVANCES - TEACHY_ADVANCES) * FRAME_DURATION / 2) - (should_use_teachy_tv ? 13700 : 0); + env.log("Load screen delay: " + std::to_string(LOAD_DELAY) + "ms"); + env.log("In-game delay: " + std::to_string(INGAME_DELAY) + "ms"); + env.log("Teachy TV delay: " + std::to_string(TEACHY_DELAY) + "ms"); + env.log("Total time: " + std::to_string(SEED_DELAY + SEED_CALIBRATION + FIXED_SEED_OFFSET + LOAD_DELAY + INGAME_DELAY + TEACHY_DELAY) + "ms"); + + if (SEED_DELAY + SEED_CALIBRATION + FIXED_SEED_OFFSET < 28000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "The title screen delay cannot be less than 28000ms. Check your seed calibration.", + env.console + ); + } + + hard_reset(context); + + if (USE_COPYRIGHT_TEXT) { + uint64_t STARTUP_DELAY = wait_for_copyright_text(env, context); + env.log("Startup delay: " + std::to_string(STARTUP_DELAY) + "ms"); + } + + set_seed_after_delay(context, SEED_DELAY, SEED_CALIBRATION, FIXED_SEED_OFFSET); + load_game_after_delay(env, context, LOAD_DELAY); + if (should_use_teachy_tv){ + wait_with_teachy_tv(context, TEACHY_DELAY); + } + + int ret; + uint64_t MODIFIED_INGAME_DELAY; + switch (TARGET){ + case Target::starters: + collect_starter_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_starter_summary(env, context); + break; + case Target::magikarp: + collect_magikarp_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_summary(env, context); + break; + case Target::hitmon: + collect_hitmon_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_summary(env, context); + break; + case Target::eevee: + collect_eevee_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_summary(env, context); + break; + case Target::lapras: + collect_lapras_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_summary(env, context); + break; + case Target::fossils: + collect_fossil_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_summary(env, context); + break; + case Target::gamecornerabra: + collect_gamecorner_after_delay(env, context, INGAME_DELAY, 0); + shiny_found = shiny_check_summary(env, context); + break; + case Target::gamecornerclefairy: + collect_gamecorner_after_delay(env, context, INGAME_DELAY, 1); + shiny_found = shiny_check_summary(env, context); + break; + case Target::gamecornerdratini: + collect_gamecorner_after_delay(env, context, INGAME_DELAY, 2); + shiny_found = shiny_check_summary(env, context); + break; + case Target::gamecornerbug: + collect_gamecorner_after_delay(env, context, INGAME_DELAY, 3); + shiny_found = shiny_check_summary(env, context); + break; + case Target::gamecornerporygon: + collect_gamecorner_after_delay(env, context, INGAME_DELAY, 4); + shiny_found = shiny_check_summary(env, context); + break; + case Target::staticencounter: + encounter_static_after_delay(env, context, INGAME_DELAY); + shiny_found = shiny_check_summary(env, context); + break; + case Target::snorlax: + encounter_snorlax_after_delay(env, context, INGAME_DELAY); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::mewtwo: + encounter_mewtwo_after_delay(env, context, INGAME_DELAY); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::hooh: + encounter_hooh_after_delay(env, context, INGAME_DELAY); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::hypno: + encounter_hypno_after_delay(env, context, INGAME_DELAY); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::sweetscent: + use_sweet_scent(env, context, INGAME_DELAY); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::fishing: + use_registered_fishing_rod(env, context, INGAME_DELAY); + ret = watch_for_shiny_encounter(env, context); + if (ret < 0 ){ + continue; + // keep going if "Not even a nibble..." + } + shiny_found = ret == 1; + context.wait_for_all_requests(); + break; + case Target::safarizonecenter: + if (INGAME_DELAY < 21000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Safari Zone Center: in-game delay cannot be less than 21000ms (2520 frames). Check your in-game advances and calibration.", + env.console + ); + } + MODIFIED_INGAME_DELAY = INGAME_DELAY - 20670; + walk_to_safarizonecenter(context); + use_sweet_scent(env, context, MODIFIED_INGAME_DELAY, true); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::safarizoneeast: + if (INGAME_DELAY < 36500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Safari Zone East: in-game delay cannot be less than 36500ms (4380 frames). Check your in-game advances and calibration.", + env.console + ); + } + MODIFIED_INGAME_DELAY = INGAME_DELAY - 36160; + walk_to_safarizoneeast(context); + use_sweet_scent(env, context, MODIFIED_INGAME_DELAY, true); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::safarizonenorth: + if (INGAME_DELAY < 38000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Safari Zone North: in-game delay cannot be less than 38000ms (4560 frames). Check your in-game advances and calibration.", + env.console + ); + } + MODIFIED_INGAME_DELAY = INGAME_DELAY - 37410; + walk_to_safarizonenorth(context); + use_sweet_scent(env, context, MODIFIED_INGAME_DELAY, true); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::safarizonewest: + if (INGAME_DELAY < 52000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Safari Zone West: in-game delay cannot be less than 52000ms (6240 frames). Check your in-game advances and calibration.", + env.console + ); + } + MODIFIED_INGAME_DELAY = INGAME_DELAY - 51430; + walk_to_safarizonewest(context); + use_sweet_scent(env, context, MODIFIED_INGAME_DELAY, true); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::safarizonesurf: + if (INGAME_DELAY < 31000){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Safari Zone Surfing: in-game delay cannot be less than 31000ms (3720 frames). Check your in-game advances and calibration.", + env.console + ); + } + MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; + walk_to_safarizonewest(context); + use_sweet_scent(env, context, MODIFIED_INGAME_DELAY, true); + shiny_found = watch_for_shiny_encounter(env, context) == 1; + context.wait_for_all_requests(); + break; + case Target::safarizonefish: + if (INGAME_DELAY < 24500){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Safari Zone Fishing: in-game delay cannot be less than 24500ms (2940 frames). Check your in-game advances and calibration.", + env.console + ); + } + MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; + walk_to_safarizonewest(context); + use_registered_fishing_rod(env, context, MODIFIED_INGAME_DELAY); + ret = watch_for_shiny_encounter(env, context); + if (ret < 0 ){ + continue; + // keep going if "Not even a nibble..." + } + shiny_found = ret == 1; + context.wait_for_all_requests(); + break; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Option not yet implemented.", + env.console + ); + } + + stats.resets++; + if (shiny_found){ + env.log("Shiny found!"); + stats.shinies++; + send_program_notification( + env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); + if (TAKE_VIDEO){ + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } + break; + }else if (stats.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." + ); + 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_RngHelper.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.h new file mode 100644 index 000000000..11707d15e --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.h @@ -0,0 +1,100 @@ +/* RNG Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_RngHelper_H +#define PokemonAutomation_PokemonFRLG_RngHelper_H + +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "Common/Cpp/Options/FloatingPointOption.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 RngHelper_Descriptor : public SingleSwitchProgramDescriptor{ +public: + RngHelper_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class RngHelper : public SingleSwitchProgramInstance{ +public: + RngHelper(); + 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, + magikarp, + hitmon, + eevee, + lapras, + fossils, + gamecornerabra, + gamecornerclefairy, + gamecornerdratini, + gamecornerbug, + gamecornerporygon, + // togepi, + staticencounter, + snorlax, + mewtwo, + hooh, + hypno, + sweetscent, + fishing, + safarizonecenter, + safarizoneeast, + safarizonenorth, + safarizonewest, + safarizonesurf, + safarizonefish, + // roaming + }; + + EnumDropdownOption TARGET; + + SimpleIntegerOption NUM_RESETS; + + SimpleIntegerOption SEED_DELAY; + SimpleIntegerOption SEED_CALIBRATION; + + SimpleIntegerOption LOAD_ADVANCES; + FloatingPointOption LOAD_CALIBRATION; + + SimpleIntegerOption INGAME_ADVANCES; + FloatingPointOption INGAME_CALIBRATION; + + BooleanCheckBoxOption USE_COPYRIGHT_TEXT; + BooleanCheckBoxOption USE_TEACHY_TV; + + BooleanCheckBoxOption TAKE_VIDEO; + 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 7f4ccde70..579326787 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1461,6 +1461,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_GiftReset.h + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp + Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryReset.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_LegendaryRunAway.cpp