diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 9a94b8864f..cd9a710d2a 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -11,6 +11,23 @@ namespace PokemonAutomation{ namespace Pokemon{ +void level_up_observed_pokemon(AdvObservedPokemon& pokemon, StatReads& newstats, EVs& evyield){ + uint8_t newlevel = pokemon.level.back() + 1; + pokemon.level.emplace_back(newlevel); + + pokemon.stats.emplace_back(newstats); + + EVs old_evs = pokemon.evs.back(); + EVs new_evs; + new_evs.hp = old_evs.hp + evyield.hp; + new_evs.attack = old_evs.attack + evyield.attack; + new_evs.defense = old_evs.defense + evyield.defense; + new_evs.spatk = old_evs.spatk + evyield.spatk; + new_evs.spdef = old_evs.spdef + evyield.spdef; + new_evs.speed = old_evs.speed + evyield.speed; + pokemon.evs.emplace_back(new_evs); +} + uint32_t increment_internal_rng_state(uint32_t& state){ return state * 0x41c64e6d + 0x6073; } @@ -66,19 +83,18 @@ AdvAbility ability_from_pid(uint32_t& pid){ AdvIvGroup iv_group_from_state(uint32_t& state){ AdvIvGroup ivgroup; - uint32_t remainingbits = state; + uint32_t remainingbits = state >> 16; - ivgroup.iv0 = (remainingbits & 0xfffff) % 32; - remainingbits = remainingbits >> 5; - ivgroup.iv1 = (remainingbits & 0xfffff) % 32; + ivgroup.iv0 = remainingbits & 0x1f; remainingbits = remainingbits >> 5; - ivgroup.iv2 = (remainingbits & 0xfffff) % 32; + ivgroup.iv1 = remainingbits & 0x1f; remainingbits = remainingbits >> 5; + ivgroup.iv2 = remainingbits & 0x1f; return ivgroup; } -AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = AdvRngMethod::Method1){ +AdvPokemonResult pokemon_from_state(AdvRngState& state){ uint32_t pid = pid_from_states(state.s0, state.s1); uint8_t gender = gender_value_from_pid(pid); AdvNature nature = nature_from_pid(pid); @@ -86,7 +102,7 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = Ad AdvIvGroup ivgroup1; AdvIvGroup ivgroup2; - switch(method){ + switch(state.method){ case AdvRngMethod::Method2: ivgroup1 = iv_group_from_state(state.s3); @@ -107,9 +123,9 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state, AdvRngMethod method = Ad ivs.hp = ivgroup1.iv0; ivs.attack = ivgroup1.iv1; ivs.defense = ivgroup1.iv2; - ivs.spatk = ivgroup2.iv0; - ivs.spdef = ivgroup2.iv1; - ivs.speed = ivgroup2.iv2; + ivs.speed = ivgroup2.iv0; + ivs.spatk = ivgroup2.iv1; + ivs.spdef = ivgroup2.iv2; return {pid, gender, nature, ability, ivs}; } @@ -134,40 +150,44 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xo && (target.ability == AdvAbility::Any || res.ability == target.ability) && (target.gender == AdvGender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) && (target.shiny == AdvShinyType::Any || shiny_type_from_pid(res.pid, tid_xor_sid) == target.shiny) - && (target.ivs.hp.low <= res.ivs.hp && target.ivs.hp.high >= res.ivs.hp) - && (target.ivs.attack.low <= res.ivs.attack && target.ivs.attack.high >= res.ivs.attack) - && (target.ivs.defense.low <= res.ivs.defense && target.ivs.defense.high >= res.ivs.defense) - && (target.ivs.spatk.low <= res.ivs.spatk && target.ivs.spatk.high >= res.ivs.spatk) - && (target.ivs.spdef.low <= res.ivs.spdef && target.ivs.spdef.high >= res.ivs.spdef) - && (target.ivs.speed.low <= res.ivs.speed && target.ivs.speed.high >= res.ivs.speed); + && ((target.ivs.hp.low <= res.ivs.hp) && (target.ivs.hp.high >= res.ivs.hp)) + && ((target.ivs.attack.low <= res.ivs.attack) && (target.ivs.attack.high >= res.ivs.attack)) + && ((target.ivs.defense.low <= res.ivs.defense) && (target.ivs.defense.high >= res.ivs.defense)) + && ((target.ivs.spatk.low <= res.ivs.spatk) && (target.ivs.spatk.high >= res.ivs.spatk)) + && ((target.ivs.spdef.low <= res.ivs.spdef) && (target.ivs.spdef.high >= res.ivs.spdef)) + && ((target.ivs.speed.low <= res.ivs.speed) && (target.ivs.speed.high >= res.ivs.speed)); } -AdvRng::AdvRng(uint16_t seed, AdvRngState state) +AdvRngSearcher::AdvRngSearcher(uint16_t seed, AdvRngState state) : seed(seed) , state(state) {} -AdvRng::AdvRng(uint16_t seed, uint64_t min_advances, AdvRngMethod method) +AdvRngSearcher::AdvRngSearcher(uint16_t seed, uint64_t min_advances, AdvRngMethod method) : seed(seed) , state(rngstate_from_seed(seed, min_advances, method)) {} -void AdvRng::advance_state(){ +void AdvRngSearcher::advance_state(){ advance_rng_state(state); } -void AdvRng::set_seed(uint16_t newseed){ +void AdvRngSearcher::set_seed(uint16_t newseed){ seed = newseed; state = rngstate_from_seed(seed, 0, state.method); } -void AdvRng::set_state_advances(uint64_t advances){ +void AdvRngSearcher::set_state_advances(uint64_t advances){ state = rngstate_from_seed(seed, advances, state.method); } -void AdvRng::search_advance_range( +AdvPokemonResult AdvRngSearcher::generate_pokemon(){ + return pokemon_from_state(state); +} + +void AdvRngSearcher::search_advance_range( std::map& hits, AdvRngFilters& target, uint64_t min_advances, @@ -194,21 +214,24 @@ void AdvRng::search_advance_range( if ((target.method != AdvRngMethod::Any) && (target.method != method)){ continue; + }else{ + state.method = method; } for (uint64_t a=min_advances; a AdvRng::search( +std::map AdvRngSearcher::search( AdvRngFilters& target, - std::vector& seeds, + const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid, @@ -343,9 +366,9 @@ void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); } -AdvRngFilters observation_to_filter(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1){ +AdvRngFilters observation_to_filters(AdvObservedPokemon& observation, BaseStats& basestats, AdvRngMethod method){ IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; - for (int i=0; i search( AdvRngFilters& target, - std::vector& seeds, + const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, uint16_t tid_xor_sid = 0, @@ -161,9 +171,6 @@ class AdvRng{ - - - } } #endif diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 70f858d603..829b5b4e01 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -21,6 +21,7 @@ #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h" #include "Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Overworld.h" #include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" +#include "Programs/RngManipulation/PokemonFRLG_SidHelper.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -47,8 +48,6 @@ 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()); - if (PreloadSettings::instance().DEVELOPER_MODE){ - } //ret.emplace_back("---- General ----"); @@ -67,6 +66,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_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp new file mode 100644 index 0000000000..e6228a0fe3 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp @@ -0,0 +1,253 @@ +/* SID Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/Pokemon_AdvRng.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_TrainerCardDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" +#include "PokemonFRLG_SidHelper.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +SidHelper_Descriptor::SidHelper_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:SidHelper", + Pokemon::STRING_POKEMON + " FRLG", "SID Helper", + "Programs/PokemonFRLG/SidHelper.html", + "Hit a specific RNG advance when starting a new game and determine the corresponding SID.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct SidHelper_Descriptor::Stats : public StatsTracker { + public: + Stats() + : errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + + std::atomic& errors; +}; + +std::unique_ptr SidHelper_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +SidHelper::SidHelper() + : TARGET_ADVANCES( + "Target Advances:
" + "The target advances for finalizing the SID. This is arbitrary unless you're attempting to hit a specific TID/SID combination.
" + "This value should always be odd.", + LockMode::LOCK_WHILE_RUNNING, + 2301, 2000 // default, min + ) + , NUM_CANDIDATES( + "# Candidate SIDs:
" + "The number of SIDs to list near the target.", + LockMode::LOCK_WHILE_RUNNING, + 5, 1, 11 // default, min, max + ) + , GO_HOME_WHEN_DONE(false) + , NOTIFICATION_SIDS("SID Candidates", true, true) + , NOTIFICATIONS({ + &NOTIFICATION_SIDS, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_FATAL, + }) +{ + PA_ADD_OPTION(TARGET_ADVANCES); + PA_ADD_OPTION(NUM_CANDIDATES); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +namespace{ + +void set_sid_from_name_screen(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const uint64_t& SID_DELAY){ + // this is performed blind to try to maximize consistency of timing + // ensure the OK button is selected + pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); + // confirm name (SID delay starts now) + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + // advance to "...about to unfold!", picking the default rival name + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + pbf_move_left_joystick(context, {0,-1}, 200ms, 300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(SID_DELAY - 15700)); // 2000 + 3000 + 1500 + 2000 + 500 + 1500 + 2000 + 3000 + 200 ms + // finish dialogue and lock in SID + pbf_press_button(context, BUTTON_A, 200ms, 0ms); + context.wait_for_all_requests(); +} + +void finish_intro_animations(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + BlackScreenWatcher black_screen; + context.wait_for_all_requests(); + int ret = wait_until( + env.console, context, 10s, + { black_screen } + ); + + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): black screen not detected within 10 seconds of setting SID", env.console + ); + } + + BlackScreenOverWatcher black_screen_over; + context.wait_for_all_requests(); + int ret2 = wait_until( + env.console, context, 10s, + { black_screen_over } + ); + + if (ret2 < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): black screen lasted longer than 10 seconds", env.console + ); + } + + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); +} + +void navigate_to_trainer_card(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + + int errors = 0; + while (true){ + if (errors >= 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): failed 5 times to navigate to the Trainer Card", env.console + ); + } + + open_start_menu(env.console, context); + + bool cursor = move_cursor_to_position(env.console, context, SelectionArrowPositionStartMenu(1)); // the positions are different before recieving a Pokemon and Pokedex + if (!cursor){ + errors++; + pbf_mash_button(context, BUTTON_B, 2000ms); + continue; + } + + TrainerCardWatcher trainer_card(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); + pbf_press_button(context, BUTTON_A, 200ms, 2300ms); + }, + { trainer_card } + ); + + if (ret < 0){ + env.log("SidHelper(): failed to detect Trainer Card"); + errors++; + pbf_mash_button(context, BUTTON_B, 2000ms); + continue; + } + + break; + } +} + +uint16_t read_tid(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + TrainerIdReader reader; + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + VideoSnapshot screen = env.console.video().snapshot(); + env.log("Trainer Card detected."); + env.log("Reading TID..."); + uint16_t tid = reader.read_tid(env.logger(), screen); + env.log("TID: " + std::to_string(tid)); + + context.wait_for_all_requests(); + + return tid; +} + +std::vector> get_sid_messages(SingleSwitchProgramEnvironment& env, ProControllerContext& context, uint16_t tid, SimpleIntegerOption& TARGET_ADVANCES, SimpleIntegerOption& NUM_CANDIDATES){ + std::vector> messages; + + int start = -1 * NUM_CANDIDATES / 2; + int end = start + NUM_CANDIDATES; + + Pokemon::AdvRngSearcher searcher(tid, TARGET_ADVANCES + 2*start); + + for (int i=start; i m; + uint16_t sid = searcher.state.s0 >> 16; + + m.first = "Advances " + std::to_string(searcher.state.advance); + m.second = std::to_string(sid); + messages.push_back(m); + + env.log(m.first + ": " + m.second); + + searcher.advance_state(); + searcher.advance_state(); // 2 by 2 + } + + return messages; +} + +} // namespace + +void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + if (TARGET_ADVANCES % 2 == 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "SidHelper(): the Target Advances setting needs to be odd", env.console + ); + } + + home_black_border_check(env.console, context); + + // SidHelper_Descriptor::Stats& stats = env.current_stats(); + + double FRAMERATE = 59.999977; // FPS + double FRAME_DURATION = 1000 / FRAMERATE; // ms + + const uint64_t SID_DELAY = uint64_t((TARGET_ADVANCES - 1) * FRAME_DURATION / 2); // advances pass 2 by 2, first one doesn't count (?) + env.log("Delay: " + std::to_string(SID_DELAY) + "ms"); + + set_sid_from_name_screen(env, context, SID_DELAY); + finish_intro_animations(env, context); + navigate_to_trainer_card(env, context); + + uint16_t tid = read_tid(env, context); + + std::vector> sid_messages = get_sid_messages(env, context, tid, TARGET_ADVANCES, NUM_CANDIDATES); + + send_program_notification(env, NOTIFICATION_SIDS, COLOR_BLUE, "Possible SIDs:", sid_messages, ""); + + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + GO_HOME_WHEN_DONE.run_end_of_program(context); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h new file mode 100644 index 0000000000..7a2a1fdb19 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h @@ -0,0 +1,56 @@ +/* SID Helper + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_SidHelper_H +#define PokemonAutomation_PokemonFRLG_SidHelper_H + +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "Common/Cpp/Options/ButtonOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" + + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class SidHelper_Descriptor : public SingleSwitchProgramDescriptor{ +public: + SidHelper_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class SidHelper : public SingleSwitchProgramInstance { +public: + SidHelper(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override { + } + +private: + + SimpleIntegerOption TARGET_ADVANCES; + SimpleIntegerOption NUM_CANDIDATES; + + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SIDS; + EventNotificationsOption NOTIFICATIONS; + +}; + +} +} +} + + + + +#endif \ No newline at end of file diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 54590f4c88..9d4b3e69f8 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1526,6 +1526,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_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/TestPrograms/PokemonFRLG_SoundListener.cpp