diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index 3d60d70d76..4417628798 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -94,10 +94,8 @@ AdvIvGroup iv_group_from_state(uint32_t& state){ return ivgroup; } -AdvPokemonResult pokemon_from_state(AdvRngState& state){ - uint32_t pid = pid_from_states(state.s0, state.s1); +AdvPokemonResult pokemon_from_state(AdvRngState& state, uint32_t pid, AdvNature nature){ uint8_t gender = gender_value_from_pid(pid); - AdvNature nature = nature_from_pid(pid); AdvAbility ability = ability_from_pid(pid); AdvIvGroup ivgroup1; @@ -130,6 +128,129 @@ AdvPokemonResult pokemon_from_state(AdvRngState& state){ return {pid, gender, nature, ability, ivs}; } +AdvPokemonResult pokemon_from_state(AdvRngState& state){ + uint32_t pid = pid_from_states(state.s0, state.s1); + AdvNature nature = nature_from_pid(pid); + return pokemon_from_state(state, pid, nature); +} + +AdvPokemonResult reroll_wild_pokemon(uint8_t nature, uint16_t seed, uint64_t advances, uint32_t state, AdvRngMethod method){ + uint32_t lowstate = state; + uint32_t highstate = increment_internal_rng_state(state); + while (true){ + uint32_t pid = pid_from_states(lowstate, highstate); + if ((pid % 25) == nature){ + AdvRngState rngstate = rngstate_from_internal_state(seed, advances, lowstate, method); + return pokemon_from_state(rngstate, pid, AdvNature(nature)); + }else{ + lowstate = increment_internal_rng_state(highstate); + highstate = increment_internal_rng_state(lowstate); + } + } +} + +uint8_t slot_number_from_roll(uint8_t roll, size_t size, bool super_rod) { + switch(size){ + case 12: // grass + if (roll < 20){ + return 0; + }else if(roll < 40){ + return 1; + }else if(roll < 50){ + return 2; + }else if(roll < 60){ + return 3; + }else if(roll < 70){ + return 4; + }else if(roll < 80){ + return 5; + }else if(roll < 85){ + return 6; + }else if(roll < 90){ + return 7; + }else if(roll < 94){ + return 8; + }else if(roll < 98){ + return 9; + }else if(roll < 99){ + return 10; + }else{ + return 11; + } + case 5: + if (super_rod){ // super rod + if (roll < 40){ + return 0; + }else if(roll < 70){ + return 1; + }else if(roll < 85){ + return 2; + }else if(roll < 95){ + return 3; + }else{ + return 4; + } + }else{ + if (roll < 60){ // surfing and rock smash + return 0; + }else if(roll < 90){ + return 1; + }else if(roll < 95){ + return 2; + }else if(roll < 99){ + return 3; + }else{ + return 4; + } + } + case 3: // good rod + if (roll < 60){ + return 0; + }else if(roll < 80){ + return 1; + }else{ + return 2; + } + case 2: // old rod + if (roll < 70){ + return 0; + }else{ + return 1; + } + default: + return 0; + } +} + +AdvWildPokemonResult wild_pokemon_from_state(AdvRngState state, std::vector slots, bool super_rod){ + + uint8_t slot_roll = (state.s0 >> 16) % 100; + uint16_t level_roll = state.s1 >> 16; + uint8_t nature = (state.s2 >> 16) % 25; + + uint8_t slot_num = slot_number_from_roll(slot_roll, slots.size(), super_rod); + AdvEncounterSlot slot = slots[slot_num]; + + uint8_t diff = slot.maxlevel - slot.minlevel; + if (slot.maxlevel < slot.minlevel) { + diff = 0; + } + uint8_t level = slot.minlevel + (level_roll % (diff + 1)); + + AdvPokemonResult temp_poke = reroll_wild_pokemon(nature, state.seed, state.advance, state.s3, state.method); + + return { + slot.species, + temp_poke.pid, + temp_poke.gender, + temp_poke.nature, + temp_poke.ability, + temp_poke.ivs, + slot_num, + level, + }; +} + AdvShinyType shiny_type_from_pid(uint32_t pid, uint16_t tid_xor_sid){ uint16_t pid0 = pid >> 16; uint16_t pid1 = pid & 0xffff; @@ -158,7 +279,20 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, int16_t gender_ && ((target.ivs.speed.low <= res.ivs.speed) && (target.ivs.speed.high >= res.ivs.speed)); } - +bool check_for_match(AdvWildPokemonResult res, AdvRngFilters target, int16_t gender_threshold, uint16_t tid_xor_sid){ + return (target.species == res.species) + && (target.level == res.level) + && (target.nature == AdvNature::Any || res.nature == target.nature) + && (target.ability == AdvAbility::Any || res.ability == target.ability) + && (target.gender == AdvGender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) + && (target.shiny == AdvShinyType::Any || shiny_type_from_pid(res.pid, tid_xor_sid) == target.shiny) + && ((target.ivs.hp.low <= res.ivs.hp) && (target.ivs.hp.high >= res.ivs.hp)) + && ((target.ivs.attack.low <= res.ivs.attack) && (target.ivs.attack.high >= res.ivs.attack)) + && ((target.ivs.defense.low <= res.ivs.defense) && (target.ivs.defense.high >= res.ivs.defense)) + && ((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)); +} AdvRngSearcher::AdvRngSearcher(uint16_t seed, AdvRngState state) : seed(seed) @@ -188,7 +322,7 @@ AdvPokemonResult AdvRngSearcher::generate_pokemon(){ } void AdvRngSearcher::search_advance_range( - std::map& hits, + std::vector& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, @@ -222,14 +356,14 @@ void AdvRngSearcher::search_advance_range( AdvPokemonResult res = pokemon_from_state(state); bool match = check_for_match(res, target, gender_threshold, tid_xor_sid); if (match){ - hits[state] = res; + hits.emplace_back(state); } advance_state(); } } } -std::map AdvRngSearcher::search( +std::vector AdvRngSearcher::search( AdvRngFilters& target, const std::vector& seeds, uint64_t min_advances, @@ -237,7 +371,7 @@ std::map AdvRngSearcher::search( int16_t gender_threshold, uint16_t tid_xor_sid ){ - std::map hits; + std::vector hits; for (uint16_t seed : seeds){ set_seed(seed); search_advance_range(hits, target, min_advances, max_advances, gender_threshold, tid_xor_sid); @@ -245,23 +379,96 @@ std::map AdvRngSearcher::search( return hits; } -void AdvRngSearcher::refine_search( - std::map& map, + +AdvRngWildSearcher::AdvRngWildSearcher(uint16_t seed, AdvRngState state, const std::vector& encounter_slots) + : seed(seed) + , state(state) + , encounter_slots(encounter_slots) +{} + +AdvRngWildSearcher::AdvRngWildSearcher(uint16_t seed, uint64_t min_advances, const std::vector& encounter_slots, AdvRngMethod method) + : seed(seed) + , state(rngstate_from_seed(seed, min_advances, method)) + , encounter_slots(encounter_slots) +{} + +void AdvRngWildSearcher::advance_state(){ + advance_rng_state(state); +} + +void AdvRngWildSearcher::set_seed(uint16_t newseed){ + seed = newseed; + state = rngstate_from_seed(seed, 0, state.method); +} + +void AdvRngWildSearcher::set_state_advances(uint64_t advances){ + state = rngstate_from_seed(seed, advances, state.method); +} + +AdvWildPokemonResult AdvRngWildSearcher::generate_pokemon(bool super_rod){ + return wild_pokemon_from_state(state, encounter_slots, super_rod); +} + +void AdvRngWildSearcher::search_advance_range( + std::vector& hits, AdvRngFilters& target, + uint64_t min_advances, + uint64_t max_advances, int16_t gender_threshold, + bool super_rod, uint16_t tid_xor_sid ){ - for (auto iter = map.begin(); iter != map.end(); ){ - state = iter->first; - AdvPokemonResult res = pokemon_from_state(state); - if (!check_for_match(res, target, gender_threshold, tid_xor_sid)){ - iter = map.erase(iter); + for (uint8_t m=0; m<3; m++){ + set_state_advances(min_advances); + + AdvRngMethod method; + switch (m){ + case 1: + method = AdvRngMethod::Method2; + break; + case 2: + method = AdvRngMethod::Method4; + break; + case 0: + default: + method = AdvRngMethod::Method1; + break; + } + + if ((target.method != AdvRngMethod::Any) && (target.method != method)){ + continue; }else{ - iter++; + state.method = method; + } + + for (uint64_t a=min_advances; a AdvRngWildSearcher::search( + AdvRngFilters& target, + const std::vector& seeds, + uint64_t min_advances, + uint64_t max_advances, + int16_t gender_threshold, + bool super_rod, + uint16_t tid_xor_sid +){ + std::vector hits; + for (uint16_t seed : seeds){ + set_seed(seed); + search_advance_range(hits, target, min_advances, max_advances, gender_threshold, super_rod, tid_xor_sid); + } + return hits; +} + Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature){ NatureAdjustments ret; @@ -396,6 +603,8 @@ AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, cons } return AdvRngFilters { + observation.species, + observation.level[0], observation.gender, observation.nature, observation.ability, diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 5b0511cd8c..d6b71191a5 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -100,6 +100,12 @@ struct AdvRngState{ } }; +struct AdvEncounterSlot{ + std::string species; + uint8_t minlevel; + uint8_t maxlevel; +}; + struct AdvPokemonResult{ uint32_t pid; uint8_t gender; @@ -108,7 +114,19 @@ struct AdvPokemonResult{ AdvIVs ivs; }; +struct AdvWildPokemonResult{ + std::string species; + uint32_t pid; + uint8_t gender; + AdvNature nature; + AdvAbility ability; + AdvIVs ivs; + uint8_t slot; + uint8_t level; +}; + struct AdvObservedPokemon{ + std::string species; AdvGender gender; AdvNature nature; AdvAbility ability; @@ -119,6 +137,8 @@ struct AdvObservedPokemon{ }; struct AdvRngFilters{ + std::string species; + uint8_t level; AdvGender gender; AdvNature nature; AdvAbility ability; @@ -152,7 +172,7 @@ class AdvRngSearcher{ AdvPokemonResult generate_pokemon(); - std::map search( + std::vector search( AdvRngFilters& target, const std::vector& seeds, uint64_t min_advances, @@ -161,20 +181,50 @@ class AdvRngSearcher{ uint16_t tid_xor_sid = 0 ); - void refine_search( - std::map& map, +private: + void search_advance_range( + std::vector& hits, + AdvRngFilters& target, + uint64_t min_advances, + uint64_t max_advances, + int16_t gender_threshold, + uint16_t tid_xor_sid + ); +}; + +class AdvRngWildSearcher{ +public: + uint16_t seed; + AdvRngState state; + const std::vector& encounter_slots; + + AdvRngWildSearcher(uint16_t seed, AdvRngState state, const std::vector& encounter_slots); + AdvRngWildSearcher(uint16_t seed, uint64_t min_advances, const std::vector& encounter_slots, AdvRngMethod method = AdvRngMethod::Any); + + void set_seed(uint16_t seed); + void set_state_advances(uint64_t advances); + void advance_state(); + + AdvWildPokemonResult generate_pokemon(bool super_rod = false); + + std::vector search( AdvRngFilters& target, + const std::vector& seeds, + uint64_t min_advances, + uint64_t max_advances, int16_t gender_threshold = 126, + bool super_rod = false, uint16_t tid_xor_sid = 0 ); private: void search_advance_range( - std::map& hits, + std::vector& hits, AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, int16_t gender_threshold, + bool super_rod, uint16_t tid_xor_sid ); }; diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp index e57945dbfe..3f7712114c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.cpp @@ -31,7 +31,7 @@ StatsReader::StatsReader(Color color) m_box_level(0.052000, 0.120140, 0.099000, 0.069416), m_box_name(0.163158, 0.122917, 0.262811, 0.066639), m_box_gender(0.430769, 0.114423, 0.034615, 0.081731), - m_box_hp(0.815558, 0.131247, 0.173049, 0.066639), + m_box_hp(0.805274, 0.131247, 0.183790, 0.066639), m_box_attack(0.891000, 0.245089, 0.097607, 0.066639), m_box_defense(0.891000, 0.325612, 0.097607, 0.066639), m_box_sp_attack(0.891000, 0.406134, 0.097607, 0.066639), @@ -54,7 +54,8 @@ void StatsReader::make_overlays(VideoOverlaySet &items) const { void StatsReader::read_page1( Logger &logger, Language language, const ImageViewRGB32 &frame, - PokemonFRLG_Stats &stats + PokemonFRLG_Stats &stats, + const std::set& subset ){ const bool save_debug_images = GlobalSettings::instance().SAVE_DEBUG_IMAGES; ImageViewRGB32 game_screen = @@ -68,11 +69,21 @@ void StatsReader::read_page1( {combine_rgb(208, 208, 208), combine_rgb(255, 255, 255)}, {combine_rgb(192, 192, 192), combine_rgb(255, 255, 255)}, }; - auto name_result = Pokemon::PokemonNameReader::instance().read_substring( - logger, language, extract_box_reference(game_screen, m_box_name), - name_text_color_ranges); - if (!name_result.results.empty()){ - stats.name = name_result.results.begin()->second.token; + + if (subset.size() > 0){ + auto name_result = Pokemon::PokemonNameReader(subset).read_substring( + logger, language, extract_box_reference(game_screen, m_box_name), + name_text_color_ranges); + if (!name_result.results.empty()){ + stats.name = name_result.results.begin()->second.token; + } + }else{ + auto name_result = Pokemon::PokemonNameReader::instance().read_substring( + logger, language, extract_box_reference(game_screen, m_box_name), + name_text_color_ranges); + if (!name_result.results.empty()){ + stats.name = name_result.results.begin()->second.token; + } } // Detect gender by comparing red vs blue pixels @@ -339,10 +350,10 @@ void StatsReader::read_page2( ); }; - // HP box: shift right 70% to clear the "/" character. + // HP box: shift right 55% to clear the "/" character. ImageFloatBox total_hp_box( - m_box_hp.x + m_box_hp.width * 0.7, m_box_hp.y, - m_box_hp.width * 0.3, m_box_hp.height + m_box_hp.x + m_box_hp.width * 0.60, m_box_hp.y, + m_box_hp.width * 0.40, m_box_hp.height ); auto assign_stat = [](std::optional& field, int value){ diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h index 46e9ebd8ea..bbcf9b996d 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h @@ -52,7 +52,8 @@ class StatsReader { // Reads from page 1 (Nature, Level, Name) void read_page1( Logger &logger, Language language, - const ImageViewRGB32 &frame, PokemonFRLG_Stats &stats + const ImageViewRGB32 &frame, PokemonFRLG_Stats &stats, + const std::set& subset = {} ); // Reads from page 2 (Stats: HP, Atk, Def, SpA, SpD, Spe) diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 24eb37f241..1bcfb8db81 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -25,6 +25,7 @@ #include "Programs/RngManipulation/PokemonFRLG_StarterRng.h" #include "Programs/RngManipulation/PokemonFRLG_GiftRng.h" #include "Programs/RngManipulation/PokemonFRLG_StaticRng.h" +#include "Programs/RngManipulation/PokemonFRLG_WildRng.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -73,6 +74,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 c8901f0edf..f2e06aac71 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp @@ -268,6 +268,15 @@ void use_sweet_scent(ProControllerContext& context, uint64_t INGAME_DELAY, bool context.wait_for_all_requests(); } +void use_rock_smash(ProControllerContext& context, uint64_t INGAME_DELAY){ + // three button presses + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 300ms); + pbf_wait(context, std::chrono::milliseconds(INGAME_DELAY - 6500)); // 4000ms + 2000ms + 500ms + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + context.wait_for_all_requests(); +} + void use_registered_fishing_rod(ProControllerContext& context, uint64_t INGAME_DELAY){ uint32_t rng_wait = 50 * random_u32(0, 20); // helps avoid always hitting "Not even a nibble" (?) pbf_wait(context, std::chrono::milliseconds(rng_wait)); @@ -370,14 +379,14 @@ void check_timings( ){ if (CONTINUE_SCREEN_DELAY < 3200){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "The Continue Screen delay cannot be less than 3200ms (192 advances). Check your Continue Screen calibration.", console ); } if (SEED_DELAY < 29500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "The title screen delay cannot be less than 29.5s. Check your seed calibration.", console ); @@ -387,7 +396,7 @@ void check_timings( case PokemonFRLG_RngTarget::starters: if (INGAME_DELAY < 7500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Starters: the in-game delay cannot be less than 7500ms (900 advances). Check your in-game advances and calibration.", console ); @@ -396,7 +405,7 @@ void check_timings( case PokemonFRLG_RngTarget::magikarp: if (INGAME_DELAY < 7500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Magikarp: the in-game delay cannot be less than 7500ms (900 advances). Check your in-game advances and calibration.", console ); @@ -407,7 +416,7 @@ void check_timings( case PokemonFRLG_RngTarget::hitmon: if (INGAME_DELAY < 4500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Hitmonchan/Hitmonlee: the in-game delay cannot be less than 4500ms (540 advances). Check your in-game advances and calibration.", console ); @@ -416,7 +425,7 @@ void check_timings( case PokemonFRLG_RngTarget::eevee: if (INGAME_DELAY < 4000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Eevee: the in-game delay cannot be less than 4000ms (480 advances). Check your in-game advances and calibration.", console ); @@ -425,7 +434,7 @@ void check_timings( case PokemonFRLG_RngTarget::lapras: if (INGAME_DELAY < 7500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Lapras: the in-game delay cannot be less than 7500ms (900 advances). Check your in-game advances and calibration.", console ); @@ -437,7 +446,7 @@ void check_timings( case PokemonFRLG_RngTarget::fossils: if (INGAME_DELAY < 6000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Fossils: the in-game delay cannot be less than 6000ms (720 advances). Check your in-game advances and calibration.", console ); @@ -452,7 +461,7 @@ void check_timings( case PokemonFRLG_RngTarget::gamecornerporygon: if (INGAME_DELAY < 8500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Game Corner: the in-game delay cannot be less than 8500ms (1020 advances). Check your in-game advances and calibration.", console ); @@ -461,7 +470,7 @@ void check_timings( case PokemonFRLG_RngTarget::togepi: if (INGAME_DELAY < 12000) { OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Togepi: the in-game delay cannot be less than 12000ms (1440 advances). Check your in-game advances and calibration.", console ); @@ -470,7 +479,7 @@ void check_timings( case PokemonFRLG_RngTarget::staticencounter: if (INGAME_DELAY < 5000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Static Encounter: the in-game delay cannot be less than 5000ms (600 advances). Check your in-game advances and calibration.", console ); @@ -479,7 +488,7 @@ void check_timings( case PokemonFRLG_RngTarget::snorlax: if (INGAME_DELAY < 16000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Snorlax: the in-game delay cannot be less than 16000ms (1920 advances). Check your in-game advances and calibration.", console ); @@ -488,7 +497,7 @@ void check_timings( case PokemonFRLG_RngTarget::mewtwo: if (INGAME_DELAY < 4500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Mewtwo: the in-game delay cannot be less than 4500ms (540 advances). Check your in-game advances and calibration.", console ); @@ -497,7 +506,7 @@ void check_timings( case PokemonFRLG_RngTarget::hooh: if (INGAME_DELAY < 4000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Ho-oh: the in-game delay cannot be less than 4000ms (480 advances). Check your in-game advances and calibration.", console ); @@ -506,7 +515,7 @@ void check_timings( case PokemonFRLG_RngTarget::hypno: if (INGAME_DELAY < 13000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Hypno: the in-game delay cannot be less than 13000ms (1560 advances). Check your in-game advances and calibration.", console ); @@ -515,23 +524,31 @@ void check_timings( case PokemonFRLG_RngTarget::sweetscent: if (!SAFARI_ZONE && INGAME_DELAY < 8500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Sweet Scent: the in-game delay cannot be less than 8500ms (1020 advances). Check your in-game advances and calibration.", console ); }else if (SAFARI_ZONE && INGAME_DELAY < 9500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Sweet Scent: the in-game delay cannot be less than 9500ms (1140 advances). Check your in-game advances and calibration.", console ); } return; + case PokemonFRLG_RngTarget::rocksmash: + if (INGAME_DELAY < 6500){ + OperationFailedException::fire( + ErrorReport::NO_ERROR_REPORT, + "Rock Smash: the in-game delay cannot be less than 7000ms (840 advances). Check your in-game advances and calibration.", + console + ); + } case PokemonFRLG_RngTarget::fishing: if (INGAME_DELAY < 5500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Fishing: the in-game delay cannot be less than 5500ms (1800 advances). Check your in-game advances and calibration.", + ErrorReport::NO_ERROR_REPORT, + "Fishing: the in-game delay cannot be less than 5500ms (660 advances). Check your in-game advances and calibration.", console ); } @@ -539,7 +556,7 @@ void check_timings( case PokemonFRLG_RngTarget::safarizonecenter: if (INGAME_DELAY < 30500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Safari Zone Center: in-game delay cannot be less than 30500ms (3660 advances). Check your in-game advances and calibration.", console ); @@ -548,7 +565,7 @@ void check_timings( case PokemonFRLG_RngTarget::safarizoneeast: if (INGAME_DELAY < 36500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Safari Zone East: in-game delay cannot be less than 36500ms (4380 advances). Check your in-game advances and calibration.", console ); @@ -557,7 +574,7 @@ void check_timings( case PokemonFRLG_RngTarget::safarizonenorth: if (INGAME_DELAY < 47500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Safari Zone North: in-game delay cannot be less than 47500ms (5700 advances). Check your in-game advances and calibration.", console ); @@ -566,7 +583,7 @@ void check_timings( case PokemonFRLG_RngTarget::safarizonewest: if (INGAME_DELAY < 61500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Safari Zone West: in-game delay cannot be less than 52000ms (7380 advances). Check your in-game advances and calibration.", console ); @@ -575,7 +592,7 @@ void check_timings( case PokemonFRLG_RngTarget::safarizonesurf: if (INGAME_DELAY < 40500){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Safari Zone Surfing: in-game delay cannot be less than 40500ms (4860 advances). Check your in-game advances and calibration.", console ); @@ -584,7 +601,7 @@ void check_timings( case PokemonFRLG_RngTarget::safarizonefish: if (INGAME_DELAY < 30000){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Safari Zone Fishing: in-game delay cannot be less than 30000ms (3600 advances). Check your in-game advances and calibration.", console ); @@ -592,7 +609,7 @@ void check_timings( return; default: OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "Option not yet implemented.", console ); @@ -688,6 +705,9 @@ void perform_blind_sequence( case PokemonFRLG_RngTarget::sweetscent: use_sweet_scent(context, INGAME_DELAY, SAFARI_ZONE); return; + case PokemonFRLG_RngTarget::rocksmash: + use_rock_smash(context, INGAME_DELAY); + return; case PokemonFRLG_RngTarget::fishing: use_registered_fishing_rod(context, INGAME_DELAY); return; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h index 9989ed9805..aa94f7d8b8 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h @@ -51,6 +51,7 @@ namespace PokemonFRLG{ hooh, hypno, sweetscent, + rocksmash, fishing, safarizonecenter, safarizoneeast, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.cpp new file mode 100644 index 0000000000..7774e3b8ed --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.cpp @@ -0,0 +1,64 @@ +/* Encounters Database + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Json/JsonValue.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "CommonFramework/Globals.h" +#include "PokemonFRLG_EncountersDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +EncountersDatabase::EncountersDatabase(const char* json_path){ + std::string path = RESOURCE_PATH() + json_path; + JsonValue json = load_json_file(path); + JsonObject& root = json.to_object_throw(path); + + for (auto& item : root){ + const std::string& encounter_type = item.first; + JsonObject& type_obj = item.second.to_object_throw(path); + std::map> loc_map; + for (auto& loc_item : type_obj){ + const std::string& loc_name = loc_item.first; + JsonArray& encs_arr = loc_item.second.to_array_throw(path); + std::vector encounter_slots; + for (auto& enc_item : encs_arr){ + JsonObject& enc_obj = enc_item.to_object_throw(path); + std::string species = enc_obj.get_string_throw("species", path); + uint8_t minlevel = uint8_t(enc_obj.get_integer_throw("minLevel", path)); + uint8_t maxlevel = uint8_t(enc_obj.get_integer_throw("maxLevel", path)); + AdvEncounterSlot slot = { species, minlevel, maxlevel }; + encounter_slots.emplace_back(slot); + } + loc_map.emplace(loc_name, encounter_slots); + } + m_database.emplace(encounter_type, loc_map); + } +} + +const std::map>& EncountersDatabase::get_throw(const std::string& slug) const{ + auto iter = m_database.find(slug); + if (iter == m_database.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Slug not found in database: " + slug); + } + return iter->second; +} +const std::map>* EncountersDatabase::get_nothrow(const std::string& slug) const{ + auto iter = m_database.find(slug); + if (iter == m_database.end()){ + return nullptr; + } + return &iter->second; +} + + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.h new file mode 100644 index 0000000000..fd306f38ad --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.h @@ -0,0 +1,45 @@ +/* Encounters Database + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_EncountersDatabase_H +#define PokemonAutomation_PokemonFRLG_EncountersDatabase_H + +#include +#include "Pokemon/Pokemon_AdvRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + +class EncountersDatabase{ +public: + EncountersDatabase(const char* json_path); + const std::map>& get_throw(const std::string& slug) const; + const std::map>* get_nothrow(const std::string& slug) const; + +public: + using const_iterator = std::map>>::const_iterator; + using iterator = std::map>>::iterator; + + const_iterator cbegin () const{ return m_database.cbegin(); } + const_iterator begin () const{ return m_database.begin(); } + iterator begin (){ return m_database.begin(); } + const_iterator cend () const{ return m_database.cend(); } + const_iterator end () const{ return m_database.end(); } + iterator end (){ return m_database.end(); } + +private: + std::map>> m_database; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index 97f9094bcb..4a03bb4b37 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -282,6 +282,7 @@ AdvObservedPokemon GiftRng::read_summary(SingleSwitchProgramEnvironment& env, Pr } AdvObservedPokemon pokemon = { + stats.name, gender, string_to_nature(stats.nature), AdvAbility::Any, @@ -297,6 +298,7 @@ AdvObservedPokemon GiftRng::read_summary(SingleSwitchProgramEnvironment& env, Pr bool GiftRng::use_rare_candy( SingleSwitchProgramEnvironment& env, ProControllerContext& context, + GiftRng_Descriptor::Stats& stats, AdvObservedPokemon& pokemon, AdvRngFilters& filters, const BaseStats& BASE_STATS, @@ -308,6 +310,7 @@ bool GiftRng::use_rare_candy( // 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); + pbf_move_left_joystick(context, {-1, 0}, 200ms, 800ms); } // use rare candy and watch for the party screen @@ -327,6 +330,7 @@ bool GiftRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to detect party menu." ); + stats.errors++; return true; } @@ -355,6 +359,7 @@ bool GiftRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to detect level-up stats." ); + stats.errors++; return true; } @@ -364,12 +369,12 @@ bool GiftRng::use_rare_candy( env.log("Reading stats..."); VideoSnapshot screen = env.console.video().snapshot(); - StatReads stats = reader.read_stats(env.logger(), screen); + StatReads statreads = reader.read_stats(env.logger(), screen); - update_filters(filters, pokemon, stats, {}, BASE_STATS); + update_filters(filters, pokemon, statreads, {}, BASE_STATS); RNG_FILTERS.set(filters); - // return to the bag (possibly learning a move, but trying to preven evolution) + // return to the bag (possibly learning a move, but trying to prevent evolution) int attempts = 0; while (true){ if (attempts > 5){ @@ -377,6 +382,7 @@ bool GiftRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to return to bag menu in 5 attempts." ); + stats.errors++; return true; } BagWatcher bag_menu(COLOR_RED); @@ -407,6 +413,7 @@ bool GiftRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to return to bag menu." ); + stats.errors++; return true; } } @@ -431,13 +438,13 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& if (SEED_POSITION == -1){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_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)); + env.log("Target Seed Value (base10): " + std::to_string(TARGET_SEED)); BaseStats BASE_STATS; int16_t GENDER_THRESHOLD = -1; @@ -533,7 +540,6 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& 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){ @@ -551,10 +557,6 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& break; } - if (wildshiny_found){ - break; - } - send_program_status_notification( env, NOTIFICATION_STATUS_UPDATE, "Calibrating." @@ -588,16 +590,13 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& 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; } + // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target + 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; @@ -661,7 +660,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& 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); + std::vector 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, @@ -675,10 +674,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& } for (uint64_t i=0; i get_search_results( +std::vector get_search_results( ConsoleHandle& console, AdvRngSearcher& searcher, AdvRngFilters& filters, @@ -119,7 +119,7 @@ std::map get_search_results( uint16_t tid_xor_sid ){ - std::map search_hits; + std::vector 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); @@ -134,6 +134,33 @@ std::map get_search_results( return search_hits; } +std::vector get_wild_search_results( + ConsoleHandle& console, + AdvRngWildSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + const uint64_t& advances_radius, + int16_t gender_threshold, + bool super_rod, + uint16_t tid_xor_sid + +){ + std::vector 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, gender_threshold, super_rod, tid_xor_sid); + if (search_hits.size() > 0){ + console.log("Number of search hits: " + std::to_string(search_hits.size())); + return search_hits; + } + } + console.log("Number of search hits: " + std::to_string(search_hits.size())); + return search_hits; +} + bool range_is_valid(IvRange iv){ return ( iv.low <= iv.high && @@ -254,7 +281,7 @@ bool update_history( const double& SEED_CALIBRATION_FRAMES, const double& ADVANCES_CALIBRATION, const double& CONTINUE_SCREEN_ADJUSTMENT, - const std::map& search_hits, + const std::vector& search_hits, uint32_t max_advance_possibilities, uint32_t advance_radius, bool force_finish @@ -273,7 +300,7 @@ bool update_history( 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); + CALIBRATION_HISTORY.results.emplace_back(search_hits[0]); 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()); @@ -287,9 +314,9 @@ bool update_history( 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); + for(auto hit : search_hits) { + advances.emplace_back(hit.advance); + hits.emplace_back(hit); } // get unique advances @@ -368,6 +395,70 @@ bool update_history( +bool are_indistinguishable(AdvPokemonResult res1, AdvPokemonResult res2){ + return ( + res1.nature == res2.nature && + res1.gender == res2.gender && + // res1.ability == res2.ability && + res1.ivs.hp == res2.ivs.hp && + res1.ivs.attack == res2.ivs.attack && + res1.ivs.defense == res2.ivs.defense && + res1.ivs.spatk == res2.ivs.spatk && + res1.ivs.spdef == res2.ivs.spdef && + res1.ivs.speed == res2.ivs.speed + ); +} + +bool are_indistinguishable(AdvWildPokemonResult res1, AdvWildPokemonResult res2){ + return ( + res1.species == res2.species && + res1.level == res2.level && + res1.nature == res2.nature && + res1.gender == res2.gender && + // res1.ability == res2.ability && + res1.ivs.hp == res2.ivs.hp && + res1.ivs.attack == res2.ivs.attack && + res1.ivs.defense == res2.ivs.defense && + res1.ivs.spatk == res2.ivs.spatk && + res1.ivs.spdef == res2.ivs.spdef && + res1.ivs.speed == res2.ivs.speed + ); +} + +bool all_indistinguishable(std::vector hits, AdvRngSearcher& searcher){ + if (hits.size() < 2){ + return true; + } + searcher.state = hits[0]; + AdvPokemonResult first_result = searcher.generate_pokemon(); + for (size_t i=1; i hits, AdvRngWildSearcher& searcher, bool super_rod){ + if (hits.size() < 2){ + return true; + } + searcher.state = hits[0]; + AdvWildPokemonResult first_result = searcher.generate_pokemon(super_rod); + + for (size_t i=1; i list); Pokemon::AdvNature string_to_nature(std::string nature_string); // get search hits for any of the provided seed values and advances range -std::map get_search_results( +std::vector get_search_results( ConsoleHandle& console, AdvRngSearcher& searcher, AdvRngFilters& filters, @@ -51,6 +51,18 @@ std::map get_search_results( uint16_t tid_xor_sid = 0 ); +std::vector get_wild_search_results( + ConsoleHandle& console, + AdvRngWildSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + const uint64_t& advances_radius, + int16_t gender_threshold = 126, + bool super_rod = false, + uint16_t tid_xor_sid = 0 +); + // update IV ranges for RNG search filters with new stats/EVs after leveling up void update_filters( AdvRngFilters& filters, @@ -79,12 +91,15 @@ bool update_history( const double& SEED_CALIBRATION_FRAMES, const double& ADVANCES_CALIBRATION, const double& CONTINUE_SCREEN_ADJUSTMENT, - const std::map& search_hits, + const std::vector& search_hits, uint32_t max_advance_possibilities = 1, uint32_t advance_radius = 2, bool force_finish = false ); +bool all_indistinguishable(std::vector hits, AdvRngSearcher& searcher); +bool all_indistinguishable(std::vector hits, AdvRngWildSearcher& searcher, bool super_rod); + } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index f1588bdd07..c69e472ea3 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -169,14 +169,6 @@ RngCalibrationDisplay::RngCalibrationDisplay() PA_ADD_STATIC(hits); } -std::vector RngCalibrationDisplay::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 RngCalibrationDisplay::get_hits_string(const std::vector& rng_states){ std::string hits_string; for (size_t i=0; i& hits_map){ - return get_hits_string(get_rng_states_from_map(hits_map)); -} void RngCalibrationDisplay::set( double s_calibration, @@ -211,15 +200,6 @@ void RngCalibrationDisplay::set( advances_calibration.set(a_calibration); hits.set(get_hits_string(rng_states)); } -void RngCalibrationDisplay::set( - double s_calibration, - double c_calibration, - double a_calibration, - const std::map& hits_map -){ - std::vector rng_states = get_rng_states_from_map(hits_map); - set(s_calibration, c_calibration, a_calibration, rng_states); -} void RngCalibrationDisplay::reset(){ // seed_calibration.set(0.0); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index 5bd3668f04..b796534c66 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -71,16 +71,9 @@ class RngCalibrationDisplay : public GroupOption{ double a_calibration, std::vector& rng_states ); - void set( - double s_calibration, - double c_calibration, - double a_calibration, - 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: diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp index a008bcb370..9d71f2b1c5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp @@ -75,6 +75,7 @@ RngHelper::RngHelper() {PokemonFRLG_RngTarget::hooh, "hooh", "Ho-oh"}, {PokemonFRLG_RngTarget::hypno, "berryforesthypno", "Berry Forest Hypno"}, {PokemonFRLG_RngTarget::sweetscent, "sweetscent", "Sweet Scent"}, + {PokemonFRLG_RngTarget::rocksmash, "rocksmash", "Rock Smash"}, {PokemonFRLG_RngTarget::fishing, "fishing", "Fishing"}, {PokemonFRLG_RngTarget::safarizonecenter, "safarizonecenter", "Safari Zone Center (Sweet Scent)"}, {PokemonFRLG_RngTarget::safarizoneeast, "safarizoneeast", "Safari Zone East (Sweet Scent)"}, @@ -229,7 +230,7 @@ void RngHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex double MODIFIED_INGAME_ADVANCES = INGAME_ADVANCES + INGAME_CALIBRATION; if (MODIFIED_INGAME_ADVANCES < 0) { OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "In-game advances cannot be negative. Check your in-game advances and calibration.", env.console ); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp index 6353d192e0..deb174f76b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -175,6 +175,7 @@ bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, Poke case PokemonFRLG_RngTarget::hooh: case PokemonFRLG_RngTarget::hypno: case PokemonFRLG_RngTarget::sweetscent: + case PokemonFRLG_RngTarget::rocksmash: case PokemonFRLG_RngTarget::fishing: case PokemonFRLG_RngTarget::safarizonecenter: case PokemonFRLG_RngTarget::safarizoneeast: diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h index 71e3a27889..1c26c708c4 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h @@ -17,6 +17,9 @@ namespace NintendoSwitch{ using ProControllerContext = ControllerContext; namespace PokemonFRLG{ + +int watch_for_shiny_encounter(ConsoleHandle& console, ProControllerContext& context); + bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, PokemonFRLG_RngTarget TARGET); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.cpp new file mode 100644 index 0000000000..1c931f09ad --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.cpp @@ -0,0 +1,67 @@ +/* RNG Stats Database + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Json/JsonValue.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "CommonFramework/Globals.h" +#include "PokemonFRLG_RngStatsDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +RngStatsDatabase::RngStatsDatabase(const char* json_path){ + std::string path = RESOURCE_PATH() + json_path; + JsonValue json = load_json_file(path); + JsonObject& root = json.to_object_throw(path); + + for (auto& item : root){ + const std::string& slug = item.first; + JsonObject& obj = item.second.to_object_throw(path); + std::vector readstats; + JsonArray& statsarr = obj.get_array_throw("baseStats", path); + for (auto& stat : statsarr){ + auto statval = stat.to_integer_throw(); + readstats.emplace_back(uint8_t(statval)); + } + if (readstats.size() != 6){ + throw FileException(nullptr, PA_CURRENT_FUNCTION, "Invalid length for base stats.", path); + } + BaseStats base_stats = { readstats[0], readstats[1], readstats[2], readstats[3], readstats[4], readstats[5] }; + + int16_t gender_threshold = int16_t(obj.get_integer_throw("genderThreshold", path)); + + RngStats rng_stats = { base_stats, gender_threshold }; + + m_database.emplace( + slug, + rng_stats + ); + } +} + +const RngStats& RngStatsDatabase::get_throw(const std::string& slug) const{ + auto iter = m_database.find(slug); + if (iter == m_database.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Slug not found in database: " + slug); + } + return iter->second; +} +const RngStats* RngStatsDatabase::get_nothrow(const std::string& slug) const{ + auto iter = m_database.find(slug); + if (iter == m_database.end()){ + return nullptr; + } + return &iter->second; +} + + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.h new file mode 100644 index 0000000000..c4b93139fe --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.h @@ -0,0 +1,51 @@ +/* RNG Database + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_RngStatsDatabase_H +#define PokemonAutomation_PokemonFRLG_RngStatsDatabase_H + +#include +#include "Pokemon/Pokemon_AdvRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + +struct RngStats{ + BaseStats base_stats; + int16_t gender_threshold; + // std::vector abilities; +}; + +class RngStatsDatabase{ +public: + RngStatsDatabase(const char* json_path); + const RngStats& get_throw(const std::string& slug) const; + const RngStats* get_nothrow(const std::string& slug) const; + +public: + using const_iterator = std::map::const_iterator; + using iterator = std::map::iterator; + + const_iterator cbegin () const{ return m_database.cbegin(); } + const_iterator begin () const{ return m_database.begin(); } + iterator begin (){ return m_database.begin(); } + const_iterator cend () const{ return m_database.cend(); } + const_iterator end () const{ return m_database.end(); } + iterator end (){ return m_database.end(); } + +private: + std::map m_database; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp index b48f164fb7..33b725ab76 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp @@ -254,7 +254,7 @@ std::vector> get_sid_messages( 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 + ErrorReport::NO_ERROR_REPORT, "SidHelper(): the Target Advances setting needs to be odd", env.console ); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index e5cfa35342..a928861104 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -265,6 +265,7 @@ AdvObservedPokemon StarterRng::read_summary(SingleSwitchProgramEnvironment& env, } AdvObservedPokemon pokemon = { + stats.name, gender, string_to_nature(stats.nature), AdvAbility::Any, @@ -636,13 +637,13 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (SEED_POSITION == -1){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_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)); + env.log("Target Seed Value (base10): " + std::to_string(TARGET_SEED)); BaseStats BASE_STATS; switch (STARTER){ @@ -743,16 +744,13 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte 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; } + // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target + 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; @@ -804,7 +802,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte 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); + std::vector 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, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp index 4cdca2a910..1890be6351 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp @@ -234,11 +234,16 @@ bool StaticRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint3 return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); } -bool StaticRng::auto_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const uint64_t& MAX_BALL_THROWS){ - for (uint64_t i=0; i= 10){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_catch(): failed to detect battle menu" + ); + stats.errors++; return false; } count++; @@ -295,6 +300,7 @@ bool StaticRng::auto_catch(SingleSwitchProgramEnvironment& env, ProControllerCon env, NOTIFICATION_ERROR_RECOVERABLE, "auto_catch(): no recognized state after 30 seconds." ); + stats.errors++; return true; } env.log("Overworld detected."); @@ -306,6 +312,8 @@ bool StaticRng::auto_catch(SingleSwitchProgramEnvironment& env, ProControllerCon break; } + if (i == MAX_BALL_THROWS) { break; } + // select BAG (selection arrow does not wrap around) pbf_move_left_joystick(context, {+1, 0}, 100ms, 150ms); pbf_move_left_joystick(context, {0, +1}, 100ms, 150ms); @@ -327,6 +335,7 @@ bool StaticRng::auto_catch(SingleSwitchProgramEnvironment& env, ProControllerCon env, NOTIFICATION_ERROR_RECOVERABLE, "auto_catch(): failed to open bag." ); + stats.errors++; return true; } @@ -427,6 +436,7 @@ AdvObservedPokemon StaticRng::read_summary(SingleSwitchProgramEnvironment& env, } AdvObservedPokemon pokemon = { + stats.name, gender, string_to_nature(stats.nature), AdvAbility::Any, @@ -442,6 +452,7 @@ AdvObservedPokemon StaticRng::read_summary(SingleSwitchProgramEnvironment& env, bool StaticRng::use_rare_candy( SingleSwitchProgramEnvironment& env, ProControllerContext& context, + StaticRng_Descriptor::Stats& stats, AdvObservedPokemon& pokemon, AdvRngFilters& filters, const BaseStats& BASE_STATS, @@ -473,6 +484,7 @@ bool StaticRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to detect party menu." ); + stats.errors++; return true; } @@ -501,6 +513,7 @@ bool StaticRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to detect level-up stats." ); + stats.errors++; return true; } @@ -510,12 +523,12 @@ bool StaticRng::use_rare_candy( env.log("Reading stats..."); VideoSnapshot screen = env.console.video().snapshot(); - StatReads stats = reader.read_stats(env.logger(), screen); + StatReads statreads = reader.read_stats(env.logger(), screen); - update_filters(filters, pokemon, stats, {}, BASE_STATS); + update_filters(filters, pokemon, statreads, {}, BASE_STATS); RNG_FILTERS.set(filters); - // return to the bag (possibly learning a move, but trying to preven evolution) + // return to the bag (possibly learning a move, but trying to prevent evolution) int attempts = 0; while (true){ if (attempts > 5){ @@ -523,6 +536,7 @@ bool StaticRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to return to bag menu in 5 attempts." ); + stats.errors++; return true; } BagWatcher bag_menu(COLOR_RED); @@ -553,6 +567,7 @@ bool StaticRng::use_rare_candy( env, NOTIFICATION_ERROR_RECOVERABLE, "use_rare_candy(): failed to return to bag menu." ); + stats.errors++; return true; } } @@ -577,13 +592,13 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex if (SEED_POSITION == -1){ OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + ErrorReport::NO_ERROR_REPORT, "StaticRng(): Target Seed is missing from the list of nearby seeds.", env.console ); } - env.log("Target Seed Value: " + std::to_string(TARGET_SEED)); + env.log("Target Seed Value (base10): " + std::to_string(TARGET_SEED)); BaseStats BASE_STATS; int16_t GENDER_THRESHOLD = -1; @@ -663,7 +678,6 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex 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){ @@ -681,10 +695,6 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex break; } - if (wildshiny_found){ - break; - } - send_program_status_notification( env, NOTIFICATION_STATUS_UPDATE, "Calibrating." @@ -718,16 +728,13 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex 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; } + // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target + 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; @@ -787,10 +794,9 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex break; } - bool failed = auto_catch(env, context, MAX_BALL_THROWS); + bool failed = auto_catch(env, context, stats, MAX_BALL_THROWS); if (failed){ env.log("Failed catch."); - stats.errors++; continue; } @@ -798,7 +804,7 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex 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); + std::vector 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, @@ -812,10 +818,7 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex } for (uint64_t i=0; i +#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 "CommonTools/VisualDetectors/BlackScreenDetector.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_BattleDialogs.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/Menus/PokemonFRLG_DexRegistrationDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.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_RngStatsDatabase.h" +#include "PokemonFRLG_EncountersDatabase.h" +#include "PokemonFRLG_WildRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +WildRng_Descriptor::WildRng_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:WildRng", + Pokemon::STRING_POKEMON + " FRLG", "Wild RNG", + "Programs/PokemonFRLG/WildRng.html", + "Automatically calibrate timings to hit a specific RNG target for FRLG random wild encounters.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct WildRng_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 WildRng_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +WildRng::WildRng() + : LANGUAGE( + "Game Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + }, + LockMode::LOCK_WHILE_RUNNING, + true + ) + , GAME_VERSION( + "Game Version:", + { + {GameVersion::firered, "firered", "FireRed"}, + {GameVersion::leafgreen, "leafgreen", "LeafGreen"} + }, + LockMode::LOCK_WHILE_RUNNING, + GameVersion::firered + ) + , ENCOUNTER_TYPE( + "Encounter Type:", + { + {EncounterType::grass, "grass", "Grass"}, + {EncounterType::rocksmash, "rocksmash", "Rock Smash"}, + {EncounterType::surfing, "surfing", "Surfing"}, + {EncounterType::oldrod, "oldrod", "Old Rod"}, + {EncounterType::goodrod, "goodrod", "Good Rod"}, + {EncounterType::superrod, "superrod", "Super Rod"}, + }, + LockMode::LOCK_WHILE_RUNNING, + EncounterType::grass + ) + , GAME_LOCATION( + "Location:", + { + {GameLocation::altering_cave, "altering_cave", "Altering Cave"}, + {GameLocation::berry_forest, "berry_forest", "Berry Forest"}, + {GameLocation::bond_bridge, "bond_bridge", "Bond Bridge"}, + {GameLocation::cape_brink, "cape_brink", "Cape Brink"}, + {GameLocation::celadon_city, "celadon_city", "Celadon City"}, + {GameLocation::cerulean_cave_1f, "cerulean_cave_1f", "Cerulean Cave 1F"}, + {GameLocation::cerulean_cave_2f, "cerulean_cave_2f", "Cerulean Cave 2F"}, + {GameLocation::cerulean_cave_b1f, "cerulean_cave_b1f", "Cerulean Cave B1F"}, + {GameLocation::cerulean_city, "cerulean_city", "Cerulean City"}, + {GameLocation::cinnabar_island, "cinnabar_island", "Cinnabar Island"}, + {GameLocation::digletts_cave, "digletts_cave", "Digletts Cave"}, + {GameLocation::five_island, "five_island", "Five Island"}, + {GameLocation::five_isle_meadow, "five_isle_meadow", "Five Isle Meadow"}, + {GameLocation::four_island, "four_island", "Four Island"}, + {GameLocation::fuchsia_city, "fuchsia_city", "Fuchsia City"}, + {GameLocation::green_path, "green_path", "Green Path"}, + {GameLocation::icefall_cave_1f, "icefall_cave_1f", "Icefall Cave 1F"}, + {GameLocation::icefall_cave_back_cavern, "icefall_cave_back_cavern", "Icefall Cave Back Cavern"}, + {GameLocation::icefall_cave_b1f, "icefall_cave_b1f", "Icefall Cave B1F"}, + {GameLocation::icefall_cave_entrance, "icefall_cave_entrance", "Icefall Cave Entrance"}, + {GameLocation::kindle_road, "kindle_road", "Kindle Road"}, + {GameLocation::lost_cave, "lost_cave", "Lost Cave"}, + {GameLocation::memorial_pillar, "memorial_pillar", "Memorial Pillar"}, + {GameLocation::mt_ember_exterior, "mt_ember_exterior", "Mt. Ember Exterior"}, + {GameLocation::mt_ember_ruby_path_1f, "mt_ember_ruby_path_1f", "Mt. Ember Ruby Path 1F"}, + {GameLocation::mt_ember_ruby_path_b1f, "mt_ember_ruby_path_b1f", "Mt. Ember Ruby Path B1F"}, + {GameLocation::mt_ember_ruby_path_b2f, "mt_ember_ruby_path_b2f", "Mt. Ember Ruby Path B2F"}, + {GameLocation::mt_ember_ruby_path_b3f, "mt_ember_ruby_path_b3f", "Mt. Ember Ruby Path B3F"}, + {GameLocation::mt_ember_summit_path_1f, "mt_ember_summit_path_1f", "Mt. Ember Summit Path 1F"}, + {GameLocation::mt_ember_summit_path_2f, "mt_ember_summit_path_2f", "Mt. Ember Summit Path 2F"}, + {GameLocation::mt_ember_summit_path_3f, "mt_ember_summit_path_3f", "Mt. Ember Summit Path 3F"}, + {GameLocation::mt_moon_1f, "mt_moon_1f", "Mt. Moon 1F"}, + {GameLocation::mt_moon_b1f, "mt_moon_b1f", "Mt. Moon B1F"}, + {GameLocation::mt_moon_b2f, "mt_moon_b2f", "Mt. Moon B2F"}, + {GameLocation::one_island, "one_island", "One Island"}, + {GameLocation::outcast_island, "outcast_island", "Outcast Island"}, + {GameLocation::pallet_town, "pallet_town", "Pallet Town"}, + {GameLocation::pattern_bush, "pattern_bush", "Pattern Bush"}, + {GameLocation::pokemon_mansion_basement, "pokemon_mansion_basement", STRING_POKEMON + " Mansion Basement"}, + {GameLocation::pokemon_mansion_upper_floors, "pokemon_mansion_upper_floors", STRING_POKEMON + " Mansion Upper Floors"}, + {GameLocation::pokemon_tower_3f, "pokemon_tower_3f", STRING_POKEMON + " Tower 3F"}, + {GameLocation::pokemon_tower_4f, "pokemon_tower_4f", STRING_POKEMON + " Tower 4F"}, + {GameLocation::pokemon_tower_5f, "pokemon_tower_5f", STRING_POKEMON + " Tower 5F"}, + {GameLocation::pokemon_tower_6f, "pokemon_tower_6f", STRING_POKEMON + " Tower 6F"}, + {GameLocation::pokemon_tower_7f, "pokemon_tower_7f", STRING_POKEMON + " Tower 7F"}, + {GameLocation::power_plant, "power_plant", "Power Plant"}, + {GameLocation::resort_gorgeous, "resort_gorgeous", "Resort Gorgeous"}, + {GameLocation::rock_tunnel_1f, "rock_tunnel_1f", "Rock Tunnel 1F"}, + {GameLocation::rock_tunnel_b1f, "rock_tunnel_b1f", "Rock Tunnel B1F"}, + {GameLocation::route_1, "route_1", "Route 1"}, + {GameLocation::route_10, "route_10", "Route 10"}, + {GameLocation::route_11, "route_11", "Route 11"}, + {GameLocation::route_12, "route_12", "Route 12"}, + {GameLocation::route_13, "route_13", "Route 13"}, + {GameLocation::route_14, "route_14", "Route 14"}, + {GameLocation::route_15, "route_15", "Route 15"}, + {GameLocation::route_16, "route_16", "Route 16"}, + {GameLocation::route_17, "route_17", "Route 17"}, + {GameLocation::route_18, "route_18", "Route 18"}, + {GameLocation::route_19, "route_19", "Route 19"}, + {GameLocation::route_2, "route_2", "Route 2"}, + {GameLocation::route_20, "route_20", "Route 20"}, + {GameLocation::route_21, "route_21", "Route 21"}, + {GameLocation::route_22, "route_22", "Route 22"}, + {GameLocation::route_23, "route_23", "Route 23"}, + {GameLocation::route_24, "route_24", "Route 24"}, + {GameLocation::route_25, "route_25", "Route 25"}, + {GameLocation::route_3, "route_3", "Route 3"}, + {GameLocation::route_4, "route_4", "Route 4"}, + {GameLocation::route_5, "route_5", "Route 5"}, + {GameLocation::route_6, "route_6", "Route 6"}, + {GameLocation::route_7, "route_7", "Route 7"}, + {GameLocation::route_8, "route_8", "Route 8"}, + {GameLocation::route_9, "route_9", "Route 9"}, + {GameLocation::ruin_valley, "ruin_valley", "Ruin Valley"}, + {GameLocation::safari_zone_area_1_east, "safari_zone_area_1_east", "Safari Zone Area 1 - East"}, + {GameLocation::safari_zone_area_2_north, "safari_zone_area_2_north", "Safari Zone Area 2 - North"}, + {GameLocation::safari_zone_area_3_west, "safari_zone_area_3_west", "Safari Zone Area 3 - West"}, + {GameLocation::safari_zone_entrance, "safari_zone_entrance", "Safari Zone Entrance"}, + {GameLocation::seafoam_islands_1f, "seafoam_islands_1f", "Seafoam Islands 1F"}, + {GameLocation::seafoam_islands_b1f, "seafoam_islands_b1f", "Seafoam Islands B1F"}, + {GameLocation::seafoam_islands_b2f, "seafoam_islands_b2f", "Seafoam Islands B2F"}, + {GameLocation::seafoam_islands_b3f, "seafoam_islands_b3f", "Seafoam Islands B3F"}, + {GameLocation::seafoam_islands_b4f, "seafoam_islands_b4f", "Seafoam Islands B4F"}, + {GameLocation::sevault_canyon, "sevault_canyon", "Sevault Canyon"}, + {GameLocation::sevault_canyon_entrance, "sevault_canyon_entrance", "Sevault Canyon Entrance"}, + {GameLocation::ss_anne, "ss_anne", "S.S. Anne"}, + {GameLocation::tanoby_ruins, "tanoby_ruins", "Tanoby Ruins"}, + {GameLocation::tanoby_ruins_chambers, "tanoby_ruins_chambers", "Tanoby Ruins Chambers"}, + {GameLocation::three_isle_port, "three_isle_port", "Three Isle Port"}, + {GameLocation::trainer_tower, "trainer_tower", "Trainer Tower"}, + {GameLocation::treasure_beach, "treasure_beach", "Treasure Beach"}, + {GameLocation::vermilion_city, "vermilion_city", "Vermilion City"}, + {GameLocation::victory_road_1f, "victory_road_1f", "Victory Road 1F"}, + {GameLocation::victory_road_2f, "victory_road_2f", "Victory Road 2F"}, + {GameLocation::victory_road_3f, "victory_road_3f", "Victory Road 3F"}, + {GameLocation::viridian_city, "viridian_city", "Viridian City"}, + {GameLocation::viridian_forest, "viridian_forest", "Viridian Forest"}, + {GameLocation::water_labyrinth, "water_labyrinth", "Water Labyrinth"}, + {GameLocation::water_path, "water_path", "Water Path"}, + }, + LockMode::LOCK_WHILE_RUNNING, + GameLocation::route_1 + ) + , 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 + ) + , MAX_BALL_THROWS( + "Max Balls Thrown:
" + "The number of " + STRING_POKEBALL + "s in your bag to attempt to throw. Make sure these are at the top position of the bag.
" + "Balls thrown during calibration will be restored after resetting.", + LockMode::UNLOCK_WHILE_RUNNING, + 20, 1, 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, 30400 // 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(GAME_VERSION); + PA_ADD_OPTION(ENCOUNTER_TYPE); + PA_ADD_OPTION(GAME_LOCATION); + PA_ADD_OPTION(MAX_RESETS); + PA_ADD_OPTION(MAX_BALL_THROWS); + 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 WildRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ + return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); +} + +bool WildRng::auto_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, WildRng_Descriptor::Stats& stats, const uint64_t& MAX_BALL_THROWS){ + for (uint64_t i=0; i<=MAX_BALL_THROWS; i++){ + int count = 0; + while(true){ + if (count >= 10){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_catch(): failed to detect battle menu" + ); + stats.errors++; + return false; + } + count++; + + BattleMenuWatcher battle_menu(COLOR_RED); + PartyMenuWatcher party_menu(COLOR_RED); + DexRegistrationWatcher dex_registration(COLOR_RED); + BlackScreenWatcher black_screen(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<60; i++){ + pbf_press_button(context, BUTTON_B, 200ms, 300ms); + } + }, + { battle_menu, party_menu, black_screen }, + 10ms + ); + + int start_ret; + switch (ret){ + case 0: + env.log("Battle menu detected"); + break; + case 1: + env.log("Party menu detected. Attempting to send out next Pokemon"); + pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms); + pbf_mash_button(context, BUTTON_A, 1000ms); + continue; + case 2: + env.log("Dex registration detected. Exiting battle..."); + pbf_mash_button(context, BUTTON_B, 5000ms); + return false; + case 3: + env.log("Black screen detected. Battle exited."); + return false; + default: + env.log("No recognized state. Try checking if in the overworld..."); + StartMenuWatcher start_menu; + context.wait_for_all_requests(); + start_ret = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<3; i++){ + pbf_press_button(context, BUTTON_PLUS, 200ms, 2800ms); + pbf_mash_button(context, BUTTON_B, 500ms); + } + }, + { start_menu } + ); + if (start_ret < 0){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_catch(): no recognized state after 30 seconds." + ); + stats.errors++; + return true; + } + env.log("Overworld detected."); + pbf_mash_button(context, BUTTON_B, 500ms); + context.wait_for_all_requests(); + return false; + } + + break; + } + + if (i == MAX_BALL_THROWS) { break; } + + // select BAG (selection arrow does not wrap around) + pbf_move_left_joystick(context, {+1, 0}, 100ms, 150ms); + pbf_move_left_joystick(context, {0, +1}, 100ms, 150ms); + pbf_move_left_joystick(context, {+1, 0}, 100ms, 150ms); + pbf_move_left_joystick(context, {0, +1}, 100ms, 150ms); + + BagWatcher bag_open(COLOR_RED); + int ret2 = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + } + }, + { bag_open } + ); + if (ret2 < 0){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_catch(): failed to open bag." + ); + stats.errors++; + return true; + } + + if (i == 0){ + // go to balls pocket (pockets do not wrap around, topmost item will already be selected) + pbf_move_left_joystick(context, {+1, 0}, 200ms, 800ms); + pbf_move_left_joystick(context, {+1, 0}, 200ms, 800ms); + pbf_move_left_joystick(context, {+1, 0}, 200ms, 800ms); + } + + // use ball + pbf_mash_button(context, BUTTON_A, 5s); + } + + env.log("auto_catch(): ran out of balls."); + return true; +} + +AdvObservedPokemon WildRng::read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const std::set& SPECIES_LIST){ + // navigate to the summary page of the last occupied (not necessarily 6th) party slot + open_party_menu_from_overworld(env.console, context); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + + SummaryWatcher page_one(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + pbf_press_button(context, BUTTON_A, 200ms, 300ms); + for (int i=0; i<5; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 3800ms); + } + }, + { page_one } + ); + + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "read_summary(): Failed to detect first summary screen.", + env.console + ); + } + + // read stats + 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, SPECIES_LIST); + + SummaryPage2Watcher page_two(COLOR_RED); + context.wait_for_all_requests(); + int ret2 = 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 (ret2 < 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 = { + stats.name, + 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 WildRng::use_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + WildRng_Descriptor::Stats& stats, + 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); + 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." + ); + stats.errors++; + 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." + ); + stats.errors++; + 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 statreads = reader.read_stats(env.logger(), screen); + + update_filters(filters, pokemon, statreads, {}, BASE_STATS); + RNG_FILTERS.set(filters); + + // return to the bag (possibly learning a move, but trying to prevent 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." + ); + stats.errors++; + 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." + ); + stats.errors++; + return true; + } + } +} + + +void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + /* + * Settings: Text Speed fast + */ + + WildRng_Descriptor::Stats& stats = env.current_stats(); + + home_black_border_check(env.console, context); + + RNG_FILTERS.reset(); + RNG_CALIBRATION.reset(); + + // prepare database of base stats and gender thresholds + RngStatsDatabase stats_data("PokemonFRLG/BaseStats.json"); + + // get the relevant encounter slots + EncountersDatabase encounters_data(GAME_VERSION == GameVersion::firered ? "PokemonFRLG/EncounterSlotsFR.json" : "PokemonFRLG/EncounterSlotsLG.json"); + + EncounterType enc = ENCOUNTER_TYPE; + int enc_idx = static_cast (enc); + auto enc_entry = ENCOUNTER_TYPE.database().find(enc_idx); + std::string enc_slug = enc_entry->slug; + + GameLocation loc = GAME_LOCATION; + int loc_idx = static_cast (loc); + auto loc_entry = GAME_LOCATION.database().find(loc_idx); + std::string loc_slug = loc_entry->slug; + + std::map> location_map = encounters_data.get_throw(enc_slug); + if (location_map.find(loc_slug)==location_map.end()){ + OperationFailedException::fire( + ErrorReport::NO_ERROR_REPORT, + "Invalid combination for encounter type and location.", + env.console + ); + } + + std::vector ENCOUNTER_SLOTS = location_map.find(loc_slug)->second; + + std::set SPECIES_LIST; + for (auto slot : ENCOUNTER_SLOTS){ + SPECIES_LIST.emplace(slot.species); + } + + const bool SUPER_ROD = ENCOUNTER_TYPE == EncounterType::superrod; + + + + // prepare timings + + 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, + "WildRng(): Target Seed is missing from the list of nearby seeds.", + env.console + ); + } + + env.log("Target Seed Value (base10): " + std::to_string(TARGET_SEED)); + + PokemonFRLG_RngTarget TARGET = PokemonFRLG_RngTarget::sweetscent; + + bool safari_zone = ( + GAME_LOCATION == GameLocation::safari_zone_area_1_east || + GAME_LOCATION == GameLocation::safari_zone_area_2_north || + GAME_LOCATION == GameLocation::safari_zone_area_3_west || + GAME_LOCATION == GameLocation::safari_zone_entrance + ); + switch (ENCOUNTER_TYPE){ + case EncounterType::rocksmash: + TARGET = PokemonFRLG_RngTarget::rocksmash; + break; + case EncounterType::grass: + if (safari_zone){ + if (GAME_LOCATION == GameLocation::safari_zone_area_1_east){ + TARGET = PokemonFRLG_RngTarget::safarizoneeast; + }else if (GAME_LOCATION == GameLocation::safari_zone_area_2_north){ + TARGET = PokemonFRLG_RngTarget::safarizonenorth; + }else if (GAME_LOCATION == GameLocation::safari_zone_area_3_west){ + TARGET = PokemonFRLG_RngTarget::safarizonewest; + }else{ + TARGET = PokemonFRLG_RngTarget::safarizonecenter; + } + break; + } + case EncounterType::surfing: + TARGET = safari_zone ? PokemonFRLG_RngTarget::safarizonesurf : PokemonFRLG_RngTarget::sweetscent; + break; + case EncounterType::oldrod: + case EncounterType::goodrod: + case EncounterType::superrod: + TARGET = safari_zone ? PokemonFRLG_RngTarget::safarizonefish : PokemonFRLG_RngTarget::fishing; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "WildRng(): Unrecognized encounter type", + env.console + ); + } + + 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; + + + AdvRngWildSearcher searcher(TARGET_SEED, ADVANCES, ENCOUNTER_SLOTS, AdvRngMethod::Any); + AdvWildPokemonResult target_result = searcher.generate_pokemon(); + env.log("Target Species: " + target_result.species); + env.log("Target Level: " + std::to_string(target_result.level)); + env.log("Target Encounter Slot: " + std::to_string(target_result.slot)); + env.log("Target PID (base10): " + std::to_string(target_result.pid)); + + RngAdvanceHistory ADVANCE_HISTORY; + RngCalibrationHistory CALIBRATION_HISTORY; + uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 8192 : 1024; + uint64_t resets = 0; + + 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; + } + + 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); + } + } + + // if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target + 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(); + + int ret = watch_for_shiny_encounter(env.console, context); + if (ret < 1){ + if (TARGET == PokemonFRLG_RngTarget::fishing || TARGET == PokemonFRLG_RngTarget::rocksmash){ + env.log("No battle triggered. Resetting..."); + }else{ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "WildRng(): Failed to trigger battle", + env.console + ); + } + } + bool shiny_found = (ret == 1); + + 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; + } + + bool failed = auto_catch(env, context, stats, MAX_BALL_THROWS); + if (failed){ + env.log("Failed catch."); + continue; + } + + AdvObservedPokemon pokemon = read_summary(env, context, SPECIES_LIST); + RngStats species_stats = stats_data.get_throw(pokemon.species); + BaseStats BASE_STATS = species_stats.base_stats; + int16_t GENDER_THRESHOLD = species_stats.gender_threshold; + + AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS, AdvRngMethod::Any); + RNG_FILTERS.set(filters); + + std::vector search_hits = get_wild_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD, SUPER_ROD); + RNG_CALIBRATION.set( + 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); + finished = finished || all_indistinguishable(search_hits, searcher, SUPER_ROD); + if (finished || (MAX_RARE_CANDIES == 0)){ + env.log("RNG search finished."); + continue; + } + + for (uint64_t i=0; i make_stats() const override; +}; + +class WildRng : public SingleSwitchProgramInstance{ +public: + WildRng(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + + enum class GameVersion{ + firered, + leafgreen + }; + + enum class EncounterType{ + grass, + rocksmash, + surfing, + oldrod, + goodrod, + superrod + }; + + enum class GameLocation{ + altering_cave, + berry_forest, + bond_bridge, + cape_brink, + celadon_city, + cerulean_cave_1f, + cerulean_cave_2f, + cerulean_cave_b1f, + cerulean_city, + cinnabar_island, + digletts_cave, + five_island, + five_isle_meadow, + four_island, + fuchsia_city, + green_path, + icefall_cave_1f, + icefall_cave_back_cavern, + icefall_cave_b1f, + icefall_cave_entrance, + kindle_road, + lost_cave, + memorial_pillar, + mt_ember_exterior, + mt_ember_ruby_path_1f, + mt_ember_ruby_path_b1f, + mt_ember_ruby_path_b2f, + mt_ember_ruby_path_b3f, + mt_ember_summit_path_1f, + mt_ember_summit_path_2f, + mt_ember_summit_path_3f, + mt_moon_1f, + mt_moon_b1f, + mt_moon_b2f, + one_island, + outcast_island, + pallet_town, + pattern_bush, + pokemon_mansion_basement, + pokemon_mansion_upper_floors, + pokemon_tower_3f, + pokemon_tower_4f, + pokemon_tower_5f, + pokemon_tower_6f, + pokemon_tower_7f, + power_plant, + resort_gorgeous, + rock_tunnel_1f, + rock_tunnel_b1f, + route_1, + route_10, + route_11, + route_12, + route_13, + route_14, + route_15, + route_16, + route_17, + route_18, + route_19, + route_2, + route_20, + route_21, + route_22, + route_23, + route_24, + route_25, + route_3, + route_4, + route_5, + route_6, + route_7, + route_8, + route_9, + ruin_valley, + safari_zone_area_1_east, + safari_zone_area_2_north, + safari_zone_area_3_west, + safari_zone_entrance, + seafoam_islands_1f, + seafoam_islands_b1f, + seafoam_islands_b2f, + seafoam_islands_b3f, + seafoam_islands_b4f, + sevault_canyon, + sevault_canyon_entrance, + ss_anne, + tanoby_ruins, + tanoby_ruins_chambers, + three_isle_port, + trainer_tower, + treasure_beach, + vermilion_city, + victory_road_1f, + victory_road_2f, + victory_road_3f, + viridian_city, + viridian_forest, + water_labyrinth, + water_path, + }; + + bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); + + AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const std::set& SPECIES_LIST); + + bool auto_catch( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + WildRng_Descriptor::Stats& stats, + const uint64_t& MAX_BALL_THROWS + ); + + bool use_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + WildRng_Descriptor::Stats& stats, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& BASE_STATS, + bool first + ); + + OCR::LanguageOCROption LANGUAGE; + + EnumDropdownOption GAME_VERSION; + EnumDropdownOption ENCOUNTER_TYPE; + EnumDropdownOption GAME_LOCATION; + + SimpleIntegerOption MAX_RESETS; + SimpleIntegerOption MAX_RARE_CANDIES; + SimpleIntegerOption MAX_BALL_THROWS; + + 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/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 16fa053fcf..2f5c82a405 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1539,6 +1539,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngStatsDatabase.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_EncountersDatabase.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp @@ -1549,6 +1553,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_WildRng.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp