diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 5b0511cd8c..6863a4f67d 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -135,6 +135,9 @@ void level_up_observed_pokemon(AdvObservedPokemon& pokemon, const StatReads& new // returns the appropriate NatureAdjustments for an AdvNature Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature); +// returns the appropriate NatureAdjustments for an AdvNature +Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature); + // returns search filters that correspond with observed stats AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, const BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1); diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index 29fe872e2d..04d8d05bf5 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -26,6 +26,7 @@ #include "PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h" #include "PokemonFRLG/Inference/Map/PokemonFRLG_MapDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h" #include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" @@ -678,6 +679,65 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext } } +void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context){ + uint16_t errors = 0; + bool start_menu_is_open = false; + while (true){ + if (errors > 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "open_party_menu_from_overworld(): Failed to open party menu 5 times in a row.", + console + ); + } + + context.wait_for_all_requests(); + if (!start_menu_is_open){ + open_start_menu(console, context); // This is unavoidable since we cannot detect the overworld. + start_menu_is_open = true; + } + + StartMenuWatcher start_menu(COLOR_RED); + BagWatcher bag(COLOR_RED); + + int ret = wait_until( + console, context, 10000ms, + { start_menu, bag } + ); + + switch (ret){ + case 0: + if (menu_context == StartMenuContext::SAFARI_ZONE){ + ret = move_cursor_to_position(console, context, SelectionArrowPositionSafariMenu::BAG); + } else { + ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::BAG); + } + + if (ret < 0){ + console.log("Failed to navigate to BAG on the start menu."); + errors++; + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + start_menu_is_open = false; + } else { + console.log("Navigated to BAG on the start menu"); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + } + continue; + case 1: + console.log("Bag opened."); + return; + default: + console.log("Failed to open bag."); + errors++; + pbf_mash_button(context, BUTTON_B, 2000ms); + start_menu_is_open = false; + continue; + } + } +} + 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 3f8ee0f6fc..c984dfb6ff 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -76,6 +76,9 @@ enum class StartMenuContext { }; void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD); +// 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 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/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 05aba7c391..01bb6accff 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -23,6 +23,7 @@ #include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" #include "Programs/RngManipulation/PokemonFRLG_SidHelper.h" #include "Programs/RngManipulation/PokemonFRLG_StarterRng.h" +#include "Programs/RngManipulation/PokemonFRLG_GiftRng.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -69,6 +70,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); } if (PreloadSettings::instance().DEVELOPER_MODE){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp index a37061114b..1c44a297db 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp @@ -62,9 +62,9 @@ void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, void load_game_after_delay(ProControllerContext& context, uint64_t CONTINUE_SCREEN_DELAY){ pbf_wait(context, std::chrono::milliseconds(CONTINUE_SCREEN_DELAY - 3000)); - pbf_press_button(context, BUTTON_A, 33ms, 1467ms); + pbf_press_button(context, BUTTON_A, 50ms, 1450ms); // skip recap - pbf_press_button(context, BUTTON_B, 33ms, 2467ms); + pbf_press_button(context, BUTTON_B, 50ms, 2450ms); // need to later subtract 4000ms from delay to hit desired number of advances } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp new file mode 100644 index 0000000000..cbe8c4aa64 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -0,0 +1,718 @@ +/* Gift 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/StartupChecks/StartProgramChecks.h" +#include "Pokemon/Pokemon_Strings.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.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_GiftRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +GiftRng_Descriptor::GiftRng_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:GiftRng", + Pokemon::STRING_POKEMON + " FRLG", "Gift RNG", + "Programs/PokemonFRLG/GiftRng.html", + "Automatically calibrate timings to hit a specific RNG target for FRLG gift " + STRING_POKEMON, + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct GiftRng_Descriptor::Stats : public StatsTracker{ + Stats() + : resets(m_stats["Resets"]) + , shinies(m_stats["Shinies"]) + , nonshiny(m_stats["Non-Shiny Hits"]) + , 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("Errors", HIDDEN_IF_ZERO); + } + std::atomic& resets; + std::atomic& shinies; + std::atomic& nonshiny; + std::atomic& errors; +}; +std::unique_ptr GiftRng_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +GiftRng::GiftRng() + : LANGUAGE( + "Game Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + }, + LockMode::LOCK_WHILE_RUNNING, + true + ) + , TARGET( + "Target:
", + { + {PokemonFRLG_RngTarget::magikarp, "magikarp", "Magikarp"}, + {PokemonFRLG_RngTarget::hitmonchan, "hitmonchan", "Hitmonchan"}, + {PokemonFRLG_RngTarget::hitmonlee, "hitmonlee", "Hitmonlee"}, + {PokemonFRLG_RngTarget::eevee, "eevee", "Eevee"}, + {PokemonFRLG_RngTarget::lapras, "lapras", "Lapras"}, + {PokemonFRLG_RngTarget::omanyte, "omanyte", "Omanyte"}, + {PokemonFRLG_RngTarget::kabuto, "kabuto", "Kabuto"}, + {PokemonFRLG_RngTarget::aerodactyl, "aerodactyl", "Aerodactyl"}, + {PokemonFRLG_RngTarget::gamecornerabra, "gamecornerabra", "Game Corner Abra"}, + {PokemonFRLG_RngTarget::gamecornerclefairy, "gamecornerclefairy", "Game Corner Clefairy"}, + {PokemonFRLG_RngTarget::gamecornerdratini, "gamecornerdratini", "Game Corner Dratini"}, + {PokemonFRLG_RngTarget::gamecornerscyther, "gamecornerscyther", "Game Corner Scyther"}, + {PokemonFRLG_RngTarget::gamecornerpinsir, "gamecornerpinsir", "Game Corner Pinsir"}, + {PokemonFRLG_RngTarget::gamecornerporygon, "gamecornerporygon", "Game Corner Porygon"}, + {PokemonFRLG_RngTarget::togepi, "togepi", "Togepi"}, + }, + LockMode::LOCK_WHILE_RUNNING, + PokemonFRLG_RngTarget::magikarp + ) + , MAX_RESETS( + "Max Resets:
", + LockMode::UNLOCK_WHILE_RUNNING, + 50, 0 // default, min + ) + , MAX_RARE_CANDIES( + "Max Rare Candies:
" + "The number of rare candies in your bag. Make sure these are at the top position of the bag.
" + "Rare candies used during calibration will be restored after resetting.", + LockMode::UNLOCK_WHILE_RUNNING, + 0, 0, 999 // default, min, max + ) + , 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 + ) + , EXTRA_BUTTON( + "Extra Button:
" + "Additional button presses that affect the seed.", + { + {BlackoutButton::None, "None", "None"}, + {BlackoutButton::L, "L", "Blackout L"}, + {BlackoutButton::R, "R", "Blackout R"}, + }, + LockMode::LOCK_WHILE_RUNNING, + BlackoutButton::None + ) + , 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_TEACHY_TV( + "Use Teachy TV:" + "
Opens the Teachy TV to quickly advance the RNG at 313x speed.
" + "Warning: can result in larger misses.", + LockMode::LOCK_WHILE_RUNNING, + false // default + ) + , PROFILE( + "User Profile Position:
" + "The position, from left to right, of the Switch profile with the FRLG save you'd like to use.
" + "If this is set to 0, Switch 1 defaults to the last-used profile, while Switch 2 defaults to the first profile (position 1)", + LockMode::LOCK_WHILE_RUNNING, + 0, 0, 8 // default, min, max + ) + , TAKE_VIDEO( + "Take Video:
Record a video when the shiny is found.", + LockMode::LOCK_WHILE_RUNNING, + true // default + ) + , GO_HOME_WHEN_DONE(true) + , NOTIFICATION_SHINY( + "Shiny found", + true, true, ImageAttachmentMode::JPG, + {"Notifs", "Showcase"} + ) + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_SHINY, + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + }) +{ + PA_ADD_OPTION(RNG_FILTERS); + PA_ADD_OPTION(RNG_CALIBRATION); + PA_ADD_OPTION(LANGUAGE); + PA_ADD_OPTION(TARGET); + PA_ADD_OPTION(MAX_RESETS); + PA_ADD_OPTION(MAX_RARE_CANDIES); + PA_ADD_OPTION(SEED); + PA_ADD_OPTION(SEED_LIST); + PA_ADD_OPTION(SEED_BUTTON); + PA_ADD_OPTION(EXTRA_BUTTON); + PA_ADD_OPTION(SEED_DELAY); + PA_ADD_OPTION(ADVANCES); + // PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); + PA_ADD_OPTION(USE_TEACHY_TV); + PA_ADD_OPTION(PROFILE); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + + + +bool GiftRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ + return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); +} + +AdvObservedPokemon GiftRng::read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // assumes we're already on the first summary page + PokemonFRLG_Stats stats; + StatsReader reader(COLOR_RED); + + env.log("Reading Page 1 (Name, Level, Nature, Gender)..."); + VideoSnapshot screen1 = env.console.video().snapshot(); + reader.read_page1(env.logger(), LANGUAGE, screen1, stats); + + SummaryPage2Watcher page_two(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + 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; +} + +bool GiftRng::use_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& BASE_STATS, + bool first +){ + // navigate to the bag (only needed for the first use) + if (first){ + open_bag_from_overworld(env.console, context); + // move left to the correct pocket (in case Teachy TV was used) + pbf_move_left_joystick(context, {-1, 0}, 200ms, 800ms); + pbf_move_left_joystick(context, {-1, 0}, 200ms, 800ms); + } + + // use rare candy and watch for the party screen + PartyMenuWatcher party_menu(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + } + }, + { party_menu } + ); + if (ret < 0){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to detect party menu." + ); + return true; + } + + // select the last party slot (unknown how full the party is, so we can't detect a particular slot) + // only needed on the first use + if (first){ + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + } + + // watch for level up stats + PartyLevelUpWatcher level_up(COLOR_RED, PartyLevelUpDialog::stats); + context.wait_for_all_requests(); + int ret2 = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<30; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + } + }, + { level_up } + ); + if (ret2 < 0){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to detect level-up stats." + ); + return true; + } + + PartyLevelUpReader 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, {}, BASE_STATS); + RNG_FILTERS.set(filters); + + // return to the bag (possibly learning a move, but trying to preven evolution) + int attempts = 0; + while (true){ + if (attempts > 5){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to return to bag menu in 5 attempts." + ); + return true; + } + BagWatcher bag_menu(COLOR_RED); + PartyMoveLearnWatcher move_learn(COLOR_RED); + context.wait_for_all_requests(); + int ret3 = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<15; i++){ + pbf_press_button(context, BUTTON_B, 200ms, 1800ms); + } + }, + { bag_menu, move_learn } + ); + attempts++; + switch (ret3){ + case 0: + env.log("Returned to bag."); + return false; + case 1: + env.log("Move learn opportunity detected."); + // don't learn move + pbf_press_button(context, BUTTON_B, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + continue; + default: + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to return to bag menu." + ); + return true; + } + } +} + + +void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + /* + * Settings: Text Speed fast + */ + + GiftRng_Descriptor::Stats& stats = env.current_stats(); + + home_black_border_check(env.console, context); + + RNG_FILTERS.reset(); + RNG_CALIBRATION.reset(); + + const uint16_t TARGET_SEED = parse_seed(env.console, SEED); + const std::vector SEED_VALUES = parse_seed_list(env.console, 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, + "GiftRng(): 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; + int16_t GENDER_THRESHOLD = -1; + switch (TARGET){ + case PokemonFRLG_RngTarget::magikarp: + BASE_STATS = { 20, 10, 55, 15, 20, 80 }; + GENDER_THRESHOLD = 126; + break; + case PokemonFRLG_RngTarget::hitmonchan: + BASE_STATS = { 50, 105, 79, 35, 110, 76 }; + GENDER_THRESHOLD = -1; + break; + case PokemonFRLG_RngTarget::hitmonlee: + BASE_STATS = { 50, 120, 53, 35, 110, 87 }; + GENDER_THRESHOLD = -1; + break; + case PokemonFRLG_RngTarget::eevee: + BASE_STATS = { 55, 55, 50, 45, 65, 55 }; + GENDER_THRESHOLD = 30; + break; + case PokemonFRLG_RngTarget::lapras: + BASE_STATS = { 130, 85, 80, 85, 95, 60 }; + GENDER_THRESHOLD = 126; + break; + case PokemonFRLG_RngTarget::omanyte: + BASE_STATS = { 35, 40, 100, 90, 55, 35 }; + GENDER_THRESHOLD = 30; + break; + case PokemonFRLG_RngTarget::kabuto: + BASE_STATS = { 30, 80, 90, 55, 45, 55 }; + GENDER_THRESHOLD = 30; + break; + case PokemonFRLG_RngTarget::aerodactyl: + BASE_STATS = { 80, 105, 65, 60, 75, 130 }; + GENDER_THRESHOLD = 30; + break; + case PokemonFRLG_RngTarget::gamecornerabra: + BASE_STATS = { 25, 20, 15, 105, 55, 90 }; + GENDER_THRESHOLD = 63; + break; + case PokemonFRLG_RngTarget::gamecornerclefairy: + BASE_STATS = { 70, 45, 48, 60, 65, 35 }; + GENDER_THRESHOLD = 190; + break; + case PokemonFRLG_RngTarget::gamecornerdratini: + BASE_STATS = { 41, 64, 45, 50, 50, 50 }; + GENDER_THRESHOLD = 126; + break; + case PokemonFRLG_RngTarget::gamecornerscyther: + BASE_STATS = { 70, 110, 80, 55, 80, 105 }; + GENDER_THRESHOLD = 126; + break; + case PokemonFRLG_RngTarget::gamecornerpinsir: + BASE_STATS = { 65, 125, 100, 55, 70, 85 }; + GENDER_THRESHOLD = 126; + break; + case PokemonFRLG_RngTarget::gamecornerporygon: + BASE_STATS = { 65, 60, 70, 85, 75, 40 }; + GENDER_THRESHOLD = -1; + break; + case PokemonFRLG_RngTarget::togepi: + BASE_STATS = { 35, 20, 65, 40, 65, 20 }; + GENDER_THRESHOLD = 30; + break; + default: + break; + } + + const double FRAMERATE = 59.999977; // FPS + const double FRAME_DURATION = 1000 / FRAMERATE; + + uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; + double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + + uint64_t CONTINUE_SCREEN_FRAMES = 200; + + const int64_t FIXED_SEED_OFFSET = -845; // milliseconds. approximate; + double SEED_CALIBRATION_FRAMES = RNG_CALIBRATION.seed_calibration / FRAME_DURATION; + double ADVANCES_CALIBRATION = RNG_CALIBRATION.advances_calibration; + double CONTINUE_SCREEN_ADJUSTMENT = RNG_CALIBRATION.csf_calibration; + + 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)); + + RngAdvanceHistory ADVANCE_HISTORY; + RngCalibrationHistory CALIBRATION_HISTORY; + uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 8192 : 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){ + SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(CALIBRATION_HISTORY, SEED_VALUES, SEED_POSITION); + ADVANCES_CALIBRATION = get_advances_calibration_frames(CALIBRATION_HISTORY, ADVANCES); + } + + if (CALIBRATION_HISTORY.results.size() > 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; + } + CONTINUE_SCREEN_ADJUSTMENT = fmod(CONTINUE_SCREEN_ADJUSTMENT, 2); + }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; + + double TEACHY_ADVANCES = 0; + bool should_use_teachy_tv = USE_TEACHY_TV && (INGAME_ADVANCES > 5000); // don't use Teachy TV for short in-game advance targets + if (should_use_teachy_tv) { + TEACHY_ADVANCES = std::floor((INGAME_ADVANCES - 5000) / 313) * 313; + } + + 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 TEACHY_DELAY = uint64_t(TEACHY_ADVANCES * FRAME_DURATION / 313); + uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * (INGAME_ADVANCES - TEACHY_ADVANCES) / 2)) - (should_use_teachy_tv ? 13700 : 0); + + env.log("Title screen duration: " + std::to_string(CALIBRATED_SEED_DELAY) + "ms"); + env.log("Continue screen duration: " + std::to_string(CONTINUE_SCREEN_DELAY) + "ms"); + if (should_use_teachy_tv){ + env.log("Teachy TV duration: " + std::to_string(TEACHY_DELAY) + "ms"); + env.log("Non-Teachy TV in-game duration: " + std::to_string(INGAME_DELAY) + "ms"); + }else{ + env.log("In-game duration: " + std::to_string(INGAME_DELAY) + "ms"); + } + + env.log("Resetting Game..."); + reset_and_perform_blind_sequence( + env.console, context, TARGET, + SEED_BUTTON, EXTRA_BUTTON, CALIBRATED_SEED_DELAY, + CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, + false, PROFILE + ); + stats.resets++; + + RNG_FILTERS.reset(); + RNG_CALIBRATION.reset(); + + bool shiny_found = check_for_shiny(env.console, context, TARGET); + + if (shiny_found){ + env.log("Shiny found!"); + stats.shinies++; + send_program_notification( + env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); + if (TAKE_VIDEO){ + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } + break; + } + + AdvObservedPokemon pokemon = read_summary(env, context); + AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); + RNG_FILTERS.set(filters); + + std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + RNG_CALIBRATION.set( + SEED_CALIBRATION_FRAMES * FRAME_DURATION, + CONTINUE_SCREEN_ADJUSTMENT, + ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + search_hits + ); + bool finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 1); + if (finished || (MAX_RARE_CANDIES == 0)){ + env.log("RNG search finished."); + continue; + } + + for (uint64_t i=0; i make_stats() const override; +}; + +class GiftRng : public SingleSwitchProgramInstance{ +public: + GiftRng(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + + bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); + + AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + + bool use_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& BASE_STATS, + bool first + ); + + OCR::LanguageOCROption LANGUAGE; + + EnumDropdownOption TARGET; + + SimpleIntegerOption MAX_RESETS; + SimpleIntegerOption MAX_RARE_CANDIES; + + RngFilterDisplay RNG_FILTERS; + RngCalibrationDisplay RNG_CALIBRATION; + + StringOption SEED; + TextEditOption SEED_LIST; + EnumDropdownOption SEED_BUTTON; + EnumDropdownOption EXTRA_BUTTON; + SimpleIntegerOption SEED_DELAY; + + SimpleIntegerOptionADVANCES; + + BooleanCheckBoxOption USE_TEACHY_TV; + + SimpleIntegerOption PROFILE; + + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp index 0751aebdea..0adbb71fb4 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp @@ -27,123 +27,172 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -void rng_start_game_from_home( +void rng_reset_and_return_home( ConsoleHandle& console, ProControllerContext& context, - uint8_t game_slot, uint8_t user_slot ){ - context.wait_for_all_requests(); - { - HomeMenuWatcher detector(console); - int ret = run_until( - console, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 10000ms); - }, - { detector } - ); - if (ret == 0){ - console.log("Detected Home screen."); - }else{ + // close the game + go_home(console, context); + close_game_from_home(console, context); + + // console specific delays between opening the game and returning to the Home screen + ConsoleType console_type = console.state().console_type(); // go_home() has already been called, so this should already be detected + Milliseconds launch_delay = (console_type == ConsoleType::Switch1) ? 450ms : 950ms; + + bool extra_resume_press = false; + WallClock deadline = current_time() + std::chrono::minutes(5); + while (true){ + if (current_time() > deadline){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "start_game_from_home_with_inference(): Failed to detect Home screen after 10 seconds.", + "rng_start_game_and_return_home(): Failed to start game and return to Home within 5 minutes.", console ); } - context.wait_for(std::chrono::milliseconds(100)); - } - - if (game_slot != 0){ - ssf_press_button(context, BUTTON_HOME, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0, 160ms); - for (uint8_t c = 1; c < game_slot; c++){ - ssf_press_dpad_ptv(context, DPAD_RIGHT, 160ms); - } - context.wait_for_all_requests(); - } - - pbf_press_button(context, BUTTON_A, 160ms, 340ms); - WallClock deadline = current_time() + std::chrono::minutes(5); - while (current_time() < deadline){ - HomeMenuWatcher home(console, std::chrono::milliseconds(2000)); StartGameUserSelectWatcher user_select(console, COLOR_GREEN); + HomeMenuWatcher home(console, std::chrono::milliseconds(2000)); UpdateMenuWatcher update_menu(console, COLOR_PURPLE); CheckOnlineWatcher check_online(COLOR_CYAN); FailedToConnectWatcher failed_to_connect(COLOR_YELLOW); - BlackScreenWatcher black_screen(COLOR_BLUE, {0.1, 0.15, 0.8, 0.7}); - // spend a little bit longer waiting for the black screen to avoid missing it - context.wait_for_all_requests(); - int ret1 = wait_until( - console, context, - std::chrono::seconds(2), - { black_screen } - ); - - switch (ret1){ - case 0: - console.log("Detected black screen. Game started..."); - return; - default: - console.log("Black screen not detected. Checking for other states..."); - } - - // handle other states + // first, get to the user select screen context.wait_for_all_requests(); int ret2 = wait_until( console, context, std::chrono::seconds(30), { - home, user_select, + home, update_menu, check_online, failed_to_connect, - black_screen, } ); // Wait for screen to stabilize. context.wait_for(std::chrono::milliseconds(100)); + // some of these states might not be relevant at this point (if they would only show up after pressing A on a profile) switch (ret2){ case 0: - console.log("Detected home screen (again).", COLOR_BLUE); - pbf_press_button(context, BUTTON_A, 160ms, 840ms); - break; - case 1: console.log("Detected user-select screen."); move_to_user(context, user_slot); - pbf_press_button(context, BUTTON_A, 160ms, 320ms); break; + case 1: + console.log("Detected Home screen.", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + continue; case 2: console.log("Detected update menu.", COLOR_BLUE); pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); - pbf_press_button(context, BUTTON_A, 160ms, 840ms); - break; + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + continue; case 3: console.log("Detected check online.", COLOR_BLUE); context.wait_for(std::chrono::seconds(1)); - break; + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + continue; case 4: console.log("Detected failed to connect.", COLOR_BLUE); pbf_press_button(context, BUTTON_A, 160ms, 840ms); - break; - case 5: - console.log("Detected black screen. Game started..."); - return; + continue; default: - console.log("start_game_from_home_with_inference(): No recognizable state after 30 seconds.", COLOR_RED); + console.log("rng_start_game_and_return_home(): No recognizable state after 30 seconds.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 160ms, 840ms); + continue; } - } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "rng_start_game_from_home(): Failed to start game after 5 minutes.", - console - ); + context.wait_for_all_requests(); + + // By this point, the user selection menu is open with the desired profile selected, and the game hasn't yet been started. + // Everything up to this point has not been time-sensitive. + // Waiting for all requests and using inference should be avoided for any button presses that happen *while the game is open*, + // but we can make sure we've gotten back to the home screen and pause there + + // open the game and go back home ASAP + if (extra_resume_press){ + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + } + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 1800ms); + + while(current_time() < deadline){ + // make sure a black screen appeared as a result of the button presses + // if a popup appears, flag that it happened even if it gets closed by the home button press + // Not sure how to handle the online check, so leaving it out for now + BlackScreenWatcher black_screen(COLOR_BLUE, {0.1, 0.15, 0.8, 0.7}); + int ret3 = wait_until( + console, context, + std::chrono::seconds(2), + { black_screen, update_menu, failed_to_connect }, + 1ms + ); + bool black_screen_detected = false; + switch (ret3){ + case 0: + console.log("Black screen detected."); + black_screen_detected = true; + break; + case 1: + console.log("Update menu detected.", COLOR_BLUE); + extra_resume_press = true; + case 2: + console.log("Failed to connect detected.", COLOR_BLUE); + extra_resume_press = true; + } + + context.wait_for_all_requests(); + + // make sure we're back at the home screen. Otherwise, handle any undesired states + int ret4 = wait_until( + console, context, + std::chrono::seconds(10), + { + home, + user_select, + update_menu, + failed_to_connect + } + ); + + switch (ret4){ + case 0: + if (black_screen_detected){ + console.log("Detected Home screen after black screen."); + return; + }else{ + console.log("Detected Home screen, but no black screen. Trying again from the beginning...", COLOR_BLUE); + launch_delay += 250ms; // bump this up in case it was too short + break; // back to the outer loop + } + return; + case 1: + console.log("Detected user-select screen. Trying again...", COLOR_BLUE); + move_to_user(context, user_slot); + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + continue; + case 2: + console.log("Detected update menu. Trying again...", COLOR_BLUE); + pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + continue; + case 3: + console.log("Detected failed to connect.", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + continue; + default: + console.log("rng_start_game_and_return_home(): No recognizable state after 10 seconds.", COLOR_RED); + pbf_press_button(context, BUTTON_HOME, 160ms, 840ms); + break; // back to outer loop + } + + break; + } + } } @@ -160,13 +209,7 @@ void reset_and_perform_blind_sequence( bool SAFARI_ZONE, uint8_t PROFILE ){ - // close the game - go_home(console, context); - close_game_from_home(console, context); - // start the game and quickly go back home - rng_start_game_from_home(console, context, uint8_t(0), PROFILE); - pbf_wait(context, 200ms); // wait a moment to ensure the game doesn't fail to launch - go_home(console, context); + rng_reset_and_return_home(console, context, PROFILE); // attempt to resume the game and perform the blind sequence // by this point, the license check should be over, so we don't need to worry about it when resuming the game @@ -217,7 +260,7 @@ void reset_and_perform_blind_sequence( void reset_and_detect_copyright_text(ConsoleHandle& console, ProControllerContext& context, uint8_t PROFILE){ go_home(console, context); close_game_from_home(console, context); - rng_start_game_from_home(console, context, uint8_t(0), PROFILE); + rng_start_game_and_return_home(console, context, uint8_t(0), PROFILE); pbf_wait(context, 200ms); // add an extra delay to try to ensure the game doesn't fail to launch go_home(console, context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index dc747105b0..f1588bdd07 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -4,6 +4,7 @@ * */ +#include #include #include #include @@ -157,7 +158,7 @@ void RngFilterDisplay::reset(){ RngCalibrationDisplay::RngCalibrationDisplay() : GroupOption("RNG Calibration", LockMode::READ_ONLY) - , seed_calibration("Seed Calibration (ms):", LockMode::LOCK_WHILE_RUNNING, 0.0) + , seed_calibration("Seed Calibration (ms):", LockMode::LOCK_WHILE_RUNNING, 0) , csf_calibration("Continue Screen Frames Calibration:", LockMode::LOCK_WHILE_RUNNING, 0.0) , advances_calibration("In-Game Advances Calibration:", LockMode::LOCK_WHILE_RUNNING, 0.0) , hits(false, "Seeds/Advances:", LockMode::READ_ONLY, "-", "") @@ -205,7 +206,7 @@ void RngCalibrationDisplay::set( double a_calibration, std::vector& rng_states ){ - seed_calibration.set(s_calibration); + seed_calibration.set(int64_t(std::round(s_calibration))); csf_calibration.set(c_calibration); advances_calibration.set(a_calibration); hits.set(get_hits_string(rng_states)); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index 804dbd027d..5bd3668f04 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -10,6 +10,7 @@ #include #include "Common/Cpp/Options/StringOption.h" #include "Common/Cpp/Options/TextEditOption.h" +#include "Common/Cpp/Options/SimpleIntegerOption.h" #include "Common/Cpp/Options/FloatingPointOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" @@ -83,7 +84,7 @@ class RngCalibrationDisplay : public GroupOption{ static std::string get_hits_string(const std::vector& rng_states); static std::string get_hits_string(const std::map& hits_map); public: - FloatingPointOption seed_calibration; + SimpleIntegerOption seed_calibration; FloatingPointOption csf_calibration; FloatingPointOption advances_calibration; StringOption hits; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp index c8f02c5724..6111ba371e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -145,13 +145,20 @@ bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, Poke case PokemonFRLG_RngTarget::togepi: hatch_togepi_egg(console, context); case PokemonFRLG_RngTarget::magikarp: + case PokemonFRLG_RngTarget::hitmonlee: + case PokemonFRLG_RngTarget::hitmonchan: case PokemonFRLG_RngTarget::hitmon: case PokemonFRLG_RngTarget::eevee: case PokemonFRLG_RngTarget::lapras: + case PokemonFRLG_RngTarget::omanyte: + case PokemonFRLG_RngTarget::kabuto: + case PokemonFRLG_RngTarget::aerodactyl: case PokemonFRLG_RngTarget::fossils: case PokemonFRLG_RngTarget::gamecornerabra: case PokemonFRLG_RngTarget::gamecornerclefairy: case PokemonFRLG_RngTarget::gamecornerdratini: + case PokemonFRLG_RngTarget::gamecornerscyther: + case PokemonFRLG_RngTarget::gamecornerpinsir: case PokemonFRLG_RngTarget::gamecornerbug: case PokemonFRLG_RngTarget::gamecornerporygon: return shiny_check_summary(console, context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 3a5e14ea06..a57a9b907f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -830,7 +830,12 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } if (pokemon.level.size() > 1){ search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set(SEED_CALIBRATION_FRAMES, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION, search_hits); + RNG_CALIBRATION.set( + SEED_CALIBRATION_FRAMES * FRAME_DURATION, + CONTINUE_SCREEN_ADJUSTMENT, + ADVANCES_CALIBRATION, + search_hits + ); env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); if (finished){ diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 7f86b1699f..e279105a8f 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1543,6 +1543,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp