From 7064af87c35aba861bdd73c76cbc8bf6a205b1fa Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Wed, 17 Jun 2026 11:58:06 -0500 Subject: [PATCH 1/2] separate out repeated and reusable patterns in FRLG RNG programs --- .../PokemonFRLG/PokemonFRLG_Navigation.cpp | 47 +++ .../PokemonFRLG/PokemonFRLG_Navigation.h | 7 + .../RngManipulation/PokemonFRLG_GiftRng.cpp | 91 +---- .../RngManipulation/PokemonFRLG_GiftRng.h | 2 - .../PokemonFRLG_RngCalibration.cpp | 29 ++ .../PokemonFRLG_RngCalibration.h | 25 ++ .../PokemonFRLG_RngLoopRoutines.cpp | 333 ++++++++++++++++++ .../PokemonFRLG_RngLoopRoutines.h | 170 +++++++++ .../PokemonFRLG_RngNavigation.cpp | 143 ++++++++ .../PokemonFRLG_RngNavigation.h | 11 + .../PokemonFRLG_RoamingLegendaryRng.cpp | 92 +---- .../PokemonFRLG_RoamingLegendaryRng.h | 2 - .../PokemonFRLG_StarterRng.cpp | 45 +-- .../RngManipulation/PokemonFRLG_StarterRng.h | 2 - .../RngManipulation/PokemonFRLG_StaticRng.cpp | 90 +---- .../RngManipulation/PokemonFRLG_StaticRng.h | 2 - .../RngManipulation/PokemonFRLG_WildRng.cpp | 76 +--- .../RngManipulation/PokemonFRLG_WildRng.h | 2 - SerialPrograms/cmake/SourceFiles.cmake | 2 + 19 files changed, 850 insertions(+), 321 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index a0791b60e3..9743df85d8 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -797,6 +797,53 @@ void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& conte } } +void use_sweet_scent_from_overworld(ConsoleHandle& console, ProControllerContext& context, int from_last){ + uint16_t errors = 0; + + while (true){ + if (errors > 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "use_teleport_from_overworld(): Failed to use Teleport 5 times in a row.", + console + ); + } + + open_party_menu_from_overworld(console, context); + // navigate to last party slot + for (int i=0; i<(2+from_last); i++){ + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + } + + PartySelectionWatcher sweetscent_selected(COLOR_RED); + + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + }, + { sweetscent_selected } + ); + + if (ret < 0){ + console.log("Failed to select Sweet Scent user."); + errors++; + pbf_mash_button(context, BUTTON_B, 3000ms); + continue; + } + + // select Sweet Scent (2nd option, but maybe HMs could change this) + pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + + context.wait_for_all_requests(); + console.log("Used Sweet Scent."); + return; + } +} + void use_teleport_from_overworld(ConsoleHandle& console, ProControllerContext& context){ uint16_t errors = 0; diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index 884277f6dd..b5a3ac3177 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -19,6 +19,9 @@ namespace NintendoSwitch{ using ProControllerContext = ControllerContext; namespace PokemonFRLG{ +using namespace std::chrono_literals; + + enum class BattleResult{ opponentfainted, playerfainted, @@ -80,6 +83,10 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext // Starting from the start menu, a sub-screen of the start menu, or the overworld, navigate to the bag void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD); +// Uses Sweet Scent, assuming that the specified party member has it learned +// The last argument is the distance of the Sweet Scent user from the last party slot +void use_sweet_scent_from_overworld(ConsoleHandle& console, ProControllerContext& context, int from_last = 0); + // Uses Teleport to return to a PokeCenter. // Assumes that Teleport is usable and the last party member has it learned void use_teleport_from_overworld(ConsoleHandle& console, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index bc19c2e448..6bb33be2f9 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -19,6 +19,7 @@ #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" #include "PokemonFRLG_RngCalibration.h" +#include "PokemonFRLG_RngLoopRoutines.h" #include "PokemonFRLG_GiftRng.h" namespace PokemonAutomation{ @@ -227,10 +228,6 @@ GiftRng::GiftRng() -bool GiftRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ - return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); -} - void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast @@ -324,8 +321,6 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; @@ -344,24 +339,14 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); RNG_TARGET.set_target(target_result, GENDER_THRESHOLD); - env.log("Target PID: " + to_hex_string(target_result.pid)); - env.log("Target Nature: " + nature_to_string(target_result.nature)); - 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)); + log_target_pokemon(env.console, target_result); RngCalibrations calibrations = { RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, RNG_CALIBRATION.csf_calibration, RNG_CALIBRATION.advances_calibration }; - env.log("Initial Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); - env.log("Initial CSF calibration (frames): " + std::to_string(calibrations.csf_offset)); - env.log("Initial In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset)); + log_calibrations(env.console, calibrations, true); Milliseconds launch_delay = INITIAL_LAUNCH_DELAY; @@ -373,7 +358,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& while (true){ if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ + if (have_hit_target(TARGET_SEED, ADVANCES, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -410,8 +395,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& } // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target - double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5]; - calibrations.seed_offset += seed_bump; + apply_seed_bump(calibrations, uncertain_history); uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; @@ -459,60 +443,23 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::vector search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set_hits(search_hits); - bool force_finish = ( - (MAX_RARE_CANDIES == 0) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD) - ); - bool finished = update_history( - env.console, uncertain_history, calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, 1, 2, force_finish - ); - - for (uint64_t i=0; i search_hits = refine_calibration_with_rare_candy( + env, context, LANGUAGE, pokemon, filters, BASE_STATS, + uncertain_history, calibration_history, calibrations, + MAX_HISTORY_LENGTH, MAX_RARE_CANDIES, AdvRngMethod::Method1, false, + stats.errors, NOTIFICATION_ERROR_RECOVERABLE, + [&](AdvRngFilters& f){ + return get_search_results(env.console, searcher, f, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + }, + [&](const std::vector& h){ RNG_CALIBRATION.set_hits(h); }, + [&](const AdvRngFilters& f){ RNG_FILTERS.set(f); }, + [&](const std::vector& h){ + return all_equal(h) || all_indistinguishable(h, searcher, GENDER_THRESHOLD); } - RNG_FILTERS.set(filters); - - search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set_hits(search_hits); - - force_finish = ( - failed - || (i == (MAX_RARE_CANDIES - 1)) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD) - ); - finished = update_history( - env.console, uncertain_history, - calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, - 1, 2, force_finish - ); - } + ); env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h index a9a2007552..5356f09ef0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h @@ -44,8 +44,6 @@ class GiftRng : public SingleSwitchProgramInstance{ private: - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); - SectionDividerOption m_calibration_displays; RngTargetDisplay RNG_TARGET; RngFilterDisplay RNG_FILTERS; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp index 16a4a5d12d..8258be4cc0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -85,6 +85,35 @@ std::string to_hex_string(const uint32_t& val){ return s.str(); } +bool have_hit_target(uint32_t target_seed, uint64_t target_advances, const AdvRngState& hit){ + return (hit.seed == target_seed) && (hit.advance == target_advances); +} + +void apply_seed_bump(RngCalibrations& calibrations, const RngUncertainHistory& uncertain_history){ + static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5]; + calibrations.seed_offset += seed_bump; +} + +void log_target_pokemon(ConsoleHandle& console, const AdvPokemonResult& result, bool normal_method_note){ + console.log("Target PID: " + to_hex_string(result.pid)); + console.log("Target Nature: " + nature_to_string(result.nature)); + console.log(normal_method_note ? "Target IVs (assuming Normal method):" : "Target IVs:"); + console.log(" HP: " + std::to_string(result.ivs.hp)); + console.log(" Atk: " + std::to_string(result.ivs.attack)); + console.log(" Def: " + std::to_string(result.ivs.defense)); + console.log(" SpA: " + std::to_string(result.ivs.spatk)); + console.log(" SpD: " + std::to_string(result.ivs.spdef)); + console.log(" Spe: " + std::to_string(result.ivs.speed)); +} + +void log_calibrations(ConsoleHandle& console, const RngCalibrations& calibrations, bool initial){ + std::string prefix = initial ? "Initial " : ""; + console.log(prefix + "Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); + console.log(prefix + "CSF calibration (frames): " + std::to_string(calibrations.csf_offset)); + console.log(prefix + "In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset)); +} + RngTimings prepare_timings( ConsoleHandle& console, PokemonFRLG_RngTarget target, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h index 83053e0a0d..6910c927dd 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h @@ -55,6 +55,31 @@ int16_t seed_position_in_list(uint16_t seed, std::vector list); std::string to_hex_string(const uint16_t& val); std::string to_hex_string(const uint32_t& val); + +// true if a search hit matches the target seed and advance +bool have_hit_target(uint32_t target_seed, uint64_t target_advances, const AdvRngState& hit); + +// if previous resets had uncertain advances, slightly nudge the seed offset to try +// to land on a different target. Cycles through {0, +1, -1, +2, -2} frames. +void apply_seed_bump(RngCalibrations& calibrations, const RngUncertainHistory& uncertain_history); + +// increment the failed-search counter when a search returned no hits, otherwise reset it. +// templated so it accepts both plain hit vectors and the egg searcher's pair vectors. +template +void update_failed_searches(uint16_t& failed_searches, const std::vector& search_hits){ + if (search_hits.size() == 0){ + failed_searches++; + }else{ + failed_searches = 0; + } +} + +// log the PID, nature, and IVs of a target pokemon result +void log_target_pokemon(ConsoleHandle& console, const AdvPokemonResult& result, bool normal_method_note = false); + +// log the three calibration offsets (seed/CSF/in-game), in frames +void log_calibrations(ConsoleHandle& console, const RngCalibrations& calibrations, bool initial = false); + RngTimings prepare_timings( ConsoleHandle& console, PokemonFRLG_RngTarget target, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.cpp new file mode 100644 index 0000000000..3e25397e3a --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.cpp @@ -0,0 +1,333 @@ +/* RNG Loop Routines + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "Common/Cpp/Time.h" +#include "CommonTools/Random.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_RngNavigation.h" +#include "PokemonFRLG_RngLoopRoutines.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +namespace { + // pull one side out of each (held, pickup) pair, sorted and de-duplicated + std::vector states_from_pairs( + const std::vector>& pairs, + bool take_first + ){ + std::vector states; + states.reserve(pairs.size()); + for (const auto& pair : pairs){ + states.emplace_back(take_first ? pair.first : pair.second); + } + std::sort(states.begin(), states.end()); + states.erase(std::unique(states.begin(), states.end()), states.end()); + return states; + } +} // namespace + + +void select_calibrations_for_frame( + SingleSwitchProgramEnvironment& env, + EggCalibrationHistories& hist, + const std::vector& seed_values, + const int16_t& seed_position, + uint64_t advances, + bool pickup_frame, + bool previously_hit_held_frame, + uint16_t times_not_held +){ + RngCalibrations& calibrations = hist.calibrations; + const RngCalibrationHistory& egg_history = hist.frame(pickup_frame); + const RngCalibrationHistory& wild_history = hist.wild; + const RngUncertainHistory& egg_uncertain_history = hist.egg_uncertain; + + if (egg_history.results.size() > 0){ + calibrations = get_calibrations(env.console, egg_history, seed_values, seed_position, advances, !pickup_frame); + } + if (wild_history.results.size() > egg_history.results.size()){ + calibrations.seed_offset = get_seed_calibration_frames(wild_history, seed_values, seed_position); + env.log("Updated Seed Calibration (frames): " + std::to_string(calibrations.seed_offset)); + } + + // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target + if (pickup_frame){ + apply_seed_bump(calibrations, egg_uncertain_history); + } + + // if the egg isn't ready over several resets, + // or if there is repeated uncertainty about advances from previous resets, + // bump the advances + if (!pickup_frame && !previously_hit_held_frame){ + if (egg_history.results.empty()){ + // avoid cumulative bumps when there is no history + if (egg_uncertain_history.calibrations.size() > 0){ + calibrations = egg_uncertain_history.calibrations.back(); + }else{ + env.log("No calibration history yet. Using initial calibrations from UI..."); + calibrations = hist.initial_calibrations; + } + } + double bumpval = std::floor(times_not_held / 4) + std::floor(egg_uncertain_history.results.size() / 2); + double advances_bump = std::pow(-1, bumpval) * std::floor((bumpval+1) / 2); // 0, -1, +1, -2, +2... + double orig_csf_offset = calibrations.csf_offset; + calibrations.csf_offset = fmod(orig_csf_offset + advances_bump, 2); + calibrations.ingame_offset += advances_bump - (calibrations.csf_offset - orig_csf_offset); + } +} + + +WildCatchOutcome catch_wild_for_seed_id( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + std::atomic& errors, + EventNotificationOption& error_notification, + Language language, + const WallClock& timestamp, + uint64_t advances, + int sweet_scent_from_last, + uint64_t& balls_left, + const std::set& species_list, + const RngStatsDatabase& stats_data, + AdvObservedPokemon& pokemon, + RngStats& species_stats, + AdvRngFilters& filters, + uint64_t& wild_advances_estimate, + const std::function& publish_filters +){ + uint32_t rng_wait = 50 * random_u32(0, 20); // avoid hitting the same wild targets + pbf_wait(context, std::chrono::milliseconds(rng_wait)); + + use_sweet_scent_from_overworld(env.console, context, sweet_scent_from_last); + + WallDuration elapsed = current_time() - timestamp; + auto elapsed_ms = std::chrono::duration_cast(elapsed); + wild_advances_estimate = uint64_t(advances + 2 * elapsed_ms.count() / FRLG_FRAME_DURATION); + env.log("Wild advances estimate: " + std::to_string(wild_advances_estimate)); + + int ret = watch_for_shiny_encounter(env.console, context); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "catch_wild_for_seed_id(): Failed to trigger battle", + env.console + ); + } + if (ret == 1){ + return WildCatchOutcome::shiny; + } + + int balls_thrown = auto_catch(env.console, context, balls_left); + if (balls_thrown < 0){ + errors++; + send_program_recoverable_error_notification( + env, error_notification, + "auto_catch() encountered an error." + ); + return WildCatchOutcome::failed; + }else if(balls_thrown == 0){ + env.log("Failed catch."); + return WildCatchOutcome::failed; + } + + balls_left -= balls_thrown; + + go_to_summary(env.console, context); + pokemon = read_summary(env.console, context, language, species_list); + try{ + species_stats = stats_data.get_throw(pokemon.species); + }catch (const InternalProgramError& err){ + env.log(err.message()); + env.log("Failed to load base stats."); + return WildCatchOutcome::failed; + } + + filters = observation_to_filters(pokemon, species_stats.base_stats, AdvRngMethod::Any); + publish_filters(filters); + return WildCatchOutcome::caught; +} + + +bool confirm_held_frame_hit( + RngCalibrationHistory& calibration_history, + const RngUncertainHistory& egg_uncertain_history, + bool locked_in, + uint16_t target_seed, + uint64_t target_advances +){ + if (locked_in){ + return have_hit_target(target_seed, target_advances, calibration_history.results.back()); + } + // an uncertain history means we can't yet trust the committed results + if (egg_uncertain_history.results.size() != 0){ + return false; + } + + // keep only the calibrations that resulted in hitting the target + bool confirmed = false; + std::vector kept_results; + std::vector kept_calibrations; + for (size_t i = 0; i < calibration_history.results.size(); i++){ + if (have_hit_target(target_seed, target_advances, calibration_history.results[i])){ + confirmed = true; + kept_results.emplace_back(calibration_history.results[i]); + kept_calibrations.emplace_back(calibration_history.calibrations[i]); + } + } + if (confirmed){ + calibration_history.results = kept_results; + calibration_history.calibrations = kept_calibrations; + } + return confirmed; +} + + +std::vector refine_calibration_with_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + Language language, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& base_stats, + RngUncertainHistory& uncertain_history, + RngCalibrationHistory& calibration_history, + const RngCalibrations& calibrations, + uint16_t max_history_length, + uint64_t max_rare_candies, + AdvRngMethod method, + bool safari_zone, + std::atomic& errors, + EventNotificationOption& error_notification, + const std::function(AdvRngFilters&)>& run_search, + const std::function&)>& publish_hits, + const std::function& publish_filters, + const std::function&)>& result_determined +){ + std::vector search_hits = run_search(filters); + publish_hits(search_hits); + + bool force_finish = (max_rare_candies == 0) || result_determined(search_hits); + bool finished = update_history( + env.console, uncertain_history, calibration_history, max_history_length, + calibrations, search_hits, 1, 2, force_finish + ); + + for (uint64_t i = 0; i < max_rare_candies; i++){ + if (finished){ + break; + } + + bool failed = use_rare_candy( + env.console, context, language, pokemon, filters, + base_stats, method, safari_zone, i == 0 + ); + if (failed){ + update_history( + env.console, uncertain_history, calibration_history, + max_history_length, calibrations, search_hits, 1, 2, true + ); + errors++; + send_program_recoverable_error_notification( + env, error_notification, + "Failed to use Rare Candy." + ); + } + publish_filters(filters); + + search_hits = run_search(filters); + publish_hits(search_hits); + + force_finish = ( + failed + || (i == (max_rare_candies - 1)) + || result_determined(search_hits) + ); + finished = update_history( + env.console, uncertain_history, + calibration_history, max_history_length, + calibrations, search_hits, + 1, 2, force_finish + ); + } + + return search_hits; +} + + +std::vector refine_egg_calibration( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + Language language, + AdvObservedPokemon& observed_egg, + AdvRngFilters& filters, + const BaseStats& base_stats, + RngUncertainHistory& uncertain_history, + RngCalibrationHistory& calibration_history, + const RngCalibrations& calibrations, + uint16_t max_history_length, + uint64_t candies_left, + const EggRefineOptions& options, + std::atomic& errors, + EventNotificationOption& error_notification, + const std::function>(AdvRngFilters&)>& run_search, + const std::function&)>& publish_hits, + const std::function& publish_filters, + const std::function>&)>& indistinguishable +){ + std::vector> search_hits = run_search(filters); + std::vector hits = states_from_pairs(search_hits, options.take_first); + publish_hits(hits); + + bool force_finish = (candies_left == 0) + || all_equal(hits) + || indistinguishable(search_hits); + bool finished = update_history( + env.console, uncertain_history, calibration_history, max_history_length, + calibrations, hits, 1, options.advance_radius, force_finish + ); + + for (uint64_t i = 0; i < candies_left; i++){ + if (finished){ + break; + } + + bool failed = use_rare_candy( + env.console, context, language, observed_egg, filters, + base_stats, AdvRngMethod::Any, false, i == 0, options.from_last + ); + if (failed){ + errors++; + send_program_recoverable_error_notification( + env, error_notification, + "Failed to use Rare Candy." + ); + } + publish_filters(filters); + + search_hits = run_search(filters); + hits = states_from_pairs(search_hits, options.take_first); + publish_hits(hits); + + force_finish = failed || (i == (candies_left - 1)) || indistinguishable(search_hits); + finished = update_history( + env.console, uncertain_history, calibration_history, max_history_length, + calibrations, hits, 1, options.advance_radius, force_finish + ); + } + + return hits; +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h new file mode 100644 index 0000000000..9268b37ffd --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h @@ -0,0 +1,170 @@ +/* RNG Loop Routines + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_RngLoopRoutines_H +#define PokemonAutomation_PokemonFRLG_RngLoopRoutines_H + +#include +#include +#include +#include +#include +#include +#include "CommonFramework/Language.h" +#include "CommonFramework/Notifications/EventNotificationOption.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "Pokemon/Pokemon_AdvRng.h" +#include "PokemonFRLG_RngCalibration.h" +#include "PokemonFRLG_RngStatsDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + + +struct EggCalibrationHistories{ + const RngCalibrations initial_calibrations; + RngCalibrations calibrations; + RngUncertainHistory wild_uncertain; + RngUncertainHistory egg_uncertain; + RngCalibrationHistory wild; + RngCalibrationHistory held; + RngCalibrationHistory pickup; + + RngCalibrationHistory& frame(bool pickup_frame){ return pickup_frame ? pickup : held; } + + void clear_uncertain(){ + wild_uncertain.results.clear(); + wild_uncertain.calibrations.clear(); + egg_uncertain.results.clear(); + egg_uncertain.calibrations.clear(); + } +}; + +// Per-frame seed configuration: the nearby-seed list, the target's position in it, and the seed delay. +struct EggFrameTarget{ + uint64_t seed_delay; + std::vector seed_values; + int16_t seed_position; +}; +struct EggFrameTargets{ + EggFrameTarget held; + EggFrameTarget pickup; + + const EggFrameTarget& frame(bool pickup_frame) const{ return pickup_frame ? pickup : held; } +}; + +// result of catching a wild encounter to identify the current seed +enum class WildCatchOutcome{ + shiny, + failed, + caught +}; + + +// Adjust the calibrations for the upcoming reset for Egg RNG +void select_calibrations_for_frame( + SingleSwitchProgramEnvironment& env, + EggCalibrationHistories& hist, + const std::vector& seed_values, + const int16_t& seed_position, + uint64_t advances, + bool pickup_frame, + bool previously_hit_held_frame, + uint16_t times_not_held +); + +// Catch a wild encounter at the daycare pond and read it, to identify which seed was hit. +WildCatchOutcome catch_wild_for_seed_id( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + std::atomic& errors, + EventNotificationOption& error_notification, + Language language, + const WallClock& timestamp, + uint64_t advances, + int sweet_scent_from_last, + uint64_t& balls_left, + const std::set& species_list, + const RngStatsDatabase& stats_data, + AdvObservedPokemon& pokemon, + RngStats& species_stats, + AdvRngFilters& filters, + uint64_t& wild_advances_estimate, + const std::function& publish_filters +); + +// Returns whether a calibration result confirms the target frame was hit. +// When confirmed before locking in by saving, this prunes calibration_history +// down to only the entries that hit the target. +bool confirm_held_frame_hit( + RngCalibrationHistory& calibration_history, + const RngUncertainHistory& egg_uncertain_history, + bool locked_in, + uint16_t target_seed, + uint64_t target_advances +); + +// Shared Rare Candy refinement loop used by RNG programs +std::vector refine_calibration_with_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + Language language, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& base_stats, + RngUncertainHistory& uncertain_history, + RngCalibrationHistory& calibration_history, + const RngCalibrations& calibrations, + uint16_t max_history_length, + uint64_t max_rare_candies, + AdvRngMethod method, + bool safari_zone, + std::atomic& errors, + EventNotificationOption& error_notification, + const std::function(AdvRngFilters&)>& run_search, + const std::function&)>& publish_hits, + const std::function& publish_filters, + const std::function&)>& result_determined +); + +// Options describing the small differences between EggRng's held-frame and pickup-frame loops. +struct EggRefineOptions{ + bool take_first; + uint32_t advance_radius; + int from_last; +}; + +// Searches with the egg searcher (pairs), extracts one side of each pair, +// and levels with Rare Candy until the result is determined (or can't be further narrowed down). +// Returns a vector of possible hits after the search completes. +std::vector refine_egg_calibration( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + Language language, + AdvObservedPokemon& observed_egg, + AdvRngFilters& filters, + const BaseStats& base_stats, + RngUncertainHistory& uncertain_history, + RngCalibrationHistory& calibration_history, + const RngCalibrations& calibrations, + uint16_t max_history_length, + uint64_t candies_left, + const EggRefineOptions& options, + std::atomic& errors, + EventNotificationOption& error_notification, + const std::function>(AdvRngFilters&)>& run_search, + const std::function&)>& publish_hits, + const std::function& publish_filters, + const std::function>&)>& indistinguishable +); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp index ac1d999e1a..4e3745f9be 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -712,6 +712,149 @@ bool check_for_shiny( } } + +void use_max_repel(ConsoleHandle& console, ProControllerContext& context){ + console.log("Using a Max Repel (2nd bag slot from top)..."); + open_bag_from_overworld(console, context); + pbf_move_left_joystick(context, {-1, 0}, 100ms, 900ms); + pbf_move_left_joystick(context, {-1, 0}, 100ms, 900ms); + pbf_move_left_joystick(context, {-1, 0}, 100ms, 900ms); + for (int i=0; i<20; i++){ + pbf_move_left_joystick(context, {0, +1}, 100ms, 100ms); + } + pbf_move_left_joystick(context, {0, -1}, 100ms, 300ms); + + pbf_mash_button(context, BUTTON_A, 3000ms); + pbf_mash_button(context, BUTTON_B, 5000ms); + +} + +void daycare_steps(ConsoleHandle& console, ProControllerContext& context){ + console.log("Taking 250 steps..."); + // walk down to the south wall + pbf_move_left_joystick(context, {0, -1}, 930ms, 570ms); + // walk to the southeast corner + pbf_move_left_joystick(context, {+1, 0}, 2500ms, 300ms); + + WhiteDialogWatcher repel_over(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<20; i++){ + pbf_move_left_joystick(context, {-1, 0}, 2700ms, 300ms); + pbf_move_left_joystick(context, {+1, 0}, 2700ms, 300ms); + } + }, + { repel_over } + ); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::NO_ERROR_REPORT, + "daycare_steps(): No Max Repel dialogue box detected.", + console + ); + } + + console.log("Max Repel wore off. Taking 4 more steps..."); + pbf_mash_button(context, BUTTON_B, 1000ms); + + // take 4 steps to the left + pbf_move_left_joystick(context, {-1, 0}, 200ms, 720ms); + pbf_move_left_joystick(context, {-1, 0}, 30ms, 720ms); + pbf_move_left_joystick(context, {-1, 0}, 30ms, 720ms); + pbf_move_left_joystick(context, {-1, 0}, 30ms, 720ms); + + console.log("254 steps taken."); +} + +bool walk_from_daycare_to_pond(ConsoleHandle& console, ProControllerContext& context){ + console.log("Walking to the daycare pond..."); + pbf_move_left_joystick(context, {0, -1}, 190ms, 300ms); + pbf_move_left_joystick(context, {+1, 0}, 2470ms, 300ms); + pbf_move_left_joystick(context, {0, -1}, 380ms, 300ms); + + // start surfing + WhiteDialogWatcher surf_dialog(COLOR_RED); + + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + { surf_dialog } + ); + if (ret < 0){ + console.log("Failed to detect surf dialog"); + return true; + } + console.log("Started surfing."); + pbf_mash_button(context, BUTTON_A, 2000ms); + context.wait_for_all_requests(); + return false; +} + +void walk_from_pond_to_daycare_man(ConsoleHandle& console, ProControllerContext& context){ + console.log("Walking to the daycare man..."); + pbf_move_left_joystick(context, {0, +1}, 1080ms, 300ms); + pbf_move_left_joystick(context, {-1, 0}, 1300ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 300ms, 300ms); +} + +void egg_pickup(ConsoleHandle& console, ProControllerContext& context){ + console.log("Picking up egg..."); + WhiteDialogWatcher dialogue(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_A, 5000ms); + }, + { dialogue } + ); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "egg_pickup(): Failed to initiate dialogue.", + console + ); + } + + pbf_mash_button(context, BUTTON_A, 5000ms); + pbf_mash_button(context, BUTTON_B, 2500ms); +} + +bool walk_from_daycare_man_to_pond(ConsoleHandle& console, ProControllerContext& context){ + console.log("Walking to the daycare man..."); + pbf_move_left_joystick(context, {0, -1}, 380ms, 300ms); + pbf_move_left_joystick(context, {+1, 0}, 1340ms, 300ms); + pbf_move_left_joystick(context, {0, -1}, 300ms, 300ms); + + // start surfing + WhiteDialogWatcher surf_dialog(COLOR_RED); + + context.wait_for_all_requests(); + int ret = run_until( + console, context, + [](ProControllerContext& context) { + ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + { surf_dialog } + ); + if (ret < 0){ + console.log("Failed to detect surf dialog"); + return true; + } + console.log("Started surfing."); + pbf_mash_button(context, BUTTON_A, 2000ms); + context.wait_for_all_requests(); + return false; +} + } } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h index 2c1faee38e..29c647a25b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h @@ -10,6 +10,7 @@ #include "CommonFramework/Language.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" #include "Pokemon/Pokemon_AdvRng.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_BlindNavigation.h" namespace PokemonAutomation{ @@ -80,6 +81,16 @@ bool check_for_shiny( void hatch_daycare_egg(ConsoleHandle& console, ProControllerContext& context); + +// Daycare (Four Island) navigation for egg RNG. +// bool methods return true when they fail to start surfing +void use_max_repel(ConsoleHandle& console, ProControllerContext& context); +void daycare_steps(ConsoleHandle& console, ProControllerContext& context); +bool walk_from_daycare_to_pond(ConsoleHandle& console, ProControllerContext& context); +void walk_from_pond_to_daycare_man(ConsoleHandle& console, ProControllerContext& context); +void egg_pickup(ConsoleHandle& console, ProControllerContext& context); +bool walk_from_daycare_man_to_pond(ConsoleHandle& console, ProControllerContext& context); + } } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp index 72913b9af8..f7ccdad480 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.cpp @@ -19,6 +19,7 @@ #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" #include "PokemonFRLG_RngCalibration.h" +#include "PokemonFRLG_RngLoopRoutines.h" #include "PokemonFRLG_RoamingLegendaryRng.h" namespace PokemonAutomation{ @@ -223,10 +224,6 @@ RoamingLegendaryRng::RoamingLegendaryRng() -bool RoamingLegendaryRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ - return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); -} - void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast @@ -271,8 +268,6 @@ void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProContro static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; @@ -284,24 +279,14 @@ void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProContro AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1, true); AdvPokemonResult target_result = searcher.generate_pokemon(); RNG_TARGET.set_target(target_result, GENDER_THRESHOLD); - env.log("Target PID: " + to_hex_string(target_result.pid)); - env.log("Target Nature: " + nature_to_string(target_result.nature)); - 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)); + log_target_pokemon(env.console, target_result); RngCalibrations calibrations = { RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, RNG_CALIBRATION.csf_calibration, RNG_CALIBRATION.advances_calibration }; - env.log("Initial Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); - env.log("Initial CSF calibration (frames): " + std::to_string(calibrations.csf_offset)); - env.log("Initial In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset)); + log_calibrations(env.console, calibrations, true); Milliseconds launch_delay = INITIAL_LAUNCH_DELAY; @@ -314,7 +299,7 @@ void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProContro while (true){ if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ + if (have_hit_target(TARGET_SEED, ADVANCES, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -361,8 +346,7 @@ void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProContro } // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target - double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5]; - calibrations.seed_offset += seed_bump; + apply_seed_bump(calibrations, uncertain_history); uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; @@ -430,61 +414,23 @@ void RoamingLegendaryRng::program(SingleSwitchProgramEnvironment& env, ProContro AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::vector search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set_hits(search_hits); - bool force_finish = ( - (MAX_RARE_CANDIES == 0) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD) - ); - bool finished = update_history( - env.console, uncertain_history, calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, 1, 2, force_finish - ); - - for (uint64_t i=0; i search_hits = refine_calibration_with_rare_candy( + env, context, LANGUAGE, pokemon, filters, BASE_STATS, + uncertain_history, calibration_history, calibrations, + MAX_HISTORY_LENGTH, MAX_RARE_CANDIES, AdvRngMethod::Method1, false, + stats.errors, NOTIFICATION_ERROR_RECOVERABLE, + [&](AdvRngFilters& f){ + return get_search_results(env.console, searcher, f, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + }, + [&](const std::vector& h){ RNG_CALIBRATION.set_hits(h); }, + [&](const AdvRngFilters& f){ RNG_FILTERS.set(f); }, + [&](const std::vector& h){ + return all_equal(h) || all_indistinguishable(h, searcher, GENDER_THRESHOLD); } - RNG_FILTERS.set(filters); - - search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set_hits(search_hits); - - force_finish = ( - failed - || (i == (MAX_RARE_CANDIES - 1)) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD) - ); - finished = update_history( - env.console, uncertain_history, - calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, - 1, 2, force_finish - ); - - } + ); env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.h index 557a194aa6..d12267a9fb 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RoamingLegendaryRng.h @@ -44,8 +44,6 @@ class RoamingLegendaryRng : public SingleSwitchProgramInstance{ private: - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); - SectionDividerOption m_calibration_displays; RngTargetDisplay RNG_TARGET; RngFilterDisplay RNG_FILTERS; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 7c655180cf..edd0a6157b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -212,10 +212,6 @@ StarterRng::StarterRng() -bool StarterRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ - return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); -} - bool StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ @@ -598,8 +594,6 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - static const uint64_t INITIAL_ADVANCES_RADIUS = 1024; static const uint8_t MAX_HISTORY_LENGTH = 10; @@ -615,24 +609,14 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); RNG_TARGET.set_target(target_result, GENDER_THRESHOLD); - env.log("Target PID: " + to_hex_string(target_result.pid)); - env.log("Target Nature: " + nature_to_string(target_result.nature)); - 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)); - + log_target_pokemon(env.console, target_result); + RngCalibrations calibrations = { RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, RNG_CALIBRATION.csf_calibration, RNG_CALIBRATION.advances_calibration }; - env.log("Initial Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); - env.log("Initial CSF calibration (frames): " + std::to_string(calibrations.csf_offset)); - env.log("Initial In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset)); + log_calibrations(env.console, calibrations, true); Milliseconds launch_delay = INITIAL_LAUNCH_DELAY; @@ -646,7 +630,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte while (true){ if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ + if (have_hit_target(TARGET_SEED, ADVANCES, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -687,8 +671,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target - double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5]; - calibrations.seed_offset += seed_bump; + apply_seed_bump(calibrations, uncertain_history); uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; @@ -745,11 +728,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte ); if (finished){ env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); continue; } @@ -783,11 +762,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte ); if (finished){ env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); continue; } } @@ -858,11 +833,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte MAX_HISTORY_LENGTH, calibrations, search_hits, 5, 2, true ); env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); break; } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index 46bb4c4303..b4ae03995d 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -48,8 +48,6 @@ class StarterRng : public SingleSwitchProgramInstance{ charmander }; - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); - bool walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context); bool auto_battle_rival( SingleSwitchProgramEnvironment& env, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp index 10d1ed28be..50569eeceb 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp @@ -16,6 +16,7 @@ #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" #include "PokemonFRLG_RngCalibration.h" +#include "PokemonFRLG_RngLoopRoutines.h" #include "PokemonFRLG_StaticRng.h" namespace PokemonAutomation{ @@ -227,10 +228,6 @@ StaticRng::StaticRng() -bool StaticRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ - return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); -} - void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast @@ -307,8 +304,6 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; @@ -326,24 +321,14 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); RNG_TARGET.set_target(target_result, GENDER_THRESHOLD); - env.log("Target PID: " + to_hex_string(target_result.pid)); - env.log("Target Nature: " + nature_to_string(target_result.nature)); - 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)); + log_target_pokemon(env.console, target_result); RngCalibrations calibrations = { RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, RNG_CALIBRATION.csf_calibration, RNG_CALIBRATION.advances_calibration }; - env.log("Initial Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); - env.log("Initial CSF calibration (frames): " + std::to_string(calibrations.csf_offset)); - env.log("Initial In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset)); + log_calibrations(env.console, calibrations, true); Milliseconds launch_delay = INITIAL_LAUNCH_DELAY; @@ -355,7 +340,7 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex while (true){ if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ + if (have_hit_target(TARGET_SEED, ADVANCES, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -385,8 +370,7 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex } // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target - double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5]; - calibrations.seed_offset += seed_bump; + apply_seed_bump(calibrations, uncertain_history); uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; @@ -453,59 +437,23 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::vector search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set_hits(search_hits); - bool force_finish = ( - (MAX_RARE_CANDIES == 0) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD) - ); - bool finished = update_history( - env.console, uncertain_history, calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, 1, 2, force_finish - ); - - for (uint64_t i=0; i search_hits = refine_calibration_with_rare_candy( + env, context, LANGUAGE, pokemon, filters, BASE_STATS, + uncertain_history, calibration_history, calibrations, + MAX_HISTORY_LENGTH, MAX_RARE_CANDIES, AdvRngMethod::Method1, false, + stats.errors, NOTIFICATION_ERROR_RECOVERABLE, + [&](AdvRngFilters& f){ + return get_search_results(env.console, searcher, f, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + }, + [&](const std::vector& h){ RNG_CALIBRATION.set_hits(h); }, + [&](const AdvRngFilters& f){ RNG_FILTERS.set(f); }, + [&](const std::vector& h){ + return all_equal(h) || all_indistinguishable(h, searcher, GENDER_THRESHOLD); } - RNG_FILTERS.set(filters); - - search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set_hits(search_hits); - - force_finish = ( - failed - || (i == (MAX_RARE_CANDIES - 1)) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD) - ); - finished = update_history( - env.console, uncertain_history, - calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, - 1, 2, force_finish - ); - } + ); env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.h index a36c3f5cfa..053636cbcf 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.h @@ -44,8 +44,6 @@ class StaticRng : public SingleSwitchProgramInstance{ private: - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); - SectionDividerOption m_calibration_displays; RngTargetDisplay RNG_TARGET; RngFilterDisplay RNG_FILTERS; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp index b675d5ab59..c8d7732096 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp @@ -18,6 +18,7 @@ #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" #include "PokemonFRLG_RngCalibration.h" +#include "PokemonFRLG_RngLoopRoutines.h" #include "PokemonFRLG_LocationsDatabase.h" #include "PokemonFRLG_RngStatsDatabase.h" #include "PokemonFRLG_EncountersDatabase.h" @@ -259,10 +260,6 @@ WildRng::WildRng() -bool WildRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ - return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); -} - void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast @@ -366,8 +363,6 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; @@ -398,9 +393,7 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& RNG_CALIBRATION.csf_calibration, RNG_CALIBRATION.advances_calibration }; - env.log("Initial Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); - env.log("Initial CSF calibration (frames): " + std::to_string(calibrations.csf_offset)); - env.log("Initial In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset)); + log_calibrations(env.console, calibrations, true); Milliseconds launch_delay = INITIAL_LAUNCH_DELAY; @@ -412,7 +405,7 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& while (true){ if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ + if (have_hit_target(TARGET_SEED, ADVANCES, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -448,8 +441,7 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& } // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target - double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5]; - calibrations.seed_offset += seed_bump; + apply_seed_bump(calibrations, uncertain_history); uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; @@ -538,55 +530,23 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& AdvRngFilters filters = observation_to_filters(pokemon, base_stats, AdvRngMethod::Any); RNG_FILTERS.set(filters); - std::vector search_hits = get_wild_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, gender_threshold, SUPER_ROD); - RNG_CALIBRATION.set_hits(search_hits); - bool force_finish = ( - (MAX_RARE_CANDIES == 0) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, gender_threshold, SUPER_ROD) - ); - bool finished = update_history(env.console, uncertain_history, calibration_history, MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, force_finish); - - for (uint64_t i=0; i search_hits = refine_calibration_with_rare_candy( + env, context, LANGUAGE, pokemon, filters, base_stats, + uncertain_history, calibration_history, calibrations, + MAX_HISTORY_LENGTH, MAX_RARE_CANDIES, AdvRngMethod::Any, safari_zone, + stats.errors, NOTIFICATION_ERROR_RECOVERABLE, + [&](AdvRngFilters& f){ + return get_wild_search_results(env.console, searcher, f, SEED_VALUES, ADVANCES, advances_radius, gender_threshold, SUPER_ROD); + }, + [&](const std::vector& h){ RNG_CALIBRATION.set_hits(h); }, + [](const AdvRngFilters&){}, // WildRng intentionally does not refresh the filter display mid-loop + [&](const std::vector& h){ + return all_equal(h) || all_indistinguishable(h, searcher, gender_threshold, SUPER_ROD); } - bool failed = use_rare_candy(env.console, context, LANGUAGE, pokemon, filters, base_stats, AdvRngMethod::Any, safari_zone, i == 0); - if (failed) { - update_history( - env.console, uncertain_history, calibration_history, - MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true - ); - stats.errors++; - send_program_recoverable_error_notification( - env, NOTIFICATION_ERROR_RECOVERABLE, - "Failed to use Rare Candy." - ); - } - - search_hits = get_wild_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, gender_threshold, SUPER_ROD); - RNG_CALIBRATION.set_hits(search_hits); - - force_finish = ( - failed - || (i == (MAX_RARE_CANDIES - 1)) - || all_equal(search_hits) - || all_indistinguishable(search_hits, searcher, gender_threshold, SUPER_ROD) - ); - finished = update_history( - env.console, uncertain_history, - calibration_history, MAX_HISTORY_LENGTH, - calibrations, search_hits, - 1, 2, force_finish - ); - } + ); env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } + update_failed_searches(failed_searches, search_hits); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.h index 481bf7c779..f2ae635a92 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.h @@ -59,8 +59,6 @@ class WildRng : public SingleSwitchProgramInstance{ superrod }; - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); - SectionDividerOption m_calibration_displays; RngTargetDisplay RNG_TARGET; RngFilterDisplay RNG_FILTERS; diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 1803b8b772..1b369609a8 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1550,6 +1550,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_LocationsDatabase.cpp From ad416eac94b5b38575442c6b9e413a69eb00a7eb Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Wed, 17 Jun 2026 13:39:07 -0500 Subject: [PATCH 2/2] add egg histories constructor --- .../Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h index 9268b37ffd..7e217cc69c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngLoopRoutines.h @@ -36,6 +36,10 @@ struct EggCalibrationHistories{ RngCalibrationHistory held; RngCalibrationHistory pickup; + EggCalibrationHistories(RngCalibrations calibrations) + : initial_calibrations(calibrations) + {}; + RngCalibrationHistory& frame(bool pickup_frame){ return pickup_frame ? pickup : held; } void clear_uncertain(){