From bbdd2af2ce75bf972d12b05f115d4071f78c7000 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Fri, 17 Apr 2026 13:46:18 -0500 Subject: [PATCH 01/10] 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 a9b0e426e8..9a94b8864f 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 8918572232..3fdafa8d1d 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 6d95d32cde..a4faafa2c1 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 b1078b4039..8ca87b92d0 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 0000000000..1bab80c19d --- /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 0000000000..3c72cb293e --- /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 0000000000..bbfe657626 --- /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 0000000000..8ca1938521 --- /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 0000000000..9b0566660a --- /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 0000000000..733a8021c7 --- /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 0000000000..c8f02c5724 --- /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 0000000000..71e3a27889 --- /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 9a8e91ba10..0000000000 --- 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 27c230ac02..0000000000 --- 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 23f49356f2..0331db3186 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 16a4eb9a46..0995d708bf 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 02/10] 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 ef2e16e783..3fa88d4f9b 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, From d54380a4f7049973457af58c22404667591709e1 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sat, 18 Apr 2026 01:29:43 -0500 Subject: [PATCH 03/10] get working seed/advance search --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 63 +- .../Source/Pokemon/Pokemon_AdvRng.h | 15 +- .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 + .../PokemonFRLG_RngDisplays.cpp | 186 ++++++ .../RngManipulation/PokemonFRLG_RngDisplays.h | 67 ++ .../PokemonFRLG_StarterRng.cpp | 596 ++++++++++++++++++ .../RngManipulation/PokemonFRLG_StarterRng.h | 97 +++ SerialPrograms/cmake/SourceFiles.cmake | 4 + 8 files changed, 1006 insertions(+), 24 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 9a94b8864f..c97096fd02 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -11,6 +11,23 @@ namespace PokemonAutomation{ namespace Pokemon{ +void level_up_observed_pokemon(AdvObservedPokemon& pokemon, StatReads& newstats, EVs& evyield){ + uint8_t newlevel = pokemon.level.back() + 1; + pokemon.level.emplace_back(newlevel); + + pokemon.stats.emplace_back(newstats); + + EVs old_evs = pokemon.evs.back(); + EVs new_evs; + new_evs.hp = old_evs.hp + evyield.hp; + new_evs.attack = old_evs.attack + evyield.attack; + new_evs.defense = old_evs.defense + evyield.defense; + new_evs.spatk = old_evs.spatk + evyield.spatk; + new_evs.spdef = old_evs.spdef + evyield.spdef; + new_evs.speed = old_evs.speed + evyield.speed; + pokemon.evs.emplace_back(new_evs); +} + uint32_t increment_internal_rng_state(uint32_t& state){ return state * 0x41c64e6d + 0x6073; } @@ -66,19 +83,18 @@ AdvAbility ability_from_pid(uint32_t& pid){ AdvIvGroup iv_group_from_state(uint32_t& state){ AdvIvGroup ivgroup; - uint32_t remainingbits = state; + uint32_t remainingbits = state >> 16; - ivgroup.iv0 = (remainingbits & 0xfffff) % 32; - remainingbits = remainingbits >> 5; - ivgroup.iv1 = (remainingbits & 0xfffff) % 32; + ivgroup.iv0 = remainingbits & 0x1f; remainingbits = remainingbits >> 5; - ivgroup.iv2 = (remainingbits & 0xfffff) % 32; + ivgroup.iv1 = remainingbits & 0x1f; remainingbits = remainingbits >> 5; + ivgroup.iv2 = remainingbits & 0x1f; return ivgroup; } -AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = AdvRngMethod::Method1){ +AdvPokemonResult pokemon_from_state(AdvRngState& state){ uint32_t pid = pid_from_states(state.s0, state.s1); uint8_t gender = gender_value_from_pid(pid); AdvNature nature = nature_from_pid(pid); @@ -86,7 +102,7 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = Ad AdvIvGroup ivgroup1; AdvIvGroup ivgroup2; - switch(method){ + switch(state.method){ case AdvRngMethod::Method2: ivgroup1 = iv_group_from_state(state.s3); @@ -107,9 +123,9 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = Ad ivs.hp = ivgroup1.iv0; ivs.attack = ivgroup1.iv1; ivs.defense = ivgroup1.iv2; - ivs.spatk = ivgroup2.iv0; - ivs.spdef = ivgroup2.iv1; - ivs.speed = ivgroup2.iv2; + ivs.speed = ivgroup2.iv0; + ivs.spatk = ivgroup2.iv1; + ivs.spdef = ivgroup2.iv2; return {pid, gender, nature, ability, ivs}; } @@ -134,12 +150,12 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xo && (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) - && (target.ivs.spatk.low <= res.ivs.spatk && target.ivs.spatk.high >= res.ivs.spatk) - && (target.ivs.spdef.low <= res.ivs.spdef && target.ivs.spdef.high >= res.ivs.spdef) - && (target.ivs.speed.low <= res.ivs.speed && target.ivs.speed.high >= res.ivs.speed); + && ((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)) + && ((target.ivs.spatk.low <= res.ivs.spatk) && (target.ivs.spatk.high >= res.ivs.spatk)) + && ((target.ivs.spdef.low <= res.ivs.spdef) && (target.ivs.spdef.high >= res.ivs.spdef)) + && ((target.ivs.speed.low <= res.ivs.speed) && (target.ivs.speed.high >= res.ivs.speed)); } @@ -167,6 +183,10 @@ void AdvRng::set_state_advances(uint64_t advances){ state = rngstate_from_seed(seed, advances, state.method); } +AdvPokemonResult AdvRng::generate_pokemon(){ + return pokemon_from_state(state); +} + void AdvRng::search_advance_range( std::map& hits, AdvRngFilters& target, @@ -194,21 +214,24 @@ void AdvRng::search_advance_range( if ((target.method != AdvRngMethod::Any) && (target.method != method)){ continue; + }else{ + state.method = method; } for (uint64_t a=min_advances; a AdvRng::search( AdvRngFilters& target, - std::vector& seeds, + const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, @@ -343,9 +366,9 @@ void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); } -AdvRngFilters observation_to_filter(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1){ +AdvRngFilters observation_to_filter(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method){ IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; - for (int i=0; i search( AdvRngFilters& target, - std::vector& seeds, + const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, @@ -161,9 +171,6 @@ class AdvRng{ - - - } } #endif diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index b2f12f5c68..98162cbd1b 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -21,6 +21,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" #include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" +#include "Programs/RngManipulation/PokemonFRLG_StarterRng.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -67,6 +68,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); } if (PreloadSettings::instance().DEVELOPER_MODE){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp new file mode 100644 index 0000000000..883c6c438d --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -0,0 +1,186 @@ +/* RNG Displays + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include "PokemonFRLG_RngDisplays.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + +RngFilterDisplay::RngFilterDisplay() + : GroupOption("Observed Stats", LockMode::READ_ONLY) + , hp(false, "HP:", LockMode::READ_ONLY, "-", "") + , atk(false, "Attack:", LockMode::READ_ONLY, "-", "") + , def(false, "Defense:", LockMode::READ_ONLY, "-", "") + , spatk(false, "Special Attack:", LockMode::READ_ONLY, "-", "") + , spdef(false, "Special Defense:", LockMode::READ_ONLY, "-", "") + , speed(false, "Speed:", LockMode::READ_ONLY, "-", "") + , gender(false, "Gender:", LockMode::READ_ONLY, "-", "") + , nature(false, "Nature:", LockMode::READ_ONLY, "-", "") +{ + PA_ADD_STATIC(hp); + PA_ADD_STATIC(atk); + PA_ADD_STATIC(def); + PA_ADD_STATIC(spatk); + PA_ADD_STATIC(spdef); + PA_ADD_STATIC(speed); + PA_ADD_STATIC(gender); + PA_ADD_STATIC(nature); + +} +std::string RngFilterDisplay::get_range_string(const IvRange& range){ + if (range.low < 0 || range.high < 0){ + return "(invalid or unable to read)"; + } + if (range.low == range.high){ + return std::to_string(range.low); + } + return std::to_string(range.low) + " - " + std::to_string(range.high); +} +std::string RngFilterDisplay::get_gender_string(const AdvGender& gender){ + switch (gender){ + case AdvGender::Male: + return "Male"; + case AdvGender::Female: + return "Female"; + default: + return "Any"; + } +} +std::string RngFilterDisplay::get_nature_string(const AdvNature& nature){ + switch (nature){ + case AdvNature::Hardy: + return "Hardy"; + case AdvNature::Lonely: + return "Lonely"; + case AdvNature::Brave: + return "Brave"; + case AdvNature::Adamant: + return "Adamant"; + case AdvNature::Naughty: + return "Naughty"; + case AdvNature::Bold: + return "Bold"; + case AdvNature::Docile: + return "Docile"; + case AdvNature::Relaxed: + return "Relaxed"; + case AdvNature::Impish: + return "Impish"; + case AdvNature::Lax: + return "Lax"; + case AdvNature::Timid: + return "Timid"; + case AdvNature::Hasty: + return "Hasty"; + case AdvNature::Serious: + return "Serious"; + case AdvNature::Jolly: + return "Jolly"; + case AdvNature::Naive: + return "Naive"; + case AdvNature::Modest: + return "Modest"; + case AdvNature::Mild: + return "Mild"; + case AdvNature::Quiet: + return "Quiet"; + case AdvNature::Bashful: + return "Bashful"; + case AdvNature::Rash: + return "Rash"; + case AdvNature::Calm: + return "Calm"; + case AdvNature::Gentle: + return "Gentle"; + case AdvNature::Sassy: + return "Sassy"; + case AdvNature::Careful: + return "Careful"; + case AdvNature::Quirky: + return "Quirky"; + default: + return "Any"; + } +} +void RngFilterDisplay::set(const AdvRngFilters& filter){ + hp.set(get_range_string(filter.ivs.hp)); + atk.set(get_range_string(filter.ivs.attack)); + def.set(get_range_string(filter.ivs.defense)); + spatk.set(get_range_string(filter.ivs.spatk)); + spdef.set(get_range_string(filter.ivs.spdef)); + speed.set(get_range_string(filter.ivs.speed)); + gender.set(get_gender_string(filter.gender)); + nature.set(get_nature_string(filter.nature)); +} + +PossibleHitsDisplay::PossibleHitsDisplay() + : GroupOption("Possible Hits", LockMode::READ_ONLY) + , seeds(false, "Seeds:", LockMode::READ_ONLY, "-", "") + , advances(false, "Advances:", LockMode::READ_ONLY, "-", "") +{ + PA_ADD_STATIC(seeds); + PA_ADD_STATIC(advances); +} + +std::vector PossibleHitsDisplay::get_rng_states_from_map(std::map& hits_map){ + std::vector rng_states; + for(std::map::iterator it = hits_map.begin(); it != hits_map.end(); ++it) { + rng_states.emplace_back(it->first); + } + return rng_states; +} + +std::string PossibleHitsDisplay::get_seeds_string(const std::vector& rng_states){ + std::string seeds_string; + for (size_t i=0; i 0){ + seeds_string += ", "; + } + uint16_t seed = rng_states[i].seed; + std::ostringstream s; + s << std::hex << seed; + seeds_string += s.str(); + } + return seeds_string; +} +std::string PossibleHitsDisplay::get_seeds_string(std::map& hits_map){ + return get_seeds_string(get_rng_states_from_map(hits_map)); +} + +std::string PossibleHitsDisplay::get_advances_string(const std::vector& rng_states){ + std::string advances_string; + for (size_t i=0; i 0){ + advances_string += ", "; + } + uint64_t adv = rng_states[i].advance; + advances_string += std::to_string(adv); + } + return advances_string; +} +std::string PossibleHitsDisplay::get_advances_string(std::map& hits_map){ + return get_advances_string(get_rng_states_from_map(hits_map)); +} + +void PossibleHitsDisplay::set(const std::vector& rng_states){ + seeds.set(get_seeds_string(rng_states)); + advances.set(get_advances_string(rng_states)); +} +void PossibleHitsDisplay::set(std::map& hits_map){ + std::vector rng_states = get_rng_states_from_map(hits_map); + seeds.set(get_seeds_string(rng_states)); + advances.set(get_advances_string(rng_states)); +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h new file mode 100644 index 0000000000..068dcb40fe --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -0,0 +1,67 @@ +/* RNG Displays + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_RngDisplays_H +#define PokemonAutomation_PokemonFRLG_RngDisplays_H + +#include +#include "Common/Cpp/Options/StringOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "Pokemon/Pokemon_AdvRng.h" + + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + +class RngFilterDisplay : public GroupOption{ +public: + RngFilterDisplay(); + + void set(const AdvRngFilters& filter); + +private: + static std::string get_range_string(const IvRange& range); + static std::string get_gender_string(const AdvGender& gender); + static std::string get_nature_string(const AdvNature& nature); + +public: + StringOption hp; + StringOption atk; + StringOption def; + StringOption spatk; + StringOption spdef; + StringOption speed; + StringOption gender; + StringOption nature; +}; + + +class PossibleHitsDisplay : public GroupOption{ +public: + PossibleHitsDisplay(); + + void set(const std::vector& rng_states); + void set(std::map& hits_map); + +private: + static std::vector get_rng_states_from_map(std::map& hits_map); + static std::string get_seeds_string(const std::vector& rng_states); + static std::string get_seeds_string(std::map& hits_map); + static std::string get_advances_string(const std::vector& rng_states); + static std::string get_advances_string(std::map& hits_map); +public: + StringOption seeds; + StringOption advances; +}; + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp new file mode 100644 index 0000000000..61068dab2f --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -0,0 +1,596 @@ +/* Starter RNG + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#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 "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/StartupChecks/StartProgramChecks.h" +#include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/Pokemon_StatsCalculation.h" +#include "Pokemon/Pokemon_AdvRng.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/Programs/NintendoSwitch_GameEntry.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h" +#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_BlindNavigation.h" +#include "PokemonFRLG_RngNavigation.h" +#include "PokemonFRLG_HardReset.h" +#include "PokemonFRLG_StarterRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +StarterRng_Descriptor::StarterRng_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:StarterRng", + Pokemon::STRING_POKEMON + " FRLG", "Starter RNG", + "Programs/PokemonFRLG/StarterRng.html", + "Automatically calibrate timings to hit a specific RNG target.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct StarterRng_Descriptor::Stats : public StatsTracker{ + Stats() + : resets(m_stats["Resets"]) + , shinies(m_stats["Shinies"]) + , errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Resets"); + m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + std::atomic& resets; + std::atomic& shinies; + std::atomic& errors; +}; +std::unique_ptr StarterRng_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +StarterRng::StarterRng() + : LANGUAGE( + "Game Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + }, + LockMode::LOCK_WHILE_RUNNING, + true + ) + , STARTER( + "Target:
", + { + {Starter::bulbasaur, "bulbasaur", "Bulbasaur"}, + {Starter::squirtle, "squirtle", "Squirtle"}, + {Starter::charmander, "charmander", "Charmander"}, + }, + LockMode::LOCK_WHILE_RUNNING, + Starter::bulbasaur + ) + , MAX_RESETS( + "Max Resets:
", + LockMode::UNLOCK_WHILE_RUNNING, + 50, 0 // default, min + ) + , SEED( + false, + "Target Seed:", + LockMode::LOCK_WHILE_RUNNING, + "70FE", "70FE", + true + ) + , SEED_LIST( + "Nearby Seeds:
" + "This box should contain a list of seeds (in order) around and including your target seed, with one seed on each line", + LockMode::LOCK_WHILE_RUNNING, + "D000\n199A\n77A1\nAABC\n280C\n70FE\nB573\n02F2\n8084\nA533\nED1E", + "D000\n199A\n77A1\nAABC\n280C\n70FE\nB573\n02F2\n8084\nA533\nED1E", + true + ) + , SEED_BUTTON( + "Seed Button:
", + { + {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 + ) + , ADVANCES( + "Advances:
The total number of RNG advances for your target.
This should be the combined amount of continue screen and in-game advances.", + LockMode::LOCK_WHILE_RUNNING, + 10000, 600 // default, min + ) + , CONTINUE_SCREEN_FRAMES( + "Continue Screen Frames:
The number of RNG advances to pass on the continue screen.
This should be less than the total number of advances above.", + LockMode::LOCK_WHILE_RUNNING, + 1000, 192 // default, min + ) + , 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 + ) + , 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(RNG_FILTERS); + PA_ADD_OPTION(POSSIBLE_HITS); + PA_ADD_OPTION(LANGUAGE); + PA_ADD_OPTION(STARTER); + PA_ADD_OPTION(MAX_RESETS); + PA_ADD_OPTION(SEED); + PA_ADD_OPTION(SEED_LIST); + PA_ADD_OPTION(SEED_BUTTON); + PA_ADD_OPTION(SEED_DELAY); + PA_ADD_OPTION(ADVANCES); + PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); + PA_ADD_OPTION(USE_COPYRIGHT_TEXT); + PA_ADD_OPTION(PROFILE); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + + +namespace { + +uint16_t parse_seed(std::string seed_string){ + std::istringstream converter(seed_string); + uint16_t value; + converter >> std::hex >> value; + return value; +} + +std::vector parse_seed_list(std::string seed_list_string){ + std::vector seed_strings = {}; + auto ss = std::stringstream{seed_list_string}; + for (std::string line; std::getline(ss, line, '\n');){ + seed_strings.push_back(line); + } + + std::vector values; + for (size_t i=0; i list){ + for (size_t i=0; i& HISTORY, const std::vector& SEED_VALUES, const int16_t& SEED_POSITION){ + double sum = 0; + uint16_t len = 0; + for (size_t i=0; i& HISTORY, uint64_t ADVANCES){ + double sum = 0; + uint16_t len = 0; + for (size_t i=0; i( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_dpad(context, DPAD_RIGHT, 200ms, 1800ms); + } + }, + { page_two } + ); + + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "read_summary(): Failed to detect second summary screen.", + env.console + ); + } + + env.log("Reading Page 2 (Stats)..."); + VideoSnapshot screen2 = env.console.video().snapshot(); + reader.read_page2(env.logger(), screen2, stats); + + StatReads statreads = { + static_cast(stats.hp.value_or(0)), + static_cast(stats.attack.value_or(0)), + static_cast(stats.defense.value_or(0)), + static_cast(stats.sp_attack.value_or(0)), + static_cast(stats.sp_defense.value_or(0)), + static_cast(stats.speed.value_or(0)) + }; + + AdvGender gender; + switch(stats.gender.value_or(SummaryGender::Genderless)){ + case SummaryGender::Male: + gender = AdvGender::Male; + break; + case SummaryGender::Female: + gender = AdvGender::Female; + break; + default: + gender = AdvGender::Any; + break; + } + + AdvObservedPokemon pokemon = { + gender, + string_to_nature(stats.nature), + AdvAbility::Any, + { uint8_t(stats.level.value_or(5)) }, + { statreads }, + { {0,0,0,0,0,0} }, + AdvShinyType::Any + }; + + return pokemon; +} + + +void StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context, Starter STARTER){ + +} + +void StarterRng::auto_battle_rival(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon){ + // Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; + // Pokemon::StatReads stats; + + + +} + + + +void StarterRng::walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + +} + +void StarterRng::walk_home_from_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + +} + +void StarterRng::heal_at_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + +} + +void StarterRng::walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + +} + +bool StarterRng::autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon){ + Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; + Pokemon::StatReads stats; + + bool leftright = false; + pbf_move_left_joystick(context, {-1, 0}, 100ms, 0ms); + context.wait_for_all_requests(); + + while (true){ + // trigger encounter + int ret = grass_spin(env.console, context, leftright); + if (ret < 0){ + env.log("autolevel_on_route1(): failed to trigger encounter."); + return false; + } + + // auto battle + BattleResult ret2 = spam_first_move(env.console, context); + + BattleLevelUpWatcher level_up(COLOR_RED, BattleLevelUpDialog::stats); + BlackScreenWatcher black_screen(COLOR_RED); + VideoSnapshot screen; + int ret3; + BattleLevelUpReader reader; + + switch (ret2){ + case BattleResult::opponentfainted: + evyield.speed++; // always rattata or pidgey + leftright = !leftright; + + context.wait_for_all_requests(); + ret3 = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_button(context, BUTTON_B, 200ms, 2800ms); + } + }, + { level_up, black_screen } + ); + + switch (ret3){ + case 0: + screen = env.console.video().snapshot(); + stats = reader.read_stats(env.logger(), screen); + case -1: + exit_wild_battle(env.console, context, false, true); + default: + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + } + + level_up_observed_pokemon(pokemon, stats, evyield); + return true; + case BattleResult::playerfainted: + walk_to_route1_from_home(env, context); + pbf_move_left_joystick(context, {-1, 0}, 100ms, 0ms); + context.wait_for_all_requests(); + continue; + case BattleResult::outofpp: + walk_home_from_route1(env, context); + heal_at_home(env, context); + walk_to_route1_from_home(env, context); + pbf_move_left_joystick(context, {-1, 0}, 100ms, 0ms); + context.wait_for_all_requests(); + case BattleResult::unknown: + default: + return false; + } + } +} + +void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + /* + * Settings: Text Speed fast + */ + + StarterRng_Descriptor::Stats& stats = env.current_stats(); + + home_black_border_check(env.console, context); + + + const uint16_t TARGET_SEED = parse_seed(SEED); + const std::vector SEED_VALUES = parse_seed_list(SEED_LIST); + const int16_t SEED_POSITION = seed_position_in_list(TARGET_SEED, SEED_VALUES); + + if (SEED_POSITION == -1){ + // error + } + + uint64_t advances_radius = 1000; + const uint64_t MAX_ADVANCES = ADVANCES + advances_radius; + const uint64_t MIN_ADVANCES = ADVANCES - std::min(uint64_t(ADVANCES), advances_radius); + + env.log("Target Seed Value: " + std::to_string(TARGET_SEED)); + env.log("Min Advances: " + std::to_string(MIN_ADVANCES)); + env.log("Max Advances: " + std::to_string(MAX_ADVANCES)); + + BaseStats BASE_STATS; + switch (STARTER){ + case Starter::bulbasaur: + BASE_STATS = { 45, 49, 49, 65, 65, 45 }; + break; + case Starter::squirtle: + BASE_STATS = { 44, 48, 65, 50, 64, 43 }; + break; + case Starter::charmander: + BASE_STATS = { 39, 52, 43, 60, 50, 65 }; + break; + default: + break; + } + + const double FRAMERATE = 59.999977; // FPS + const double FRAME_DURATION = 1000 / FRAMERATE; + + uint64_t CONTINUE_SCREEN_FRAMES = 500; + + const uint64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -2140 : -845; // milliseconds. approximate; + double SEED_CALIBRATION = 0; + double ADVANCES_CALIBRATION = 0; + + AdvRng rng(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); + AdvPokemonResult target_result = rng.generate_pokemon(); + env.log("Target IVs:"); + env.log("HP: " + std::to_string(target_result.ivs.hp)); + env.log("Atk: " + std::to_string(target_result.ivs.attack)); + env.log("Def: " + std::to_string(target_result.ivs.defense)); + env.log("SpA: " + std::to_string(target_result.ivs.spatk)); + env.log("SpD: " + std::to_string(target_result.ivs.spdef)); + env.log("Spe: " + std::to_string(target_result.ivs.speed)); + env.log("Target PID: " + std::to_string(target_result.pid)); + + std::vector HISTORY = {}; + uint64_t resets = 0; + + while (true){ + + if (resets > MAX_RESETS){ + + break; + } + + SEED_CALIBRATION = FRAME_DURATION * get_seed_calibration_frames(HISTORY, SEED_VALUES, SEED_POSITION); + ADVANCES_CALIBRATION = get_advances_calibration_frames(HISTORY, ADVANCES); + + double CALIBRATED_ADVANCES = ADVANCES + ADVANCES_CALIBRATION; + double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES; + + uint64_t CALIBRATED_SEED_DELAY = uint64_t(std::round(SEED_DELAY + FIXED_SEED_OFFSET + SEED_CALIBRATION)); + uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * CONTINUE_SCREEN_FRAMES)); + uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * INGAME_ADVANCES)); + + if (USE_COPYRIGHT_TEXT){ + reset_and_detect_copyright_text(env.console, context, PROFILE); + perform_blind_sequence(context, PokemonFRLG_RngTarget::starters, SEED_BUTTON, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, 0, INGAME_DELAY, false); + }else{ + reset_and_perform_blind_sequence( + env.console, context, PokemonFRLG_RngTarget::starters, SEED_BUTTON, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, 0, INGAME_DELAY, false, PROFILE); + } + + bool shiny_found = check_for_shiny(env.console, context, PokemonFRLG_RngTarget::starters); + + if (shiny_found){ + // handle + } + + AdvObservedPokemon pokemon = read_summary(env, context); + AdvRngFilters filters = observation_to_filter(pokemon, BASE_STATS); + RNG_FILTERS.set(filters); + + std::map search_hits = rng.search(filters, SEED_VALUES, MIN_ADVANCES, MAX_ADVANCES, 0, 30); + env.log("Number of search hits: " + std::to_string(search_hits.size())); + POSSIBLE_HITS.set(search_hits); + + // if (search_hits.size() == 0){ + // env.log("No matches found. Resetting..."); + // }else if (search_hits.size() == 1){ + + // } + + // walk_to_rival_battle(env, context, STARTER); + // auto_battle_rival(env, context, pokemon); + + // walk_to_route1_from_lab(env, context); + + stats.resets++; + break; + } + + +} + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h new file mode 100644 index 0000000000..a47e8edae6 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -0,0 +1,97 @@ +/* Starter RNG + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_StarterRng_H +#define PokemonAutomation_PokemonFRLG_StarterRng_H + +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "Common/Cpp/Options/FloatingPointOption.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "Common/Cpp/Options/TextEditOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "CommonTools/Options/LanguageOCROption.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "Pokemon/Pokemon_StatsCalculation.h" +#include "PokemonFRLG_RngDisplays.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class StarterRng_Descriptor : public SingleSwitchProgramDescriptor{ +public: + StarterRng_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class StarterRng : public SingleSwitchProgramInstance{ +public: + StarterRng(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + enum class Starter{ + bulbasaur, + squirtle, + charmander + }; + + AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + + void update_filter_display(AdvRngFilters& filters); + void update_search_results(std::map& possible_hits); + + void walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context, Starter STARTER); + void auto_battle_rival(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon); + + void walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + void walk_home_from_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + void heal_at_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + void walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon); + + + OCR::LanguageOCROption LANGUAGE; + + EnumDropdownOption STARTER; + + SimpleIntegerOption MAX_RESETS; + + RngFilterDisplay RNG_FILTERS; + PossibleHitsDisplay POSSIBLE_HITS; + + StringOption SEED; + TextEditOption SEED_LIST; + EnumDropdownOption SEED_BUTTON; + SimpleIntegerOption SEED_DELAY; + + SimpleIntegerOptionADVANCES; + SimpleIntegerOptionCONTINUE_SCREEN_FRAMES; + + BooleanCheckBoxOption USE_COPYRIGHT_TEXT; + + SimpleIntegerOption PROFILE; + + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} +#endif + + + diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index bf2323086e..8aa34ddd53 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1520,10 +1520,14 @@ file(GLOB LIBRARY_SOURCES 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_RngDisplays.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.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/RngManipulation/PokemonFRLG_StarterRng.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp From 36da38bdc4c8af6746387d5ab2a7a62d01dccb9b Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 20 Apr 2026 22:53:38 -0500 Subject: [PATCH 04/10] implement most of the program --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 18 +- .../Source/Pokemon/Pokemon_AdvRng.h | 8 +- .../PokemonFRLG_RngDisplays.cpp | 73 +- .../RngManipulation/PokemonFRLG_RngDisplays.h | 11 +- .../PokemonFRLG_StarterRng.cpp | 677 +++++++++++++++--- .../RngManipulation/PokemonFRLG_StarterRng.h | 61 +- 6 files changed, 678 insertions(+), 170 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index c97096fd02..cd9a710d2a 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -160,34 +160,34 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xo -AdvRng::AdvRng(uint16_t seed, AdvRngState state) +AdvRngSearcher::AdvRngSearcher(uint16_t seed, AdvRngState state) : seed(seed) , state(state) {} -AdvRng::AdvRng(uint16_t seed, uint64_t min_advances, AdvRngMethod method) +AdvRngSearcher::AdvRngSearcher(uint16_t seed, uint64_t min_advances, AdvRngMethod method) : seed(seed) , state(rngstate_from_seed(seed, min_advances, method)) {} -void AdvRng::advance_state(){ +void AdvRngSearcher::advance_state(){ advance_rng_state(state); } -void AdvRng::set_seed(uint16_t newseed){ +void AdvRngSearcher::set_seed(uint16_t newseed){ seed = newseed; state = rngstate_from_seed(seed, 0, state.method); } -void AdvRng::set_state_advances(uint64_t advances){ +void AdvRngSearcher::set_state_advances(uint64_t advances){ state = rngstate_from_seed(seed, advances, state.method); } -AdvPokemonResult AdvRng::generate_pokemon(){ +AdvPokemonResult AdvRngSearcher::generate_pokemon(){ return pokemon_from_state(state); } -void AdvRng::search_advance_range( +void AdvRngSearcher::search_advance_range( std::map& hits, AdvRngFilters& target, uint64_t min_advances, @@ -229,7 +229,7 @@ void AdvRng::search_advance_range( } } -std::map AdvRng::search( +std::map AdvRngSearcher::search( AdvRngFilters& target, const std::vector& seeds, uint64_t min_advances, @@ -366,7 +366,7 @@ void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); } -AdvRngFilters observation_to_filter(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method){ +AdvRngFilters observation_to_filters(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method){ IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; for (size_t i=0; iHP:", LockMode::READ_ONLY, "-", "") - , atk(false, "Attack:", LockMode::READ_ONLY, "-", "") - , def(false, "Defense:", LockMode::READ_ONLY, "-", "") - , spatk(false, "Special Attack:", LockMode::READ_ONLY, "-", "") - , spdef(false, "Special Defense:", LockMode::READ_ONLY, "-", "") - , speed(false, "Speed:", LockMode::READ_ONLY, "-", "") + , hp(false, "HP IV:", LockMode::READ_ONLY, "-", "") + , atk(false, "Attack IV:", LockMode::READ_ONLY, "-", "") + , def(false, "Defense IV:", LockMode::READ_ONLY, "-", "") + , spatk(false, "Special Attack IV:", LockMode::READ_ONLY, "-", "") + , spdef(false, "Special Defense IV:", LockMode::READ_ONLY, "-", "") + , speed(false, "Speed IV:", LockMode::READ_ONLY, "-", "") , gender(false, "Gender:", LockMode::READ_ONLY, "-", "") , nature(false, "Nature:", LockMode::READ_ONLY, "-", "") { @@ -122,13 +122,22 @@ void RngFilterDisplay::set(const AdvRngFilters& filter){ nature.set(get_nature_string(filter.nature)); } +void RngFilterDisplay::reset(){ + hp.set("-"); + atk.set("-"); + def.set("-"); + spatk.set("-"); + spdef.set("-"); + speed.set("-"); + gender.set("-"); + nature.set("-"); +} + PossibleHitsDisplay::PossibleHitsDisplay() : GroupOption("Possible Hits", LockMode::READ_ONLY) - , seeds(false, "Seeds:", LockMode::READ_ONLY, "-", "") - , advances(false, "Advances:", LockMode::READ_ONLY, "-", "") + , hits(false, "Seeds/Advances:", LockMode::READ_ONLY, "-", "") { - PA_ADD_STATIC(seeds); - PA_ADD_STATIC(advances); + PA_ADD_STATIC(hits); } std::vector PossibleHitsDisplay::get_rng_states_from_map(std::map& hits_map){ @@ -139,46 +148,36 @@ std::vector PossibleHitsDisplay::get_rng_states_from_map(std::map& rng_states){ - std::string seeds_string; +std::string PossibleHitsDisplay::get_hits_string(const std::vector& rng_states){ + std::string hits_string; for (size_t i=0; i 0){ - seeds_string += ", "; + hits_string += ", "; } - uint16_t seed = rng_states[i].seed; + AdvRngState hit = rng_states[i]; + uint16_t seed = hit.seed; std::ostringstream s; s << std::hex << seed; - seeds_string += s.str(); + hits_string += s.str(); + hits_string += "/"; + hits_string += std::to_string(hit.advance); } - return seeds_string; + return hits_string; } -std::string PossibleHitsDisplay::get_seeds_string(std::map& hits_map){ - return get_seeds_string(get_rng_states_from_map(hits_map)); -} - -std::string PossibleHitsDisplay::get_advances_string(const std::vector& rng_states){ - std::string advances_string; - for (size_t i=0; i 0){ - advances_string += ", "; - } - uint64_t adv = rng_states[i].advance; - advances_string += std::to_string(adv); - } - return advances_string; -} -std::string PossibleHitsDisplay::get_advances_string(std::map& hits_map){ - return get_advances_string(get_rng_states_from_map(hits_map)); +std::string PossibleHitsDisplay::get_hits_string(std::map& hits_map){ + return get_hits_string(get_rng_states_from_map(hits_map)); } void PossibleHitsDisplay::set(const std::vector& rng_states){ - seeds.set(get_seeds_string(rng_states)); - advances.set(get_advances_string(rng_states)); + hits.set(get_hits_string(rng_states)); } void PossibleHitsDisplay::set(std::map& hits_map){ std::vector rng_states = get_rng_states_from_map(hits_map); - seeds.set(get_seeds_string(rng_states)); - advances.set(get_advances_string(rng_states)); + set(rng_states); +} + +void PossibleHitsDisplay::reset(){ + hits.set("-"); } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index 068dcb40fe..bc375d56cf 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -25,6 +25,7 @@ class RngFilterDisplay : public GroupOption{ RngFilterDisplay(); void set(const AdvRngFilters& filter); + void reset(); private: static std::string get_range_string(const IvRange& range); @@ -49,16 +50,14 @@ class PossibleHitsDisplay : public GroupOption{ void set(const std::vector& rng_states); void set(std::map& hits_map); + void reset(); private: static std::vector get_rng_states_from_map(std::map& hits_map); - static std::string get_seeds_string(const std::vector& rng_states); - static std::string get_seeds_string(std::map& hits_map); - static std::string get_advances_string(const std::vector& rng_states); - static std::string get_advances_string(std::map& hits_map); + static std::string get_hits_string(const std::vector& rng_states); + static std::string get_hits_string(std::map& hits_map); public: - StringOption seeds; - StringOption advances; + StringOption hits; }; } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 61068dab2f..183258affc 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -20,12 +20,15 @@ #include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Pokemon_AdvRng.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" #include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h" #include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" #include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h" #include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" @@ -56,14 +59,20 @@ struct StarterRng_Descriptor::Stats : public StatsTracker{ Stats() : resets(m_stats["Resets"]) , shinies(m_stats["Shinies"]) + , nonshiny(m_stats["Non-Shiny Hits"]) + , wildshinies(m_stats["Wild Shinies"]) , errors(m_stats["Errors"]) { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Non-Shiny Hits, HIDDEN_IF_ZERO"); + m_display_order.emplace_back("Wild Shinies, HIDDEN_IF_ZERO"); m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; std::atomic& shinies; + std::atomic& nonshiny; + std::atomic& wildshinies; std::atomic& errors; }; std::unique_ptr StarterRng_Descriptor::make_stats() const{ @@ -127,7 +136,7 @@ StarterRng::StarterRng() , 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 + 31338, 28000 // default, min ) , ADVANCES( "Advances:
The total number of RNG advances for your target.
This should be the combined amount of continue screen and in-game advances.", @@ -144,6 +153,11 @@ StarterRng::StarterRng() LockMode::LOCK_WHILE_RUNNING, true // default ) + , IGNORE_WILD_SHINIES( + "Ignore wild shinies
Do not stop the program when a wild shiny is encountered.", + 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.
" @@ -190,23 +204,59 @@ StarterRng::StarterRng() namespace { -uint16_t parse_seed(std::string seed_string){ +void check_seed_validity(SingleSwitchProgramEnvironment& env, std::string seed_string){ + static const std::map MAP{ + {'1', '1'}, + {'2', '2'}, + {'3', '3'}, + {'4', '4'}, + {'5', '5'}, + {'6', '6'}, + {'7', '7'}, + {'8', '8'}, + {'9', '9'}, + {'0', '0'}, + {'A', 'A'}, {'a', 'A'}, + {'B', 'B'}, {'b', 'B'}, + {'C', 'C'}, {'c', 'C'}, + {'D', 'D'}, {'d', 'D'}, + {'E', 'E'}, {'e', 'E'}, + {'F', 'F'}, {'f', 'F'} + }; + + if (seed_string.size() != 4){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "StarterRng(): Invalid seed length. Seeds should be 4 characters.", + env.console + ); + } + + for (char ch : seed_string){ + auto iter = MAP.find(ch); + if (iter == MAP.end()){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "StarterRng(): Invalid seed character. Seeds should be hex strings (valid characters are 0-9 and A-F).", + env.console + ); + } + } +} + +uint16_t parse_seed(SingleSwitchProgramEnvironment& env, std::string seed_string){ + check_seed_validity(env, seed_string); std::istringstream converter(seed_string); uint16_t value; converter >> std::hex >> value; return value; } -std::vector parse_seed_list(std::string seed_list_string){ - std::vector seed_strings = {}; +std::vector parse_seed_list(SingleSwitchProgramEnvironment& env, std::string seed_list_string){ + std::vector values; auto ss = std::stringstream{seed_list_string}; for (std::string line; std::getline(ss, line, '\n');){ - seed_strings.push_back(line); - } - - std::vector values; - for (size_t i=0; i& HISTORY, const std::vector& SEED_VALUES, const int16_t& SEED_POSITION){ - double sum = 0; - uint16_t len = 0; - for (size_t i=0; i& HISTORY, uint64_t ADVANCES){ - double sum = 0; - uint16_t len = 0; - for (size_t i=0; i StarterRng::get_starter_search_results( + SingleSwitchProgramEnvironment& env, + AdvRngSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + uint64_t& advances_radius, + AdvObservedPokemon& pokemon +){ + std::map search_hits; + for (int i=0; i<2; i++){ + uint64_t min_adv = ADVANCES - std::min(uint64_t(ADVANCES), advances_radius); + uint64_t max_adv = ADVANCES + advances_radius; + search_hits = searcher.search(filters, SEED_VALUES, min_adv, max_adv, 0, 30); + if (search_hits.size() > 0){ + env.log("Number of search hits: " + std::to_string(search_hits.size())); + POSSIBLE_HITS.set(search_hits); + return search_hits; + } + } + env.log("Number of search hits: " + std::to_string(search_hits.size())); + POSSIBLE_HITS.set(search_hits); + return search_hits; +} +double StarterRng::get_seed_calibration_frames( + StarterRngCalibrationHistory& HISTORY, + const std::vector& SEED_VALUES, + const int16_t& SEED_POSITION +){ + double sum = 0; + uint16_t len = 0; + for (size_t i=0; i& search_hits +){ + if (search_hits.size() == 0){ + env.log("No matches found."); + return true; + } + + std::vector pids; + for(std::map::iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { + pids.emplace_back(it->second.pid); + } + std::sort(pids.begin(), pids.end()); + std::vector::iterator iter; + iter = std::unique(pids.begin(), pids.begin() + pids.size()); + pids.resize(std::distance(pids.begin(), iter)); + + size_t num_unique = pids.size(); // for the same method, equal PIDs will have equal stats + + if (num_unique == 1){ + AdvRngState hit = search_hits.begin()->first; + if (search_hits.size() > 1){ + // for identical candidates, use the closest hit to the target advances + int64_t best_dist = hit.advance - ADVANCES; + best_dist = std::abs(best_dist); + for(std::map::iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { + AdvRngState test_hit = it->first; + int64_t dist = test_hit.advance - ADVANCES; + dist = std::abs(dist); + if (dist < best_dist){ + hit = test_hit; + best_dist = dist; + } + } + } + env.log("Single search match found: " + std::to_string(hit.seed) + " / " + std::to_string(hit.advance)); + env.log("Updating calibrations..."); + HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); + HISTORY.advance_calibrations.emplace_back(ADVANCES_CALIBRATION); + HISTORY.results.emplace_back(hit); + if (HISTORY.results.size() > MAX_HISTORY_LENGTH){ + HISTORY.seed_calibrations.erase(HISTORY.seed_calibrations.begin()); + HISTORY.advance_calibrations.erase(HISTORY.advance_calibrations.begin()); + HISTORY.results.erase(HISTORY.results.begin()); + } + return true; + }else{ + return false; + } } -void StarterRng::walk_home_from_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +bool StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // return to the overworld + pbf_mash_button(context, BUTTON_B, 5000ms); + int num_steps_to_the_left; + switch(STARTER){ + case Starter::bulbasaur: + num_steps_to_the_left = 2; + break; + case Starter::squirtle: + num_steps_to_the_left = 3; + break; + case Starter::charmander: + num_steps_to_the_left = 4; + break; + default: + num_steps_to_the_left = 2; + } + + // line up with the doorway + pbf_move_left_joystick(context, {-1, 0}, 40ms, 460ms); // pivot left + for (int i=0; i( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 20000ms, 0ms); + ssf_mash1_button(context, BUTTON_B, 20000ms); + } + }, + { black_screen } + ); + + return (ret < 0); +} + +bool StarterRng::auto_battle_rival( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + BaseStats& BASE_STATS +){ + Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; + switch(STARTER){ + case Starter::bulbasaur: + evyield.speed = 1; // from charmander + break; + case Starter::squirtle: + evyield.spatk = 1; // from bulbasaur + break; + case Starter::charmander: + evyield.defense = 1; // from squirtle + } + + // detect the battle menu + BattleMenuWatcher battle_ready(COLOR_RED); + context.wait_for_all_requests(); + int ret1 = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_B, 30s); + }, + { battle_ready } + ); + if (ret1 < 0){ + env.log("auto_battle_rival(): failed to detect the battle menu."); + return true; + } + env.log("Battle started. Using first move..."); + + // perform the first move and get through Oak's dialogue, + // which messes up most detectors when it dims the screen + pbf_mash_button(context, BUTTON_A, 1000ms); // execute first move + context.wait_for_all_requests(); + int ret2 = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_B, 30s); + }, + { battle_ready } + ); + if (ret2 < 0){ + env.log("auto_battle_rival(): failed to detect the battle menu."); + return true; + } + env.log("Oak tutorial dialogue finished. Mashing A..."); + + // mash A until somebody faints + BattleOpponentFaintWatcher player_won(COLOR_RED); + BattleFaintWatcher player_lost(COLOR_RED); + context.wait_for_all_requests(); + int ret3 = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_A, 300s); + }, + { player_won, player_lost } + ); + + switch(ret3){ + case 0: + env.log("Won battle against rival. Watching for level-up stats..."); + break; + case 1: + env.log("Lost battle against rival."); + pbf_mash_button(context, BUTTON_B, 20s); // exit battle and dialogue + return false; + default: + env.log("auto_battle_rival(): no fainting detected with 5 minutes."); + return true; + } + + // slowly advance dialog until level-up stats are visible + BattleLevelUpWatcher level_up_stats(COLOR_RED, BattleLevelUpDialog::stats); + BlackScreenWatcher black_screen(COLOR_RED); + context.wait_for_all_requests(); + int ret4 = run_until( + env.console, context, + [](ProControllerContext& context) { + for(int i=0; i<60; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + } + }, + { level_up_stats, black_screen } + ); + + switch(ret4){ + case 0: + env.log("Level-up stats detected."); + break; + case 1: + env.log("Battle exited without detecting level-up stats"); + return true; // will cause issues with keeping track of level and EVs + default: + env.log("auto_battle_rival(): no recognized state within 2 minutes of winning battle."); + return true; + } + + // read stats + BattleLevelUpReader reader(COLOR_RED); + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + env.log("Reading stats..."); + VideoSnapshot screen = env.console.video().snapshot(); + StatReads stats = reader.read_stats(env.logger(), screen); + + update_filters(pokemon, stats, evyield, BASE_STATS); + + // exit battle + pbf_mash_button(context, BUTTON_B, 20s); + context.wait_for_all_requests(); + + return false; } -void StarterRng::heal_at_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + +bool StarterRng::walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // exit the lab + env.log("Exiting the lab..."); + BlackScreenWatcher black_screen(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_move_left_joystick(context, {0, -1}, 10s, 0ms); + }, + { black_screen } + ); + + if (ret < 0){ + env.log("walk_to_route1_from_lab(): failed to exit lab."); + return true; + } + + env.log("Lab exited. Walking to Route 1..."); + pbf_wait(context, 5000ms); + pbf_move_left_joystick(context, {-1, 0}, 1280ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 3150ms, 300ms); + pbf_move_left_joystick(context, {+1, 0}, 330ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 720ms, 300ms); + context.wait_for_all_requests(); + return false; } -void StarterRng::walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +bool StarterRng::walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // exit the house + env.log("Exiting the house..."); + BlackScreenWatcher black_screen(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_move_left_joystick(context, {0, -1}, 1000ms, 300ms); + pbf_move_left_joystick(context, {-1, 0}, 900ms, 300ms); + pbf_move_left_joystick(context, {0, -1}, 1000ms, 300ms); + }, + { black_screen } + ); + if (ret < 0){ + env.log("walk_to_route1_from_home(): failed to exit the house."); + return true; + } + + env.log("House exited. Walking to Route 1..."); + pbf_wait(context, 5s); + pbf_move_left_joystick(context, {+1, 0}, 1370ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 1450ms, 300ms); + pbf_move_left_joystick(context, {+1, 0}, 300ms, 300ms); + pbf_move_left_joystick(context, {+1, 0}, 1250ms, 300ms); + + return false; } -bool StarterRng::autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon){ +int StarterRng::autolevel_on_route1( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + BaseStats& BASE_STATS +){ Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; Pokemon::StatReads stats; + env.log("Arrived at Route 1."); bool leftright = false; pbf_move_left_joystick(context, {-1, 0}, 100ms, 0ms); context.wait_for_all_requests(); while (true){ // trigger encounter + env.log("Triggering wild encounters..."); int ret = grass_spin(env.console, context, leftright); if (ret < 0){ env.log("autolevel_on_route1(): failed to trigger encounter."); return false; } + env.log("Encounter detected!"); + if (ret == 1){ + return 1; + } // auto battle BattleResult ret2 = spam_first_move(env.console, context); @@ -419,6 +773,7 @@ bool StarterRng::autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProCon BlackScreenWatcher black_screen(COLOR_RED); VideoSnapshot screen; int ret3; + bool failed; BattleLevelUpReader reader; switch (ret2){ @@ -439,6 +794,7 @@ bool StarterRng::autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProCon switch (ret3){ case 0: + env.log("Level-up stats detected. Reading stats..."); screen = env.console.video().snapshot(); stats = reader.read_stats(env.logger(), screen); case -1: @@ -448,22 +804,24 @@ bool StarterRng::autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProCon context.wait_for_all_requests(); } - level_up_observed_pokemon(pokemon, stats, evyield); - return true; + update_filters(pokemon, stats, evyield, BASE_STATS); + return 0; case BattleResult::playerfainted: - walk_to_route1_from_home(env, context); + env.log("Pokemon fainted. Mashing B through dialogues..."); + pbf_mash_button(context, BUTTON_B, 60s); // skip through a few transitions and lots of dialogue + failed = walk_to_route1_from_home(env, context); + if (failed){ + return -1; + } pbf_move_left_joystick(context, {-1, 0}, 100ms, 0ms); context.wait_for_all_requests(); continue; case BattleResult::outofpp: - walk_home_from_route1(env, context); - heal_at_home(env, context); - walk_to_route1_from_home(env, context); - pbf_move_left_joystick(context, {-1, 0}, 100ms, 0ms); - context.wait_for_all_requests(); + // give up and reset if it's taking this long + return false; case BattleResult::unknown: default: - return false; + return -1; } } } @@ -478,21 +836,15 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte home_black_border_check(env.console, context); - const uint16_t TARGET_SEED = parse_seed(SEED); - const std::vector SEED_VALUES = parse_seed_list(SEED_LIST); + const uint16_t TARGET_SEED = parse_seed(env, SEED); + const std::vector SEED_VALUES = parse_seed_list(env, SEED_LIST); const int16_t SEED_POSITION = seed_position_in_list(TARGET_SEED, SEED_VALUES); if (SEED_POSITION == -1){ // error } - uint64_t advances_radius = 1000; - const uint64_t MAX_ADVANCES = ADVANCES + advances_radius; - const uint64_t MIN_ADVANCES = ADVANCES - std::min(uint64_t(ADVANCES), advances_radius); - env.log("Target Seed Value: " + std::to_string(TARGET_SEED)); - env.log("Min Advances: " + std::to_string(MIN_ADVANCES)); - env.log("Max Advances: " + std::to_string(MAX_ADVANCES)); BaseStats BASE_STATS; switch (STARTER){ @@ -515,11 +867,11 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte uint64_t CONTINUE_SCREEN_FRAMES = 500; const uint64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -2140 : -845; // milliseconds. approximate; - double SEED_CALIBRATION = 0; + double SEED_CALIBRATION_FRAMES = 0; double ADVANCES_CALIBRATION = 0; - AdvRng rng(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); - AdvPokemonResult target_result = rng.generate_pokemon(); + AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); + AdvPokemonResult target_result = searcher.generate_pokemon(); env.log("Target IVs:"); env.log("HP: " + std::to_string(target_result.ivs.hp)); env.log("Atk: " + std::to_string(target_result.ivs.attack)); @@ -527,65 +879,182 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte env.log("SpA: " + std::to_string(target_result.ivs.spatk)); env.log("SpD: " + std::to_string(target_result.ivs.spdef)); env.log("Spe: " + std::to_string(target_result.ivs.speed)); - env.log("Target PID: " + std::to_string(target_result.pid)); - std::vector HISTORY = {}; + StarterRngCalibrationHistory HISTORY; + uint8_t MAX_HISTORY_LENGTH = 5; + uint64_t advances_radius = 2048; uint64_t resets = 0; + bool wildshiny_found = false; while (true){ + if (have_hit_target(env, TARGET_SEED, HISTORY.results.back())){ + env.log("Target Hit!"); + stats.nonshiny++; + break; + } if (resets > MAX_RESETS){ + env.log("Max resets reached."); + break; + } + if (wildshiny_found){ break; } - SEED_CALIBRATION = FRAME_DURATION * get_seed_calibration_frames(HISTORY, SEED_VALUES, SEED_POSITION); + if (advances_radius > 4) { + advances_radius = advances_radius / 2; + } + + SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(HISTORY, SEED_VALUES, SEED_POSITION); ADVANCES_CALIBRATION = get_advances_calibration_frames(HISTORY, ADVANCES); + env.log("Seed calibration (frames): " + std::to_string(SEED_CALIBRATION_FRAMES)); + env.log("Advance calibration (frames / 2): " + std::to_string(ADVANCES_CALIBRATION)); double CALIBRATED_ADVANCES = ADVANCES + ADVANCES_CALIBRATION; double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES; + double continue_screen_tweak = 0; + + if (HISTORY.results.size() > 0){ + double prev_advance_miss = double(HISTORY.results.back().advance - ADVANCES); + if (prev_advance_miss != 0 && std::abs(prev_advance_miss) < 2){ + if (prev_advance_miss > 0){ + continue_screen_tweak = -1; + INGAME_ADVANCES += 1; + }else{ + continue_screen_tweak = 1; + INGAME_ADVANCES -= 1; + } + } + }; - uint64_t CALIBRATED_SEED_DELAY = uint64_t(std::round(SEED_DELAY + FIXED_SEED_OFFSET + SEED_CALIBRATION)); - uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * CONTINUE_SCREEN_FRAMES)); - uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * INGAME_ADVANCES)); + uint64_t CALIBRATED_SEED_DELAY = uint64_t(std::round(SEED_DELAY + FIXED_SEED_OFFSET + FRAME_DURATION * SEED_CALIBRATION_FRAMES)); + uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * (CONTINUE_SCREEN_FRAMES + continue_screen_tweak))); + uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * INGAME_ADVANCES / 2)); + env.log("Resetting Game..."); if (USE_COPYRIGHT_TEXT){ reset_and_detect_copyright_text(env.console, context, PROFILE); + env.log("Starting blind button presses."); perform_blind_sequence(context, PokemonFRLG_RngTarget::starters, SEED_BUTTON, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, 0, INGAME_DELAY, false); }else{ reset_and_perform_blind_sequence( env.console, context, PokemonFRLG_RngTarget::starters, SEED_BUTTON, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, 0, INGAME_DELAY, false, PROFILE); } + stats.resets++; + + RNG_FILTERS.reset(); + POSSIBLE_HITS.reset(); bool shiny_found = check_for_shiny(env.console, context, PokemonFRLG_RngTarget::starters); if (shiny_found){ - // handle + 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; } AdvObservedPokemon pokemon = read_summary(env, context); - AdvRngFilters filters = observation_to_filter(pokemon, BASE_STATS); + AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::map search_hits = rng.search(filters, SEED_VALUES, MIN_ADVANCES, MAX_ADVANCES, 0, 30); - env.log("Number of search hits: " + std::to_string(search_hits.size())); - POSSIBLE_HITS.set(search_hits); + std::map search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); + bool finished = update_calibration_history(env, HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, search_hits); + if (finished){ + env.log("RNG search finished."); + continue; + } - // if (search_hits.size() == 0){ - // env.log("No matches found. Resetting..."); - // }else if (search_hits.size() == 1){ + bool failed = walk_to_rival_battle(env, context); + if (failed){ + stats.errors++; + env.log("Failed to initiate rival battle."); + continue; // reset game + } - // } + failed = auto_battle_rival(env, context, pokemon, BASE_STATS); + if (failed){ + stats.errors++; + continue; // reset game + } + if (pokemon.level.size() > 1){ + search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); + finished = update_calibration_history(env, HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, search_hits); + if (finished){ + env.log("RNG search finished."); + continue; + } + } - // walk_to_rival_battle(env, context, STARTER); - // auto_battle_rival(env, context, pokemon); + failed = walk_to_route1_from_lab(env, context); + if (failed){ + stats.errors++; + continue; // reset game + } - // walk_to_route1_from_lab(env, context); + auto num_levels = pokemon.level.size(); + int MAX_LEVELS = 5; + while(true){ + if (num_levels >= MAX_LEVELS){ + env.log("RNG search not complete after 5 level-ups."); + break; + } + + int ret2 = autolevel_on_route1(env, context, pokemon, BASE_STATS); + if (ret2 < 0){ + env.log("Error encountered while auto-leveling."); + stats.errors++; + break; + }else if(ret2 == 1){ + env.log("Wild shiny found!"); + stats.wildshinies++; + send_program_notification( + env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Wild Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); + if (TAKE_VIDEO){ + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } + if (!IGNORE_WILD_SHINIES){ + wildshiny_found = true; + break; + } + } + + if (pokemon.level.size() > num_levels){ + num_levels = pokemon.level.size(); + search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); + finished = update_calibration_history(env, HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, search_hits); + if (finished){ + env.log("RNG search finished."); + break; + } + } + } - stats.resets++; - break; } + 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_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index a47e8edae6..c9f8ad36c0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -16,6 +16,7 @@ #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" #include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" #include "Pokemon/Pokemon_StatsCalculation.h" +#include "Pokemon/Pokemon_AdvRng.h" #include "PokemonFRLG_RngDisplays.h" namespace PokemonAutomation{ @@ -45,19 +46,57 @@ class StarterRng : public SingleSwitchProgramInstance{ charmander }; - AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + struct StarterRngCalibrationHistory{ + std::vector seed_calibrations; + std::vector advance_calibrations; + std::vector results; + }; - void update_filter_display(AdvRngFilters& filters); - void update_search_results(std::map& possible_hits); + bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t TARGET_SEED, AdvRngState& hit); - void walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context, Starter STARTER); - void auto_battle_rival(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon); + AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - void walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - void walk_home_from_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - void heal_at_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - void walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - bool autolevel_on_route1(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon); + AdvRngFilters update_filters(AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS); + std::map get_starter_search_results( + SingleSwitchProgramEnvironment& env, + AdvRngSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + uint64_t& advances_radius, + AdvObservedPokemon& pokemon + ); + double get_seed_calibration_frames( + StarterRngCalibrationHistory& HISTORY, + const std::vector& SEED_VALUES, + const int16_t& SEED_POSITION + ); + double get_advances_calibration_frames(StarterRngCalibrationHistory& HISTORY, uint64_t ADVANCES); + bool update_calibration_history( + SingleSwitchProgramEnvironment& env, + StarterRngCalibrationHistory& HISTORY, + const uint16_t& MAX_HISTORY_LENGTH, + double& SEED_CALIBRATION_FRAMES, + double& ADVANCES_CALIBRATION, + std::map& search_hits + ); + + bool walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool auto_battle_rival( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + BaseStats& BASE_STATS + ); + + bool walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + int autolevel_on_route1( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + BaseStats& BASE_STATS + ); OCR::LanguageOCROption LANGUAGE; @@ -79,6 +118,8 @@ class StarterRng : public SingleSwitchProgramInstance{ BooleanCheckBoxOption USE_COPYRIGHT_TEXT; + BooleanCheckBoxOption IGNORE_WILD_SHINIES; + SimpleIntegerOption PROFILE; BooleanCheckBoxOption TAKE_VIDEO; From 33824d076e239c7448ef6cb17d51cc2d218ad9ae Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Tue, 21 Apr 2026 17:17:27 -0500 Subject: [PATCH 05/10] fixes --- .../PokemonFRLG_StarterRng.cpp | 309 ++++++++++++------ .../RngManipulation/PokemonFRLG_StarterRng.h | 26 +- 2 files changed, 233 insertions(+), 102 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 183258affc..a14b766af9 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -65,8 +65,8 @@ struct StarterRng_Descriptor::Stats : public StatsTracker{ { m_display_order.emplace_back("Resets"); m_display_order.emplace_back("Shinies"); - m_display_order.emplace_back("Non-Shiny Hits, HIDDEN_IF_ZERO"); - m_display_order.emplace_back("Wild Shinies, HIDDEN_IF_ZERO"); + m_display_order.emplace_back("Non-Shiny Hits", HIDDEN_IF_ZERO); + m_display_order.emplace_back("Wild Shinies", HIDDEN_IF_ZERO); m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& resets; @@ -141,13 +141,13 @@ StarterRng::StarterRng() , ADVANCES( "Advances:
The total number of RNG advances for your target.
This should be the combined amount of continue screen and in-game advances.", LockMode::LOCK_WHILE_RUNNING, - 10000, 600 // default, min - ) - , CONTINUE_SCREEN_FRAMES( - "Continue Screen Frames:
The number of RNG advances to pass on the continue screen.
This should be less than the total number of advances above.", - LockMode::LOCK_WHILE_RUNNING, - 1000, 192 // default, min + 10000, 600, 1000000000 // default, min ) + // , CONTINUE_SCREEN_FRAMES( + // "Continue Screen Frames:
The number of RNG advances to pass on the continue screen.
This should be less than the total number of advances above.", + // LockMode::LOCK_WHILE_RUNNING, + // 1000, 192 // default, min + // ) , 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, @@ -193,7 +193,7 @@ StarterRng::StarterRng() PA_ADD_OPTION(SEED_BUTTON); PA_ADD_OPTION(SEED_DELAY); PA_ADD_OPTION(ADVANCES); - PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); + // PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); PA_ADD_OPTION(USE_COPYRIGHT_TEXT); PA_ADD_OPTION(PROFILE); PA_ADD_OPTION(TAKE_VIDEO); @@ -394,9 +394,10 @@ std::map StarterRng::get_starter_search_results( AdvObservedPokemon& pokemon ){ std::map search_hits; - for (int i=0; i<2; i++){ - uint64_t min_adv = ADVANCES - std::min(uint64_t(ADVANCES), advances_radius); - uint64_t max_adv = ADVANCES + advances_radius; + for (int i=0; i<4; i++){ + uint64_t adv_radius = advances_radius * (uint64_t(1) << i); + uint64_t min_adv = ADVANCES - std::min(uint64_t(ADVANCES), adv_radius); + uint64_t max_adv = ADVANCES + adv_radius; search_hits = searcher.search(filters, SEED_VALUES, min_adv, max_adv, 0, 30); if (search_hits.size() > 0){ env.log("Number of search hits: " + std::to_string(search_hits.size())); @@ -419,7 +420,7 @@ double StarterRng::get_seed_calibration_frames( for (size_t i=0; i& search_hits + int64_t& CONTINUE_SCREEN_ADJUSTMENT, + std::map& search_hits, + bool force_finish ){ + const int MAX_ADVANCE_POSSIBILITIES = 20; + const uint32_t ADVANCE_RADIUS = 2; + if (search_hits.size() == 0){ env.log("No matches found."); return true; } + + if (!force_finish && search_hits.size() > MAX_ADVANCE_POSSIBILITIES){ + return false; + } + + if (search_hits.size() == 1){ + env.log("Updating calibrations..."); + CALIBRATION_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); + CALIBRATION_HISTORY.advance_calibrations.emplace_back(ADVANCES_CALIBRATION); + CALIBRATION_HISTORY.continue_screen_adjustments.emplace_back(CONTINUE_SCREEN_ADJUSTMENT); + CALIBRATION_HISTORY.results.emplace_back(search_hits.begin()->first); + if (CALIBRATION_HISTORY.results.size() > MAX_HISTORY_LENGTH){ + CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); + CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); + CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); + CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); + } + ADVANCE_HISTORY.results.clear(); + ADVANCE_HISTORY.seed_calibrations.clear(); + return true; + } - std::vector pids; + std::vector advances; + std::vector hits; for(std::map::iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { - pids.emplace_back(it->second.pid); + advances.emplace_back(it->first.advance); + hits.emplace_back(it->first); } - std::sort(pids.begin(), pids.end()); - std::vector::iterator iter; - iter = std::unique(pids.begin(), pids.begin() + pids.size()); - pids.resize(std::distance(pids.begin(), iter)); - - size_t num_unique = pids.size(); // for the same method, equal PIDs will have equal stats - if (num_unique == 1){ - AdvRngState hit = search_hits.begin()->first; - if (search_hits.size() > 1){ - // for identical candidates, use the closest hit to the target advances - int64_t best_dist = hit.advance - ADVANCES; - best_dist = std::abs(best_dist); - for(std::map::iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { - AdvRngState test_hit = it->first; - int64_t dist = test_hit.advance - ADVANCES; - dist = std::abs(dist); - if (dist < best_dist){ - hit = test_hit; - best_dist = dist; + // get unique advances + std::sort(advances.begin(), advances.end()); + std::vector::iterator iter; + iter = std::unique(advances.begin(), advances.begin() + advances.size()); + advances.resize(std::distance(advances.begin(), iter)); + + ADVANCE_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); + ADVANCE_HISTORY.results.emplace_back(hits); + + // check advance history for repeated values + std::vector counts; + uint64_t best = 0; + uint64_t mode = 0; + bool tie = false; + for (uint64_t& adv : advances){ + uint64_t count = 0; + for (auto& res : ADVANCE_HISTORY.results){ + for (auto& state : res){ + if (std::abs(int64_t(state.advance) - int64_t(adv)) <= ADVANCE_RADIUS){ + count++; + break; // only count one possible hit from each attempt } } } - env.log("Single search match found: " + std::to_string(hit.seed) + " / " + std::to_string(hit.advance)); - env.log("Updating calibrations..."); - HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); - HISTORY.advance_calibrations.emplace_back(ADVANCES_CALIBRATION); - HISTORY.results.emplace_back(hit); - if (HISTORY.results.size() > MAX_HISTORY_LENGTH){ - HISTORY.seed_calibrations.erase(HISTORY.seed_calibrations.begin()); - HISTORY.advance_calibrations.erase(HISTORY.advance_calibrations.begin()); - HISTORY.results.erase(HISTORY.results.begin()); + if (count > best){ + mode = adv; + best = count; + tie = false; + }else if (count == best){ + tie = true; } + } + + if (tie){ + env.log("More than 1 possible advances value hit."); return true; - }else{ - return false; } + + + // add the closest possibility to the advances mode for each attempt to the calibration history + env.log("Inferred hits from previous " + std::to_string(ADVANCE_HISTORY.results.size()) + " attempts: "); + for (size_t i=0; i MAX_HISTORY_LENGTH){ + CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); + CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); + CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); + CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); + } + + ADVANCE_HISTORY.results.clear(); + ADVANCE_HISTORY.seed_calibrations.clear(); + + return true; } + bool StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // return to the overworld pbf_mash_button(context, BUTTON_B, 5000ms); @@ -536,10 +605,14 @@ bool StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProCo num_steps_to_the_left = 2; } + // dodge rival + pbf_move_left_joystick(context, {0, -1}, 40ms, 460ms); + pbf_move_left_joystick(context, {0, -1}, 100ms, 400ms); + // line up with the doorway pbf_move_left_joystick(context, {-1, 0}, 40ms, 460ms); // pivot left for (int i=0; i SEED_VALUES = parse_seed_list(env, SEED_LIST); const int16_t SEED_POSITION = seed_position_in_list(TARGET_SEED, SEED_VALUES); if (SEED_POSITION == -1){ - // error + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "StarterRng(): Target Seed is missing from the list of nearby seeds.", + env.console + ); } env.log("Target Seed Value: " + std::to_string(TARGET_SEED)); @@ -864,11 +942,15 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte const double FRAMERATE = 59.999977; // FPS const double FRAME_DURATION = 1000 / FRAMERATE; - uint64_t CONTINUE_SCREEN_FRAMES = 500; + uint8_t MAX_HISTORY_LENGTH = 10; + double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + + uint64_t CONTINUE_SCREEN_FRAMES = 200; const uint64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -2140 : -845; // milliseconds. approximate; double SEED_CALIBRATION_FRAMES = 0; double ADVANCES_CALIBRATION = 0; + int64_t CONTINUE_SCREEN_ADJUSTMENT = 0; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); @@ -880,17 +962,21 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte env.log("SpD: " + std::to_string(target_result.ivs.spdef)); env.log("Spe: " + std::to_string(target_result.ivs.speed)); - StarterRngCalibrationHistory HISTORY; - uint8_t MAX_HISTORY_LENGTH = 5; - uint64_t advances_radius = 2048; + StarterRngAdvanceHistory ADVANCE_HISTORY; + StarterRngCalibrationHistory CALIBRATION_HISTORY; + uint64_t INITIAL_ADVANCES_RADIUS = 1024; uint64_t resets = 0; bool wildshiny_found = false; while (true){ - if (have_hit_target(env, TARGET_SEED, HISTORY.results.back())){ - env.log("Target Hit!"); - stats.nonshiny++; - break; + if (CALIBRATION_HISTORY.results.size() > 0){ + env.log("Checking for nonshiny target hit..."); + if (have_hit_target(env, TARGET_SEED, CALIBRATION_HISTORY.results.back())){ + env.log("Target Hit!"); + stats.nonshiny++; + break; + } + env.log("Missed target."); } if (resets > MAX_RESETS){ @@ -902,36 +988,63 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte break; } - if (advances_radius > 4) { + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Calibrating." + ); + env.update_stats(); + + uint64_t advances_radius = INITIAL_ADVANCES_RADIUS; + for (size_t i=0; i 0){ - double prev_advance_miss = double(HISTORY.results.back().advance - ADVANCES); + if (CALIBRATION_HISTORY.results.size() > 0){ + AdvRngState prev_hit = CALIBRATION_HISTORY.results.back(); + int64_t prev_csf_calibration = CALIBRATION_HISTORY.continue_screen_adjustments.back(); + int64_t prev_advance_miss = int64_t(prev_hit.advance) - int64_t(ADVANCES); if (prev_advance_miss != 0 && std::abs(prev_advance_miss) < 2){ + env.log("Attempting to correct for off-by-one miss by modifying continue screen frames."); if (prev_advance_miss > 0){ - continue_screen_tweak = -1; + CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration - 1; INGAME_ADVANCES += 1; }else{ - continue_screen_tweak = 1; + CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration + 1; INGAME_ADVANCES -= 1; } + }else{ + // we're still not that close. Slightly vary the seed to more reliably hone in on advances + double seed_bump = SEED_BUMPS[ADVANCE_HISTORY.results.size() % 5]; + SEED_CALIBRATION_FRAMES += seed_bump; } - }; + }else{ + double seed_bump = SEED_BUMPS[ADVANCE_HISTORY.results.size() % 5]; + SEED_CALIBRATION_FRAMES += seed_bump; + } + + env.log("Seed calibration (frames): " + std::to_string(SEED_CALIBRATION_FRAMES)); + env.log("Advance calibration (frames / 2): " + std::to_string(ADVANCES_CALIBRATION)); + env.log("Continue screen adjustment (frames): " + std::to_string(CONTINUE_SCREEN_ADJUSTMENT)); uint64_t CALIBRATED_SEED_DELAY = uint64_t(std::round(SEED_DELAY + FIXED_SEED_OFFSET + FRAME_DURATION * SEED_CALIBRATION_FRAMES)); - uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * (CONTINUE_SCREEN_FRAMES + continue_screen_tweak))); + uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * (CONTINUE_SCREEN_FRAMES + CONTINUE_SCREEN_ADJUSTMENT))); uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * INGAME_ADVANCES / 2)); + env.log("Title screen duration: " + std::to_string(CALIBRATED_SEED_DELAY) + "ms"); + env.log("Continue screen duration: " + std::to_string(CONTINUE_SCREEN_DELAY) + "ms"); + env.log("In-game duration: " + std::to_string(INGAME_DELAY) + "ms"); + env.log("Resetting Game..."); if (USE_COPYRIGHT_TEXT){ reset_and_detect_copyright_text(env.console, context, PROFILE); @@ -971,7 +1084,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte RNG_FILTERS.set(filters); std::map search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); - bool finished = update_calibration_history(env, HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, search_hits); + bool finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ env.log("RNG search finished."); continue; @@ -991,7 +1104,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } if (pokemon.level.size() > 1){ search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); - finished = update_calibration_history(env, HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, search_hits); + finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ env.log("RNG search finished."); continue; @@ -1005,13 +1118,17 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } auto num_levels = pokemon.level.size(); - int MAX_LEVELS = 5; + uint16_t MAX_LEVELS = 3; while(true){ - if (num_levels >= MAX_LEVELS){ - env.log("RNG search not complete after 5 level-ups."); + if (num_levels > MAX_LEVELS){ + env.log("RNG search not complete after 3 level-ups."); + update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, true); break; } + env.log("Level: " + std::to_string(4 + pokemon.level.size())); + env.log("Speed EVs: " + std::to_string(pokemon.evs.back().speed)); + int ret2 = autolevel_on_route1(env, context, pokemon, BASE_STATS); if (ret2 < 0){ env.log("Error encountered while auto-leveling."); @@ -1041,7 +1158,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); - finished = update_calibration_history(env, HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, search_hits); + finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ env.log("RNG search finished."); break; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index c9f8ad36c0..7d28e79394 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -46,10 +46,17 @@ class StarterRng : public SingleSwitchProgramInstance{ charmander }; + struct StarterRngAdvanceHistory{ + std::vector seed_calibrations; + std::vector> results; + }; + struct StarterRngCalibrationHistory{ std::vector seed_calibrations; std::vector advance_calibrations; + std::vector continue_screen_adjustments; std::vector results; + }; bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t TARGET_SEED, AdvRngState& hit); @@ -67,18 +74,25 @@ class StarterRng : public SingleSwitchProgramInstance{ AdvObservedPokemon& pokemon ); double get_seed_calibration_frames( - StarterRngCalibrationHistory& HISTORY, + StarterRngCalibrationHistory& CALIBRATION_HISTORY, const std::vector& SEED_VALUES, const int16_t& SEED_POSITION ); - double get_advances_calibration_frames(StarterRngCalibrationHistory& HISTORY, uint64_t ADVANCES); - bool update_calibration_history( + double get_advances_calibration_frames( + StarterRngCalibrationHistory& CALIBRATION_HISTORY, + uint64_t ADVANCES + ); + + bool update_history( SingleSwitchProgramEnvironment& env, - StarterRngCalibrationHistory& HISTORY, + StarterRngAdvanceHistory& ADVANCE_HISTORY, + StarterRngCalibrationHistory& CALIBRATION_HISTORY, const uint16_t& MAX_HISTORY_LENGTH, double& SEED_CALIBRATION_FRAMES, double& ADVANCES_CALIBRATION, - std::map& search_hits + int64_t& CONTINUE_SCREEN_ADJUSTMENT, + std::map& search_hits, + bool force_finish = false ); bool walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context); @@ -114,7 +128,7 @@ class StarterRng : public SingleSwitchProgramInstance{ SimpleIntegerOption SEED_DELAY; SimpleIntegerOptionADVANCES; - SimpleIntegerOptionCONTINUE_SCREEN_FRAMES; + // SimpleIntegerOptionCONTINUE_SCREEN_FRAMES; BooleanCheckBoxOption USE_COPYRIGHT_TEXT; From 495b5a621d145c00e464a09858afd742d40dab16 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Tue, 21 Apr 2026 20:41:18 -0500 Subject: [PATCH 06/10] tweaks --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 17 ++++++++++++ .../Source/Pokemon/Pokemon_AdvRng.h | 7 +++++ .../PokemonFRLG_StarterRng.cpp | 26 +++++++++---------- .../RngManipulation/PokemonFRLG_StarterRng.h | 4 +-- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index cd9a710d2a..ee22ccef25 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -245,6 +245,23 @@ std::map AdvRngSearcher::search( return hits; } +void AdvRngSearcher::refine_search( + std::map& map, + AdvRngFilters& target, + uint16_t tid_xor_sid, + uint8_t gender_threshold +){ + for (auto iter = map.begin(); iter != map.end(); ){ + state = iter->first; + AdvPokemonResult res = pokemon_from_state(state); + if (!check_for_match(res, target, tid_xor_sid, gender_threshold)){ + iter = map.erase(iter); + }else{ + iter++; + } + } +} + Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature){ NatureAdjustments ret; diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 4b4db3b3ab..51320ec2ba 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -158,6 +158,13 @@ class AdvRngSearcher{ uint8_t gender_threshold = 126 ); + void refine_search( + std::map& map, + AdvRngFilters& target, + uint16_t tid_xor_sid = 0, + uint8_t gender_threshold = 126 + ); + private: void search_advance_range( std::map& hits, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index a14b766af9..b61ab616f5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -469,7 +469,7 @@ bool StarterRng::update_history( const uint16_t& MAX_HISTORY_LENGTH, double& SEED_CALIBRATION_FRAMES, double& ADVANCES_CALIBRATION, - int64_t& CONTINUE_SCREEN_ADJUSTMENT, + double& CONTINUE_SCREEN_ADJUSTMENT, std::map& search_hits, bool force_finish ){ @@ -569,8 +569,6 @@ bool StarterRng::update_history( env.log(" " + std::to_string(most_likely_hit.seed) + " / " + std::to_string(most_likely_hit.advance)); } - env.log("Average advances hit: " + std::to_string(mode)); - while (CALIBRATION_HISTORY.results.size() > MAX_HISTORY_LENGTH){ CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); @@ -950,7 +948,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte const uint64_t FIXED_SEED_OFFSET = USE_COPYRIGHT_TEXT ? -2140 : -845; // milliseconds. approximate; double SEED_CALIBRATION_FRAMES = 0; double ADVANCES_CALIBRATION = 0; - int64_t CONTINUE_SCREEN_ADJUSTMENT = 0; + double CONTINUE_SCREEN_ADJUSTMENT = 0; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); @@ -1007,21 +1005,16 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(CALIBRATION_HISTORY, SEED_VALUES, SEED_POSITION); ADVANCES_CALIBRATION = get_advances_calibration_frames(CALIBRATION_HISTORY, ADVANCES); - double CALIBRATED_ADVANCES = ADVANCES + ADVANCES_CALIBRATION; - double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES; - if (CALIBRATION_HISTORY.results.size() > 0){ AdvRngState prev_hit = CALIBRATION_HISTORY.results.back(); - int64_t prev_csf_calibration = CALIBRATION_HISTORY.continue_screen_adjustments.back(); + double prev_csf_calibration = CALIBRATION_HISTORY.continue_screen_adjustments.back(); int64_t prev_advance_miss = int64_t(prev_hit.advance) - int64_t(ADVANCES); if (prev_advance_miss != 0 && std::abs(prev_advance_miss) < 2){ env.log("Attempting to correct for off-by-one miss by modifying continue screen frames."); if (prev_advance_miss > 0){ - CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration - 1; - INGAME_ADVANCES += 1; + CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration - 0.5; }else{ - CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration + 1; - INGAME_ADVANCES -= 1; + CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration + 0.5; } }else{ // we're still not that close. Slightly vary the seed to more reliably hone in on advances @@ -1033,6 +1026,9 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte SEED_CALIBRATION_FRAMES += seed_bump; } + double CALIBRATED_ADVANCES = ADVANCES + ADVANCES_CALIBRATION; + double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES - CONTINUE_SCREEN_ADJUSTMENT; + env.log("Seed calibration (frames): " + std::to_string(SEED_CALIBRATION_FRAMES)); env.log("Advance calibration (frames / 2): " + std::to_string(ADVANCES_CALIBRATION)); env.log("Continue screen adjustment (frames): " + std::to_string(CONTINUE_SCREEN_ADJUSTMENT)); @@ -1103,7 +1099,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte continue; // reset game } if (pokemon.level.size() > 1){ - search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); + searcher.refine_search(search_hits, filters, 0, 30); + env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ env.log("RNG search finished."); @@ -1157,7 +1154,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); - search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); + searcher.refine_search(search_hits, filters, 0, 30); + env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ env.log("RNG search finished."); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index 7d28e79394..ddfe640331 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -54,7 +54,7 @@ class StarterRng : public SingleSwitchProgramInstance{ struct StarterRngCalibrationHistory{ std::vector seed_calibrations; std::vector advance_calibrations; - std::vector continue_screen_adjustments; + std::vector continue_screen_adjustments; std::vector results; }; @@ -90,7 +90,7 @@ class StarterRng : public SingleSwitchProgramInstance{ const uint16_t& MAX_HISTORY_LENGTH, double& SEED_CALIBRATION_FRAMES, double& ADVANCES_CALIBRATION, - int64_t& CONTINUE_SCREEN_ADJUSTMENT, + double& CONTINUE_SCREEN_ADJUSTMENT, std::map& search_hits, bool force_finish = false ); From 0a03a83ec2d85666b8ab4e11217c23871ce0eba0 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Wed, 22 Apr 2026 12:34:56 -0500 Subject: [PATCH 07/10] fix filters update --- .../RngManipulation/PokemonFRLG_StarterRng.cpp | 18 ++++++++++-------- .../RngManipulation/PokemonFRLG_StarterRng.h | 4 +++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 24ad06a8d6..58758c44f7 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -374,14 +374,14 @@ AdvObservedPokemon StarterRng::read_summary(SingleSwitchProgramEnvironment& env, } -AdvRngFilters StarterRng::update_filters(AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS){ +void StarterRng::update_filters(AdvRngFilters& filters, AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS){ pokemon.level.emplace_back(pokemon.level.back() + 1); pokemon.stats.emplace_back(stats); pokemon.evs.emplace_back(evyield); - AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); + AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS); + filters.ivs = new_filters.ivs; RNG_FILTERS.set(filters); - return filters; } std::map StarterRng::get_starter_search_results( @@ -473,7 +473,7 @@ bool StarterRng::update_history( std::map& search_hits, bool force_finish ){ - const int MAX_ADVANCE_POSSIBILITIES = 20; + const int MAX_ADVANCE_POSSIBILITIES = 5; const uint32_t ADVANCE_RADIUS = 2; if (search_hits.size() == 0){ @@ -634,6 +634,7 @@ bool StarterRng::auto_battle_rival( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon, + AdvRngFilters& filters, BaseStats& BASE_STATS ){ Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; @@ -741,7 +742,7 @@ bool StarterRng::auto_battle_rival( VideoSnapshot screen = env.console.video().snapshot(); StatReads stats = reader.read_stats(env.logger(), screen); - update_filters(pokemon, stats, evyield, BASE_STATS); + update_filters(filters, pokemon, stats, evyield, BASE_STATS); // exit battle pbf_mash_button(context, BUTTON_B, 20s); @@ -814,6 +815,7 @@ int StarterRng::autolevel_on_route1( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon, + AdvRngFilters& filters, BaseStats& BASE_STATS ){ Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; @@ -868,7 +870,7 @@ int StarterRng::autolevel_on_route1( env.log("Level-up stats detected. Reading stats..."); screen = env.console.video().snapshot(); stats = reader.read_stats(env.logger(), screen); - update_filters(pokemon, stats, evyield, BASE_STATS); + update_filters(filters, pokemon, stats, evyield, BASE_STATS); exit_wild_battle(env.console, context, false, true); return 0; case -1: @@ -1093,7 +1095,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte continue; // reset game } - failed = auto_battle_rival(env, context, pokemon, BASE_STATS); + failed = auto_battle_rival(env, context, pokemon, filters, BASE_STATS); if (failed){ stats.errors++; continue; // reset game @@ -1126,7 +1128,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte env.log("Level: " + std::to_string(4 + pokemon.level.size())); env.log("Speed EVs: " + std::to_string(pokemon.evs.back().speed)); - int ret2 = autolevel_on_route1(env, context, pokemon, BASE_STATS); + int ret2 = autolevel_on_route1(env, context, pokemon, filters, BASE_STATS); if (ret2 < 0){ env.log("Error encountered while auto-leveling."); stats.errors++; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index ddfe640331..9714a6ef6f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -63,7 +63,7 @@ class StarterRng : public SingleSwitchProgramInstance{ AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - AdvRngFilters update_filters(AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS); + void update_filters(AdvRngFilters& filters, AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS); std::map get_starter_search_results( SingleSwitchProgramEnvironment& env, AdvRngSearcher& searcher, @@ -100,6 +100,7 @@ class StarterRng : public SingleSwitchProgramInstance{ SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon, + AdvRngFilters& filters, BaseStats& BASE_STATS ); @@ -109,6 +110,7 @@ class StarterRng : public SingleSwitchProgramInstance{ SingleSwitchProgramEnvironment& env, ProControllerContext& context, AdvObservedPokemon& pokemon, + AdvRngFilters& filters, BaseStats& BASE_STATS ); From 1afb7d9f80b7a7d797e717bd0fc8cff294311efa Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Wed, 22 Apr 2026 12:35:40 -0500 Subject: [PATCH 08/10] add get_hits_string() message for no hits --- .../Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index f3cf4bd3e3..5430503c6f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -162,6 +162,9 @@ std::string PossibleHitsDisplay::get_hits_string(const std::vector& hits_string += "/"; hits_string += std::to_string(hit.advance); } + if (hits_string.size() == 0){ + hits_string += "No matches found"; + } return hits_string; } std::string PossibleHitsDisplay::get_hits_string(std::map& hits_map){ From 3da14a8a97fc171638b4937d6d68433e00c8b11d Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Wed, 22 Apr 2026 13:46:15 -0500 Subject: [PATCH 09/10] add missing hits display update --- .../Programs/RngManipulation/PokemonFRLG_StarterRng.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 58758c44f7..2cfbd85975 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -1102,6 +1102,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } if (pokemon.level.size() > 1){ searcher.refine_search(search_hits, filters, 0, 30); + POSSIBLE_HITS.set(search_hits); env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ @@ -1157,6 +1158,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); searcher.refine_search(search_hits, filters, 0, 30); + POSSIBLE_HITS.set(search_hits); env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); if (finished){ From f09866021181042bbb1eb0c033a130df4adb15eb Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Thu, 23 Apr 2026 09:15:22 -0500 Subject: [PATCH 10/10] make a few things const --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 2 +- .../Source/Pokemon/Pokemon_AdvRng.h | 2 +- .../PokemonFRLG_RngDisplays.cpp | 8 ++--- .../RngManipulation/PokemonFRLG_RngDisplays.h | 6 ++-- .../PokemonFRLG_StarterRng.cpp | 32 +++++++++++-------- .../RngManipulation/PokemonFRLG_StarterRng.h | 32 +++++++++++-------- 6 files changed, 47 insertions(+), 35 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index ee22ccef25..c1ae25df4d 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -383,7 +383,7 @@ void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); } -AdvRngFilters observation_to_filters(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method){ +AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, const BaseStats& basestats, AdvRngMethod method){ IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; for (size_t i=0; i PossibleHitsDisplay::get_rng_states_from_map(std::map& hits_map){ +std::vector PossibleHitsDisplay::get_rng_states_from_map(const std::map& hits_map){ std::vector rng_states; - for(std::map::iterator it = hits_map.begin(); it != hits_map.end(); ++it) { + for(std::map::const_iterator it = hits_map.begin(); it != hits_map.end(); ++it) { rng_states.emplace_back(it->first); } return rng_states; @@ -167,14 +167,14 @@ std::string PossibleHitsDisplay::get_hits_string(const std::vector& } return hits_string; } -std::string PossibleHitsDisplay::get_hits_string(std::map& hits_map){ +std::string PossibleHitsDisplay::get_hits_string(const std::map& hits_map){ return get_hits_string(get_rng_states_from_map(hits_map)); } void PossibleHitsDisplay::set(const std::vector& rng_states){ hits.set(get_hits_string(rng_states)); } -void PossibleHitsDisplay::set(std::map& hits_map){ +void PossibleHitsDisplay::set(const std::map& hits_map){ std::vector rng_states = get_rng_states_from_map(hits_map); set(rng_states); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index bc375d56cf..9ba631caa7 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -49,13 +49,13 @@ class PossibleHitsDisplay : public GroupOption{ PossibleHitsDisplay(); void set(const std::vector& rng_states); - void set(std::map& hits_map); + void set(const std::map& hits_map); void reset(); private: - static std::vector get_rng_states_from_map(std::map& hits_map); + static std::vector get_rng_states_from_map(const std::map& hits_map); static std::string get_hits_string(const std::vector& rng_states); - static std::string get_hits_string(std::map& hits_map); + static std::string get_hits_string(const std::map& hits_map); public: StringOption hits; }; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 2cfbd85975..fd45881c66 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -301,7 +301,7 @@ AdvNature string_to_nature(std::string nature_string){ } // namespace -bool StarterRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t TARGET_SEED, AdvRngState& hit){ +bool StarterRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); } @@ -374,7 +374,13 @@ AdvObservedPokemon StarterRng::read_summary(SingleSwitchProgramEnvironment& env, } -void StarterRng::update_filters(AdvRngFilters& filters, AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS){ +void StarterRng::update_filters( + AdvRngFilters& filters, + AdvObservedPokemon& pokemon, + const StatReads& stats, + const EVs& evyield, + const BaseStats& BASE_STATS +){ pokemon.level.emplace_back(pokemon.level.back() + 1); pokemon.stats.emplace_back(stats); pokemon.evs.emplace_back(evyield); @@ -390,8 +396,8 @@ std::map StarterRng::get_starter_search_results( AdvRngFilters& filters, const std::vector& SEED_VALUES, const uint64_t& ADVANCES, - uint64_t& advances_radius, - AdvObservedPokemon& pokemon + const uint64_t& advances_radius, + const AdvObservedPokemon& pokemon ){ std::map search_hits; for (int i=0; i<4; i++){ @@ -411,7 +417,7 @@ std::map StarterRng::get_starter_search_results( } double StarterRng::get_seed_calibration_frames( - StarterRngCalibrationHistory& HISTORY, + const StarterRngCalibrationHistory& HISTORY, const std::vector& SEED_VALUES, const int16_t& SEED_POSITION ){ @@ -443,7 +449,7 @@ double StarterRng::get_seed_calibration_frames( return average_offset; } -double StarterRng::get_advances_calibration_frames(StarterRngCalibrationHistory& CALIBRATION_HISTORY, uint64_t ADVANCES){ +double StarterRng::get_advances_calibration_frames(const StarterRngCalibrationHistory& CALIBRATION_HISTORY, const uint64_t& ADVANCES){ double sum = 0; uint16_t len = 0; for (size_t i=0; i& search_hits, + const double& SEED_CALIBRATION_FRAMES, + const double& ADVANCES_CALIBRATION, + const double& CONTINUE_SCREEN_ADJUSTMENT, + const std::map& search_hits, bool force_finish ){ const int MAX_ADVANCE_POSSIBILITIES = 5; @@ -504,7 +510,7 @@ bool StarterRng::update_history( std::vector advances; std::vector hits; - for(std::map::iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { + for(std::map::const_iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { advances.emplace_back(it->first.advance); hits.emplace_back(it->first); } @@ -635,7 +641,7 @@ bool StarterRng::auto_battle_rival( ProControllerContext& context, AdvObservedPokemon& pokemon, AdvRngFilters& filters, - BaseStats& BASE_STATS + const BaseStats& BASE_STATS ){ Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; switch(STARTER){ @@ -816,7 +822,7 @@ int StarterRng::autolevel_on_route1( ProControllerContext& context, AdvObservedPokemon& pokemon, AdvRngFilters& filters, - BaseStats& BASE_STATS + const BaseStats& BASE_STATS ){ Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; Pokemon::StatReads stats; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index 9714a6ef6f..863973d77a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -59,28 +59,34 @@ class StarterRng : public SingleSwitchProgramInstance{ }; - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t TARGET_SEED, AdvRngState& hit); + bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - void update_filters(AdvRngFilters& filters, AdvObservedPokemon& pokemon, StatReads& stats, EVs& evyield, BaseStats& BASE_STATS); + void update_filters( + AdvRngFilters& filters, + AdvObservedPokemon& pokemon, + const StatReads& stats, + const EVs& evyield, + const BaseStats& BASE_STATS + ); std::map get_starter_search_results( SingleSwitchProgramEnvironment& env, AdvRngSearcher& searcher, AdvRngFilters& filters, const std::vector& SEED_VALUES, const uint64_t& ADVANCES, - uint64_t& advances_radius, - AdvObservedPokemon& pokemon + const uint64_t& advances_radius, + const AdvObservedPokemon& pokemon ); double get_seed_calibration_frames( - StarterRngCalibrationHistory& CALIBRATION_HISTORY, + const StarterRngCalibrationHistory& CALIBRATION_HISTORY, const std::vector& SEED_VALUES, const int16_t& SEED_POSITION ); double get_advances_calibration_frames( - StarterRngCalibrationHistory& CALIBRATION_HISTORY, - uint64_t ADVANCES + const StarterRngCalibrationHistory& CALIBRATION_HISTORY, + const uint64_t& ADVANCES ); bool update_history( @@ -88,10 +94,10 @@ class StarterRng : public SingleSwitchProgramInstance{ StarterRngAdvanceHistory& ADVANCE_HISTORY, StarterRngCalibrationHistory& CALIBRATION_HISTORY, const uint16_t& MAX_HISTORY_LENGTH, - double& SEED_CALIBRATION_FRAMES, - double& ADVANCES_CALIBRATION, - double& CONTINUE_SCREEN_ADJUSTMENT, - std::map& search_hits, + const double& SEED_CALIBRATION_FRAMES, + const double& ADVANCES_CALIBRATION, + const double& CONTINUE_SCREEN_ADJUSTMENT, + const std::map& search_hits, bool force_finish = false ); @@ -101,7 +107,7 @@ class StarterRng : public SingleSwitchProgramInstance{ ProControllerContext& context, AdvObservedPokemon& pokemon, AdvRngFilters& filters, - BaseStats& BASE_STATS + const BaseStats& BASE_STATS ); bool walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, ProControllerContext& context); @@ -111,7 +117,7 @@ class StarterRng : public SingleSwitchProgramInstance{ ProControllerContext& context, AdvObservedPokemon& pokemon, AdvRngFilters& filters, - BaseStats& BASE_STATS + const BaseStats& BASE_STATS );