diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index cd9a710d2a..c1ae25df4d 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; @@ -366,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& 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/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index b30dce32ae..05aba7c391 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -22,6 +22,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" #include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" #include "Programs/RngManipulation/PokemonFRLG_SidHelper.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("---- Untested/Beta/WIP ----"); 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..8598682f6b --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -0,0 +1,188 @@ +/* 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 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, "-", "") +{ + 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)); +} + +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) + , hits(false, "Seeds/Advances:", LockMode::READ_ONLY, "-", "") +{ + PA_ADD_STATIC(hits); +} + +std::vector PossibleHitsDisplay::get_rng_states_from_map(const std::map& hits_map){ + std::vector rng_states; + for(std::map::const_iterator it = hits_map.begin(); it != hits_map.end(); ++it) { + rng_states.emplace_back(it->first); + } + return rng_states; +} + +std::string PossibleHitsDisplay::get_hits_string(const std::vector& rng_states){ + std::string hits_string; + for (size_t i=0; i 0){ + hits_string += ", "; + } + AdvRngState hit = rng_states[i]; + uint16_t seed = hit.seed; + std::ostringstream s; + s << std::hex << seed; + hits_string += s.str(); + 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(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(const std::map& hits_map){ + std::vector rng_states = get_rng_states_from_map(hits_map); + set(rng_states); +} + +void PossibleHitsDisplay::reset(){ + hits.set("-"); +} + +} +} +} \ 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..9ba631caa7 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -0,0 +1,66 @@ +/* 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); + void reset(); + +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(const std::map& hits_map); + void reset(); + +private: + 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(const std::map& hits_map); +public: + StringOption hits; +}; + +} +} +} +#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..fd45881c66 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -0,0 +1,1190 @@ +/* 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/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" +#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 for FRLG starters.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +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{ + 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, + 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.", + LockMode::LOCK_WHILE_RUNNING, + 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, + 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.
" + "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 { + +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(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');){ + values.push_back(parse_seed(env, line)); + } + return values; +} + +int16_t seed_position_in_list(uint16_t seed, std::vector list){ + 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::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); + + AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS); + filters.ivs = new_filters.ivs; + RNG_FILTERS.set(filters); +} + +std::map StarterRng::get_starter_search_results( + SingleSwitchProgramEnvironment& env, + AdvRngSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + const uint64_t& advances_radius, + const AdvObservedPokemon& pokemon +){ + std::map search_hits; + 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())); + 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( + const 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, + bool force_finish +){ + const int MAX_ADVANCE_POSSIBILITIES = 5; + 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 advances; + std::vector hits; + 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); + } + + // 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 + } + } + } + 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; + } + + + // 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); + 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; + } + + // 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( + 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, + AdvRngFilters& filters, + const 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(filters, pokemon, stats, evyield, BASE_STATS); + + // exit battle + pbf_mash_button(context, BUTTON_B, 20s); + context.wait_for_all_requests(); + + return false; +} + + + +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; +} + +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, {0, +1}, 1250ms, 300ms); + + return false; +} + +int StarterRng::autolevel_on_route1( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& BASE_STATS +){ + Pokemon::EVs evyield = {0, 0, 0, 0, 0, 0}; + Pokemon::StatReads stats; + + env.log("Arrived at Route 1."); + bool leftright = true; + 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 -1; + } + + if (ret == 1){ // shiny + return 1; + } + + // 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; + bool failed; + BattleLevelUpReader reader; + + switch (ret2){ + case BattleResult::opponentfainted: + env.log("Opponent fainted."); + 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, 1800ms); + } + }, + { level_up, black_screen } + ); + + switch (ret3){ + case 0: + env.log("Level-up stats detected. Reading stats..."); + screen = env.console.video().snapshot(); + stats = reader.read_stats(env.logger(), screen); + update_filters(filters, pokemon, stats, evyield, BASE_STATS); + exit_wild_battle(env.console, context, false, true); + return 0; + case -1: + exit_wild_battle(env.console, context, false, true); + default: + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + continue; + } + case BattleResult::playerfainted: + env.log("Pokemon fainted. Mashing B through dialogues..."); + pbf_mash_button(context, BUTTON_B, 30s); // skip through a few transitions and lots of dialogue + failed = walk_to_route1_from_home(env, context); + if (failed){ + return -1; + } + context.wait_for_all_requests(); + leftright = true; + continue; + case BattleResult::outofpp: + case BattleResult::unknown: + default: + return -1; + } + } +} + +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); + + RNG_FILTERS.reset(); + POSSIBLE_HITS.reset(); + + 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){ + 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)); + + 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; + + 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; + double CONTINUE_SCREEN_ADJUSTMENT = 0; + + 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)); + 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)); + + StarterRngAdvanceHistory ADVANCE_HISTORY; + StarterRngCalibrationHistory CALIBRATION_HISTORY; + uint64_t INITIAL_ADVANCES_RADIUS = 1024; + uint64_t resets = 0; + bool wildshiny_found = false; + + 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())){ + env.log("Target Hit!"); + stats.nonshiny++; + break; + } + env.log("Missed target."); + } + + if (resets > MAX_RESETS){ + env.log("Max resets reached."); + break; + } + + if (wildshiny_found){ + break; + } + + 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){ + AdvRngState prev_hit = CALIBRATION_HISTORY.results.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 - 0.5; + }else{ + 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 + 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; + } + + 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)); + + 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_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); + 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){ + 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_filters(pokemon, BASE_STATS); + RNG_FILTERS.set(filters); + + std::map search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); + 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; + } + + 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, filters, BASE_STATS); + if (failed){ + stats.errors++; + continue; // reset game + } + 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){ + env.log("RNG search finished."); + continue; + } + } + + failed = walk_to_route1_from_lab(env, context); + if (failed){ + stats.errors++; + continue; // reset game + } + + auto num_levels = pokemon.level.size(); + uint16_t MAX_LEVELS = 3; + while(true){ + 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, filters, 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(); + 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){ + env.log("RNG search finished."); + 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 new file mode 100644 index 0000000000..863973d77a --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -0,0 +1,160 @@ +/* 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 "Pokemon/Pokemon_AdvRng.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 + }; + + 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, const AdvRngState& hit); + + AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + + 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, + const uint64_t& advances_radius, + const AdvObservedPokemon& pokemon + ); + double get_seed_calibration_frames( + const StarterRngCalibrationHistory& CALIBRATION_HISTORY, + const std::vector& SEED_VALUES, + const int16_t& SEED_POSITION + ); + double get_advances_calibration_frames( + const StarterRngCalibrationHistory& CALIBRATION_HISTORY, + const uint64_t& ADVANCES + ); + + bool update_history( + SingleSwitchProgramEnvironment& env, + StarterRngAdvanceHistory& ADVANCE_HISTORY, + StarterRngCalibrationHistory& CALIBRATION_HISTORY, + const uint16_t& MAX_HISTORY_LENGTH, + const double& SEED_CALIBRATION_FRAMES, + const double& ADVANCES_CALIBRATION, + const double& CONTINUE_SCREEN_ADJUSTMENT, + const std::map& search_hits, + bool force_finish = false + ); + + bool walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool auto_battle_rival( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const 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, + AdvRngFilters& filters, + const BaseStats& BASE_STATS + ); + + + 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; + + BooleanCheckBoxOption IGNORE_WILD_SHINIES; + + 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 9d4b3e69f8..ce2cc9b5b7 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1524,12 +1524,16 @@ 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_SidHelper.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.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