From bbdd2af2ce75bf972d12b05f115d4071f78c7000 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Fri, 17 Apr 2026 13:46:18 -0500 Subject: [PATCH 1/2] reorganize FRLG RNG programs --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 210 ++- .../Source/Pokemon/Pokemon_AdvRng.h | 44 +- .../PokemonFRLG_BattleLevelUpReader.cpp | 27 +- .../PokemonFRLG_BattleLevelUpReader.h | 13 +- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 4 +- .../PokemonFRLG_BlindNavigation.cpp | 673 +++++++++ .../PokemonFRLG_BlindNavigation.h | 81 ++ .../RngManipulation/PokemonFRLG_HardReset.cpp | 285 ++++ .../RngManipulation/PokemonFRLG_HardReset.h | 43 + .../RngManipulation/PokemonFRLG_RngHelper.cpp | 316 ++++ .../RngManipulation/PokemonFRLG_RngHelper.h | 70 + .../PokemonFRLG_RngNavigation.cpp | 183 +++ .../PokemonFRLG_RngNavigation.h | 26 + .../ShinyHunting/PokemonFRLG_RngHelper.cpp | 1268 ----------------- .../ShinyHunting/PokemonFRLG_RngHelper.h | 136 -- .../PokemonFRLG_ReadBattleLevelUp.cpp | 14 +- SerialPrograms/cmake/SourceFiles.cmake | 10 +- 17 files changed, 1915 insertions(+), 1488 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h delete mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp delete mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.h diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index a9b0e426e..9a94b8864 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -5,6 +5,7 @@ */ #include +#include #include "Pokemon_AdvRng.h" namespace PokemonAutomation{ @@ -14,7 +15,7 @@ uint32_t increment_internal_rng_state(uint32_t& state){ return state * 0x41c64e6d + 0x6073; } -AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint32_t& state, RngMethod method){ +AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint32_t& state, AdvRngMethod method){ uint32_t s0 = state; uint32_t s1 = increment_internal_rng_state(s0); uint32_t s2 = increment_internal_rng_state(s1); @@ -24,7 +25,7 @@ AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint3 return {seed, advances, method, s0, s1, s2, s3, s4}; } -AdvRngState rngstate_from_seed(uint16_t& seed, uint64_t advances, RngMethod method){ +AdvRngState rngstate_from_seed(uint16_t seed, uint64_t advances, AdvRngMethod method){ uint32_t state = seed; state = increment_internal_rng_state(state); for (uint64_t i=0; i> 16; uint16_t pid1 = pid & 0xffff; uint16_t pid_xor (pid0 ^ pid1); if (pid_xor == tid_xor_sid){ - return ShinyType::Square; + return AdvShinyType::Square; }else if ((pid_xor ^ tid_xor_sid) < 8){ - return ShinyType::Star; + return AdvShinyType::Star; }else{ - return ShinyType::Normal; + return AdvShinyType::Normal; } } bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xor_sid, uint8_t gender_threshold){ - return (target.nature == Nature::Any || res.nature == target.nature) - && (target.ability == Ability::Any || res.ability == target.ability) - && (target.gender == Gender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) - && (target.shiny == ShinyType::Any || shiny_type_from_pid(res.pid, tid_xor_sid) == target.shiny) + return (target.nature == AdvNature::Any || res.nature == target.nature) + && (target.ability == AdvAbility::Any || res.ability == target.ability) + && (target.gender == AdvGender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) + && (target.shiny == AdvShinyType::Any || shiny_type_from_pid(res.pid, tid_xor_sid) == target.shiny) && (target.ivs.hp.low <= res.ivs.hp && target.ivs.hp.high >= res.ivs.hp) && (target.ivs.attack.low <= res.ivs.attack && target.ivs.attack.high >= res.ivs.attack) && (target.ivs.defense.low <= res.ivs.defense && target.ivs.defense.high >= res.ivs.defense) @@ -148,7 +149,7 @@ AdvRng::AdvRng(uint16_t seed, AdvRngState state) , state(state) {} -AdvRng::AdvRng(uint16_t seed, uint64_t min_advances, RngMethod method) +AdvRng::AdvRng(uint16_t seed, uint64_t min_advances, AdvRngMethod method) : seed(seed) , state(rngstate_from_seed(seed, min_advances, method)) {} @@ -177,21 +178,21 @@ void AdvRng::search_advance_range( for (uint8_t m=0; m<3; m++){ set_state_advances(min_advances); - RngMethod method; + AdvRngMethod method; switch (m){ case 1: - method = RngMethod::Method2; + method = AdvRngMethod::Method2; break; case 2: - method = RngMethod::Method4; + method = AdvRngMethod::Method4; break; case 0: default: - method = RngMethod::Method1; + method = AdvRngMethod::Method1; break; } - if ((target.method != RngMethod::Any) && (target.method != method)){ + if ((target.method != AdvRngMethod::Any) && (target.method != method)){ continue; } @@ -222,5 +223,148 @@ std::map AdvRng::search( } +Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature){ + NatureAdjustments ret; + ret.attack = NatureAdjustment::NEUTRAL; + ret.defense = NatureAdjustment::NEUTRAL; + ret.spatk = NatureAdjustment::NEUTRAL; + ret.spdef = NatureAdjustment::NEUTRAL; + ret.speed = NatureAdjustment::NEUTRAL; + + switch (nature){ + case AdvNature::Bashful: + case AdvNature::Docile: + case AdvNature::Hardy: + case AdvNature::Quirky: + case AdvNature::Serious: + return ret; + + case AdvNature::Bold: + ret.attack = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Modest: + ret.attack = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Calm: + ret.attack = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Timid: + ret.attack = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Lonely: + ret.defense = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Mild: + ret.defense = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Gentle: + ret.defense = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Hasty: + ret.defense = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Adamant: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Impish: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Careful: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Jolly: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Naughty: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Lax: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Rash: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Naive: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Brave: + ret.speed = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Relaxed: + ret.speed = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Quiet: + ret.speed = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Sassy: + ret.speed = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown Nature: " + std::to_string((int)nature)); + } +} + +void shrink_iv_range(IvRange& mutated_range, IvRange& fixed_range){ + mutated_range.low = std::max(mutated_range.low, fixed_range.low); + mutated_range.high = std::min(mutated_range.high, fixed_range.high); +} + +void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ + shrink_iv_range(mutated_ranges.hp, fixed_ranges.hp); + shrink_iv_range(mutated_ranges.attack, fixed_ranges.attack); + shrink_iv_range(mutated_ranges.defense, fixed_ranges.defense); + shrink_iv_range(mutated_ranges.spatk, fixed_ranges.spatk); + shrink_iv_range(mutated_ranges.spdef, fixed_ranges.spdef); + shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); +} + +AdvRngFilters observation_to_filter(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1){ + IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; + for (int i=0; i level; + std::vector stats; + std::vector evs; + AdvShinyType shiny; }; struct AdvRngFilters{ - Gender gender; - Nature nature; - Ability ability; + AdvGender gender; + AdvNature nature; + AdvAbility ability; IvRanges ivs; - ShinyType shiny; - RngMethod method; + AdvShinyType shiny; + AdvRngMethod method; }; class AdvRng{ @@ -123,7 +133,7 @@ class AdvRng{ AdvRngState state; AdvRng(uint16_t seed, AdvRngState state); - AdvRng(uint16_t seed, uint64_t min_advances, RngMethod method = RngMethod::Method1); + AdvRng(uint16_t seed, uint64_t min_advances, AdvRngMethod method = AdvRngMethod::Method1); void set_seed(uint16_t seed); void set_state_advances(uint64_t advances); diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp index 891857223..3fdafa8d1 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp @@ -4,7 +4,7 @@ * */ -#include "PokemonFRLG_BattleLevelUpReader.h" +#include #include "Common/Cpp/Color.h" #include "Common/Cpp/Exceptions.h" #include "CommonFramework/GlobalSettingsPanel.h" @@ -15,11 +15,13 @@ #include "CommonTools/Images/ImageManip.h" #include "CommonTools/OCR/OCR_NumberReader.h" #include "CommonTools/OCR/OCR_Routines.h" +#include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Inference/Pokemon_NameReader.h" #include "Pokemon/Inference/Pokemon_NatureReader.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" #include "PokemonFRLG_DigitReader.h" -#include +#include "PokemonFRLG_BattleLevelUpReader.h" + namespace PokemonAutomation { namespace NintendoSwitch { @@ -46,7 +48,7 @@ void BattleLevelUpReader::make_overlays(VideoOverlaySet &items) const { items.add(m_color, GAME_BOX.inner_to_outer(m_box_speed)); } -PokemonFRLG_LevelUpStats BattleLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& frame){ +StatReads BattleLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& frame) const{ ImageViewRGB32 game_screen = extract_box_reference(frame, GameSettings::instance().GAME_BOX); @@ -75,18 +77,13 @@ PokemonFRLG_LevelUpStats BattleLevelUpReader::read_stats(Logger &logger, const I ); }; - PokemonFRLG_LevelUpStats stats; - auto assign_stat = [](std::optional& field, int value){ - if (value != -1){ - field = static_cast(value); - } - }; - assign_stat(stats.hp, read_stat(m_box_hp, "hp")); - assign_stat(stats.attack, read_stat(m_box_attack, "attack")); - assign_stat(stats.defense, read_stat(m_box_defense, "defense")); - assign_stat(stats.sp_attack, read_stat(m_box_sp_attack, "spatk")); - assign_stat(stats.sp_defense, read_stat(m_box_sp_defense, "spdef")); - assign_stat(stats.speed, read_stat(m_box_speed, "speed")); + StatReads stats; + stats.hp = uint16_t(read_stat(m_box_hp, "hp")); + stats.attack = uint16_t(read_stat(m_box_attack, "attack")); + stats.defense = uint16_t(read_stat(m_box_defense, "defense")); + stats.spatk = uint16_t(read_stat(m_box_sp_attack, "spatk")); + stats.spdef = uint16_t(read_stat(m_box_sp_defense, "spdef")); + stats.speed = uint16_t(read_stat(m_box_speed, "speed")); return stats; } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h index 6d95d32cd..a4faafa2c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h @@ -12,6 +12,7 @@ #include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" +#include "Pokemon/Pokemon_StatsCalculation.h" namespace PokemonAutomation{ @@ -23,14 +24,8 @@ class VideoOverlaySet; namespace NintendoSwitch{ namespace PokemonFRLG{ -struct PokemonFRLG_LevelUpStats{ - std::optional hp; - std::optional attack; - std::optional defense; - std::optional sp_attack; - std::optional sp_defense; - std::optional speed; -}; +using namespace Pokemon; + class BattleLevelUpReader { public: @@ -38,7 +33,7 @@ class BattleLevelUpReader { void make_overlays(VideoOverlaySet &items) const; - PokemonFRLG_LevelUpStats read_stats(Logger &logger, const ImageViewRGB32& frame); + StatReads read_stats(Logger &logger, const ImageViewRGB32& frame) const; private: Color m_color; diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index b1078b403..8ca87b92d 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -15,12 +15,12 @@ #include "Programs/Farming/PokemonFRLG_PickupFarmer.h" #include "Programs/Farming/PokemonFRLG_EvTrainer.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" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" +#include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -59,6 +59,8 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back("---- RNG Manipulation ----"); + if (IS_BETA_VERSION || PreloadSettings::instance().DEVELOPER_MODE){ ret.emplace_back("---- Untested/Beta/WIP ----"); ret.emplace_back(make_single_switch_program()); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp new file mode 100644 index 000000000..1bab80c19 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp @@ -0,0 +1,673 @@ +/* Blind Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Random.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "PokemonFRLG_BlindNavigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, int64_t SEED_DELAY){ + // wait on title screen for the specified delay + pbf_wait(context, std::chrono::milliseconds(SEED_DELAY)); + // hold the specified button for a few seconds through the transition to the Continue Screen + Button button; + switch (SEED_BUTTON){ + case SeedButton::A: + button = BUTTON_A; + break; + case SeedButton::Start: + button = BUTTON_PLUS; + break; + case SeedButton::L: + button = BUTTON_L; + break; + default: + button = BUTTON_A; + break; + } + pbf_press_button(context, button, 3000ms, 0ms); +} + +void load_game_after_delay(ProControllerContext& context, uint64_t CONTINUE_SCREEN_DELAY){ + pbf_wait(context, std::chrono::milliseconds(CONTINUE_SCREEN_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 +} + + +void collect_starter_after_delay(ProControllerContext& context, uint64_t INGAME_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(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(); +} + +void collect_magikarp_after_delay(ProControllerContext& context, uint64_t INGAME_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(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(); +} + +void collect_hitmon_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ + // 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(); +} + +void collect_eevee_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ + // 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(); +} + +void collect_lapras_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ + // 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(); +} + +void collect_fossil_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ + // 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(); +} + +void collect_gamecorner_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY, int SLOT){ + // 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 0){ + wait_with_teachy_tv(context, TEACHY_DELAY); + } + + uint64_t MODIFIED_INGAME_DELAY; + switch (TARGET){ + case PokemonFRLG_RngTarget::starters: + collect_starter_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::magikarp: + collect_magikarp_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::hitmon: + collect_hitmon_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::eevee: + collect_eevee_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::lapras: + collect_lapras_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::fossils: + collect_fossil_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::gamecornerabra: + collect_gamecorner_after_delay(context, INGAME_DELAY, 0); + return; + case PokemonFRLG_RngTarget::gamecornerclefairy: + collect_gamecorner_after_delay(context, INGAME_DELAY, 1); + return; + case PokemonFRLG_RngTarget::gamecornerdratini: + collect_gamecorner_after_delay(context, INGAME_DELAY, 2); + return; + case PokemonFRLG_RngTarget::gamecornerbug: + collect_gamecorner_after_delay(context, INGAME_DELAY, 3); + return; + case PokemonFRLG_RngTarget::gamecornerporygon: + collect_gamecorner_after_delay(context, INGAME_DELAY, 4); + return; + case PokemonFRLG_RngTarget::togepi: + collect_togepi_egg_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::staticencounter: + encounter_static_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::snorlax: + encounter_snorlax_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::mewtwo: + encounter_mewtwo_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::hooh: + encounter_hooh_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::hypno: + encounter_hypno_after_delay(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::sweetscent: + use_sweet_scent(context, INGAME_DELAY, SAFARI_ZONE); + return; + case PokemonFRLG_RngTarget::fishing: + use_registered_fishing_rod(context, INGAME_DELAY); + return; + case PokemonFRLG_RngTarget::safarizonecenter: + MODIFIED_INGAME_DELAY = INGAME_DELAY - 20670; + walk_to_safarizonecenter(context); + use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + return; + case PokemonFRLG_RngTarget::safarizoneeast: + MODIFIED_INGAME_DELAY = INGAME_DELAY - 36160; + walk_to_safarizoneeast(context); + use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + return; + case PokemonFRLG_RngTarget::safarizonenorth: + MODIFIED_INGAME_DELAY = INGAME_DELAY - 37410; + walk_to_safarizonenorth(context); + use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + return; + case PokemonFRLG_RngTarget::safarizonewest: + MODIFIED_INGAME_DELAY = INGAME_DELAY - 51430; + walk_to_safarizonewest(context); + use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + case PokemonFRLG_RngTarget::safarizonesurf: + MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; + walk_to_safarizonesurf(context); + use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + return; + case PokemonFRLG_RngTarget::safarizonefish: + MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; + walk_to_safarizonefish(context); + use_registered_fishing_rod(context, MODIFIED_INGAME_DELAY); + return; + } +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h new file mode 100644 index 000000000..3c72cb293 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h @@ -0,0 +1,81 @@ +/* Blind Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_BlindNavigation_H +#define PokemonAutomation_PokemonFRLG_BlindNavigation_H + +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ + class ConsoleHandle; + class ProController; + using ProControllerContext = ControllerContext; +namespace PokemonFRLG{ + + + enum class PokemonFRLG_RngTarget{ + 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 + }; + + enum class SeedButton{ + A, + Start, + L + }; + + // checks seed, continue screen, and in-game timings for the specificed RNG manipulation target + // and fires an error if any of the timings are too short. + void check_timings( + ConsoleHandle& console, + PokemonFRLG_RngTarget TARGET, + uint64_t SEED_DELAY, + uint64_t CONTINUE_SCREEN_DELAY, + uint64_t INGAME_DELAY, + bool SAFARI_ZONE + ); + + // performs the blind sequence between launching the game and arriving at the RNG manipulation target + void perform_blind_sequence( + ProControllerContext& context, + PokemonFRLG_RngTarget TARGET, + SeedButton SEED_BUTTON, + uint64_t SEED_DELAY, + uint64_t CONTINUE_SCREEN_DELAY, + uint64_t TEACHY_DELAY, + uint64_t INGAME_DELAY, + bool SAFARI_ZONE + ); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp new file mode 100644 index 000000000..bbfe65762 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp @@ -0,0 +1,285 @@ +/* Hard Reset + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.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 "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" +#include "NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_CloseGameDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_StartGameUserSelectDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "PokemonFRLG_BlindNavigation.h" +#include "PokemonFRLG_HardReset.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +void rng_start_game_from_home( + ConsoleHandle& console, ProControllerContext& context, + uint8_t game_slot, + uint8_t user_slot +){ + context.wait_for_all_requests(); + { + HomeMenuWatcher detector(console); + int ret = run_until( + console, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 10000ms); + }, + { detector } + ); + if (ret == 0){ + console.log("Detected Home screen."); + }else{ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "start_game_from_home_with_inference(): Failed to detect Home screen after 10 seconds.", + console + ); + } + context.wait_for(std::chrono::milliseconds(100)); + } + + if (game_slot != 0){ + ssf_press_button(context, BUTTON_HOME, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0, 160ms); + for (uint8_t c = 1; c < game_slot; c++){ + ssf_press_dpad_ptv(context, DPAD_RIGHT, 160ms); + } + context.wait_for_all_requests(); + } + + pbf_press_button(context, BUTTON_A, 160ms, 340ms); + + WallClock deadline = current_time() + std::chrono::minutes(5); + while (current_time() < deadline){ + HomeMenuWatcher home(console, std::chrono::milliseconds(2000)); + StartGameUserSelectWatcher user_select(console, COLOR_GREEN); + UpdateMenuWatcher update_menu(console, COLOR_PURPLE); + CheckOnlineWatcher check_online(COLOR_CYAN); + FailedToConnectWatcher failed_to_connect(COLOR_YELLOW); + BlackScreenWatcher black_screen(COLOR_BLUE, {0.1, 0.15, 0.8, 0.7}); + + // spend a little bit longer waiting for the black screen to avoid missing it + context.wait_for_all_requests(); + int ret1 = wait_until( + console, context, + std::chrono::seconds(2), + { black_screen } + ); + + switch (ret1){ + case 0: + console.log("Detected black screen. Game started..."); + return; + default: + console.log("Black screen not detected. Checking for other states..."); + } + + // handle other states + context.wait_for_all_requests(); + int ret2 = wait_until( + console, context, + std::chrono::seconds(30), + { + home, + user_select, + update_menu, + check_online, + failed_to_connect, + black_screen, + } + ); + + // Wait for screen to stabilize. + context.wait_for(std::chrono::milliseconds(100)); + + switch (ret2){ + case 0: + console.log("Detected home screen (again).", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + break; + case 1: + console.log("Detected user-select screen."); + move_to_user(context, user_slot); + pbf_press_button(context, BUTTON_A, 160ms, 320ms); + break; + case 2: + console.log("Detected update menu.", COLOR_BLUE); + pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + break; + case 3: + console.log("Detected check online.", COLOR_BLUE); + context.wait_for(std::chrono::seconds(1)); + break; + case 4: + console.log("Detected failed to connect.", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + break; + case 5: + console.log("Detected black screen. Game started..."); + return; + default: + console.log("start_game_from_home_with_inference(): No recognizable state after 30 seconds.", COLOR_RED); + pbf_press_button(context, BUTTON_HOME, 160ms, 840ms); + } + } + + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "rng_start_game_from_home(): Failed to start game after 5 minutes.", + console + ); +} + + +void reset_and_perform_blind_sequence( + ConsoleHandle& console, + ProControllerContext& context, + PokemonFRLG_RngTarget TARGET, + SeedButton SEED_BUTTON, + uint64_t SEED_DELAY, + uint64_t CONTINUE_SCREEN_DELAY, + uint64_t TEACHY_DELAY, + uint64_t INGAME_DELAY, + bool SAFARI_ZONE, + uint8_t PROFILE +){ + // close the game + go_home(console, context); + close_game_from_home(console, context); + // start the game and quickly go back home + rng_start_game_from_home(console, context, uint8_t(0), PROFILE); + pbf_wait(context, 200ms); // wait a moment to ensure the game doesn't fail to launch + go_home(console, context); + + // attempt to resume the game and perform the blind sequence + // by this point, the license check should be over, so we don't need to worry about it when resuming the game + uint8_t attempts = 0; + while(true){ + if (attempts >= 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "RngHelper(): Failed to reset the game 5 times in a row.", + console + ); + } + console.log("Starting blind button presses..."); + UpdateMenuWatcher update_detector(console); + StartGameUserSelectWatcher user_selection_detector(console); + // any other fail conditions should be added here + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [TARGET, SEED_BUTTON, SEED_DELAY, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE](ProControllerContext& context) { + perform_blind_sequence(context, TARGET, SEED_BUTTON, SEED_DELAY, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE); + }, + { update_detector, user_selection_detector }, + 1000ms + ); + + switch (ret){ + case 0: + attempts++; + console.log("Detected update window.", COLOR_RED); + pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); + pbf_press_button(context, BUTTON_A, 80ms, 4000ms); + context.wait_for_all_requests(); + continue; + case 1: + attempts++; + console.log("Detected the user selection screen. Reattempting to start the game"); + pbf_press_button(context, BUTTON_A, 160ms, 1040ms); + go_home(console, context); + continue; + default: + return; + } + } +} + +void reset_and_detect_copyright_text(ConsoleHandle& console, ProControllerContext& context, uint8_t PROFILE){ + go_home(console, context); + close_game_from_home(console, context); + rng_start_game_from_home(console, context, uint8_t(0), PROFILE); + pbf_wait(context, 200ms); // add an extra delay to try to ensure the game doesn't fail to launch + go_home(console, context); + + uint8_t attempts = 0; + while(true){ + if (attempts >= 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to resume the game 5 times in a row.", + console + ); + } + + UpdateMenuWatcher update_detector(console); + StartGameUserSelectWatcher user_selection_detector(console); + BlackScreenWatcher blackscreen_detector(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + pbf_press_button(context, BUTTON_A, 80ms, 9920ms); + }, + { update_detector, user_selection_detector, blackscreen_detector } + ); + + BlackScreenOverWatcher copyright_detector(COLOR_RED); + int ret2; + switch (ret){ + case 0: + attempts++; + console.log("Detected update window.", COLOR_RED); + pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); + pbf_press_button(context, BUTTON_A, 80ms, 4000ms); + context.wait_for_all_requests(); + continue; + case 1: + attempts++; + console.log("Detected the user selection screen. Reattempting to start the game"); + pbf_press_button(context, BUTTON_A, 160ms, 1040ms); + go_home(console, context); + continue; + case 2: + context.wait_for_all_requests(); + ret2 = wait_until( + console, context, 10000ms, + {copyright_detector }, + 1ms // catch black screen as quickly as possible + ); + if (ret2 < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Black screen detected for more than 10 seconds after starting game.", + console + ); + } + return; + default: + console.log("No black screen or update popup detected. Pressing A again..."); + continue; + } + } + +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h new file mode 100644 index 000000000..8ca193852 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h @@ -0,0 +1,43 @@ +/* Hard Reset + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_HardReset_H +#define PokemonAutomation_PokemonFRLG_HardReset_H + + +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "PokemonFRLG_BlindNavigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +void reset_and_perform_blind_sequence( + ConsoleHandle& console, + ProControllerContext& context, + PokemonFRLG_RngTarget TARGET, + SeedButton SEED_BUTTON, + uint64_t SEED_DELAY, + uint64_t CONTINUE_SCREEN_DELAY, + uint64_t TEACHY_DELAY, + uint64_t INGAME_DELAY, + bool SAFARI_ZONE, + uint8_t PROFILE +); + +void reset_and_detect_copyright_text( + ConsoleHandle& console, + ProControllerContext& context, + uint8_t PROFILE = 0 +); + +} +} +} +#endif + + + diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp new file mode 100644 index 000000000..9b0566660 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp @@ -0,0 +1,316 @@ +/* RNG Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#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 "Pokemon/Pokemon_Strings.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_BlindNavigation.h" +#include "PokemonFRLG_RngNavigation.h" +#include "PokemonFRLG_HardReset.h" +#include "PokemonFRLG_RngNavigation.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_RequiresPrecision, + 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:", + { + {PokemonFRLG_RngTarget::starters, "starters", "Bulbasaur / Squirtle / Charmander"}, + {PokemonFRLG_RngTarget::magikarp, "magikarp", "Magikarp"}, + {PokemonFRLG_RngTarget::hitmon, "hitmon", "Hitmonlee / Hitmonchan"}, + {PokemonFRLG_RngTarget::eevee, "eevee", "Eevee"}, + {PokemonFRLG_RngTarget::lapras, "lapras", "Lapras"}, + {PokemonFRLG_RngTarget::fossils, "fossils", "Omanyte / Kabuto / Aerodactyl"}, + {PokemonFRLG_RngTarget::gamecornerabra, "gamecornerabra", "Game Corner Abra"}, + {PokemonFRLG_RngTarget::gamecornerclefairy, "gamecornerclefairy", "Game Corner Clefairy"}, + {PokemonFRLG_RngTarget::gamecornerdratini, "gamecornerdratini", "Game Corner Dratini"}, + {PokemonFRLG_RngTarget::gamecornerbug, "gamecornerbug", "Game Corner Bug (Scyther / Pinsir)"}, + {PokemonFRLG_RngTarget::gamecornerporygon, "gamecornerporygon", "Game Corner Porygon"}, + {PokemonFRLG_RngTarget::togepi, "togepi", "Togepi"}, + {PokemonFRLG_RngTarget::staticencounter, "staticencounter", "Static Overworld Encounters"}, + {PokemonFRLG_RngTarget::snorlax, "snorlax", "Snorlax"}, + {PokemonFRLG_RngTarget::mewtwo, "mewtwo", "Mewtwo"}, + {PokemonFRLG_RngTarget::hooh, "hooh", "Ho-oh"}, + {PokemonFRLG_RngTarget::hypno, "berryforesthypno", "Berry Forest Hypno"}, + {PokemonFRLG_RngTarget::sweetscent, "sweetscent", "Sweet Scent"}, + {PokemonFRLG_RngTarget::fishing, "fishing", "Fishing"}, + {PokemonFRLG_RngTarget::safarizonecenter, "safarizonecenter", "Safari Zone Center (Sweet Scent)"}, + {PokemonFRLG_RngTarget::safarizoneeast, "safarizoneeast", "Safari Zone East (Sweet Scent)"}, + {PokemonFRLG_RngTarget::safarizonenorth, "safarizonenorth", "Safari Zone North (Sweet Scent)"}, + {PokemonFRLG_RngTarget::safarizonewest, "safarizonewest", "Safari Zone West (Sweet Scent)"}, + {PokemonFRLG_RngTarget::safarizonesurf, "safarizonesurf", "Safari Zone Surfing"}, + {PokemonFRLG_RngTarget::safarizonefish, "safarizonefish", "Safari Zone Fishing"}, + // {PokemonFRLG_RngTarget::roaming, "roaming", "Roaming Legendaries"} + }, + LockMode::LOCK_WHILE_RUNNING, + PokemonFRLG_RngTarget::starters + ) + , NUM_RESETS( + "Max Resets:
" + "This program requires manual calibration, so this should usually be set to 1 while calibrating.", + LockMode::UNLOCK_WHILE_RUNNING, + 1, 0 // default, min + ) + , SEED_BUTTON( + "Seed Button:
" + "The button to be pressed on the title screen to set the seed.", + { + {SeedButton::A, "A", "A"}, + {SeedButton::Start, "Start", "Start"}, + {SeedButton::L, "L", "L (L=A)"}, + }, + LockMode::LOCK_WHILE_RUNNING, + SeedButton::A + ) + , 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. This should be changed in the opposite of the direction that you missed your seed.
" + "Example: if you missed your target seed by +16ms (meaning the button press was too late), decrease your seed calibration by -16 (shortening the delay).", + LockMode::UNLOCK_WHILE_RUNNING, + 0 // default + ) + , CONTINUE_SCREEN_FRAMES( + "Continue Screen Frames:" + "
The number of RNG advances before loading the game.
" + "These pass at the \"normal\" rate compared to other consoles.", + LockMode::LOCK_WHILE_RUNNING, + 1000, 192 // default, min + ) + , CONTINUE_SCREEN_CALIBRATION( + "Continue Screen Frames Calibration:" + "
A \"fine adjustment\" that modifies the RNG advances passed on the Continue Screen.
" + "Example: if your target advance was 10000 and you hit 10025, you can decrease your calibration value by 25.", + LockMode::UNLOCK_WHILE_RUNNING, + 0 // default + ) + , INGAME_ADVANCES( + "In-Game Advances:" + "
The number of in-game RNG advances before triggering the gift/encounter.
" + "These pass at double the rate compared to other consoles, where every frame results in 2 advances.
" + "Warning: this needs to be long enough to accomodate all in-game button presses prior to the gift/encounter", + LockMode::LOCK_WHILE_RUNNING, + 12345, 480 // default, min + ) + , INGAME_CALIBRATION( + "In-Game Advances Calibration:" + "
A \"coarse adjustment\" that modifies the RNG advances passed after loading the game.
" + "Example: if your target advance was 10000 and you hit 8500, you can increase your calibration value by 1500.", + 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 if your seeds are inconsistent.", + LockMode::LOCK_WHILE_RUNNING, + true // default + ) + , USE_TEACHY_TV( + "Use Teachy TV:" + "
Opens the Teachy TV to quickly advance the RNG at 313x speed.
" + "Warning: can result in larger misses.", + LockMode::LOCK_WHILE_RUNNING, + false // default + ) + , PROFILE( + "User Profile Position:
" + "The position, from left to right, of the Switch profile with the FRLG save you'd like to use.
" + "If this is set to 0, Switch 1 defaults to the last-used profile, while Switch 2 defaults to the first profile (position 1)", + LockMode::LOCK_WHILE_RUNNING, + 0, 0, 8 // default, min, max + ) + , 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_BUTTON); + PA_ADD_OPTION(SEED_DELAY); + PA_ADD_OPTION(SEED_CALIBRATION); + PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); + PA_ADD_OPTION(CONTINUE_SCREEN_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(PROFILE); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +void RngHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + /* + * Settings: Text Speed fast + */ + + RngHelper_Descriptor::Stats& stats = env.current_stats(); + + home_black_border_check(env.console, context); + + bool shiny_found = false; + + double FRAMERATE = 59.999977; // FPS + double FRAME_DURATION = 1000 / FRAMERATE; + + int64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -2140 : -845; // milliseconds. approximate + + while (!shiny_found){ + // prepare timings + uint64_t TOTAL_SEED_DELAY = SEED_DELAY + SEED_CALIBRATION + FIXED_SEED_OFFSET; + + double MODIFIED_INGAME_ADVANCES = INGAME_ADVANCES + 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; + + const bool SAFARI_ZONE = (TARGET == PokemonFRLG_RngTarget::safarizonecenter + || TARGET == PokemonFRLG_RngTarget::safarizoneeast + || TARGET == PokemonFRLG_RngTarget::safarizonenorth + || TARGET == PokemonFRLG_RngTarget::safarizonewest + || TARGET == PokemonFRLG_RngTarget::safarizonesurf + || TARGET == PokemonFRLG_RngTarget::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 && (TARGET != PokemonFRLG_RngTarget::starters) && (MODIFIED_INGAME_ADVANCES > TEACHY_TV_BUFFER); // don't use Teachy 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); + } + + const uint64_t CONTINUE_SCREEN_DELAY = uint64_t((CONTINUE_SCREEN_FRAMES + CONTINUE_SCREEN_CALIBRATION) * FRAME_DURATION); + const uint64_t TEACHY_DELAY = uint64_t(TEACHY_ADVANCES * FRAME_DURATION / 313); + const uint64_t INGAME_DELAY = uint64_t((MODIFIED_INGAME_ADVANCES - TEACHY_ADVANCES) * FRAME_DURATION / 2) - (should_use_teachy_tv ? 13700 : 0); + env.log("Continue Screen delay: " + std::to_string(CONTINUE_SCREEN_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 + CONTINUE_SCREEN_DELAY + INGAME_DELAY + TEACHY_DELAY) + "ms"); + + check_timings(env.console, TARGET, TOTAL_SEED_DELAY, CONTINUE_SCREEN_DELAY, INGAME_DELAY, SAFARI_ZONE); + + + // handle the blind part + if (USE_COPYRIGHT_TEXT){ + reset_and_detect_copyright_text(env.console, context, PROFILE); + env.log("Starting blind button presses..."); + perform_blind_sequence(context, TARGET, SEED_BUTTON, TOTAL_SEED_DELAY, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE); + }else{ + reset_and_perform_blind_sequence(env.console, context, TARGET, SEED_BUTTON, TOTAL_SEED_DELAY, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE, PROFILE); + } + env.log("Blind button presses complete."); + stats.resets++; + + // detect shinies + shiny_found = check_for_shiny(env.console, context, TARGET); + 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/RngManipulation/PokemonFRLG_RngHelper.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h new file mode 100644 index 000000000..733a8021c --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h @@ -0,0 +1,70 @@ +/* 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" +#include "PokemonFRLG_BlindNavigation.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: + EnumDropdownOption TARGET; + SimpleIntegerOption NUM_RESETS; + + EnumDropdownOption SEED_BUTTON; + SimpleIntegerOption SEED_DELAY; + SimpleIntegerOption SEED_CALIBRATION; + + SimpleIntegerOption CONTINUE_SCREEN_FRAMES; + FloatingPointOption CONTINUE_SCREEN_CALIBRATION; + + SimpleIntegerOption INGAME_ADVANCES; + FloatingPointOption INGAME_CALIBRATION; + + BooleanCheckBoxOption USE_COPYRIGHT_TEXT; + BooleanCheckBoxOption USE_TEACHY_TV; + + SimpleIntegerOption PROFILE; + + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} +#endif + + + diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp new file mode 100644 index 000000000..c8f02c572 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -0,0 +1,183 @@ +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" +#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_BlindNavigation.h" +#include "PokemonFRLG_RngNavigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +void go_to_starter_summary(ConsoleHandle& console, ProControllerContext& context){ + // Navigate to summary (1st party slot) + open_start_menu(console, context); // Don't have a Pokedex yet, so arrow will already by over POKeMON + + SummaryWatcher summary_open(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + for (int i=0; i<3; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + } + }, + { summary_open } + ); + + if (ret < 0){ + console.log("go_to_starter_summary(): failed to open the summary."); + }else{ + console.log("Summary opened."); + } +} + +bool shiny_check_starter_summary(ConsoleHandle& console, ProControllerContext& context){ + go_to_starter_summary(console, context); + context.wait_for_all_requests(); + VideoSnapshot screen = console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); + return shiny_checker.read(console.logger(), screen); +} + +void go_to_last_summary(ConsoleHandle& console, ProControllerContext& context){ + // navigate to the last occupied party slot + open_party_menu_from_overworld(console, context); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + + // open summary + SummaryWatcher summary_open(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + for (int i=0; i<3; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + } + }, + { summary_open } + ); + + if (ret < 0){ + console.log("go_to_last_summary(): failed to open the summary."); + } else { + console.log("Summary opened."); + } +} + +bool shiny_check_summary(ConsoleHandle& console, ProControllerContext& context){ + go_to_last_summary(console, context); + context.wait_for_all_requests(); + VideoSnapshot screen = console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); + return shiny_checker.read(console.logger(), screen); +} + +void hatch_togepi_egg(ConsoleHandle& console, ProControllerContext& context){ + // assumes the player is already on a bike and that the nearby trainer has been defeated + // cycle to the right + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 200ms); + pbf_move_left_joystick(context, {-1, 0}, 100ms, 500ms); + WhiteDialogWatcher egg_dialog(COLOR_RED); + context.wait_for_all_requests(); + WallClock deadline = current_time() + 600s; + console.log("Hatching Togepi egg..."); + int ret = run_until( + console, context, + [deadline](ProControllerContext& context) { + // cycle back and forth + while (current_time() < deadline){ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 0ms); + pbf_move_left_joystick(context, {+1, 0}, 400ms, 0ms); + } + }, + { egg_dialog } + ); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Togepi: failed to hatch egg within 10 minutes. Check your in-game setup.", + console + ); + } + + // watch hatching animation and decline nickname + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); +} + +int watch_for_shiny_encounter(ConsoleHandle& console, ProControllerContext& context){ + BlackScreenWatcher battle_entered(COLOR_RED); + context.wait_for_all_requests(); + console.log("Wild encounter started."); + int ret = wait_until( + console, context, 10000ms, + {battle_entered} + ); + if (ret < 0){ + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // "Failed to initiate encounter.", + // console + // ); + return -1; + } + bool encounter_shiny = handle_encounter(console, context, false); + return encounter_shiny ? 1 : 0; +} + +bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, PokemonFRLG_RngTarget TARGET){ + switch (TARGET){ + case PokemonFRLG_RngTarget::starters: + return shiny_check_starter_summary(console, context); + case PokemonFRLG_RngTarget::togepi: + hatch_togepi_egg(console, context); + case PokemonFRLG_RngTarget::magikarp: + case PokemonFRLG_RngTarget::hitmon: + case PokemonFRLG_RngTarget::eevee: + case PokemonFRLG_RngTarget::lapras: + case PokemonFRLG_RngTarget::fossils: + case PokemonFRLG_RngTarget::gamecornerabra: + case PokemonFRLG_RngTarget::gamecornerclefairy: + case PokemonFRLG_RngTarget::gamecornerdratini: + case PokemonFRLG_RngTarget::gamecornerbug: + case PokemonFRLG_RngTarget::gamecornerporygon: + return shiny_check_summary(console, context); + case PokemonFRLG_RngTarget::staticencounter: + case PokemonFRLG_RngTarget::snorlax: + case PokemonFRLG_RngTarget::mewtwo: + case PokemonFRLG_RngTarget::hooh: + case PokemonFRLG_RngTarget::hypno: + case PokemonFRLG_RngTarget::sweetscent: + case PokemonFRLG_RngTarget::fishing: + case PokemonFRLG_RngTarget::safarizonecenter: + case PokemonFRLG_RngTarget::safarizoneeast: + case PokemonFRLG_RngTarget::safarizonenorth: + case PokemonFRLG_RngTarget::safarizonewest: + case PokemonFRLG_RngTarget::safarizonesurf: + case PokemonFRLG_RngTarget::safarizonefish: + return watch_for_shiny_encounter(console, context) == 1; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Option not yet implemented.", + console + ); + } +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h new file mode 100644 index 000000000..71e3a2788 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h @@ -0,0 +1,26 @@ +/* Rng Navigation + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_RngNavigation_H +#define PokemonAutomation_PokemonFRLG_RngNavigation_H + +#include "PokemonFRLG_BlindNavigation.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ + class ConsoleHandle; + class ProController; + using ProControllerContext = ControllerContext; +namespace PokemonFRLG{ + +bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, PokemonFRLG_RngTarget TARGET); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp deleted file mode 100644 index 9a8e91ba1..000000000 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.cpp +++ /dev/null @@ -1,1268 +0,0 @@ -/* 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 "NintendoSwitch/NintendoSwitch_Settings.h" -#include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h" -#include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" -#include "NintendoSwitch/Inference/NintendoSwitch_StartGameUserSelectDetector.h" -#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" -#include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" -#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" -#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" -#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.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_RequiresPrecision, - 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() - : PROFILE( - "User Profile Position:
" - "The position, from left to right, of the Switch profile with the FRLG save you'd like to use.
" - "If this is set to 0, Switch 1 defaults to the last-used profile, while Switch 2 defaults to the first profile (position 1)", - LockMode::LOCK_WHILE_RUNNING, - 0, 0, 8 // default, min, max - ) - , 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:
" - "This program requires manual calibration, so this should usually be set to 1 while calibrating.", - LockMode::UNLOCK_WHILE_RUNNING, - 1, 0 // default, min - ) - , SEED_BUTTON( - "Seed Button:
" - "The button to be pressed on the title screen to set the seed.", - { - {SeedButton::A, "A", "A"}, - {SeedButton::Start, "Start", "Start"}, - {SeedButton::L, "L", "L (L=A)"}, - }, - LockMode::LOCK_WHILE_RUNNING, - SeedButton::A - ) - , 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. This should be changed in the opposite of the direction that you missed your seed.
" - "Example: if you missed your target seed by +16ms (meaning the button press was too late), decrease your seed calibration by -16 (shortening the delay).", - LockMode::UNLOCK_WHILE_RUNNING, - 0 // default - ) - , CONTINUE_SCREEN_FRAMES( - "Continue Screen Frames:" - "
The number of RNG advances before loading the game.
" - "These pass at the \"normal\" rate compared to other consoles.", - LockMode::LOCK_WHILE_RUNNING, - 1000, 192 // default, min - ) - , CONTINUE_SCREEN_CALIBRATION( - "Continue Screen Frames Calibration:" - "
A \"fine adjustment\" that modifies the RNG advances passed on the Continue Screen.
" - "Example: if your target advance was 10000 and you hit 10025, you can decrease your calibration value by 25.", - LockMode::UNLOCK_WHILE_RUNNING, - 0 // default - ) - , INGAME_ADVANCES( - "In-Game Advances:" - "
The number of in-game RNG advances before triggering the gift/encounter.
" - "These pass at double the rate compared to other consoles, where every frame results in 2 advances.
" - "Warning: this needs to be long enough to accomodate all in-game button presses prior to the gift/encounter", - LockMode::LOCK_WHILE_RUNNING, - 12345, 480 // default, min - ) - , INGAME_CALIBRATION( - "In-Game Advances Calibration:" - "
A \"coarse adjustment\" that modifies the RNG advances passed after loading the game.
" - "Example: if your target advance was 10000 and you hit 8500, you can increase your calibration value by 1500.", - 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 if your seeds are inconsistent.", - LockMode::LOCK_WHILE_RUNNING, - true // default - ) - , USE_TEACHY_TV( - "Use Teachy TV:" - "
Opens the Teachy TV to quickly advance the RNG 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(PROFILE); - PA_ADD_OPTION(TARGET); - PA_ADD_OPTION(NUM_RESETS); - PA_ADD_OPTION(SEED_BUTTON); - PA_ADD_OPTION(SEED_DELAY); - PA_ADD_OPTION(SEED_CALIBRATION); - PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); - PA_ADD_OPTION(CONTINUE_SCREEN_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 collect_starter_after_delay(ProControllerContext& context, const uint64_t& INGAME_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(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(); -} - -void collect_magikarp_after_delay(ProControllerContext& context, const uint64_t& INGAME_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(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(); -} - -void collect_hitmon_after_delay(ProControllerContext& context, const uint64_t& INGAME_DELAY){ - // 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(); -} - -void collect_eevee_after_delay(ProControllerContext& context, const uint64_t& INGAME_DELAY){ - // 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(); -} - -void collect_lapras_after_delay(ProControllerContext& context, const uint64_t& INGAME_DELAY){ - // 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(); -} - -void collect_fossil_after_delay(ProControllerContext& context, const uint64_t& INGAME_DELAY){ - // 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(); -} - -void collect_gamecorner_after_delay(ProControllerContext& context, const uint64_t& INGAME_DELAY, int SLOT){ - // 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( - env.console, context, - [](ProControllerContext& context) { - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - for (int i=0; i<3; i++){ - pbf_press_button(context, BUTTON_A, 200ms, 2800ms); - } - }, - { summary_open } - ); - - if (ret < 0){ - env.log("go_to_starter_summary(): failed to open the summary."); - }else{ - env.log("Summary opened."); - } -} - -bool shiny_check_starter_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - go_to_starter_summary(env, context); - context.wait_for_all_requests(); - VideoSnapshot screen = env.console.video().snapshot(); - ShinySymbolDetector shiny_checker(COLOR_YELLOW); - return shiny_checker.read(env.console.logger(), screen); -} - -void go_to_last_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - // navigate to the last occupied party slot - open_party_menu_from_overworld(env.console, context); - pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); - - // open summary - SummaryWatcher summary_open(COLOR_RED); - context.wait_for_all_requests(); - int ret = run_until( - env.console, context, - [](ProControllerContext& context) { - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - for (int i=0; i<3; i++){ - pbf_press_button(context, BUTTON_A, 200ms, 2800ms); - } - }, - { summary_open } - ); - - if (ret < 0){ - env.log("go_to_last_summary(): failed to open the summary."); - } else { - env.log("Summary opened."); - } -} - -bool shiny_check_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - go_to_last_summary(env, context); - context.wait_for_all_requests(); - VideoSnapshot screen = env.console.video().snapshot(); - ShinySymbolDetector shiny_checker(COLOR_YELLOW); - return shiny_checker.read(env.console.logger(), screen); -} - -void hatch_togepi_egg(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - // assumes the player is already on a bike and that the nearby trainer has been defeated - // cycle to the right - pbf_move_left_joystick(context, {+1, 0}, 1000ms, 200ms); - pbf_move_left_joystick(context, {-1, 0}, 100ms, 500ms); - WhiteDialogWatcher egg_dialog(COLOR_RED); - context.wait_for_all_requests(); - WallClock deadline = current_time() + 600s; - env.log("Hatching Togepi egg..."); - int ret = run_until( - env.console, context, - [deadline](ProControllerContext& context) { - // cycle back and forth - while (current_time() < deadline){ - pbf_move_left_joystick(context, {-1, 0}, 400ms, 0ms); - pbf_move_left_joystick(context, {+1, 0}, 400ms, 0ms); - } - }, - { egg_dialog } - ); - if (ret < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Togepi: failed to hatch egg within 10 minutes. Check your in-game setup.", - env.console - ); - } - - // watch hatching animation and decline nickname - pbf_mash_button(context, BUTTON_B, 15000ms); - context.wait_for_all_requests(); -} - -int 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} - ); - if (ret < 0){ - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, - // "Failed to initiate encounter.", - // env.console - // ); - return -1; - } - bool encounter_shiny = handle_encounter(env.console, context, false); - return encounter_shiny ? 1 : 0; -} - -void enter_safarizone(ProControllerContext& context){ - // walk up to initiate dialogue - pbf_move_left_joystick(context, {0, +1}, 600ms, 400ms); - // Advance through the dialogue (waiting a little longer when Safari Balls are recieved) - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 3300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - // finish dialogue and automatically enter the Safari Zone - pbf_press_button(context, BUTTON_A, 200ms, 4800ms); - // total duration: 18500ms -} - -void walk_to_safarizonefish(ProControllerContext& context){ - enter_safarizone(context); // 18500ms - // walk from the entrance to the pond in the central area - pbf_move_left_joystick(context, {0, +1}, 2200ms, 300ms); - pbf_move_left_joystick(context, {+1, 0}, 1600ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 600ms, 300ms); - // total duration: 23800ms -} - -void walk_to_safarizonesurf(ProControllerContext& context){ - walk_to_safarizonefish(context); // 23800ms - // start surfing - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, 3300ms); - // total duration: 30300ms -} - -void walk_to_safarizonecenter(ProControllerContext& context){ - enter_safarizone(context); // 18500ms - // walk from the entrance to the nearest grass - pbf_move_left_joystick(context, {0, +1}, 460ms, 300ms); - pbf_move_left_joystick(context, {-1, 0}, 1110ms, 300ms); - // total duration: 20670ms -} - -void walk_to_safarizoneeast(ProControllerContext& context){ - enter_safarizone(context); // 18500ms - // walk from the entrance to the east area - pbf_move_left_joystick(context, {0, +1}, 160ms, 300ms); - pbf_move_left_joystick(context, {+1, 0}, 4400ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 3550ms, 300ms); - // walk to the nearest grass - pbf_move_left_joystick(context, {+1, 0}, 3600ms, 300ms); - pbf_move_left_joystick(context, {0, -1}, 700ms, 300ms); - pbf_move_left_joystick(context, {+1, 0}, 3450ms, 300ms); - // total duration: 36160ms -} - -void walk_to_safarizonenorth(ProControllerContext& context){ - walk_to_safarizonesurf(context); // 30300ms - // from the pond to the grass in the north area - pbf_move_left_joystick(context, {0, +1}, 2810ms, 300ms); - pbf_move_left_joystick(context, {-1, 0}, 1530ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 1870ms, 300ms); - // total duration: 37410ms -} - -void walk_to_safarizonewest(ProControllerContext& context){ - walk_to_safarizonesurf(context); // 30300ms - // surf past the hedge and exit the pond - pbf_move_left_joystick(context, {-1, 0}, 500ms, 300ms); - pbf_move_left_joystick(context, {0, -1}, 260ms, 500ms); - // walk to the west area - pbf_move_left_joystick(context, {-1, 0}, 5500ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 240ms, 300ms); - pbf_move_left_joystick(context, {-1, 0}, 1200ms, 500ms); - // walk to the grass - pbf_move_left_joystick(context, {-1, 0}, 2860ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 1390ms, 300ms); - pbf_move_left_joystick(context, {-1, 0}, 1510ms, 300ms); - pbf_move_left_joystick(context, {0, -1}, 770ms, 300ms); - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 300ms); - pbf_move_left_joystick(context, {0, -1}, 600ms, 300ms); - // total duration: 51430ms -} - -} // namespace - - -void RngHelper::set_seed_after_delay(ProControllerContext& context, 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 the specified button for a few seconds through the transition to the Continue Screen - Button button; - switch (SEED_BUTTON){ - case SeedButton::A: - button = BUTTON_A; - break; - case SeedButton::Start: - button = BUTTON_PLUS; - break; - case SeedButton::L: - button = BUTTON_L; - break; - default: - button = BUTTON_A; - break; - } - pbf_press_button(context, button, 3000ms, 0ms); -} - -void RngHelper::load_game_after_delay(ProControllerContext& context, const uint64_t& CONTINUE_SCREEN_DELAY){ - pbf_wait(context, std::chrono::milliseconds(CONTINUE_SCREEN_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 RngHelper::wait_with_teachy_tv(ProControllerContext& context, const 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 -} - -void RngHelper::check_timings( - SingleSwitchProgramEnvironment& env, - int64_t FIXED_SEED_OFFSET, - const uint64_t& CONTINUE_SCREEN_DELAY, - const uint64_t& INGAME_DELAY, - bool SAFARI_ZONE -){ - if (CONTINUE_SCREEN_DELAY < 3200){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "The Continue Screen delay cannot be less than 3200ms (192 advances). Check your Continue Screen calibration.", - env.console - ); - } - 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 - ); - } - - switch (TARGET){ - case Target::starters: - if (INGAME_DELAY < 7500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Starters: the in-game delay cannot be less than 7500ms (900 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::magikarp: - if (INGAME_DELAY < 7500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Magikarp: the in-game delay cannot be less than 7500ms (900 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::hitmon: - if (INGAME_DELAY < 4500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Hitmonchan/Hitmonlee: the in-game delay cannot be less than 4500ms (540 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::eevee: - if (INGAME_DELAY < 4000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Eevee: the in-game delay cannot be less than 4000ms (480 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::lapras: - if (INGAME_DELAY < 7500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Lapras: the in-game delay cannot be less than 7500ms (900 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::fossils: - if (INGAME_DELAY < 6000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Fossils: the in-game delay cannot be less than 6000ms (720 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::gamecornerabra: - case Target::gamecornerclefairy: - case Target::gamecornerdratini: - case Target::gamecornerbug: - case Target::gamecornerporygon: - if (INGAME_DELAY < 8500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Game Corner: the in-game delay cannot be less than 8500ms (1020 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::togepi: - if (INGAME_DELAY < 12000) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Togepi: the in-game delay cannot be less than 12000ms (1440 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::staticencounter: - if (INGAME_DELAY < 5000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Static Encounter: the in-game delay cannot be less than 5000ms (600 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::snorlax: - if (INGAME_DELAY < 16000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Snorlax: the in-game delay cannot be less than 16000ms (1920 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::mewtwo: - if (INGAME_DELAY < 4500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Mewtwo: the in-game delay cannot be less than 4500ms (540 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::hooh: - if (INGAME_DELAY < 4000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Ho-oh: the in-game delay cannot be less than 4000ms (480 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::hypno: - if (INGAME_DELAY < 13000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Hypno: the in-game delay cannot be less than 13000ms (1560 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::sweetscent: - if (!SAFARI_ZONE && INGAME_DELAY < 8500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Sweet Scent: the in-game delay cannot be less than 8500ms (1020 advances). Check your in-game advances and calibration.", - env.console - ); - }else if (SAFARI_ZONE && INGAME_DELAY < 9500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Sweet Scent: the in-game delay cannot be less than 9500ms (1140 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::fishing: - if (INGAME_DELAY < 5500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Fishing: the in-game delay cannot be less than 5500ms (1800 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::safarizonecenter: - if (INGAME_DELAY < 30500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Safari Zone Center: in-game delay cannot be less than 30500ms (3660 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - 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 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::safarizonenorth: - if (INGAME_DELAY < 47500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Safari Zone North: in-game delay cannot be less than 47500ms (5700 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::safarizonewest: - if (INGAME_DELAY < 61500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Safari Zone West: in-game delay cannot be less than 52000ms (7380 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::safarizonesurf: - if (INGAME_DELAY < 40500){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Safari Zone Surfing: in-game delay cannot be less than 40500ms (4860 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - case Target::safarizonefish: - if (INGAME_DELAY < 30000){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Safari Zone Fishing: in-game delay cannot be less than 30000ms (3600 advances). Check your in-game advances and calibration.", - env.console - ); - } - return; - default: - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Option not yet implemented.", - env.console - ); - } -} - -void RngHelper::perform_blind_sequence( - ProControllerContext& context, - int64_t FIXED_SEED_OFFSET, - const uint64_t& CONTINUE_SCREEN_DELAY, - const uint64_t& TEACHY_DELAY, - const uint64_t& INGAME_DELAY, - bool SAFARI_ZONE -){ - pbf_press_button(context, BUTTON_A, 80ms, 0ms); // start the game from the Home screen - set_seed_after_delay(context, FIXED_SEED_OFFSET); - load_game_after_delay(context, CONTINUE_SCREEN_DELAY); - if (TEACHY_DELAY > 0){ - wait_with_teachy_tv(context, TEACHY_DELAY); - } - - uint64_t MODIFIED_INGAME_DELAY; - switch (TARGET){ - case Target::starters: - collect_starter_after_delay(context, INGAME_DELAY); - return; - case Target::magikarp: - collect_magikarp_after_delay(context, INGAME_DELAY); - return; - case Target::hitmon: - collect_hitmon_after_delay(context, INGAME_DELAY); - return; - case Target::eevee: - collect_eevee_after_delay(context, INGAME_DELAY); - return; - case Target::lapras: - collect_lapras_after_delay(context, INGAME_DELAY); - return; - case Target::fossils: - collect_fossil_after_delay(context, INGAME_DELAY); - return; - case Target::gamecornerabra: - collect_gamecorner_after_delay(context, INGAME_DELAY, 0); - return; - case Target::gamecornerclefairy: - collect_gamecorner_after_delay(context, INGAME_DELAY, 1); - return; - case Target::gamecornerdratini: - collect_gamecorner_after_delay(context, INGAME_DELAY, 2); - return; - case Target::gamecornerbug: - collect_gamecorner_after_delay(context, INGAME_DELAY, 3); - return; - case Target::gamecornerporygon: - collect_gamecorner_after_delay(context, INGAME_DELAY, 4); - return; - case Target::togepi: - collect_togepi_egg_after_delay(context, INGAME_DELAY); - return; - case Target::staticencounter: - encounter_static_after_delay(context, INGAME_DELAY); - return; - case Target::snorlax: - encounter_snorlax_after_delay(context, INGAME_DELAY); - return; - case Target::mewtwo: - encounter_mewtwo_after_delay(context, INGAME_DELAY); - return; - case Target::hooh: - encounter_hooh_after_delay(context, INGAME_DELAY); - return; - case Target::hypno: - encounter_hypno_after_delay(context, INGAME_DELAY); - return; - case Target::sweetscent: - use_sweet_scent(context, INGAME_DELAY, SAFARI_ZONE); - return; - case Target::fishing: - use_registered_fishing_rod(context, INGAME_DELAY); - return; - case Target::safarizonecenter: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 20670; - walk_to_safarizonecenter(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); - return; - case Target::safarizoneeast: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 36160; - walk_to_safarizoneeast(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); - return; - case Target::safarizonenorth: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 37410; - walk_to_safarizonenorth(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); - return; - case Target::safarizonewest: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 51430; - walk_to_safarizonewest(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); - case Target::safarizonesurf: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; - walk_to_safarizonesurf(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); - return; - case Target::safarizonefish: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; - walk_to_safarizonefish(context); - use_registered_fishing_rod(context, MODIFIED_INGAME_DELAY); - return; - } -} - -void RngHelper::reset_and_perform_blind_sequence( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - int64_t FIXED_SEED_OFFSET, - const uint64_t& CONTINUE_SCREEN_DELAY, - const uint64_t& TEACHY_DELAY, - const uint64_t& INGAME_DELAY, - bool SAFARI_ZONE -){ - // close the game - go_home(env.console, context); - close_game_from_home(env.console, context); - // start the game and quickly go back home - start_game_from_home(env.console, context, ConsoleSettings::instance().TOLERATE_SYSTEM_UPDATE_MENU_FAST, uint8_t(0), PROFILE); - pbf_wait(context, 200ms); // wait a moment to ensure the game doesn't fail to launch - go_home(env.console, context); - - // attempt to resume the game and perform the blind sequence - // by this point, the license check should be over, so we don't need to worry about it when resuming the game - uint8_t attempts = 0; - while(true){ - if (attempts >= 5){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "RngHelper(): Failed to reset the game 5 times in a row.", - env.console - ); - } - env.log("Starting blind button presses..."); - UpdateMenuWatcher update_detector(env.console); - StartGameUserSelectWatcher user_selection_detector(env.console); - // any other fail conditions should be added here - context.wait_for_all_requests(); - int ret = run_until( - env.console, context, - [this, FIXED_SEED_OFFSET, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE](ProControllerContext& context) { - perform_blind_sequence(context, FIXED_SEED_OFFSET, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE); - }, - { update_detector, user_selection_detector } - ); - - switch (ret){ - case 0: - attempts++; - env.log("Detected update window.", COLOR_RED); - pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); - pbf_press_button(context, BUTTON_A, 80ms, 4000ms); - context.wait_for_all_requests(); - continue; - case 1: - attempts++; - env.log("Detected the user selection screen. Reattempting to start the game"); - pbf_press_button(context, BUTTON_A, 160ms, 1040ms); - go_home(env.console, context); - continue; - default: - return; - } - } -} - -void RngHelper::reset_and_detect_copyright_text(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - go_home(env.console, context); - close_game_from_home(env.console, context); - start_game_from_home(env.console, context, ConsoleSettings::instance().TOLERATE_SYSTEM_UPDATE_MENU_FAST, uint8_t(0), PROFILE); - pbf_wait(context, 200ms); // add an extra delay to try to ensure the game doesn't fail to launch - go_home(env.console, context); - - uint8_t attempts = 0; - while(true){ - if (attempts >= 5){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "RngHelper(): Failed to resume the game 5 times in a row.", - env.console - ); - } - - UpdateMenuWatcher update_detector(env.console); - StartGameUserSelectWatcher user_selection_detector(env.console); - BlackScreenWatcher blackscreen_detector(COLOR_RED); - context.wait_for_all_requests(); - int ret = run_until( - env.console, context, - [](ProControllerContext& context) { - pbf_press_button(context, BUTTON_A, 80ms, 9920ms); - }, - { update_detector, user_selection_detector, blackscreen_detector }, - 1ms - ); - - BlackScreenOverWatcher copyright_detector(COLOR_RED); - int ret2; - switch (ret){ - case 0: - attempts++; - env.log("Detected update window.", COLOR_RED); - pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); - pbf_press_button(context, BUTTON_A, 80ms, 4000ms); - context.wait_for_all_requests(); - continue; - case 1: - attempts++; - env.log("Detected the user selection screen. Reattempting to start the game"); - pbf_press_button(context, BUTTON_A, 160ms, 1040ms); - go_home(env.console, context); - continue; - case 2: - context.wait_for_all_requests(); - ret2 = wait_until( - env.console, context, 10000ms, - {copyright_detector }, - 1ms - ); - if (ret2 < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Black screen detected for more than 10 seconds after starting game.", - env.console - ); - } - return; - default: - env.log("No black screen or update popup detected. Pressing A again..."); - continue; - } - } - -} - -bool RngHelper::check_for_shiny(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - switch (TARGET){ - case Target::starters: - return shiny_check_starter_summary(env, context); - case Target::togepi: - hatch_togepi_egg(env, context); - case Target::magikarp: - case Target::hitmon: - case Target::eevee: - case Target::lapras: - case Target::fossils: - case Target::gamecornerabra: - case Target::gamecornerclefairy: - case Target::gamecornerdratini: - case Target::gamecornerbug: - case Target::gamecornerporygon: - return shiny_check_summary(env, context); - case Target::staticencounter: - case Target::snorlax: - case Target::mewtwo: - case Target::hooh: - case Target::hypno: - case Target::sweetscent: - case Target::fishing: - case Target::safarizonecenter: - case Target::safarizoneeast: - case Target::safarizonenorth: - case Target::safarizonewest: - case Target::safarizonesurf: - case Target::safarizonefish: - return watch_for_shiny_encounter(env, context) == 1; - default: - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Option not yet implemented.", - env.console - ); - } -} - - -void RngHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - /* - * Settings: Text Speed fast - */ - - RngHelper_Descriptor::Stats& stats = env.current_stats(); - - home_black_border_check(env.console, context); - - bool shiny_found = false; - - double FRAMERATE = 59.999977; // FPS - double FRAME_DURATION = 1000 / FRAMERATE; - - int64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -2140 : -845; // milliseconds. approximate - - while (!shiny_found){ - // prepare timings - double MODIFIED_INGAME_ADVANCES = INGAME_ADVANCES + 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; - - const 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 && (TARGET != Target::starters) && (MODIFIED_INGAME_ADVANCES > TEACHY_TV_BUFFER); // don't use Teachy 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); - } - - const uint64_t CONTINUE_SCREEN_DELAY = uint64_t((CONTINUE_SCREEN_FRAMES + CONTINUE_SCREEN_CALIBRATION) * FRAME_DURATION); - const uint64_t TEACHY_DELAY = uint64_t(TEACHY_ADVANCES * FRAME_DURATION / 313); - const uint64_t INGAME_DELAY = uint64_t((MODIFIED_INGAME_ADVANCES - TEACHY_ADVANCES) * FRAME_DURATION / 2) - (should_use_teachy_tv ? 13700 : 0); - env.log("Continue Screen delay: " + std::to_string(CONTINUE_SCREEN_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 + CONTINUE_SCREEN_DELAY + INGAME_DELAY + TEACHY_DELAY) + "ms"); - - check_timings(env, FIXED_SEED_OFFSET, CONTINUE_SCREEN_DELAY, INGAME_DELAY, SAFARI_ZONE); - - - // handle the blind part - if (USE_COPYRIGHT_TEXT){ - reset_and_detect_copyright_text(env, context); - env.log("Starting blind button presses..."); - perform_blind_sequence(context, FIXED_SEED_OFFSET, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE); - }else{ - reset_and_perform_blind_sequence(env, context, FIXED_SEED_OFFSET, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE); - } - env.log("Blind button presses complete."); - stats.resets++; - - // detect shinies - shiny_found = check_for_shiny(env, context); - 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 deleted file mode 100644 index 27c230ac0..000000000 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_RngHelper.h +++ /dev/null @@ -1,136 +0,0 @@ -/* 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 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 - }; - - enum class SeedButton{ - A, - Start, - L - }; - - void set_seed_after_delay(ProControllerContext& context, int64_t& FIXED_SEED_OFFSET); - void load_game_after_delay(ProControllerContext& context, const uint64_t& LOAD_DELAY); - void wait_with_teachy_tv(ProControllerContext& context, const uint64_t& TEACHY_DELAY); - - - void check_timings( - SingleSwitchProgramEnvironment& env, - int64_t FIXED_SEED_OFFSET, - const uint64_t& CONTINUE_SCREEN_DELAY, - const uint64_t& INGAME_DELAY, - bool SAFARI_ZONE - ); - void perform_blind_sequence( - ProControllerContext& context, - int64_t FIXED_SEED_OFFSET, - const uint64_t& CONTINUE_SCREEN_DELAY, - const uint64_t& TEACHY_DELAY, - const uint64_t& INGAME_DELAY, - bool SAFARI_ZONE - ); - void reset_and_perform_blind_sequence( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - int64_t FIXED_SEED_OFFSET, - const uint64_t& CONTINUE_SCREEN_DELAY, - const uint64_t& TEACHY_DELAY, - const uint64_t& INGAME_DELAY, - bool SAFARI_ZONE - ); - void reset_and_detect_copyright_text(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - bool check_for_shiny(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - - SimpleIntegerOption PROFILE; - - EnumDropdownOption TARGET; - SimpleIntegerOption NUM_RESETS; - - EnumDropdownOption SEED_BUTTON; - SimpleIntegerOption SEED_DELAY; - SimpleIntegerOption SEED_CALIBRATION; - - SimpleIntegerOption CONTINUE_SCREEN_FRAMES; - FloatingPointOption CONTINUE_SCREEN_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/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp index 23f49356f..0331db318 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.cpp @@ -70,19 +70,19 @@ void ReadBattleLevelUp::program( env.log("Reading stats..."); VideoSnapshot screen1 = env.console.video().snapshot(); - PokemonFRLG_LevelUpStats stats = reader.read_stats(env.logger(), screen1); + StatReads stats = reader.read_stats(env.logger(), screen1); - env.log("Max HP: " + (stats.hp.has_value() ? std::to_string(*stats.hp) : "???")); + env.log("Max HP: " + (stats.hp > 0 ? std::to_string(stats.hp) : "???")); env.log("Attack: " + - (stats.attack.has_value() ? std::to_string(*stats.attack) : "???")); + (stats.attack > 0 ? std::to_string(stats.attack) : "???")); env.log("Defense: " + - (stats.defense.has_value() ? std::to_string(*stats.defense) : "???")); + (stats.defense > 0 ? std::to_string(stats.defense) : "???")); env.log("Sp. Attack: " + - (stats.sp_attack.has_value() ? std::to_string(*stats.sp_attack) : "???")); + (stats.spatk > 0 ? std::to_string(stats.spatk) : "???")); env.log("Sp. Defense: " + - (stats.sp_defense.has_value() ? std::to_string(*stats.sp_defense) : "???")); + (stats.spdef > 0 ? std::to_string(stats.spdef) : "???")); env.log("Speed: " + - (stats.speed.has_value() ? std::to_string(*stats.speed) : "???")); + (stats.speed > 0 ? std::to_string(stats.speed) : "???")); env.log("Finished Reading Stats. Verification boxes are on overlay.", COLOR_BLUE); diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 16a4eb9a4..0995d708b 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1504,8 +1504,6 @@ 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 @@ -1516,6 +1514,14 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.cpp Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp From e739f5b8430904de83a2cb966ac0200c138a809a Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Fri, 17 Apr 2026 15:14:07 -0500 Subject: [PATCH 2/2] expose move_to_user --- .../Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.h b/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.h index ef2e16e78..3fa88d4f9 100644 --- a/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.h +++ b/SerialPrograms/Source/NintendoSwitch/Programs/NintendoSwitch_GameEntry.h @@ -35,6 +35,9 @@ void resume_game_from_home( bool skip_home_press = false ); +void move_to_user(ProControllerContext& context, uint8_t user_slot); +void move_to_user(JoyconContext& context, uint8_t user_slot); + void start_game_from_home( ConsoleHandle& console, ProControllerContext& context, bool tolerate_update_menu,