diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index c9fa2a166d..971c6f183a 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -38,8 +38,9 @@ AdvRngState rngstate_from_internal_state(uint16_t seed, uint64_t advances, uint3 uint32_t s2 = increment_internal_rng_state(s1); uint32_t s3 = increment_internal_rng_state(s2); uint32_t s4 = increment_internal_rng_state(s3); + uint32_t s5 = increment_internal_rng_state(s4); - return {seed, advances, method, s0, s1, s2, s3, s4}; + return {seed, advances, method, s0, s1, s2, s3, s4, s5}; } AdvRngState rngstate_from_seed(uint16_t seed, uint64_t advances, AdvRngMethod method){ @@ -58,7 +59,8 @@ void advance_rng_state(AdvRngState& state){ state.s1 = state.s2; state.s2 = state.s3; state.s3 = state.s4; - state.s4 = increment_internal_rng_state(state.s4); + state.s4 = state.s5; + state.s5 = increment_internal_rng_state(state.s5); } uint32_t pid_from_states(uint32_t& s0, uint32_t& s1){ @@ -251,6 +253,124 @@ AdvWildPokemonResult wild_pokemon_from_state(AdvRngState state, std::vector> 16) * 100) / 0xffff) < compat_threshold; +} + +AdvIVs get_inherited_ivs(AdvRngState state){ + uint8_t inherited_index_0 = (state.s0 >> 16) % 6; + uint8_t inherited_index_1 = (state.s1 >> 16) % 5; + uint8_t inherited_index_2 = (state.s2 >> 16) % 4; + + uint8_t parent_0 = (state.s3 >> 16) % 2; + uint8_t parent_1 = (state.s4 >> 16) % 2; + uint8_t parent_2 = (state.s5 >> 16) % 2; + + std::vector stats_left = { 0, 1, 2, 3, 4, 5 }; + + AdvIVs inherited_ivs; // 0 -> not inherited, 1 -> from parent A, 2 -> from parent B + + uint8_t stat_index = stats_left[inherited_index_0]; + inherited_ivs[stat_index] = parent_0 + 1; + stats_left.erase(stats_left.begin() + stat_index); + + stat_index = stats_left[inherited_index_1]; + inherited_ivs[stat_index] = parent_1 + 1; + if (stat_index < 5){ + stats_left.erase(stats_left.begin() + stat_index); + } + + stat_index = stats_left[inherited_index_2]; + inherited_ivs[stat_index] = parent_2 + 1; + + return inherited_ivs; +} + +AdvIVs apply_inherited_ivs( + AdvIVs& ivs, + AdvIVs& inherited_ivs, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs +){ + AdvIVs final_ivs; + for (uint8_t i=0; i<6; i++){ + if (inherited_ivs[i] == 1){ + final_ivs[i] = parentA_ivs[i]; + }else if (inherited_ivs[i] == 2){ + final_ivs[i] = parentB_ivs[i]; + }else{ + final_ivs[i] = ivs[i]; + } + } + + return final_ivs; +} + +AdvPokemonResult egg_to_pokemon( + AdvEggResult& egg_result, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs +){ + AdvIVs final_ivs = apply_inherited_ivs( + egg_result.ivs, egg_result.inherited_ivs, + parentA_ivs, parentB_ivs + ); + + return { + egg_result.pid, egg_result.gender, egg_result.nature, egg_result.ability, final_ivs + }; +} + +AdvEggResult egg_from_pickup_state(AdvRngState state, uint16_t held_pid_half){ + uint16_t pickup_pid_half = state.s0 >> 16; + + AdvIVs ivs; + + uint8_t iv1_advances = (state.method == AdvRngMethod::Method2 || state.method == AdvRngMethod::Method4) ? 1 : 2; + for (uint8_t i=0; i> 16; uint16_t pid1 = pid & 0xffff; @@ -267,10 +387,10 @@ AdvShinyType shiny_type_from_pid(uint32_t pid, uint16_t tid_xor_sid){ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, int16_t gender_threshold, uint16_t tid_xor_sid){ - return (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) + return (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)) @@ -282,10 +402,10 @@ bool check_for_match(AdvPokemonResult res, AdvRngFilters target, int16_t gender_ 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.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)) @@ -294,6 +414,265 @@ bool check_for_match(AdvWildPokemonResult res, AdvRngFilters target, int16_t gen && ((target.ivs.speed.low <= res.ivs.speed) && (target.ivs.speed.high >= res.ivs.speed)); } +// bool check_for_match(AdvEggResult res, AdvRngFilters target, int16_t gender_threshold, AdvIVs parentA_ivs, AdvIVs parentB_ivs, uint16_t tid_xor_sid){ +// AdvIVs final_ivs = apply_inherited_ivs(res.ivs, res.inherited_ivs, parentA_ivs, parentB_ivs); +// return (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 <= final_ivs.hp) && (target.ivs.hp.high >= final_ivs.hp)) +// && ((target.ivs.attack.low <= final_ivs.attack) && (target.ivs.attack.high >= final_ivs.attack)) +// && ((target.ivs.defense.low <= final_ivs.defense) && (target.ivs.defense.high >= final_ivs.defense)) +// && ((target.ivs.spatk.low <= final_ivs.spatk) && (target.ivs.spatk.high >= final_ivs.spatk)) +// && ((target.ivs.spdef.low <= final_ivs.spdef) && (target.ivs.spdef.high >= final_ivs.spdef)) +// && ((target.ivs.speed.low <= final_ivs.speed) && (target.ivs.speed.high >= final_ivs.speed)); +// } + +Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature){ + NatureAdjustments ret; + ret.attack = NatureAdjustment::NEUTRAL; + ret.defense = NatureAdjustment::NEUTRAL; + ret.spatk = NatureAdjustment::NEUTRAL; + ret.spdef = NatureAdjustment::NEUTRAL; + ret.speed = NatureAdjustment::NEUTRAL; + + switch (nature){ + case AdvNature::Bashful: + case AdvNature::Docile: + case AdvNature::Hardy: + case AdvNature::Quirky: + case AdvNature::Serious: + return ret; + + case AdvNature::Bold: + ret.attack = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Modest: + ret.attack = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Calm: + ret.attack = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Timid: + ret.attack = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Lonely: + ret.defense = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Mild: + ret.defense = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Gentle: + ret.defense = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Hasty: + ret.defense = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Adamant: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Impish: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Careful: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Jolly: + ret.spatk = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Naughty: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Lax: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Rash: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Naive: + ret.spdef = NatureAdjustment::NEGATIVE; + ret.speed = NatureAdjustment::POSITIVE; + return ret; + + case AdvNature::Brave: + ret.speed = NatureAdjustment::NEGATIVE; + ret.attack = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Relaxed: + ret.speed = NatureAdjustment::NEGATIVE; + ret.defense = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Quiet: + ret.speed = NatureAdjustment::NEGATIVE; + ret.spatk = NatureAdjustment::POSITIVE; + return ret; + case AdvNature::Sassy: + ret.speed = NatureAdjustment::NEGATIVE; + ret.spdef = NatureAdjustment::POSITIVE; + return ret; + + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown Nature: " + std::to_string((int)nature)); + } +} + +AdvNature string_to_nature(const std::string& nature_string){ + if (nature_string == "Hardy") return AdvNature::Hardy; + if (nature_string == "Lonely") return AdvNature::Lonely; + if (nature_string == "Brave") return AdvNature::Brave; + if (nature_string == "Adamant") return AdvNature::Adamant; + if (nature_string == "Naughty") return AdvNature::Naughty; + if (nature_string == "Bold") return AdvNature::Bold; + if (nature_string == "Docile") return AdvNature::Docile; + if (nature_string == "Relaxed") return AdvNature::Relaxed; + if (nature_string == "Impish") return AdvNature::Impish; + if (nature_string == "Lax") return AdvNature::Lax; + if (nature_string == "Timid") return AdvNature::Timid; + if (nature_string == "Hasty") return AdvNature::Hasty; + if (nature_string == "Serious") return AdvNature::Serious; + if (nature_string == "Jolly") return AdvNature::Jolly; + if (nature_string == "Naive") return AdvNature::Naive; + if (nature_string == "Modest") return AdvNature::Modest; + if (nature_string == "Mild") return AdvNature::Mild; + if (nature_string == "Quiet") return AdvNature::Quiet; + if (nature_string == "Bashful") return AdvNature::Bashful; + if (nature_string == "Rash") return AdvNature::Rash; + if (nature_string == "Calm") return AdvNature::Calm; + if (nature_string == "Gentle") return AdvNature::Gentle; + if (nature_string == "Sassy") return AdvNature::Sassy; + if (nature_string == "Careful") return AdvNature::Careful; + if (nature_string == "Quirky") return AdvNature::Quirky; + return AdvNature::Any; +} + +std::string nature_to_string(const AdvNature& nature){ + switch (nature){ + case AdvNature::Hardy: + return "Hardy"; + case AdvNature::Lonely: + return "Lonely"; + case AdvNature::Brave: + return "Brave"; + case AdvNature::Adamant: + return "Adamant"; + case AdvNature::Naughty: + return "Naughty"; + case AdvNature::Bold: + return "Bold"; + case AdvNature::Docile: + return "Docile"; + case AdvNature::Relaxed: + return "Relaxed"; + case AdvNature::Impish: + return "Impish"; + case AdvNature::Lax: + return "Lax"; + case AdvNature::Timid: + return "Timid"; + case AdvNature::Hasty: + return "Hasty"; + case AdvNature::Serious: + return "Serious"; + case AdvNature::Jolly: + return "Jolly"; + case AdvNature::Naive: + return "Naive"; + case AdvNature::Modest: + return "Modest"; + case AdvNature::Mild: + return "Mild"; + case AdvNature::Quiet: + return "Quiet"; + case AdvNature::Bashful: + return "Bashful"; + case AdvNature::Rash: + return "Rash"; + case AdvNature::Calm: + return "Calm"; + case AdvNature::Gentle: + return "Gentle"; + case AdvNature::Sassy: + return "Sassy"; + case AdvNature::Careful: + return "Careful"; + case AdvNature::Quirky: + return "Quirky"; + default: + return "Any"; + } +} + + + + +void shrink_iv_range(IvRange& mutated_range, IvRange& fixed_range){ + mutated_range.low = std::max(mutated_range.low, fixed_range.low); + mutated_range.high = std::min(mutated_range.high, fixed_range.high); +} + +void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ + shrink_iv_range(mutated_ranges.hp, fixed_ranges.hp); + shrink_iv_range(mutated_ranges.attack, fixed_ranges.attack); + shrink_iv_range(mutated_ranges.defense, fixed_ranges.defense); + shrink_iv_range(mutated_ranges.spatk, fixed_ranges.spatk); + shrink_iv_range(mutated_ranges.spdef, fixed_ranges.spdef); + shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); +} + +AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, const BaseStats& basestats, AdvRngMethod method){ + IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; + for (size_t i=0; i AdvRngSearcher::search( } + AdvRngWildSearcher::AdvRngWildSearcher(uint16_t seed, AdvRngState state, const std::vector& encounter_slots) : seed(seed) , state(state) @@ -470,148 +850,191 @@ std::vector AdvRngWildSearcher::search( } -Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature){ - NatureAdjustments ret; - ret.attack = NatureAdjustment::NEUTRAL; - ret.defense = NatureAdjustment::NEUTRAL; - ret.spatk = NatureAdjustment::NEUTRAL; - ret.spdef = NatureAdjustment::NEUTRAL; - ret.speed = NatureAdjustment::NEUTRAL; - switch (nature){ - case AdvNature::Bashful: - case AdvNature::Docile: - case AdvNature::Hardy: - case AdvNature::Quirky: - case AdvNature::Serious: - return ret; +AdvRngEggSearcher::AdvRngEggSearcher(uint16_t held_seed, AdvRngState held_state, uint16_t pickup_seed, AdvRngState pickup_state) + : held_seed(held_seed) + , held_state(held_state) + , pickup_seed(held_seed) + , pickup_state(held_state) +{} - case AdvNature::Bold: - ret.attack = NatureAdjustment::NEGATIVE; - ret.defense = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Modest: - ret.attack = NatureAdjustment::NEGATIVE; - ret.spatk = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Calm: - ret.attack = NatureAdjustment::NEGATIVE; - ret.spdef = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Timid: - ret.attack = NatureAdjustment::NEGATIVE; - ret.speed = NatureAdjustment::POSITIVE; - return ret; +AdvRngEggSearcher::AdvRngEggSearcher( + uint16_t held_seed, + uint64_t min_held_advances, + uint16_t pickup_seed, + uint64_t min_pickup_advances, + AdvRngMethod method +) + : held_seed(held_seed) + , held_state(rngstate_from_seed(held_seed, min_held_advances, method)) + , pickup_seed(pickup_seed) + , pickup_state(rngstate_from_seed(pickup_seed, min_pickup_advances, method)) +{} - case AdvNature::Lonely: - ret.defense = NatureAdjustment::NEGATIVE; - ret.attack = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Mild: - ret.defense = NatureAdjustment::NEGATIVE; - ret.spatk = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Gentle: - ret.defense = NatureAdjustment::NEGATIVE; - ret.spdef = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Hasty: - ret.defense = NatureAdjustment::NEGATIVE; - ret.speed = NatureAdjustment::POSITIVE; - return ret; +void AdvRngEggSearcher::advance_held_state(){ + advance_rng_state(held_state); +} - case AdvNature::Adamant: - ret.spatk = NatureAdjustment::NEGATIVE; - ret.attack = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Impish: - ret.spatk = NatureAdjustment::NEGATIVE; - ret.defense = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Careful: - ret.spatk = NatureAdjustment::NEGATIVE; - ret.spdef = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Jolly: - ret.spatk = NatureAdjustment::NEGATIVE; - ret.speed = NatureAdjustment::POSITIVE; - return ret; +void AdvRngEggSearcher::set_held_seed(uint16_t newseed){ + held_seed = newseed; + held_state = rngstate_from_seed(held_seed, 0, held_state.method); +} - case AdvNature::Naughty: - ret.spdef = NatureAdjustment::NEGATIVE; - ret.attack = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Lax: - ret.spdef = NatureAdjustment::NEGATIVE; - ret.defense = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Rash: - ret.spdef = NatureAdjustment::NEGATIVE; - ret.spatk = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Naive: - ret.spdef = NatureAdjustment::NEGATIVE; - ret.speed = NatureAdjustment::POSITIVE; - return ret; +void AdvRngEggSearcher::set_held_state_advances(uint64_t advances){ + held_state = rngstate_from_seed(held_seed, advances, held_state.method); +} - case AdvNature::Brave: - ret.speed = NatureAdjustment::NEGATIVE; - ret.attack = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Relaxed: - ret.speed = NatureAdjustment::NEGATIVE; - ret.defense = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Quiet: - ret.speed = NatureAdjustment::NEGATIVE; - ret.spatk = NatureAdjustment::POSITIVE; - return ret; - case AdvNature::Sassy: - ret.speed = NatureAdjustment::NEGATIVE; - ret.spdef = NatureAdjustment::POSITIVE; - return ret; +void AdvRngEggSearcher::advance_pickup_state(){ + advance_rng_state(pickup_state); +} - default: - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown Nature: " + std::to_string((int)nature)); - } +void AdvRngEggSearcher::set_pickup_seed(uint16_t newseed){ + pickup_seed = newseed; + pickup_state = rngstate_from_seed(pickup_seed, 0, pickup_state.method); } -void shrink_iv_range(IvRange& mutated_range, IvRange& fixed_range){ - mutated_range.low = std::max(mutated_range.low, fixed_range.low); - mutated_range.high = std::min(mutated_range.high, fixed_range.high); +void AdvRngEggSearcher::set_pickup_state_advances(uint64_t advances){ + pickup_state = rngstate_from_seed(pickup_seed, advances, pickup_state.method); } -void shrink_iv_ranges(IvRanges& mutated_ranges, IvRanges& fixed_ranges){ - shrink_iv_range(mutated_ranges.hp, fixed_ranges.hp); - shrink_iv_range(mutated_ranges.attack, fixed_ranges.attack); - shrink_iv_range(mutated_ranges.defense, fixed_ranges.defense); - shrink_iv_range(mutated_ranges.spatk, fixed_ranges.spatk); - shrink_iv_range(mutated_ranges.spdef, fixed_ranges.spdef); - shrink_iv_range(mutated_ranges.speed, fixed_ranges.speed); +AdvEggResult AdvRngEggSearcher::generate_egg(){ + uint16_t held_pid_half = (((held_state.s1) >> 16) % 0xfffe) + 1; + return egg_from_pickup_state(pickup_state, held_pid_half); } -AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, const BaseStats& basestats, AdvRngMethod method){ - IvRanges filter_iv_ranges = {{0,31},{0,31},{0,31},{0,31},{0,31},{0,31}}; - for (size_t i=0; i>& hits, + AdvRngFilters& target, + uint16_t held_pid_half, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + int16_t gender_threshold, + uint16_t tid_xor_sid + ){ + for (uint8_t m=0; m<4; m++){ + set_pickup_state_advances(min_pickup_advances); + + AdvRngMethod method; + switch (m){ + case 1: + method = AdvRngMethod::Method2; + break; + case 2: + method = AdvRngMethod::Method3; + break; + case 3: + method = AdvRngMethod::Method4; + break; + case 0: + default: + method = AdvRngMethod::Method1; + break; + } + + if ((target.method != AdvRngMethod::Any) && (target.method != method)){ + continue; + }else{ + pickup_state.method = method; + } + + for (uint64_t a=min_pickup_advances; a state_pair = { held_state, pickup_state }; + hits.emplace_back(state_pair); + } + advance_pickup_state(); + } } +} - return AdvRngFilters { - observation.species, - observation.level[0], - observation.gender, - observation.nature, - observation.ability, - filter_iv_ranges, - observation.shiny, - method - }; +void AdvRngEggSearcher::search_pickups( + std::vector>& hits, + AdvRngFilters& target, + uint16_t held_pid_half, + const std::vector& pickup_seeds, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + int16_t gender_threshold, + uint16_t tid_xor_sid +){ + for (uint16_t pickup_seed : pickup_seeds){ + set_pickup_seed(pickup_seed); + search_pickup_advances_range(hits, target, held_pid_half, min_pickup_advances, max_pickup_advances, parentA_ivs, parentB_ivs, gender_threshold, tid_xor_sid); + } +} + +void AdvRngEggSearcher::search_held_advances_range( + std::vector>& hits, + AdvRngFilters& target, + uint64_t min_held_advances, + uint64_t max_held_advances, + const std::vector& pickup_seeds, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + AdvEggCompatibility compatibility, + int16_t gender_threshold, + uint16_t tid_xor_sid +){ + set_held_state_advances(min_held_advances); + for (uint64_t a=min_held_advances; a> 16) % 0xfffe) + 1; + search_pickups( + hits, target, + held_pid_half, pickup_seeds, + min_pickup_advances, max_pickup_advances, + parentA_ivs, parentB_ivs, + gender_threshold, tid_xor_sid + ); + } + + advance_held_state(); + } + + +} + +std::vector> AdvRngEggSearcher::search( + AdvRngFilters& target, + const std::vector& held_seeds, + uint64_t min_held_advances, + uint64_t max_held_advances, + const std::vector& pickup_seeds, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + AdvEggCompatibility compatibility, + int16_t gender_threshold, + uint16_t tid_xor_sid +){ + std::vector> hits; + for (uint16_t h_seed : held_seeds){ + set_held_seed(h_seed); + search_held_advances_range( + hits, target, + min_held_advances, max_held_advances, + pickup_seeds, min_pickup_advances, max_pickup_advances, + parentA_ivs, parentB_ivs, compatibility, + gender_threshold, tid_xor_sid + ); + } + + return hits; } diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index d6b71191a5..aad6107e37 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "Pokemon_StatsCalculation.h" namespace PokemonAutomation{ @@ -28,6 +29,25 @@ struct AdvIVs{ uint8_t spatk = 0; uint8_t spdef = 0; uint8_t speed = 0; + + uint8_t& operator[](int index){ + switch (index){ + case 0: + return hp; + case 1: + return attack; + case 2: + return defense; + case 3: + return speed; + case 4: + return spatk; + case 5: + return spdef; + default: + throw std::runtime_error("Invalid IVs index. Please report this as a bug."); + } + } }; enum class AdvNature{ @@ -79,12 +99,19 @@ enum class AdvShinyType{ }; enum class AdvRngMethod{ - Method1, - Method2, - Method4, + Method1, // egg normal + Method2, // egg split + Method3, // egg alternate + Method4, // egg mixed Any }; +enum class AdvEggCompatibility{ + low, + medium, + high +}; + struct AdvRngState{ uint16_t seed; uint64_t advance; @@ -94,10 +121,18 @@ struct AdvRngState{ uint32_t s2; uint32_t s3; uint32_t s4; + uint32_t s5; bool operator<(const AdvRngState& rhs) const noexcept{ return this->advance < rhs.advance; } + + bool operator==(const AdvRngState& rhs) const noexcept{ + return ( + this->advance == rhs.advance + && this->seed == rhs.seed\ + ); + } }; struct AdvEncounterSlot{ @@ -125,6 +160,15 @@ struct AdvWildPokemonResult{ uint8_t level; }; +struct AdvEggResult{ + uint32_t pid; + uint8_t gender; + AdvNature nature; + AdvAbility ability; + AdvIVs ivs; + AdvIVs inherited_ivs; +}; + struct AdvObservedPokemon{ std::string species; AdvGender gender; @@ -155,9 +199,22 @@ void level_up_observed_pokemon(AdvObservedPokemon& pokemon, const StatReads& new // returns the appropriate NatureAdjustments for an AdvNature Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature); +Pokemon::AdvNature string_to_nature(const std::string& nature_string); +std::string nature_to_string(const AdvNature& nature); + +std::string gender_to_string(const AdvGender& gender); + +AdvGender gender_from_gender_value(uint8_t gender_value, int16_t threshold); + // returns search filters that correspond with observed stats AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, const BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1); +AdvPokemonResult egg_to_pokemon( + AdvEggResult& egg_result, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs +); + class AdvRngSearcher{ public: uint16_t seed; @@ -230,6 +287,90 @@ class AdvRngWildSearcher{ }; +class AdvRngEggSearcher{ +public: + uint16_t held_seed; + AdvRngState held_state; + + uint16_t pickup_seed; + AdvRngState pickup_state; + + AdvRngEggSearcher(uint16_t held_seed, AdvRngState held_state, uint16_t pickup_seed, AdvRngState pickup_state); + AdvRngEggSearcher( + uint16_t held_seed, uint64_t min_seed_advances, + uint16_t pickup_seed, uint64_t min_pickup_advances, + AdvRngMethod method = AdvRngMethod::Any + ); + + void set_held_seed(uint16_t seed); + void set_held_state_advances(uint64_t advances); + void advance_held_state(); + + void set_pickup_seed(uint16_t seed); + void set_pickup_state_advances(uint64_t advances); + void advance_pickup_state(); + + AdvEggResult generate_egg(); + AdvPokemonResult generate_pokemon(AdvIVs& parentA_ivs, AdvIVs& parentB_ivs); + + std::vector> search( + AdvRngFilters& target, + const std::vector& held_seeds, + uint64_t min_held_advances, + uint64_t max_held_advances, + const std::vector& pickup_seeds, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + AdvEggCompatibility compatibility, + int16_t gender_threshold = 126, + uint16_t tid_xor_sid = 0 + ); + +private: + + void search_held_advances_range( + std::vector>& hits, + AdvRngFilters& target, + uint64_t min_held_advances, + uint64_t max_held_advances, + const std::vector& pickup_seeds, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + AdvEggCompatibility compatibility, + int16_t gender_threshold, + uint16_t tid_xor_sid + ); + + void search_pickups( + std::vector>& hits, + AdvRngFilters& target, + uint16_t held_pid_half, + const std::vector& pickup_seeds, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + int16_t gender_threshold, + uint16_t tid_xor_sid + ); + + void search_pickup_advances_range( + std::vector>& hits, + AdvRngFilters& target, + uint16_t held_pid_half, + uint64_t min_pickup_advances, + uint64_t max_pickup_advances, + AdvIVs& parentA_ivs, + AdvIVs& parentB_ivs, + int16_t gender_threshold, + uint16_t tid_xor_sid + ); +}; + } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp index f03a848839..7c67402923 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.cpp @@ -60,6 +60,28 @@ ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPosit throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Safari Selection Arrow Position"); } +ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionNoDexMenu position){ + // Safari Zone menu has the same 7 slots as the overworld menu; RETIRE occupies slot 0, + // shifting POKEDEX..EXIT each down one relative to the overworld enum. + switch (position){ + case SelectionArrowPositionNoDexMenu::POKEMON: + return ImageFloatBox(0.727692, 0.0523077, 0.0369231, 0.0778846); + case SelectionArrowPositionNoDexMenu::BAG: + return ImageFloatBox(0.727692, 0.1457692, 0.0369231, 0.0778846); + case SelectionArrowPositionNoDexMenu::TRAINER: + return ImageFloatBox(0.727692, 0.2392307, 0.0369231, 0.0778846); + case SelectionArrowPositionNoDexMenu::SAVE: + return ImageFloatBox(0.727692, 0.3378846, 0.0369231, 0.0778846); + case SelectionArrowPositionNoDexMenu::OPTION: + return ImageFloatBox(0.727692, 0.4261538, 0.0369231, 0.0778846); + case SelectionArrowPositionNoDexMenu::EXIT: + return ImageFloatBox(0.727692, 0.5248076, 0.0369231, 0.0778846); + default: + break; + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid FRLG Safari Selection Arrow Position"); +} + ImageFloatBox SelectionArrowDetector::arrow_box_for_position(SelectionArrowPositionConfirmationMenu position){ switch (position){ case SelectionArrowPositionConfirmationMenu::YES: diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h index 49564d77b9..a552e9c12f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h @@ -26,6 +26,16 @@ enum class SelectionArrowPositionSafariMenu { EXIT }; +const int NO_DEX_START_MENU_OPTION_COUNT = 6; +enum class SelectionArrowPositionNoDexMenu{ + POKEMON, + BAG, + TRAINER, + SAVE, + OPTION, + EXIT +}; + const int START_MENU_OPTION_COUNT = 7; enum class SelectionArrowPositionStartMenu{ POKEDEX, @@ -72,6 +82,8 @@ class SelectionArrowDetector : public StaticScreenDetector{ static ImageFloatBox arrow_box_for_position(SelectionArrowPositionSafariMenu position); + static ImageFloatBox arrow_box_for_position(SelectionArrowPositionNoDexMenu position); + static ImageFloatBox arrow_box_for_position(SelectionArrowPositionConfirmationMenu position); const ImageFloatBox& last_detected() const { return m_last_detected; } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index 8cdae72559..62a26d48b4 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -650,6 +650,8 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext case 0: if (menu_context == StartMenuContext::SAFARI_ZONE){ ret = move_cursor_to_position(console, context, SelectionArrowPositionSafariMenu::POKEMON); + } else if (menu_context == StartMenuContext::NO_DEX){ + ret = move_cursor_to_position(console, context, SelectionArrowPositionNoDexMenu::POKEMON); } else { ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::POKEMON); } @@ -709,6 +711,8 @@ void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& conte case 0: if (menu_context == StartMenuContext::SAFARI_ZONE){ ret = move_cursor_to_position(console, context, SelectionArrowPositionSafariMenu::BAG); + } else if (menu_context == StartMenuContext::NO_DEX){ + ret = move_cursor_to_position(console, context, SelectionArrowPositionNoDexMenu::BAG); } else { ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::BAG); } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index c984dfb6ff..884277f6dd 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -72,7 +72,8 @@ bool exit_wild_battle(ConsoleHandle& console, ProControllerContext& context, boo // Starting from the start menu, a sub-screen of the start menu, or the overworld, navigate to the party screen enum class StartMenuContext { STANDARD, - SAFARI_ZONE + SAFARI_ZONE, + NO_DEX, }; void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp index a81719467a..d810a9234a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.cpp @@ -134,6 +134,16 @@ bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& conte ); } +bool move_cursor_to_position(ConsoleHandle& console, ProControllerContext& context, SelectionArrowPositionNoDexMenu destination){ + return move_cursor_impl( + console, context, + MENU_ARROW_BOX, + NO_DEX_START_MENU_OPTION_COUNT, + [](int i){ return SelectionArrowDetector::arrow_box_for_position(static_cast(i)); }, + static_cast(destination) + ); +} + void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context){ WallClock start = current_time(); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h index e0e76ce94f..43ec22dea2 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h @@ -47,6 +47,13 @@ bool move_cursor_to_position( SelectionArrowPositionSafariMenu destination ); +// Starting from the start menu before the PokeDex has been received, move the selection arrow to the specified position. +// Return true if successful, false otherwise (e.g. if selection arrow is not detected). +bool move_cursor_to_position( + ConsoleHandle& console, ProControllerContext& context, + SelectionArrowPositionNoDexMenu destination +); + // Starting from either the overworld or the main menu, save the game. // This function returns in the overworld. void save_game_to_overworld(ConsoleHandle& console, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp index 9e003cab83..efdd55518c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp @@ -17,7 +17,7 @@ namespace NintendoSwitch{ namespace PokemonFRLG{ -void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, BlackoutButton BLACKOUT_BUTTON, int64_t SEED_DELAY, ConsoleType console_type){ +void set_seed_after_delay(ProControllerContext& context, const SeedButton& seed_button, const BlackoutButton& extra_button, int64_t seed_delay, ConsoleType console_type){ // be warned: not tested with all console types switch (console_type){ case ConsoleType::Switch1: @@ -30,9 +30,9 @@ void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, // wait on title screen for the specified delay // hold the "blackout" button starting from the black screen after the copyright text until getting to the continue screen - if (BLACKOUT_BUTTON != BlackoutButton::None){ + if (extra_button != BlackoutButton::None){ Button b_button; - switch (BLACKOUT_BUTTON){ + switch (extra_button){ case BlackoutButton::L: b_button = BUTTON_L; break; @@ -43,17 +43,17 @@ void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, b_button = BUTTON_L; } Milliseconds blackout_wait = 3800ms; // wait for the copyright text to disappear - Milliseconds blackout_delay = std::chrono::milliseconds(SEED_DELAY) - blackout_wait; + Milliseconds blackout_delay = std::chrono::milliseconds(seed_delay) - blackout_wait; Milliseconds blackout_hold = 30000ms; // wait for leaves/flames to appear on the title screen. It's okay if this is held over the seed button press ssf_do_nothing(context, blackout_wait); ssf_press_button(context, b_button, blackout_delay, blackout_hold, 0ms); }else{ - pbf_wait(context, std::chrono::milliseconds(SEED_DELAY)); + pbf_wait(context, std::chrono::milliseconds(seed_delay)); } // hold the specified button for a few seconds through the transition to the Continue Screen Button s_button; - switch (SEED_BUTTON){ + switch (seed_button){ case SeedButton::A: s_button = BUTTON_A; break; @@ -70,15 +70,15 @@ void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, pbf_press_button(context, s_button, 3000ms, 0ms); } -void load_game_after_delay(ProControllerContext& context, uint64_t CONTINUE_SCREEN_DELAY){ - pbf_wait(context, std::chrono::milliseconds(CONTINUE_SCREEN_DELAY - 3000)); +void load_game_after_delay(ProControllerContext& context, const uint64_t& csf_delay){ + pbf_wait(context, std::chrono::milliseconds(csf_delay - 3000)); pbf_press_button(context, BUTTON_A, 50ms, 1450ms); // skip recap pbf_press_button(context, BUTTON_B, 50ms, 2450ms); // need to later subtract 4000ms from delay to hit desired number of advances } -void wait_with_teachy_tv(ProControllerContext& context, uint64_t TEACHY_DELAY){ +void wait_with_teachy_tv(ProControllerContext& context, const uint64_t& teachy_delay){ // open start menu -> bag -> key items -> Teachy TV -> use pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms); @@ -87,7 +87,7 @@ void wait_with_teachy_tv(ProControllerContext& context, uint64_t TEACHY_DELAY){ pbf_move_left_joystick(context, {+1, 0}, 200ms, 2300ms); pbf_press_button(context, BUTTON_A, 200ms, 300ms); pbf_press_button(context, BUTTON_A, 200ms, 367ms); // the "static fuzz" that adds RNG advances doesn't kick in right away - pbf_wait(context, std::chrono::milliseconds(TEACHY_DELAY)); + pbf_wait(context, std::chrono::milliseconds(teachy_delay)); // close teachy tv -> close bag -> reset start menu cursor position - > close start menu pbf_press_button(context, BUTTON_B, 200ms, 2300ms); pbf_press_button(context, BUTTON_B, 200ms, 2300ms); @@ -98,11 +98,11 @@ void wait_with_teachy_tv(ProControllerContext& context, uint64_t TEACHY_DELAY){ } -void collect_starter_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_starter_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // Advance through starter dialogue and wait on "really quite energetic!" pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 7200)); // 4000ms + 3000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 7200)); // 4000ms + 3000ms + 200ms // Finish dialogue (hits the target advance) pbf_press_button(context, BUTTON_A, 200ms, 5800ms); // Decline nickname @@ -112,11 +112,11 @@ void collect_starter_after_delay(ProControllerContext& context, uint64_t INGAME_ context.wait_for_all_requests(); } -void collect_magikarp_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_magikarp_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // Advance through starter dialogue and wait on YES/NO pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 7200)); // 4000ms + 3000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 7200)); // 4000ms + 3000ms + 200ms // Finish dialogue (hits the target advance) pbf_press_button(context, BUTTON_A, 200ms, 3800ms); // Decline nickname @@ -124,9 +124,9 @@ void collect_magikarp_after_delay(ProControllerContext& context, uint64_t INGAME context.wait_for_all_requests(); } -void collect_hitmon_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_hitmon_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // One dialog before accepting - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 4200)); // 4000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 4200)); // 4000ms + 200ms // Confirm selection pbf_press_button(context, BUTTON_A, 200ms, 1800ms); // Decline nickname @@ -134,9 +134,9 @@ void collect_hitmon_after_delay(ProControllerContext& context, uint64_t INGAME_D context.wait_for_all_requests(); } -void collect_eevee_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_eevee_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // No dialogue to advance through -- just wait - pbf_wait(context, std::chrono::milliseconds(INGAME_DELAY - 4000)); + pbf_wait(context, std::chrono::milliseconds(ingame_delay - 4000)); // Interact with the pokeball pbf_press_button(context, BUTTON_A, 200ms, 3800ms); // Decline nickname @@ -144,11 +144,11 @@ void collect_eevee_after_delay(ProControllerContext& context, uint64_t INGAME_DE context.wait_for_all_requests(); } -void collect_lapras_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_lapras_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // 3 dialog presses pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 7200)); // 4000ms + 3000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 7200)); // 4000ms + 3000ms + 200ms // Accept Lapras on target frame pbf_press_button(context, BUTTON_A, 200ms, 3800ms); // Decline nickname and exit dialog @@ -156,10 +156,10 @@ void collect_lapras_after_delay(ProControllerContext& context, uint64_t INGAME_D context.wait_for_all_requests(); } -void collect_fossil_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_fossil_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // 2 dialog presses pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 5700)); // 4000ms + 1500ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 5700)); // 4000ms + 1500ms + 200ms // Advance dialog on target frame pbf_press_button(context, BUTTON_A, 200ms, 2800ms); // Decline nickname @@ -167,7 +167,7 @@ void collect_fossil_after_delay(ProControllerContext& context, uint64_t INGAME_D context.wait_for_all_requests(); } -void collect_gamecorner_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY, int SLOT){ +void collect_gamecorner_after_delay(ProControllerContext& context, const uint64_t& ingame_delay, int SLOT){ // 2 dialog presses pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); @@ -179,7 +179,7 @@ void collect_gamecorner_after_delay(ProControllerContext& context, uint64_t INGA pbf_wait(context, 400ms); } // select option and wait on confirmation - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 8200)); // 4000ms + 3000ms + (400ms * 5) + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 8200)); // 4000ms + 3000ms + (400ms * 5) + 200ms // confirm prize pbf_press_button(context, BUTTON_A, 200ms, 2800ms); // decline nickname @@ -187,14 +187,14 @@ void collect_gamecorner_after_delay(ProControllerContext& context, uint64_t INGA context.wait_for_all_requests(); } -void collect_togepi_egg_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void collect_togepi_egg_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // 6 dialog presses pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 11700)); // 4000ms + 7500ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 11700)); // 4000ms + 7500ms + 200ms // accept egg pbf_press_button(context, BUTTON_A, 200ms, 2800ms); // exit dialogue @@ -202,10 +202,32 @@ void collect_togepi_egg_after_delay(ProControllerContext& context, uint64_t INGA context.wait_for_all_requests(); } -void encounter_static_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void trigger_held_daycare_egg_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ + // No dialogue to advance through -- just wait + pbf_wait(context, std::chrono::milliseconds(ingame_delay - 4000)); + // Trigger the encounter (WALK LEFT) + pbf_move_left_joystick(context, {-1, 0}, 200ms, 800ms); + context.wait_for_all_requests(); +} + +void collect_daycare_egg_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ + // 5 dialog presses + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 10200)); // 4000ms + 6000ms + 200ms + // accept egg + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + // exit dialogue + pbf_mash_button(context, BUTTON_B, 2500ms); + context.wait_for_all_requests(); +} + +void encounter_static_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // No dialogue to advance through -- just wait in the start menu (avoids extra RNG advances by boulders Mt Ember and Seafoam Islands) pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); - pbf_wait(context, std::chrono::milliseconds(INGAME_DELAY - 5000)); // 4000ms + 1000ms + pbf_wait(context, std::chrono::milliseconds(ingame_delay - 5000)); // 4000ms + 1000ms pbf_press_button(context, BUTTON_B, 200ms, 300ms); // Interact with the static encounter pbf_press_button(context, BUTTON_A, 200ms, 800ms); @@ -213,32 +235,32 @@ void encounter_static_after_delay(ProControllerContext& context, uint64_t INGAME context.wait_for_all_requests(); } -void encounter_snorlax_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void encounter_snorlax_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // Interact with Snorlax, YES to PokeFlute, wait on "woke up!" pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 9800ms); // PokeFlute tune - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 15700)); // 4000ms + 1500ms + 10000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 15700)); // 4000ms + 1500ms + 10000ms + 200ms pbf_press_button(context, BUTTON_A, 200ms, 200ms); context.wait_for_all_requests(); } -void encounter_mewtwo_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void encounter_mewtwo_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // one dialogue before the encounter happens - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 4200)); // 4000ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 4200)); // 4000ms + 200ms // Initiate encounter pbf_press_button(context, BUTTON_A, 200ms, 200ms); context.wait_for_all_requests(); } -void encounter_hooh_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void encounter_hooh_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // No dialogue to advance through -- just wait - pbf_wait(context, std::chrono::milliseconds(INGAME_DELAY - 4000)); + pbf_wait(context, std::chrono::milliseconds(ingame_delay - 4000)); // Trigger the encounter (WALK UP) pbf_move_left_joystick(context, {0, +1}, 800ms, 700ms); context.wait_for_all_requests(); } -void encounter_hypno_after_delay(ProControllerContext& context, uint64_t INGAME_DELAY){ +void encounter_hypno_after_delay(ProControllerContext& context, const uint64_t& ingame_delay){ // 5 dialog advances, with the 5th needing some extra time pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 1300ms); @@ -246,17 +268,17 @@ void encounter_hypno_after_delay(ProControllerContext& context, uint64_t INGAME_ pbf_press_button(context, BUTTON_A, 200ms, 1300ms); pbf_press_button(context, BUTTON_A, 200ms, 2300ms); // Wait after the 6th - pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(INGAME_DELAY - 12700)); // 4000ms + 8500ms + 200ms + pbf_press_button(context, BUTTON_A, 200ms, std::chrono::milliseconds(ingame_delay - 12700)); // 4000ms + 8500ms + 200ms // Initiate encounter pbf_press_button(context, BUTTON_A, 200ms, 200ms); context.wait_for_all_requests(); } -void use_sweet_scent(ProControllerContext& context, uint64_t INGAME_DELAY, bool SAFARI_ZONE = false){ +void use_sweet_scent(ProControllerContext& context, const uint64_t& ingame_delay, bool safari_zone = false){ // navigate to last party slot pbf_press_button(context, BUTTON_PLUS, 200ms, 300ms); pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms); - if (SAFARI_ZONE) { // there is an extra menu option at the top of the start menu + if (safari_zone) { // there is an extra menu option at the top of the start menu pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms); } pbf_press_button(context, BUTTON_A, 200ms, 1000ms); @@ -264,24 +286,24 @@ void use_sweet_scent(ProControllerContext& context, uint64_t INGAME_DELAY, bool pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); pbf_press_button(context, BUTTON_A, 200ms, 300ms); // hover over Sweet Scent (2nd option, but maybe HMs could change this) - pbf_move_left_joystick(context, {0, -1}, 200ms, std::chrono::milliseconds(INGAME_DELAY - (SAFARI_ZONE ? 7900 : 7400))); + pbf_move_left_joystick(context, {0, -1}, 200ms, std::chrono::milliseconds(ingame_delay - (safari_zone ? 7900 : 7400))); pbf_press_button(context, BUTTON_A, 200ms, 800ms); context.wait_for_all_requests(); } -void use_rock_smash(ProControllerContext& context, uint64_t INGAME_DELAY){ +void use_rock_smash(ProControllerContext& context, const 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_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){ +void use_registered_fishing_rod(ProControllerContext& context, const 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)); - pbf_press_button(context, BUTTON_MINUS, 200ms, std::chrono::milliseconds(INGAME_DELAY - rng_wait - 4200)); + pbf_press_button(context, BUTTON_MINUS, 200ms, std::chrono::milliseconds(ingame_delay - rng_wait - 4200)); pbf_press_button(context, BUTTON_A, 200ms, 800ms); context.wait_for_all_requests(); } @@ -372,20 +394,18 @@ void walk_to_safarizonewest(ProControllerContext& context){ void check_timings( ConsoleHandle& console, - PokemonFRLG_RngTarget TARGET, - uint64_t SEED_DELAY, - uint64_t CONTINUE_SCREEN_DELAY, - uint64_t INGAME_DELAY, - bool SAFARI_ZONE + const PokemonFRLG_RngTarget& TARGET, + const RngTimings& timings, + bool safari_zone ){ - if (CONTINUE_SCREEN_DELAY < 3200){ + if (timings.csf_delay < 3200){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "The Continue Screen delay cannot be less than 3200ms (192 advances). Check your Continue Screen calibration.", console ); } - if (SEED_DELAY < 30000){ + if (timings.seed_delay < 29650){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "The title screen delay cannot be less than 30s. Check your seed delay and calibration.", @@ -395,7 +415,7 @@ void check_timings( switch (TARGET){ case PokemonFRLG_RngTarget::starters: - if (INGAME_DELAY < 7500){ + if (timings.ingame_delay < 7500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Starters: the in-game delay cannot be less than 7500ms (740 advances). Check your in-game advances and calibration or pick a new target.", @@ -404,7 +424,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::magikarp: - if (INGAME_DELAY < 7500){ + if (timings.ingame_delay < 7500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Magikarp: the in-game delay cannot be less than 7500ms (740 advances). Check your in-game advances and calibration or pick a new target.", @@ -415,7 +435,7 @@ void check_timings( case PokemonFRLG_RngTarget::hitmonchan: case PokemonFRLG_RngTarget::hitmonlee: case PokemonFRLG_RngTarget::hitmon: - if (INGAME_DELAY < 4500){ + if (timings.ingame_delay < 4500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Hitmonchan/Hitmonlee: the in-game delay cannot be less than 4500ms (380 advances). Check your in-game advances and calibration or pick a new target.", @@ -424,7 +444,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::eevee: - if (INGAME_DELAY < 4000){ + if (timings.ingame_delay < 4000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Eevee: the in-game delay cannot be less than 4000ms (320 advances). Check your in-game advances and calibration or pick a new target.", @@ -433,7 +453,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::lapras: - if (INGAME_DELAY < 7500){ + if (timings.ingame_delay < 7500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Lapras: the in-game delay cannot be less than 7500ms (740 advances). Check your in-game advances and calibration or pick a new target.", @@ -445,7 +465,7 @@ void check_timings( case PokemonFRLG_RngTarget::kabuto: case PokemonFRLG_RngTarget::aerodactyl: case PokemonFRLG_RngTarget::fossils: - if (INGAME_DELAY < 6000){ + if (timings.ingame_delay < 6000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Fossils: the in-game delay cannot be less than 6000ms (560 advances). Check your in-game advances and calibration or pick a new target.", @@ -460,7 +480,7 @@ void check_timings( case PokemonFRLG_RngTarget::gamecornerscyther: case PokemonFRLG_RngTarget::gamecornerpinsir: case PokemonFRLG_RngTarget::gamecornerporygon: - if (INGAME_DELAY < 8500){ + if (timings.ingame_delay < 8500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Game Corner: the in-game delay cannot be less than 8500ms (860 advances). Check your in-game advances and calibration or pick a new target.", @@ -469,7 +489,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::togepi: - if (INGAME_DELAY < 12000) { + if (timings.ingame_delay < 12000) { OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Togepi: the in-game delay cannot be less than 12000ms (1280 advances). Check your in-game advances and calibration or pick a new target.", @@ -477,8 +497,33 @@ void check_timings( ); } return; + case PokemonFRLG_RngTarget::eggheld: + if (timings.ingame_delay < 4000) { + OperationFailedException::fire( + ErrorReport::NO_ERROR_REPORT, + "Togepi: the in-game delay cannot be less than 4000ms (320 advances). Check your in-game advances and calibration or pick a new target.", + console + ); + } + return; + case PokemonFRLG_RngTarget::eggpickup: + if (timings.ingame_delay < 4000) { + OperationFailedException::fire( + ErrorReport::NO_ERROR_REPORT, + "Togepi: the in-game delay cannot be less than 10500ms (1100 advances). Check your in-game advances and calibration or pick a new target.", + console + ); + } + return; + case PokemonFRLG_RngTarget::electrode: + case PokemonFRLG_RngTarget::articuno: + case PokemonFRLG_RngTarget::zapdos: + case PokemonFRLG_RngTarget::moltres: + case PokemonFRLG_RngTarget::lugia: + case PokemonFRLG_RngTarget::deoxys_attack: + case PokemonFRLG_RngTarget::deoxys_defense: case PokemonFRLG_RngTarget::staticencounter: - if (INGAME_DELAY < 5000){ + if (timings.ingame_delay < 5000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Static Encounter: the in-game delay cannot be less than 5000ms (440 advances). Check your in-game advances and calibration or pick a new target.", @@ -487,7 +532,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::snorlax: - if (INGAME_DELAY < 16000){ + if (timings.ingame_delay < 16000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Snorlax: the in-game delay cannot be less than 16000ms (1760 advances). Check your in-game advances and calibration or pick a new target.", @@ -496,7 +541,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::mewtwo: - if (INGAME_DELAY < 4500){ + if (timings.ingame_delay < 4500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Mewtwo: the in-game delay cannot be less than 4500ms (380 advances). Check your in-game advances and calibration or pick a new target.", @@ -505,7 +550,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::hooh: - if (INGAME_DELAY < 4000){ + if (timings.ingame_delay < 4000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Ho-oh: the in-game delay cannot be less than 4000ms (320 advances). Check your in-game advances and calibration or pick a new target.", @@ -514,7 +559,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::hypno: - if (INGAME_DELAY < 13000){ + if (timings.ingame_delay < 13000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Hypno: the in-game delay cannot be less than 13000ms (1400 advances). Check your in-game advances and calibration or pick a new target.", @@ -523,13 +568,13 @@ void check_timings( } return; case PokemonFRLG_RngTarget::sweetscent: - if (!SAFARI_ZONE && INGAME_DELAY < 8500){ + if (!safari_zone && timings.ingame_delay < 8500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Sweet Scent: the in-game delay cannot be less than 8500ms (1372 advances). Check your in-game advances and calibration or pick a new target.", console ); - }else if (SAFARI_ZONE && INGAME_DELAY < 9500){ + }else if (safari_zone && timings.ingame_delay < 9500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Sweet Scent: the in-game delay cannot be less than 9500ms (1492 advances). Check your in-game advances and calibration or pick a new target.", @@ -538,7 +583,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::rocksmash: - if (INGAME_DELAY < 6500){ + if (timings.ingame_delay < 6500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Rock Smash: the in-game delay cannot be less than 7000ms (1192 advances). Check your in-game advances and calibration or pick a new target.", @@ -546,7 +591,7 @@ void check_timings( ); } case PokemonFRLG_RngTarget::fishing: - if (INGAME_DELAY < 5500){ + if (timings.ingame_delay < 5500){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Fishing: the in-game delay cannot be less than 5500ms (500 advances). Check your in-game advances and calibration or pick a new target.", @@ -555,7 +600,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::safarizonecenter: - if (INGAME_DELAY < 39000){ + if (timings.ingame_delay < 39000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Safari Zone Center: in-game delay cannot be less than 39000ms (5032 advances). Check your in-game advances and calibration or pick a new target.", @@ -564,7 +609,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::safarizoneeast: - if (INGAME_DELAY < 45000){ + if (timings.ingame_delay < 45000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Safari Zone East: in-game delay cannot be less than 45000ms (5752 advances). Check your in-game advances and calibration or pick a new target.", @@ -573,7 +618,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::safarizonenorth: - if (INGAME_DELAY < 46000){ + if (timings.ingame_delay < 46000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Safari Zone North: in-game delay cannot be less than 46000ms (5872 advances). Check your in-game advances and calibration or pick a new target.", @@ -582,7 +627,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::safarizonewest: - if (INGAME_DELAY < 60000){ + if (timings.ingame_delay < 60000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Safari Zone West: in-game delay cannot be less than 60000ms (7552 advances). Check your in-game advances and calibration or pick a new target.", @@ -591,7 +636,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::safarizonesurf: - if (INGAME_DELAY < 46000){ + if (timings.ingame_delay < 46000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Safari Zone Surfing: in-game delay cannot be less than 46000ms (5872 advances). Check your in-game advances and calibration or pick a new target.", @@ -600,7 +645,7 @@ void check_timings( } return; case PokemonFRLG_RngTarget::safarizonefish: - if (INGAME_DELAY < 35000){ + if (timings.ingame_delay < 35000){ OperationFailedException::fire( ErrorReport::NO_ERROR_REPORT, "Safari Zone Fishing: in-game delay cannot be less than 35000ms (4040 advances). Check your in-game advances and calibration or pick a new target.", @@ -619,67 +664,70 @@ void check_timings( void perform_blind_sequence( ProControllerContext& context, - PokemonFRLG_RngTarget TARGET, - SeedButton SEED_BUTTON, - BlackoutButton BLACKOUT_BUTTON, - uint64_t SEED_DELAY, - uint64_t CONTINUE_SCREEN_DELAY, - uint64_t TEACHY_DELAY, - uint64_t INGAME_DELAY, - bool SAFARI_ZONE, + PokemonFRLG_RngTarget target, + const SeedButton& seed_button, + const BlackoutButton& extra_button, + const RngTimings& timings, + bool safari_zone, ConsoleType console_type ){ pbf_press_button(context, BUTTON_A, 80ms, 0ms); // start the game from the Home screen - set_seed_after_delay(context, SEED_BUTTON, BLACKOUT_BUTTON, SEED_DELAY, console_type); - load_game_after_delay(context, CONTINUE_SCREEN_DELAY); - if (TEACHY_DELAY > 0){ - wait_with_teachy_tv(context, TEACHY_DELAY); + set_seed_after_delay(context, seed_button, extra_button, timings.seed_delay, console_type); + load_game_after_delay(context, timings.csf_delay); + if (timings.teachy_delay > 0){ + wait_with_teachy_tv(context, timings.teachy_delay); } - uint64_t MODIFIED_INGAME_DELAY; - switch (TARGET){ + uint64_t modified_ingame_delay; + switch (target){ case PokemonFRLG_RngTarget::starters: - collect_starter_after_delay(context, INGAME_DELAY); + collect_starter_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::magikarp: - collect_magikarp_after_delay(context, INGAME_DELAY); + collect_magikarp_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::hitmonchan: case PokemonFRLG_RngTarget::hitmonlee: case PokemonFRLG_RngTarget::hitmon: - collect_hitmon_after_delay(context, INGAME_DELAY); + collect_hitmon_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::eevee: - collect_eevee_after_delay(context, INGAME_DELAY); + collect_eevee_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::lapras: - collect_lapras_after_delay(context, INGAME_DELAY); + collect_lapras_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::omanyte: case PokemonFRLG_RngTarget::kabuto: case PokemonFRLG_RngTarget::aerodactyl: case PokemonFRLG_RngTarget::fossils: - collect_fossil_after_delay(context, INGAME_DELAY); + collect_fossil_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::gamecornerabra: - collect_gamecorner_after_delay(context, INGAME_DELAY, 0); + collect_gamecorner_after_delay(context, timings.ingame_delay, 0); return; case PokemonFRLG_RngTarget::gamecornerclefairy: - collect_gamecorner_after_delay(context, INGAME_DELAY, 1); + collect_gamecorner_after_delay(context, timings.ingame_delay, 1); return; case PokemonFRLG_RngTarget::gamecornerdratinifr: case PokemonFRLG_RngTarget::gamecornerpinsir: - collect_gamecorner_after_delay(context, INGAME_DELAY, 2); + collect_gamecorner_after_delay(context, timings.ingame_delay, 2); return; case PokemonFRLG_RngTarget::gamecornerscyther: case PokemonFRLG_RngTarget::gamecornerdratinilg: - collect_gamecorner_after_delay(context, INGAME_DELAY, 3); + collect_gamecorner_after_delay(context, timings.ingame_delay, 3); return; case PokemonFRLG_RngTarget::gamecornerporygon: - collect_gamecorner_after_delay(context, INGAME_DELAY, 4); + collect_gamecorner_after_delay(context, timings.ingame_delay, 4); return; case PokemonFRLG_RngTarget::togepi: - collect_togepi_egg_after_delay(context, INGAME_DELAY); + collect_togepi_egg_after_delay(context, timings.ingame_delay); + return; + case PokemonFRLG_RngTarget::eggheld: + trigger_held_daycare_egg_after_delay(context, timings.ingame_delay); + return; + case PokemonFRLG_RngTarget::eggpickup: + collect_daycare_egg_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::electrode: case PokemonFRLG_RngTarget::articuno: @@ -689,57 +737,57 @@ void perform_blind_sequence( case PokemonFRLG_RngTarget::deoxys_attack: case PokemonFRLG_RngTarget::deoxys_defense: case PokemonFRLG_RngTarget::staticencounter: - encounter_static_after_delay(context, INGAME_DELAY); + encounter_static_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::snorlax: - encounter_snorlax_after_delay(context, INGAME_DELAY); + encounter_snorlax_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::mewtwo: - encounter_mewtwo_after_delay(context, INGAME_DELAY); + encounter_mewtwo_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::hooh: - encounter_hooh_after_delay(context, INGAME_DELAY); + encounter_hooh_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::hypno: - encounter_hypno_after_delay(context, INGAME_DELAY); + encounter_hypno_after_delay(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::sweetscent: - use_sweet_scent(context, INGAME_DELAY, SAFARI_ZONE); + use_sweet_scent(context, timings.ingame_delay, safari_zone); return; case PokemonFRLG_RngTarget::rocksmash: - use_rock_smash(context, INGAME_DELAY); + use_rock_smash(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::fishing: - use_registered_fishing_rod(context, INGAME_DELAY); + use_registered_fishing_rod(context, timings.ingame_delay); return; case PokemonFRLG_RngTarget::safarizonecenter: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 20670; + modified_ingame_delay = timings.ingame_delay - 20670; walk_to_safarizonecenter(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + use_sweet_scent(context, modified_ingame_delay, true); return; case PokemonFRLG_RngTarget::safarizoneeast: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 36160; + modified_ingame_delay = timings.ingame_delay - 36160; walk_to_safarizoneeast(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + use_sweet_scent(context, modified_ingame_delay, true); return; case PokemonFRLG_RngTarget::safarizonenorth: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 37410; + modified_ingame_delay = timings.ingame_delay - 37410; walk_to_safarizonenorth(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + use_sweet_scent(context, modified_ingame_delay, true); return; case PokemonFRLG_RngTarget::safarizonewest: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 51430; + modified_ingame_delay = timings.ingame_delay - 51430; walk_to_safarizonewest(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + use_sweet_scent(context, modified_ingame_delay, true); case PokemonFRLG_RngTarget::safarizonesurf: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; + modified_ingame_delay = timings.ingame_delay - 30300; walk_to_safarizonesurf(context); - use_sweet_scent(context, MODIFIED_INGAME_DELAY, true); + use_sweet_scent(context, modified_ingame_delay, true); return; case PokemonFRLG_RngTarget::safarizonefish: - MODIFIED_INGAME_DELAY = INGAME_DELAY - 30300; + modified_ingame_delay = timings.ingame_delay - 30300; walk_to_safarizonefish(context); - use_registered_fishing_rod(context, MODIFIED_INGAME_DELAY); + use_registered_fishing_rod(context, modified_ingame_delay); return; } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h index 1e9032a3db..18adc7b4ab 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h @@ -38,6 +38,8 @@ namespace PokemonFRLG{ gamecornerpinsir, gamecornerporygon, togepi, + eggheld, + eggpickup, electrode, articuno, zapdos, @@ -74,28 +76,31 @@ namespace PokemonFRLG{ R }; + struct RngTimings{ + uint64_t seed_delay; + uint64_t csf_delay; + uint64_t teachy_delay; + uint64_t ingame_delay; + }; + + // checks seed, continue screen, and in-game timings for the specificed RNG manipulation target // and fires an error if any of the timings are too short. void check_timings( ConsoleHandle& console, - PokemonFRLG_RngTarget TARGET, - uint64_t SEED_DELAY, - uint64_t CONTINUE_SCREEN_DELAY, - uint64_t INGAME_DELAY, - bool SAFARI_ZONE + const PokemonFRLG_RngTarget& TARGET, + const RngTimings& timings, + bool safari_zone ); // performs the blind sequence between launching the game and arriving at the RNG manipulation target void perform_blind_sequence( ProControllerContext& context, - PokemonFRLG_RngTarget TARGET, - SeedButton SEED_BUTTON, - BlackoutButton BLACKOUT_BUTTON, - uint64_t SEED_DELAY, - uint64_t CONTINUE_SCREEN_DELAY, - uint64_t TEACHY_DELAY, - uint64_t INGAME_DELAY, - bool SAFARI_ZONE, + PokemonFRLG_RngTarget target, + const SeedButton& seed_button, + const BlackoutButton& extra_button, + const RngTimings& timings, + bool safari_zone, ConsoleType console_type ); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index d719ab23cb..43f5bc05c8 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -4,31 +4,21 @@ * */ -#include -#include -#include -#include "CommonTools/Random.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/Language.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/StartupChecks/StartProgramChecks.h" #include "Pokemon/Pokemon_Strings.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" -#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" -#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h" -#include "PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h" -#include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" -#include "PokemonFRLG_BlindNavigation.h" #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" +#include "PokemonFRLG_RngCalibration.h" #include "PokemonFRLG_GiftRng.h" namespace PokemonAutomation{ @@ -229,200 +219,6 @@ bool GiftRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_ return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); } -AdvObservedPokemon GiftRng::read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - // assumes we're already on the first summary page - PokemonFRLG_Stats stats; - StatsReader reader(COLOR_RED); - - env.log("Reading Page 1 (Name, Level, Nature, Gender)..."); - VideoSnapshot screen1 = env.console.video().snapshot(); - reader.read_page1(env.logger(), LANGUAGE, screen1, stats); - - SummaryPage2Watcher page_two(COLOR_RED); - context.wait_for_all_requests(); - int ret = run_until( - env.console, context, - [](ProControllerContext& context) { - for (int i=0; i<5; i++){ - pbf_press_dpad(context, DPAD_RIGHT, 200ms, 1800ms); - } - }, - { page_two } - ); - - if (ret < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "read_summary(): Failed to detect second summary screen.", - env.console - ); - } - - env.log("Reading Page 2 (Stats)..."); - VideoSnapshot screen2 = env.console.video().snapshot(); - reader.read_page2(env.logger(), LANGUAGE, 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 GiftRng::use_rare_candy( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - GiftRng_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, LANGUAGE); - 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 GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast @@ -517,41 +313,45 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& break; } - const double FRAMERATE = 59.999977; // FPS - const double FRAME_DURATION = 1000 / FRAMERATE; + static const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate + static const int64_t FIXED_ADVANCES_OFFSET = 160; // frames, approximate - uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; - double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - uint64_t CONTINUE_SCREEN_FRAMES = 200; + static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate - const int64_t FIXED_ADVANCES_OFFSET = 160; // frames, approximate + const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; - 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; + const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; + + + RngCalibrations calibrations = { + RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, + RNG_CALIBRATION.advances_calibration, + RNG_CALIBRATION.csf_calibration + }; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); + env.log("Target PID (base 10): " + std::to_string(target_result.pid)); + env.log("Target Nature: " + nature_to_string(target_result.nature)); env.log("Target IVs:"); - env.log("HP: " + std::to_string(target_result.ivs.hp)); - env.log("Atk: " + std::to_string(target_result.ivs.attack)); - env.log("Def: " + std::to_string(target_result.ivs.defense)); - env.log("SpA: " + std::to_string(target_result.ivs.spatk)); - env.log("SpD: " + std::to_string(target_result.ivs.spdef)); - env.log("Spe: " + std::to_string(target_result.ivs.speed)); + env.log(" HP: " + std::to_string(target_result.ivs.hp)); + env.log(" Atk: " + std::to_string(target_result.ivs.attack)); + env.log(" Def: " + std::to_string(target_result.ivs.defense)); + env.log(" SpA: " + std::to_string(target_result.ivs.spatk)); + env.log(" SpD: " + std::to_string(target_result.ivs.spdef)); + env.log(" Spe: " + std::to_string(target_result.ivs.speed)); - RngAdvanceHistory ADVANCE_HISTORY; - RngCalibrationHistory CALIBRATION_HISTORY; - uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 8192 : 1024; + RngAdvanceHistory advance_history; + RngCalibrationHistory calibration_history; uint16_t failed_searches = 0; while (true){ - if (CALIBRATION_HISTORY.results.size() > 0){ + if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, CALIBRATION_HISTORY.results.back())){ + if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -580,74 +380,30 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& ); 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); - } + uint64_t advances_radius = get_advances_radius(env.console, calibration_history, INITIAL_ADVANCES_RADIUS); - 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 (calibration_history.results.size() > 0){ + calibrations = get_calibrations(env.console, calibration_history, SEED_VALUES, SEED_POSITION, ADVANCES); } // 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 + FIXED_ADVANCES_OFFSET; - 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 > 10000); // don't use Teachy TV for short in-game advance targets - if (should_use_teachy_tv) { - TEACHY_ADVANCES = std::floor((INGAME_ADVANCES - 2500) / 313) * 313; - } + double seed_bump = SEED_BUMPS[advance_history.results.size() % 5]; + calibrations.seed_offset += seed_bump; - 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 ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; - 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 ? 14067 : 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"); - } - - check_timings(env.console, TARGET, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, INGAME_DELAY, false); + RngTimings timings = prepare_timings( + env.console, TARGET, + SEED_DELAY, CONTINUE_SCREEN_FRAMES, ingame_advances, + USE_TEACHY_TV, calibrations, + FIXED_SEED_OFFSET, FIXED_ADVANCES_OFFSET + ); 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, + SEED_BUTTON, EXTRA_BUTTON, timings, false, PROFILE ); stats.resets++; @@ -675,57 +431,59 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& break; } - AdvObservedPokemon pokemon = read_summary(env, context); + AdvObservedPokemon pokemon = read_summary(env.console, context, LANGUAGE); AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); std::vector search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); RNG_CALIBRATION.set( - SEED_CALIBRATION_FRAMES * FRAME_DURATION, - CONTINUE_SCREEN_ADJUSTMENT, - ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + calibrations.seed_offset * FRLG_FRAME_DURATION, + calibrations.csf_offset, + calibrations.ingame_offset, search_hits - ); - bool finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 1); - if (finished || (MAX_RARE_CANDIES == 0)){ - env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } - continue; - } + ); + bool finished = update_history( + env.console, advance_history, calibration_history, MAX_HISTORY_LENGTH, + calibrations, search_hits, 1 + ); for (uint64_t i=0; i( console, context, - [TARGET, SEED_BUTTON, BLACKOUT_BUTTON, SEED_DELAY, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE, console_type](ProControllerContext& context) { - perform_blind_sequence(context, TARGET, SEED_BUTTON, BLACKOUT_BUTTON, SEED_DELAY, CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, SAFARI_ZONE, console_type); + [target, seed_button, extra_button, timings, safari_zone, console_type](ProControllerContext& context) { + perform_blind_sequence(context, target, seed_button, extra_button, timings, safari_zone, console_type); }, { update_detector, user_selection_detector }, - 1000ms + 5000ms ); switch (ret){ @@ -249,10 +246,10 @@ void reset_and_perform_blind_sequence( } #if 0 -void reset_and_detect_copyright_text(ConsoleHandle& console, ProControllerContext& context, uint8_t PROFILE){ +void reset_and_detect_copyright_text(ConsoleHandle& console, ProControllerContext& context, uint8_t profile){ go_home(console, context); close_game_from_home(console, context); - rng_start_game_and_return_home(console, context, uint8_t(0), PROFILE); + rng_start_game_and_return_home(console, context, uint8_t(0), profile); pbf_wait(context, 200ms); // add an extra delay to try to ensure the game doesn't fail to launch go_home(console, context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h index 4d488daff3..693066f8a6 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h @@ -18,15 +18,12 @@ namespace PokemonFRLG{ void reset_and_perform_blind_sequence( ConsoleHandle& console, ProControllerContext& context, - PokemonFRLG_RngTarget TARGET, - SeedButton SEED_BUTTON, - BlackoutButton BLACKOUT_BUTTON, - uint64_t SEED_DELAY, - uint64_t CONTINUE_SCREEN_DELAY, - uint64_t TEACHY_DELAY, - uint64_t INGAME_DELAY, - bool SAFARI_ZONE, - uint8_t PROFILE + PokemonFRLG_RngTarget target, + const SeedButton& seed_button, + const BlackoutButton& extra_button, + const RngTimings& timings, + bool safari_zone, + uint8_t profile ); #if 0 diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp index dfcb2b5111..17f7822878 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -4,8 +4,12 @@ * */ +#include +#include #include +#include #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "PokemonFRLG_BlindNavigation.h" #include "PokemonFRLG_RngCalibration.h" namespace PokemonAutomation{ @@ -78,33 +82,60 @@ int16_t seed_position_in_list(uint16_t seed, std::vector list){ return -1; } -AdvNature string_to_nature(std::string nature_string){ - if (nature_string == "Hardy") return AdvNature::Hardy; - if (nature_string == "Lonely") return AdvNature::Lonely; - if (nature_string == "Brave") return AdvNature::Brave; - if (nature_string == "Adamant") return AdvNature::Adamant; - if (nature_string == "Naughty") return AdvNature::Naughty; - if (nature_string == "Bold") return AdvNature::Bold; - if (nature_string == "Docile") return AdvNature::Docile; - if (nature_string == "Relaxed") return AdvNature::Relaxed; - if (nature_string == "Impish") return AdvNature::Impish; - if (nature_string == "Lax") return AdvNature::Lax; - if (nature_string == "Timid") return AdvNature::Timid; - if (nature_string == "Hasty") return AdvNature::Hasty; - if (nature_string == "Serious") return AdvNature::Serious; - if (nature_string == "Jolly") return AdvNature::Jolly; - if (nature_string == "Naive") return AdvNature::Naive; - if (nature_string == "Modest") return AdvNature::Modest; - if (nature_string == "Mild") return AdvNature::Mild; - if (nature_string == "Quiet") return AdvNature::Quiet; - if (nature_string == "Bashful") return AdvNature::Bashful; - if (nature_string == "Rash") return AdvNature::Rash; - if (nature_string == "Calm") return AdvNature::Calm; - if (nature_string == "Gentle") return AdvNature::Gentle; - if (nature_string == "Sassy") return AdvNature::Sassy; - if (nature_string == "Careful") return AdvNature::Careful; - if (nature_string == "Quirky") return AdvNature::Quirky; - return AdvNature::Any; + +RngTimings prepare_timings( + ConsoleHandle& console, + PokemonFRLG_RngTarget target, + const uint64_t& SEED_DELAY, + const uint64_t& CONTINUE_SCREEN_FRAMES, + const uint64_t& INGAME_ADVANCES, + const bool& USE_TEACHY_TV, + const RngCalibrations& calibrations, + const int64_t& FIXED_SEED_OFFSET, + const int64_t& FIXED_ADVANCES_OFFSET +){ + double modified_ingame_advances = INGAME_ADVANCES + calibrations.ingame_offset + FIXED_ADVANCES_OFFSET; + if (modified_ingame_advances < 0) { + OperationFailedException::fire( + ErrorReport::NO_ERROR_REPORT, + "In-game advances cannot be negative. Check your in-game advances and calibration.", + console + ); + } + + bool safari_zone = ( + target == PokemonFRLG_RngTarget::safarizonecenter || + target == PokemonFRLG_RngTarget::safarizoneeast || + target == PokemonFRLG_RngTarget::safarizonenorth || + target == PokemonFRLG_RngTarget::safarizonewest || + target == PokemonFRLG_RngTarget::safarizonesurf || + target == PokemonFRLG_RngTarget::safarizonefish + ); + + uint64_t TEACHY_ADVANCES = 0; + + uint64_t TEACHY_TV_BUFFER = safari_zone ? 20000 : 12000; // Safari zone targets need extra time to walk to the right position + + bool should_use_teachy_tv = USE_TEACHY_TV && (modified_ingame_advances > TEACHY_TV_BUFFER); // don't use Teachy TV for short in-game advance targets + if (should_use_teachy_tv) { + TEACHY_ADVANCES = uint64_t((int)std::floor((modified_ingame_advances - TEACHY_TV_BUFFER + 7500) / 313) * 313); + } + + RngTimings timings; + timings.seed_delay = uint64_t(SEED_DELAY + calibrations.seed_offset + FIXED_SEED_OFFSET); + timings.csf_delay = uint64_t((CONTINUE_SCREEN_FRAMES + calibrations.csf_offset) * FRLG_FRAME_DURATION); + timings.teachy_delay = uint64_t(TEACHY_ADVANCES * FRLG_FRAME_DURATION / 313); + timings.ingame_delay = uint64_t((modified_ingame_advances - TEACHY_ADVANCES) * FRLG_FRAME_DURATION / 2) - (should_use_teachy_tv ? 14067 : 0); + + console.log("Seed delay: " + std::to_string(timings.seed_delay) + "ms"); + console.log("Continue Screen delay: " + std::to_string(timings.csf_delay) + "ms"); + console.log("Teachy TV delay: " + std::to_string(timings.teachy_delay) + "ms"); + console.log("In-game delay: " + std::to_string(timings.ingame_delay) + "ms"); + console.log("Total time: " + std::to_string(timings.seed_delay + timings.csf_delay + timings.teachy_delay + timings.ingame_delay) + "ms"); + + check_timings(console, target, timings, safari_zone); + + return timings; } @@ -161,6 +192,46 @@ std::vector get_wild_search_results( return search_hits; } +std::vector> get_egg_search_results( + ConsoleHandle& console, + AdvRngEggSearcher& searcher, + AdvRngFilters& filters, + const std::vector& HELD_SEED_VALUES, + const std::vector& PICKUP_SEED_VALUES, + const uint64_t& HELD_ADVANCES, + const uint64_t& held_advances_radius, + const uint64_t& PICKUP_ADVANCES, + const uint64_t& pickup_advances_radius, + AdvIVs& parentA, + AdvIVs& parentB, + AdvEggCompatibility compatibility, + int16_t gender_threshold, + uint16_t tid_xor_sid +){ + std::vector> search_hits; + for (int i=0; i<4; i++){ + uint64_t held_adv_radius = held_advances_radius * (uint64_t(1) << i); + uint64_t held_min_adv = HELD_ADVANCES - std::min(uint64_t(HELD_ADVANCES), held_adv_radius); + uint64_t held_max_adv = HELD_ADVANCES + held_adv_radius; + uint64_t pickup_adv_radius = pickup_advances_radius * (uint64_t(1) << i); + uint64_t pickup_min_adv = PICKUP_ADVANCES - std::min(uint64_t(PICKUP_ADVANCES), pickup_adv_radius); + uint64_t pickup_max_adv = PICKUP_ADVANCES + pickup_adv_radius; + search_hits = searcher.search( + filters, HELD_SEED_VALUES, + held_min_adv, held_max_adv, + PICKUP_SEED_VALUES, pickup_min_adv, pickup_max_adv, + parentA, parentB, compatibility, + gender_threshold, 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 && @@ -185,7 +256,8 @@ void update_filters( AdvObservedPokemon& pokemon, const StatReads& stats, const EVs& evyield, - const BaseStats& BASE_STATS + const BaseStats& BASE_STATS, + AdvRngMethod method ){ level_up_observed_pokemon(pokemon, stats, evyield); @@ -196,7 +268,7 @@ void update_filters( return; } - AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS); + AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS, method); if (!ranges_are_valid(new_filters.ivs)){ IvRanges new_stat_ivs = calc_iv_ranges(BASE_STATS, pokemon.level.back(), pokemon.evs.back(), pokemon.stats.back(), nature_to_adjustment(pokemon.nature)); @@ -220,18 +292,37 @@ void update_filters( } + +uint64_t get_advances_radius( + ConsoleHandle& console, + const RngCalibrationHistory& calibration_history, + const uint64_t& initial_radius +){ + uint64_t advances_radius = initial_radius; + for (size_t i=0; i& SEED_VALUES, - const int16_t& SEED_POSITION + const RngCalibrationHistory& history, + const std::vector& seed_values, + const int16_t& seed_position ){ double sum = 0; uint16_t len = 0; - for (size_t i=0; i& seed_values, + const int16_t& seed_position, + const uint64_t& advances +){ + RngCalibrations calibrations; + + if (history.results.size() > 0){ + calibrations.seed_offset = get_seed_calibration_frames(history, seed_values, seed_position); + calibrations.ingame_offset = get_advances_calibration_frames(history, advances); + } + + if (history.results.size() > 0){ + AdvRngState prev_hit = history.results.back(); + double prev_csf_offset = history.calibrations.back().csf_offset; + int64_t prev_advance_miss = int64_t(prev_hit.advance) - int64_t(advances); + if (prev_advance_miss != 0 && std::abs(prev_advance_miss) < 2){ + console.log("Attempting to correct for off-by-one miss by modifying continue screen frames."); + if (prev_advance_miss > 0){ + calibrations.csf_offset = prev_csf_offset - 0.5; + }else{ + calibrations.csf_offset = prev_csf_offset + 0.5; + } + calibrations.csf_offset = fmod(calibrations.csf_offset, 2); + double csf_diff = calibrations.csf_offset - prev_csf_offset; + calibrations.ingame_offset -= csf_diff; + } + } + + console.log("Seed calibration (frames): " + std::to_string(calibrations.seed_offset)); + console.log("Continue screen adjustment (frames): " + std::to_string(calibrations.csf_offset)); + console.log("Advance calibration (frames / 2): " + std::to_string(calibrations.ingame_offset)); + return calibrations; +} + bool update_history( ConsoleHandle& console, - RngAdvanceHistory& ADVANCE_HISTORY, - RngCalibrationHistory& CALIBRATION_HISTORY, - const uint16_t& MAX_HISTORY_LENGTH, - const double& SEED_CALIBRATION_FRAMES, - const double& ADVANCES_CALIBRATION, - const double& CONTINUE_SCREEN_ADJUSTMENT, + RngAdvanceHistory& advance_history, + RngCalibrationHistory& calibration_history, + const uint16_t& max_history_length, + const RngCalibrations calibrations, const std::vector& search_hits, uint32_t max_advance_possibilities, uint32_t advance_radius, @@ -297,18 +423,14 @@ bool update_history( if (search_hits.size() == 1){ console.log("Updating calibrations..."); - CALIBRATION_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); - CALIBRATION_HISTORY.advance_calibrations.emplace_back(ADVANCES_CALIBRATION); - CALIBRATION_HISTORY.continue_screen_adjustments.emplace_back(CONTINUE_SCREEN_ADJUSTMENT); - CALIBRATION_HISTORY.results.emplace_back(search_hits[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()); - CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); - CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); + calibration_history.calibrations.emplace_back(calibrations); + calibration_history.results.emplace_back(search_hits[0]); + if (calibration_history.results.size() > max_history_length){ + calibration_history.calibrations.erase(calibration_history.calibrations.begin()); + calibration_history.results.erase(calibration_history.results.begin()); } - ADVANCE_HISTORY.results.clear(); - ADVANCE_HISTORY.seed_calibrations.clear(); + advance_history.results.clear(); + advance_history.seed_calibrations.clear(); return true; } @@ -325,8 +447,8 @@ bool update_history( iter = std::unique(advances.begin(), advances.begin() + advances.size()); advances.resize(std::distance(advances.begin(), iter)); - ADVANCE_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); - ADVANCE_HISTORY.results.emplace_back(hits); + advance_history.seed_calibrations.emplace_back(calibrations.seed_offset); + advance_history.results.emplace_back(hits); // check advance history for repeated values std::vector counts; @@ -335,7 +457,7 @@ bool update_history( bool tie = false; for (uint64_t& adv : advances){ uint64_t count = 0; - for (auto& res : ADVANCE_HISTORY.results){ + for (auto& res : advance_history.results){ for (auto& state : res){ if (std::abs(int64_t(state.advance) - int64_t(adv)) <= advance_radius){ count++; @@ -359,10 +481,9 @@ bool update_history( // add the closest possibility to the advances mode for each attempt to the calibration history - console.log("Inferred hits from previous " + std::to_string(ADVANCE_HISTORY.results.size()) + " attempts: "); - for (size_t i=0; i MAX_HISTORY_LENGTH){ - CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); - CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); - CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); - CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); + while (calibration_history.results.size() > max_history_length){ + calibration_history.calibrations.erase(calibration_history.calibrations.begin()); + calibration_history.results.erase(calibration_history.results.begin()); } - ADVANCE_HISTORY.results.clear(); - ADVANCE_HISTORY.seed_calibrations.clear(); + advance_history.results.clear(); + advance_history.seed_calibrations.clear(); return true; } -bool are_indistinguishable(AdvPokemonResult res1, AdvPokemonResult res2){ +bool are_indistinguishable(AdvPokemonResult res1, AdvPokemonResult res2, const int16_t& gender_threshold){ return ( res1.nature == res2.nature && - res1.gender == res2.gender && + gender_from_gender_value(res1.gender, gender_threshold) == gender_from_gender_value(res2.gender, gender_threshold) && // res1.ability == res2.ability && res1.ivs.hp == res2.ivs.hp && res1.ivs.attack == res2.ivs.attack && @@ -409,12 +526,12 @@ bool are_indistinguishable(AdvPokemonResult res1, AdvPokemonResult res2){ ); } -bool are_indistinguishable(AdvWildPokemonResult res1, AdvWildPokemonResult res2){ +bool are_indistinguishable(AdvWildPokemonResult res1, AdvWildPokemonResult res2, const int16_t& gender_threshold){ return ( res1.species == res2.species && res1.level == res2.level && res1.nature == res2.nature && - res1.gender == res2.gender && + gender_from_gender_value(res1.gender, gender_threshold) == gender_from_gender_value(res2.gender, gender_threshold) && // res1.ability == res2.ability && res1.ivs.hp == res2.ivs.hp && res1.ivs.attack == res2.ivs.attack && @@ -425,7 +542,7 @@ bool are_indistinguishable(AdvWildPokemonResult res1, AdvWildPokemonResult res2) ); } -bool all_indistinguishable(std::vector hits, AdvRngSearcher& searcher){ +bool all_indistinguishable(const std::vector& hits, AdvRngSearcher& searcher, const int16_t& gender_threshold){ if (hits.size() < 2){ return true; } @@ -434,14 +551,14 @@ bool all_indistinguishable(std::vector hits, AdvRngSearcher& search for (size_t i=1; i hits, AdvRngWildSearcher& searcher, bool super_rod){ +bool all_indistinguishable(const std::vector& hits, AdvRngWildSearcher& searcher, const int16_t& gender_threshold, bool super_rod){ if (hits.size() < 2){ return true; } @@ -451,7 +568,46 @@ bool all_indistinguishable(std::vector hits, AdvRngWildSearcher& se for (size_t i=1; i>& hits, + AdvRngEggSearcher& searcher, + const int16_t& gender_threshold, + AdvIVs& parentA_ivs, AdvIVs& parentB_ivs +){ + if (hits.size() < 2){ + return true; + } + searcher.held_state = hits[0].first; + searcher.pickup_state = hits[0].second; + + AdvPokemonResult first_result = searcher.generate_pokemon(parentA_ivs, parentB_ivs); + + for (size_t i=1; i hits){ + if (hits.size() < 2){ + return true; + } + uint16_t first_seed = hits[0].seed; + + for (size_t i=1; i -#include #include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" #include "Pokemon/Pokemon_AdvRng.h" +#include "PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -19,15 +19,23 @@ namespace PokemonFRLG{ using namespace Pokemon; +static const double FRLG_FRAMERATE = 59.999977; // FPS +static const double FRLG_FRAME_DURATION = 1000.0 / FRLG_FRAMERATE; + + struct RngAdvanceHistory{ std::vector seed_calibrations; std::vector> results; }; +struct RngCalibrations{ + double seed_offset; + double csf_offset; + double ingame_offset; +}; + struct RngCalibrationHistory{ - std::vector seed_calibrations; - std::vector advance_calibrations; - std::vector continue_screen_adjustments; + std::vector calibrations; std::vector results; }; @@ -37,7 +45,18 @@ uint16_t parse_seed(ConsoleHandle& console, std::string seed_string); std::vector parse_seed_list(ConsoleHandle& console, std::string seed_list_string); int16_t seed_position_in_list(uint16_t seed, std::vector list); -Pokemon::AdvNature string_to_nature(std::string nature_string); + +RngTimings prepare_timings( + ConsoleHandle& console, + PokemonFRLG_RngTarget target, + const uint64_t& SEED_DELAY, + const uint64_t& CONTINUE_SCREEN_FRAMES, + const uint64_t& INGAME_ADVANCES, + const bool& USE_TEACHY_TV, + const RngCalibrations& calibrations, + const int64_t& FIXED_SEED_OFFSET, + const int64_t& FIXED_ADVANCES_OFFSET +); // get search hits for any of the provided seed values and advances range std::vector get_search_results( @@ -63,43 +82,82 @@ std::vector get_wild_search_results( uint16_t tid_xor_sid = 0 ); +std::vector> get_egg_search_results( + ConsoleHandle& console, + AdvRngEggSearcher& searcher, + AdvRngFilters& filters, + const std::vector& HELD_SEED_VALUES, + const std::vector& PICKUP_SEED_VALUES, + const uint64_t& HELD_ADVANCES, + const uint64_t& held_advances_radius, + const uint64_t& PICKUP_ADVANCES, + const uint64_t& pickup_advances_radius, + AdvIVs& parentA, + AdvIVs& parentB, + AdvEggCompatibility compatibility, + int16_t gender_threshold, + uint16_t tid_xor_sid +); + // update IV ranges for RNG search filters with new stats/EVs after leveling up void update_filters( AdvRngFilters& filters, AdvObservedPokemon& pokemon, const StatReads& stats, const EVs& evyield, - const BaseStats& BASE_STATS + const BaseStats& BASE_STATS, + AdvRngMethod method = AdvRngMethod::Method1 +); + +// get the search radius across advances based on the length of the RNG calibration history +uint64_t get_advances_radius( + ConsoleHandle& console, + const RngCalibrationHistory& calibration_history, + const uint64_t& initial_radius ); // get seed calibration based on average offset in the RNG calibration history double get_seed_calibration_frames( - const RngCalibrationHistory& HISTORY, - const std::vector& SEED_VALUES, - const int16_t& SEED_POSITION + const RngCalibrationHistory& history, + const std::vector& seed_values, + const int16_t& seed_position ); // get advances calibration based on average offset in the RNG calibration history -double get_advances_calibration_frames(const RngCalibrationHistory& CALIBRATION_HISTORY, const uint64_t& ADVANCES); +double get_advances_calibration_frames(const RngCalibrationHistory& calibration_history, const uint64_t& advances); + +// get RngCalibrations from the RNG calibration history +RngCalibrations get_calibrations( + ConsoleHandle& console, + const RngCalibrationHistory& history, + const std::vector& seed_values, + const int16_t& seed_position, + const uint64_t& advances +); // infer hit seeds/advances, update the calibration history, and return whether or not the search is finished bool update_history( ConsoleHandle& console, - RngAdvanceHistory& ADVANCE_HISTORY, - RngCalibrationHistory& CALIBRATION_HISTORY, - const uint16_t& MAX_HISTORY_LENGTH, - const double& SEED_CALIBRATION_FRAMES, - const double& ADVANCES_CALIBRATION, - const double& CONTINUE_SCREEN_ADJUSTMENT, + RngAdvanceHistory& advance_history, + RngCalibrationHistory& calibration_history, + const uint16_t& max_history_length, + const RngCalibrations calibrations, 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); +bool all_indistinguishable(const std::vector& hits, AdvRngSearcher& searcher, const int16_t& gender_threshold); +bool all_indistinguishable(const std::vector& hits, AdvRngWildSearcher& searcher, const int16_t& gender_threshold, bool super_rod); +bool all_indistinguishable( + const std::vector>& hits, + AdvRngEggSearcher& searcher, + const int16_t& gender_threshold, + AdvIVs& parentA_ivs, AdvIVs& parentB_ivs +); +bool same_seeds(std::vector hits); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index c69e472ea3..3feee9b7a9 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -68,72 +68,7 @@ std::string RngFilterDisplay::get_range_string(const IvRange& range){ } return std::to_string(range.low) + " - " + std::to_string(range.high); } -std::string RngFilterDisplay::get_gender_string(const AdvGender& gender){ - switch (gender){ - case AdvGender::Male: - return "Male"; - case AdvGender::Female: - return "Female"; - default: - return "Any"; - } -} -std::string RngFilterDisplay::get_nature_string(const AdvNature& nature){ - switch (nature){ - case AdvNature::Hardy: - return "Hardy"; - case AdvNature::Lonely: - return "Lonely"; - case AdvNature::Brave: - return "Brave"; - case AdvNature::Adamant: - return "Adamant"; - case AdvNature::Naughty: - return "Naughty"; - case AdvNature::Bold: - return "Bold"; - case AdvNature::Docile: - return "Docile"; - case AdvNature::Relaxed: - return "Relaxed"; - case AdvNature::Impish: - return "Impish"; - case AdvNature::Lax: - return "Lax"; - case AdvNature::Timid: - return "Timid"; - case AdvNature::Hasty: - return "Hasty"; - case AdvNature::Serious: - return "Serious"; - case AdvNature::Jolly: - return "Jolly"; - case AdvNature::Naive: - return "Naive"; - case AdvNature::Modest: - return "Modest"; - case AdvNature::Mild: - return "Mild"; - case AdvNature::Quiet: - return "Quiet"; - case AdvNature::Bashful: - return "Bashful"; - case AdvNature::Rash: - return "Rash"; - case AdvNature::Calm: - return "Calm"; - case AdvNature::Gentle: - return "Gentle"; - case AdvNature::Sassy: - return "Sassy"; - case AdvNature::Careful: - return "Careful"; - case AdvNature::Quirky: - return "Quirky"; - default: - return "Any"; - } -} + void RngFilterDisplay::set(const AdvRngFilters& filter){ hp.set(get_range_string(filter.ivs.hp)); atk.set(get_range_string(filter.ivs.attack)); @@ -141,8 +76,8 @@ void RngFilterDisplay::set(const AdvRngFilters& filter){ spatk.set(get_range_string(filter.ivs.spatk)); spdef.set(get_range_string(filter.ivs.spdef)); speed.set(get_range_string(filter.ivs.speed)); - gender.set(get_gender_string(filter.gender)); - nature.set(get_nature_string(filter.nature)); + gender.set(gender_to_string(filter.gender)); + nature.set(nature_to_string(filter.nature)); } void RngFilterDisplay::reset(){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index b796534c66..ce7a738a67 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -46,8 +46,6 @@ class RngFilterDisplay : public GroupOption{ private: static std::string get_range_string(const IvRange& range); - static std::string get_gender_string(const AdvGender& gender); - static std::string get_nature_string(const AdvNature& nature); public: StringOption hp; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp index 4fd3f8f1cc..abca148165 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp @@ -4,8 +4,6 @@ * */ -#include -#include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" @@ -13,10 +11,9 @@ #include "Pokemon/Pokemon_Strings.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" -#include "PokemonFRLG_BlindNavigation.h" #include "PokemonFRLG_RngNavigation.h" +#include "PokemonFRLG_RngCalibration.h" #include "PokemonFRLG_HardReset.h" -#include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_RngHelper.h" namespace PokemonAutomation{ @@ -71,6 +68,8 @@ RngHelper::RngHelper() {PokemonFRLG_RngTarget::gamecornerpinsir, "gamecornerpinsir", "Game Corner Pinsir"}, {PokemonFRLG_RngTarget::gamecornerporygon, "gamecornerporygon", "Game Corner Porygon"}, {PokemonFRLG_RngTarget::togepi, "togepi", "Togepi"}, + {PokemonFRLG_RngTarget::eggheld, "eggheld", "Daycare Egg Held Frame"}, + {PokemonFRLG_RngTarget::eggpickup, "eggpickup", "Daycare Egg Pickup"}, {PokemonFRLG_RngTarget::staticencounter, "staticencounter", "Static Overworld Encounters"}, {PokemonFRLG_RngTarget::snorlax, "snorlax", "Snorlax"}, {PokemonFRLG_RngTarget::mewtwo, "mewtwo", "Mewtwo"}, @@ -221,9 +220,6 @@ void RngHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex bool shiny_found = false; - double FRAMERATE = 59.999977; // FPS - double FRAME_DURATION = 1000 / FRAMERATE; - bool sweet_scent = ( TARGET == PokemonFRLG_RngTarget::sweetscent || TARGET == PokemonFRLG_RngTarget::rocksmash || @@ -234,54 +230,36 @@ void RngHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex TARGET == PokemonFRLG_RngTarget::safarizonesurf ); - const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate + static const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate const int64_t FIXED_ADVANCES_OFFSET = sweet_scent ? -352 : 160; // frames, approximate - while (!shiny_found){ - // prepare timings - uint64_t TOTAL_SEED_DELAY = SEED_DELAY + SEED_CALIBRATION + FIXED_SEED_OFFSET; + const bool SAFARI_ZONE = (TARGET == PokemonFRLG_RngTarget::safarizonecenter + || TARGET == PokemonFRLG_RngTarget::safarizoneeast + || TARGET == PokemonFRLG_RngTarget::safarizonenorth + || TARGET == PokemonFRLG_RngTarget::safarizonewest + || TARGET == PokemonFRLG_RngTarget::safarizonesurf + || TARGET == PokemonFRLG_RngTarget::safarizonefish + ); - double MODIFIED_INGAME_ADVANCES = INGAME_ADVANCES + INGAME_CALIBRATION + FIXED_ADVANCES_OFFSET; - if (MODIFIED_INGAME_ADVANCES < 0) { - OperationFailedException::fire( - ErrorReport::NO_ERROR_REPORT, - "In-game advances cannot be negative. Check your in-game advances and calibration.", - env.console - ); - } - uint64_t TEACHY_ADVANCES = 0; + const RngCalibrations CALIBRATIONS = { + static_cast(SEED_CALIBRATION), + CONTINUE_SCREEN_CALIBRATION, + INGAME_CALIBRATION + }; - const bool SAFARI_ZONE = (TARGET == PokemonFRLG_RngTarget::safarizonecenter - || TARGET == PokemonFRLG_RngTarget::safarizoneeast - || TARGET == PokemonFRLG_RngTarget::safarizonenorth - || TARGET == PokemonFRLG_RngTarget::safarizonewest - || TARGET == PokemonFRLG_RngTarget::safarizonesurf - || TARGET == PokemonFRLG_RngTarget::safarizonefish + while (!shiny_found){ + RngTimings timings = prepare_timings( + env.console, TARGET, + SEED_DELAY, CONTINUE_SCREEN_FRAMES, INGAME_ADVANCES, + USE_TEACHY_TV, CALIBRATIONS, + FIXED_SEED_OFFSET, FIXED_ADVANCES_OFFSET ); - - uint64_t TEACHY_TV_BUFFER = SAFARI_ZONE ? 20000 : 10000; // Safari zone targets need extra time to walk to the right position - - bool should_use_teachy_tv = USE_TEACHY_TV && (MODIFIED_INGAME_ADVANCES > TEACHY_TV_BUFFER); // don't use Teachy TV for short in-game advance targets - if (should_use_teachy_tv) { - TEACHY_ADVANCES = uint64_t((int)std::floor((MODIFIED_INGAME_ADVANCES - TEACHY_TV_BUFFER + 7500) / 313) * 313); - } - - const uint64_t CONTINUE_SCREEN_DELAY = uint64_t((CONTINUE_SCREEN_FRAMES + CONTINUE_SCREEN_CALIBRATION) * FRAME_DURATION); - const uint64_t TEACHY_DELAY = uint64_t(TEACHY_ADVANCES * FRAME_DURATION / 313); - const uint64_t INGAME_DELAY = uint64_t((MODIFIED_INGAME_ADVANCES - TEACHY_ADVANCES) * FRAME_DURATION / 2) - (should_use_teachy_tv ? 14067 : 0); - env.log("Continue Screen delay: " + std::to_string(CONTINUE_SCREEN_DELAY) + "ms"); - env.log("In-game delay: " + std::to_string(INGAME_DELAY) + "ms"); - env.log("Teachy TV delay: " + std::to_string(TEACHY_DELAY) + "ms"); - env.log("Total time: " + std::to_string(TOTAL_SEED_DELAY + CONTINUE_SCREEN_DELAY + INGAME_DELAY + TEACHY_DELAY) + "ms"); - - check_timings(env.console, TARGET, TOTAL_SEED_DELAY, CONTINUE_SCREEN_DELAY, INGAME_DELAY, SAFARI_ZONE); - // handle the blind part reset_and_perform_blind_sequence( env.console, context, TARGET, - SEED_BUTTON, EXTRA_BUTTON, TOTAL_SEED_DELAY, - CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, + SEED_BUTTON, EXTRA_BUTTON, + timings, SAFARI_ZONE, PROFILE ); env.log("Blind button presses complete."); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp index f80b9339d7..045dde5830 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -4,24 +4,40 @@ #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" #include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.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_StatsReader.h" +#include "PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h" #include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_BlindNavigation.h" +#include "PokemonFRLG_RngCalibration.h" #include "PokemonFRLG_RngNavigation.h" namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -void go_to_starter_summary(ConsoleHandle& console, ProControllerContext& context){ - // Navigate to summary (1st party slot) - open_start_menu(console, context); // Don't have a Pokedex yet, so arrow will already by over POKeMON - +using namespace Pokemon; + +void go_to_summary(ConsoleHandle& console, ProControllerContext& context, int from_last, StartMenuContext menu_context){ + // navigate to the specified party slot + open_party_menu_from_overworld(console, context, menu_context); + for (int i=0; i<(2+from_last); i++){ + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + } + + // open summary SummaryWatcher summary_open(COLOR_RED); context.wait_for_all_requests(); int ret = run_until( @@ -36,53 +52,316 @@ void go_to_starter_summary(ConsoleHandle& console, ProControllerContext& context ); if (ret < 0){ - console.log("go_to_starter_summary(): failed to open the summary."); - }else{ + console.log("go_to_summary(): failed to open the summary."); + } else { console.log("Summary opened."); } } -bool shiny_check_starter_summary(ConsoleHandle& console, ProControllerContext& context){ - go_to_starter_summary(console, context); +bool shiny_check_summary(ConsoleHandle& console, ProControllerContext& context, int from_last, StartMenuContext menu_context){ + go_to_summary(console, context, from_last, menu_context); context.wait_for_all_requests(); VideoSnapshot screen = console.video().snapshot(); ShinySymbolDetector shiny_checker(COLOR_YELLOW); return shiny_checker.read(console.logger(), screen); } -void go_to_last_summary(ConsoleHandle& console, ProControllerContext& context){ - // navigate to the last occupied party slot - open_party_menu_from_overworld(console, context); - pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); - pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); +AdvObservedPokemon read_summary( + ConsoleHandle& console, ProControllerContext& context, + const Language& language, std::set species +){ + // read stats + PokemonFRLG_Stats stats; + StatsReader reader(COLOR_RED); - // open summary - SummaryWatcher summary_open(COLOR_RED); + console.log("Reading Page 1 (Name, Level, Nature, Gender)..."); + VideoSnapshot screen1 = console.video().snapshot(); + reader.read_page1(console.logger(), language, screen1, stats, species); + + SummaryPage2Watcher page_two(COLOR_RED); + context.wait_for_all_requests(); + int ret2 = run_until( + 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.", + console + ); + } + + console.log("Reading Page 2 (Stats)..."); + VideoSnapshot screen2 = console.video().snapshot(); + reader.read_page2(console.logger(), language, 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; +} + + +int auto_catch( + ConsoleHandle& console, + ProControllerContext& context, + const uint64_t& max_ball_throws, + bool safari_zone +){ + for (uint64_t i=0; i<=max_ball_throws; i++){ + int count = 0; + while(true){ + if (count >= 10){ + console.log("auto_catch(): failed to detect battle menu"); + return -1; + } + 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( + 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: + console.log("Battle menu detected"); + break; + case 1: + console.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: + console.log("Dex registration detected. Exiting battle..."); + pbf_mash_button(context, BUTTON_B, 5000ms); + return static_cast(i); + case 3: + console.log("Black screen detected. Battle exited."); + return static_cast(i); + default: + console.log("No recognized state. Try checking if in the overworld..."); + StartMenuWatcher start_menu; + context.wait_for_all_requests(); + start_ret = run_until( + 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){ + console.log("auto_catch(): no recognized state after 30 seconds."); + return true; + } + console.log("Overworld detected."); + pbf_mash_button(context, BUTTON_B, 500ms); + context.wait_for_all_requests(); + return static_cast(i); + } + + break; + } + + if (i == max_ball_throws) { break; } + + if (!safari_zone){ + // 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( + console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + } + }, + { bag_open } + ); + if (ret2 < 0){ + console.log("auto_catch(): failed to open bag."); + return -1; + } + + 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); + } + + console.log("auto_catch(): ran out of balls."); + return 0; +} + +bool use_rare_candy( + ConsoleHandle& console, + ProControllerContext& context, + const Language& language, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& base_stats, + AdvRngMethod method, + bool safari_zone, + bool first +){ + // navigate to the bag (only needed for the first use) + if (first){ + open_bag_from_overworld(console, context, safari_zone ? PokemonFRLG::StartMenuContext::SAFARI_ZONE : PokemonFRLG::StartMenuContext::STANDARD); + // 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( console, context, [](ProControllerContext& context) { - pbf_press_button(context, BUTTON_A, 200ms, 1000ms); - for (int i=0; i<3; i++){ + for (int i=0; i<5; i++){ pbf_press_button(context, BUTTON_A, 200ms, 2800ms); } }, - { summary_open } + { party_menu } ); - if (ret < 0){ - console.log("go_to_last_summary(): failed to open the summary."); - } else { - console.log("Summary opened."); + console.log("use_rare_candy(): failed to detect party menu."); + return true; + } + + // select the last party slot (unknown how full the party is, so we can't detect a particular slot) + // only needed on the first use + if (first){ + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); } -} -bool shiny_check_summary(ConsoleHandle& console, ProControllerContext& context){ - go_to_last_summary(console, context); + // watch for level up stats + PartyLevelUpWatcher level_up(COLOR_RED, PartyLevelUpDialog::stats, language); context.wait_for_all_requests(); + int ret2 = run_until( + console, context, + [](ProControllerContext& context) { + for (int i=0; i<30; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + } + }, + { level_up } + ); + if (ret2 < 0){ + console.log("use_rare_candy(): failed to detect level-up stats."); + return true; + } + + PartyLevelUpReader reader(COLOR_RED); + VideoOverlaySet overlays(console.overlay()); + reader.make_overlays(overlays); + + console.log("Reading stats..."); VideoSnapshot screen = console.video().snapshot(); - ShinySymbolDetector shiny_checker(COLOR_YELLOW); - return shiny_checker.read(console.logger(), screen); + StatReads statreads = reader.read_stats(console.logger(), screen); + + update_filters(filters, pokemon, statreads, {}, base_stats, method); + // 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){ + console.log("use_rare_candy(): failed to return to bag menu in 5 attempts."); + return true; + } + BagWatcher bag_menu(COLOR_RED); + PartyMoveLearnWatcher move_learn(COLOR_RED); + context.wait_for_all_requests(); + int ret3 = run_until( + 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: + console.log("Returned to bag."); + return false; + case 1: + console.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: + console.log("use_rare_candy(): failed to return to bag menu."); + return true; + } + } } void hatch_togepi_egg(ConsoleHandle& console, ProControllerContext& context){ @@ -118,10 +397,41 @@ void hatch_togepi_egg(ConsoleHandle& console, ProControllerContext& context){ context.wait_for_all_requests(); } +void hatch_daycare_egg(ConsoleHandle& console, ProControllerContext& context){ + // assumes the player is already on a bike + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 500ms); + pbf_move_left_joystick(context, {+1, 0}, 60ms, 440ms); + WhiteDialogWatcher egg_dialog(COLOR_RED); + context.wait_for_all_requests(); + WallClock deadline = current_time() + 2700s; + console.log("Hatching Day Care egg..."); + int ret = run_until( + console, context, + [deadline](ProControllerContext& context) { + // cycle back and forth + while (current_time() < deadline){ + pbf_move_left_joystick(context, {+1, 0}, 1700ms, 30ms); + pbf_move_left_joystick(context, {-1, 0}, 1700ms, 30ms); + } + }, + { egg_dialog } + ); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Daycare Egg: failed to hatch egg within 45 minutes. Check your in-game setup.", + console + ); + } + + // watch hatching animation and decline nickname + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); +} + int watch_for_shiny_encounter(ConsoleHandle& console, ProControllerContext& context){ BlackScreenWatcher battle_entered(COLOR_RED); context.wait_for_all_requests(); - console.log("Wild encounter started."); int ret = wait_until( console, context, 10000ms, {battle_entered} @@ -134,14 +444,20 @@ int watch_for_shiny_encounter(ConsoleHandle& console, ProControllerContext& cont // ); return -1; } + console.log("Wild encounter started."); bool encounter_shiny = handle_encounter(console, context, false); return encounter_shiny ? 1 : 0; } bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, PokemonFRLG_RngTarget TARGET){ switch (TARGET){ + case PokemonFRLG_RngTarget::eggheld: + return false; + case PokemonFRLG_RngTarget::eggpickup: + hatch_daycare_egg(console, context); + return shiny_check_summary(console, context); case PokemonFRLG_RngTarget::starters: - return shiny_check_starter_summary(console, context); + return shiny_check_summary(console, context, -2, StartMenuContext::NO_DEX); case PokemonFRLG_RngTarget::togepi: hatch_togepi_egg(console, context); case PokemonFRLG_RngTarget::magikarp: @@ -195,4 +511,4 @@ bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, Poke } } -} \ No newline at end of file +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h index 1c26c708c4..1d6b4c0229 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.h @@ -7,8 +7,10 @@ #ifndef PokemonAutomation_PokemonFRLG_RngNavigation_H #define PokemonAutomation_PokemonFRLG_RngNavigation_H -#include "PokemonFRLG_BlindNavigation.h" +#include "CommonFramework/Language.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "Pokemon/Pokemon_AdvRng.h" +#include "PokemonFRLG_BlindNavigation.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -17,11 +19,51 @@ namespace NintendoSwitch{ using ProControllerContext = ControllerContext; namespace PokemonFRLG{ +using namespace Pokemon; +void go_to_summary( + ConsoleHandle& console, + ProControllerContext& context, + int from_last = 0, + StartMenuContext menu_context = StartMenuContext::STANDARD +); + +bool shiny_check_summary( + ConsoleHandle& console, ProControllerContext& context, + int from_last = 0, StartMenuContext menu_context = StartMenuContext::STANDARD +); + +AdvObservedPokemon read_summary( + ConsoleHandle& console, + ProControllerContext& context, + const Language& language, + std::set species = {} +); + +int auto_catch( + ConsoleHandle& console, + ProControllerContext& context, + const uint64_t& max_ball_throws, + bool safari_zone = false +); + +bool use_rare_candy( + ConsoleHandle& console, + ProControllerContext& context, + const Language& language, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& base_stats, + AdvRngMethod method = AdvRngMethod::Method1, + bool safari_zone = false, + bool first = false +); + int watch_for_shiny_encounter(ConsoleHandle& console, ProControllerContext& context); bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, PokemonFRLG_RngTarget TARGET); +void hatch_daycare_egg(ConsoleHandle& console, ProControllerContext& context); } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp index ac902fe525..ffeef8427e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp @@ -18,6 +18,7 @@ #include "PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" +#include "PokemonFRLG_RngCalibration.h" #include "PokemonFRLG_SidHelper.h" namespace PokemonAutomation{ @@ -269,9 +270,6 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex // SidHelper_Descriptor::Stats& stats = env.current_stats(); - double FRAMERATE = 59.999977; // FPS - double FRAME_DURATION = 1000 / FRAMERATE; // ms - uint64_t FINAL_TEXT_FRAMES; Language lang = LANGUAGE; switch (lang){ @@ -297,14 +295,14 @@ void SidHelper::program(SingleSwitchProgramEnvironment& env, ProControllerContex FINAL_TEXT_FRAMES = 249; } - const double& FIXED_ADVANCES_OFFSET = 7; // determined empirically. Probably not console/setup dependent + static const double FIXED_ADVANCES_OFFSET = 7; // determined empirically. Probably not console/setup dependent bool extra_press_at_end = ( LANGUAGE == Language::German || LANGUAGE == Language::Japanese ); - const uint64_t SID_DELAY = uint64_t((TARGET_ADVANCES - 2*FINAL_TEXT_FRAMES + FIXED_ADVANCES_OFFSET) * FRAME_DURATION / 2); // advances pass 2 by 2 + const uint64_t SID_DELAY = uint64_t((TARGET_ADVANCES - 2*FINAL_TEXT_FRAMES + FIXED_ADVANCES_OFFSET) * FRLG_FRAME_DURATION / 2); // advances pass 2 by 2 env.log("Delay: " + std::to_string(SID_DELAY) + "ms"); set_sid_from_name_screen(env, context, SID_DELAY, extra_press_at_end); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index f7ba0f8a79..a5f25fa7aa 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -4,32 +4,22 @@ * */ -#include -#include -#include -#include "CommonTools/Random.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/StartupChecks/StartProgramChecks.h" #include "Pokemon/Pokemon_Strings.h" -#include "Pokemon/Pokemon_StatsCalculation.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" -#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" -#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h" -#include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" -#include "PokemonFRLG_BlindNavigation.h" #include "PokemonFRLG_RngNavigation.h" #include "PokemonFRLG_HardReset.h" #include "PokemonFRLG_StarterRng.h" @@ -211,74 +201,6 @@ bool StarterRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); } -AdvObservedPokemon StarterRng::read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - // assumes we're already on the first summary page - PokemonFRLG_Stats stats; - StatsReader reader(COLOR_RED); - - env.log("Reading Page 1 (Name, Level, Nature, Gender)..."); - VideoSnapshot screen1 = env.console.video().snapshot(); - reader.read_page1(env.logger(), LANGUAGE, screen1, stats); - - SummaryPage2Watcher page_two(COLOR_RED); - context.wait_for_all_requests(); - int ret = run_until( - env.console, context, - [](ProControllerContext& context) { - for (int i=0; i<5; i++){ - pbf_press_dpad(context, DPAD_RIGHT, 200ms, 1800ms); - } - }, - { page_two } - ); - - if (ret < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "read_summary(): Failed to detect second summary screen.", - env.console - ); - } - - env.log("Reading Page 2 (Stats)..."); - VideoSnapshot screen2 = env.console.video().snapshot(); - reader.read_page2(env.logger(), LANGUAGE, 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 StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ @@ -662,44 +584,51 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte break; } - const int16_t GENDER_THRESHOLD = 30; + static const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate + static const int64_t FIXED_ADVANCES_OFFSET = 160; // frames, approximate + + static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - const double FRAMERATE = 59.999977; // FPS - const double FRAME_DURATION = 1000 / FRAMERATE; + static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - uint8_t MAX_HISTORY_LENGTH = 10; - double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + static const uint64_t INITIAL_ADVANCES_RADIUS = 1024; - uint64_t CONTINUE_SCREEN_FRAMES = 200; + static const uint8_t MAX_HISTORY_LENGTH = 10; - const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate - const int64_t FIXED_ADVANCES_OFFSET = 160; // frames, approximate + static const int16_t GENDER_THRESHOLD = 30; + + + RngCalibrations calibrations = { + RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, + RNG_CALIBRATION.advances_calibration, + RNG_CALIBRATION.csf_calibration + }; - double SEED_CALIBRATION_FRAMES = RNG_CALIBRATION.seed_calibration / FRAME_DURATION; - double ADVANCES_CALIBRATION = RNG_CALIBRATION.advances_calibration; - double CONTINUE_SCREEN_ADJUSTMENT = RNG_CALIBRATION.csf_calibration; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); + env.log("Target PID (base 10): " + std::to_string(target_result.pid)); + env.log("Target Nature: " + nature_to_string(target_result.nature)); env.log("Target IVs:"); - env.log("HP: " + std::to_string(target_result.ivs.hp)); - env.log("Atk: " + std::to_string(target_result.ivs.attack)); - env.log("Def: " + std::to_string(target_result.ivs.defense)); - env.log("SpA: " + std::to_string(target_result.ivs.spatk)); - env.log("SpD: " + std::to_string(target_result.ivs.spdef)); - env.log("Spe: " + std::to_string(target_result.ivs.speed)); - - RngAdvanceHistory ADVANCE_HISTORY; - RngCalibrationHistory CALIBRATION_HISTORY; - uint64_t INITIAL_ADVANCES_RADIUS = 1024; + env.log(" HP: " + std::to_string(target_result.ivs.hp)); + env.log(" Atk: " + std::to_string(target_result.ivs.attack)); + env.log(" Def: " + std::to_string(target_result.ivs.defense)); + env.log(" SpA: " + std::to_string(target_result.ivs.spatk)); + env.log(" SpD: " + std::to_string(target_result.ivs.spdef)); + env.log(" Spe: " + std::to_string(target_result.ivs.speed)); + + + RngAdvanceHistory advance_history; + RngCalibrationHistory calibration_history; + bool wildshiny_found = false; uint16_t failed_searches = 0; while (true){ - if (CALIBRATION_HISTORY.results.size() > 0){ + if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, CALIBRATION_HISTORY.results.back())){ + if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -732,62 +661,30 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte ); 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); - } + uint64_t advances_radius = get_advances_radius(env.console, calibration_history, INITIAL_ADVANCES_RADIUS); + + if (calibration_history.results.size() > 0){ + calibrations = get_calibrations(env.console, calibration_history, SEED_VALUES, SEED_POSITION, ADVANCES); } // 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 + FIXED_ADVANCES_OFFSET; - double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES - CONTINUE_SCREEN_ADJUSTMENT; - - env.log("Seed calibration (frames): " + std::to_string(SEED_CALIBRATION_FRAMES)); - env.log("Advance calibration (frames / 2): " + std::to_string(ADVANCES_CALIBRATION)); - env.log("Continue screen adjustment (frames): " + std::to_string(CONTINUE_SCREEN_ADJUSTMENT)); + double seed_bump = SEED_BUMPS[advance_history.results.size() % 5]; + calibrations.seed_offset += seed_bump; - uint64_t CALIBRATED_SEED_DELAY = uint64_t(std::round(SEED_DELAY + FIXED_SEED_OFFSET + FRAME_DURATION * SEED_CALIBRATION_FRAMES)); - uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * (CONTINUE_SCREEN_FRAMES + CONTINUE_SCREEN_ADJUSTMENT))); - uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * INGAME_ADVANCES / 2)); + uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; - env.log("Title screen duration: " + std::to_string(CALIBRATED_SEED_DELAY) + "ms"); - env.log("Continue screen duration: " + std::to_string(CONTINUE_SCREEN_DELAY) + "ms"); - env.log("In-game duration: " + std::to_string(INGAME_DELAY) + "ms"); - - check_timings(env.console, PokemonFRLG_RngTarget::starters, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, INGAME_DELAY, false); + RngTimings timings = prepare_timings( + env.console, PokemonFRLG_RngTarget::starters, + SEED_DELAY, CONTINUE_SCREEN_FRAMES, ingame_advances, + false, calibrations, + FIXED_SEED_OFFSET, FIXED_ADVANCES_OFFSET + ); env.log("Resetting Game..."); reset_and_perform_blind_sequence( env.console, context, PokemonFRLG_RngTarget::starters, - SEED_BUTTON, EXTRA_BUTTON, CALIBRATED_SEED_DELAY, - CONTINUE_SCREEN_DELAY, 0, INGAME_DELAY, + SEED_BUTTON, EXTRA_BUTTON, timings, false, PROFILE ); stats.resets++; @@ -815,18 +712,22 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte break; } - AdvObservedPokemon pokemon = read_summary(env, context); + // Stage 1: initial search -- starter received + AdvObservedPokemon pokemon = read_summary(env.console, context, LANGUAGE); AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); std::vector search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); RNG_CALIBRATION.set( - SEED_CALIBRATION_FRAMES * FRAME_DURATION, - CONTINUE_SCREEN_ADJUSTMENT, - ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + calibrations.seed_offset * FRLG_FRAME_DURATION, + calibrations.csf_offset, + calibrations.ingame_offset, 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); + bool finished = update_history( + env.console, advance_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 1 + ); if (finished){ env.log("RNG search finished."); if (search_hits.size() == 0){ @@ -837,6 +738,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte continue; } + // Stage 2: first search update -- post-rival-battle bool failed = walk_to_rival_battle(env, context); if (failed){ stats.errors++; @@ -851,13 +753,16 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > 1){ search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); RNG_CALIBRATION.set( - SEED_CALIBRATION_FRAMES * FRAME_DURATION, - CONTINUE_SCREEN_ADJUSTMENT, - ADVANCES_CALIBRATION, + calibrations.seed_offset * FRLG_FRAME_DURATION, + calibrations.csf_offset, + calibrations.ingame_offset, search_hits - ); + ); env.log("Number of search hits: " + std::to_string(search_hits.size())); - finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); + finished = update_history( + env.console, advance_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 5 + ); if (finished){ env.log("RNG search finished."); if (search_hits.size() == 0){ @@ -869,6 +774,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } } + // Stage 3: subsequent search updates -- leveling up from wild encounters failed = walk_to_route1_from_lab(env, context); if (failed){ stats.errors++; @@ -880,7 +786,10 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte while(true){ if (num_levels > MAX_LEVELS){ env.log("RNG search not complete after 3 level-ups."); - update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, true); + finished = update_history( + env.console, advance_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 5 + ); break; } @@ -912,26 +821,28 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } } + // force the search to finish after enough level-ups if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); RNG_CALIBRATION.set( - SEED_CALIBRATION_FRAMES * FRAME_DURATION, - CONTINUE_SCREEN_ADJUSTMENT, - ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + calibrations.seed_offset * FRLG_FRAME_DURATION, + calibrations.csf_offset, + calibrations.ingame_offset, search_hits - ); + ); env.log("Number of search hits: " + std::to_string(search_hits.size())); - finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); - if (finished){ - env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } - break; + update_history( + env.console, advance_history, calibration_history, + MAX_HISTORY_LENGTH, calibrations, search_hits, 5, 2, true + ); + env.log("RNG search finished."); + if (search_hits.size() == 0){ + failed_searches++; + }else{ + failed_searches = 0; } + break; } } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index 470e9d1270..a1b01ec170 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -49,8 +49,6 @@ class StarterRng : public SingleSwitchProgramInstance{ bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); - AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - bool walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context); bool auto_battle_rival( SingleSwitchProgramEnvironment& env, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp index 7a214ad636..bcab619784 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StaticRng.cpp @@ -4,35 +4,18 @@ * */ -#include -#include -#include -#include "CommonTools/Random.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/StartupChecks/StartProgramChecks.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonFramework/Language.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_RngCalibration.h" #include "PokemonFRLG_StaticRng.h" namespace PokemonAutomation{ @@ -236,346 +219,6 @@ 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, StaticRng_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 StaticRng::read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - // 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); - - 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(), LANGUAGE, 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 StaticRng::use_rare_candy( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - StaticRng_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, LANGUAGE); - 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 StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ /* * Settings: Text Speed fast @@ -653,41 +296,45 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex break; } - const double FRAMERATE = 59.999977; // FPS - const double FRAME_DURATION = 1000 / FRAMERATE; + static const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate + static const int64_t FIXED_ADVANCES_OFFSET = 160; // frames, approximate + + static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; - double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - uint64_t CONTINUE_SCREEN_FRAMES = 200; + const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; - const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate - const int64_t FIXED_ADVANCES_OFFSET = 160; // frames, approximate + const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; - 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; + + RngCalibrations calibrations = { + RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, + RNG_CALIBRATION.advances_calibration, + RNG_CALIBRATION.csf_calibration + }; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); + env.log("Target PID (base 10): " + std::to_string(target_result.pid)); + env.log("Target Nature: " + nature_to_string(target_result.nature)); env.log("Target IVs:"); - env.log("HP: " + std::to_string(target_result.ivs.hp)); - env.log("Atk: " + std::to_string(target_result.ivs.attack)); - env.log("Def: " + std::to_string(target_result.ivs.defense)); - env.log("SpA: " + std::to_string(target_result.ivs.spatk)); - env.log("SpD: " + std::to_string(target_result.ivs.spdef)); - env.log("Spe: " + std::to_string(target_result.ivs.speed)); + env.log(" HP: " + std::to_string(target_result.ivs.hp)); + env.log(" Atk: " + std::to_string(target_result.ivs.attack)); + env.log(" Def: " + std::to_string(target_result.ivs.defense)); + env.log(" SpA: " + std::to_string(target_result.ivs.spatk)); + env.log(" SpD: " + std::to_string(target_result.ivs.spdef)); + env.log(" Spe: " + std::to_string(target_result.ivs.speed)); - RngAdvanceHistory ADVANCE_HISTORY; - RngCalibrationHistory CALIBRATION_HISTORY; - uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 8192 : 1024; + RngAdvanceHistory advance_history; + RngCalibrationHistory calibration_history; uint16_t failed_searches = 0; while (true){ - if (CALIBRATION_HISTORY.results.size() > 0){ + if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, CALIBRATION_HISTORY.results.back())){ + if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -716,77 +363,32 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex ); 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); - } + uint64_t advances_radius = get_advances_radius(env.console, calibration_history, INITIAL_ADVANCES_RADIUS); - 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 (calibration_history.results.size() > 0){ + calibrations = get_calibrations(env.console, calibration_history, SEED_VALUES, SEED_POSITION, ADVANCES); } // 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 seed_bump = SEED_BUMPS[advance_history.results.size() % 5]; + calibrations.seed_offset += seed_bump; - double CALIBRATED_ADVANCES = ADVANCES + ADVANCES_CALIBRATION + FIXED_ADVANCES_OFFSET; - double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES - CONTINUE_SCREEN_ADJUSTMENT; + uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; - double TEACHY_ADVANCES = 0; - bool should_use_teachy_tv = USE_TEACHY_TV && (INGAME_ADVANCES > 10000); // don't use Teachy TV for short in-game advance targets - if (should_use_teachy_tv) { - TEACHY_ADVANCES = std::floor((INGAME_ADVANCES - 2500) / 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 ? 14067 : 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"); - } - - check_timings(env.console, TARGET, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, INGAME_DELAY, false); + RngTimings timings = prepare_timings( + env.console, TARGET, + SEED_DELAY, CONTINUE_SCREEN_FRAMES, ingame_advances, + USE_TEACHY_TV, calibrations, + FIXED_SEED_OFFSET, FIXED_ADVANCES_OFFSET + ); 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, + SEED_BUTTON, EXTRA_BUTTON, timings, false, PROFILE ); - stats.resets++; + stats.resets++; RNG_FILTERS.reset(); RNG_CALIBRATION.reset(); @@ -811,63 +413,72 @@ void StaticRng::program(SingleSwitchProgramEnvironment& env, ProControllerContex break; } - bool failed = auto_catch(env, context, stats, MAX_BALL_THROWS); - if (failed){ + int balls_thrown = auto_catch(env.console, context, MAX_BALL_THROWS); + if (balls_thrown < 0){ + stats.errors++; + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_catch() encountered an error." + ); + continue; + }else if(balls_thrown == 0){ env.log("Failed catch."); continue; } - AdvObservedPokemon pokemon = read_summary(env, context); + go_to_summary(env.console, context); + AdvObservedPokemon pokemon = read_summary(env.console, context, LANGUAGE); AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); std::vector search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); RNG_CALIBRATION.set( - SEED_CALIBRATION_FRAMES * FRAME_DURATION, - CONTINUE_SCREEN_ADJUSTMENT, - ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + calibrations.seed_offset * FRLG_FRAME_DURATION, + calibrations.csf_offset, + calibrations.ingame_offset, search_hits - ); - bool finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 1); - if (finished || (MAX_RARE_CANDIES == 0)){ - env.log("RNG search finished."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } - continue; - } + ); + bool finished = update_history( + env.console, advance_history, calibration_history, MAX_HISTORY_LENGTH, + calibrations, search_hits, 1 + ); 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" @@ -15,24 +11,13 @@ #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_RngCalibration.h" #include "PokemonFRLG_LocationsDatabase.h" #include "PokemonFRLG_RngStatsDatabase.h" #include "PokemonFRLG_EncountersDatabase.h" @@ -252,360 +237,6 @@ bool WildRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_ 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, - bool safari_zone -){ - 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; } - - if (!safari_zone){ - // 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, - bool safari_zone -){ - // navigate to the summary page of the last occupied (not necessarily 6th) party slot - open_party_menu_from_overworld(env.console, context, safari_zone ? StartMenuContext::SAFARI_ZONE : StartMenuContext::STANDARD); - 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(), LANGUAGE, 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 safari_zone, - bool first -){ - // navigate to the bag (only needed for the first use) - if (first){ - open_bag_from_overworld(env.console, context, safari_zone ? PokemonFRLG::StartMenuContext::SAFARI_ZONE : PokemonFRLG::StartMenuContext::STANDARD); - // 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, LANGUAGE); - 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 @@ -619,7 +250,7 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& RNG_CALIBRATION.reset(); // prepare database of base stats and gender thresholds - RngStatsDatabase stats_data("PokemonFRLG/BaseStats.json"); + RngStatsDatabase STATS_DATA("PokemonFRLG/BaseStats.json"); // get the relevant encounter slots EncountersDatabase encounters_data(GAME_VERSION == GameVersion::firered ? "PokemonFRLG/EncounterSlotsFR.json" : "PokemonFRLG/EncounterSlotsLG.json"); @@ -714,39 +345,47 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& ); } - const double FRAMERATE = 59.999977; // FPS - const double FRAME_DURATION = 1000 / FRAMERATE; + static const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate + static const int64_t FIXED_ADVANCES_OFFSET = -352; // frames, approximate - uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; - double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + static const uint64_t CONTINUE_SCREEN_FRAMES = 200; - uint64_t CONTINUE_SCREEN_FRAMES = 200; + static const double SEED_BUMPS[] = {0, 1, -1, 2, -2}; - const int64_t FIXED_SEED_OFFSET = -845; // milliseconds, approximate - const int64_t FIXED_ADVANCES_OFFSET = -352; // frames, approximate + const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024; - 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; + const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; + RngCalibrations calibrations = { + RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION, + RNG_CALIBRATION.advances_calibration, + RNG_CALIBRATION.csf_calibration + }; + AdvRngWildSearcher searcher(TARGET_SEED, ADVANCES, ENCOUNTER_SLOTS, AdvRngMethod::Any); - AdvWildPokemonResult target_result = searcher.generate_pokemon(); + AdvWildPokemonResult target_result = searcher.generate_pokemon(SUPER_ROD); 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; + env.log("Target Nature: " + nature_to_string(target_result.nature)); + env.log("Target IVs (assuming Method 1):"); + env.log(" HP: " + std::to_string(target_result.ivs.hp)); + env.log(" Atk: " + std::to_string(target_result.ivs.attack)); + env.log(" Def: " + std::to_string(target_result.ivs.defense)); + env.log(" SpA: " + std::to_string(target_result.ivs.spatk)); + env.log(" SpD: " + std::to_string(target_result.ivs.spdef)); + env.log(" Spe: " + std::to_string(target_result.ivs.speed)); + RngAdvanceHistory advance_history; + RngCalibrationHistory calibration_history; uint16_t failed_searches = 0; while (true){ - if (CALIBRATION_HISTORY.results.size() > 0){ + if (calibration_history.results.size() > 0){ env.log("Checking for nonshiny target hit..."); - if (have_hit_target(env, TARGET_SEED, CALIBRATION_HISTORY.results.back())){ + if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){ env.log("Target Hit!"); stats.nonshiny++; break; @@ -775,77 +414,30 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& ); 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); - } + uint64_t advances_radius = get_advances_radius(env.console, calibration_history, INITIAL_ADVANCES_RADIUS); - 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 (calibration_history.results.size() > 0){ + calibrations = get_calibrations(env.console, calibration_history, SEED_VALUES, SEED_POSITION, ADVANCES); } // 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 + FIXED_ADVANCES_OFFSET; - double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES - CONTINUE_SCREEN_ADJUSTMENT; - - uint64_t TEACHY_TV_BUFFER = safari_zone ? 20000 : 10000; // Safari zone targets need extra time to walk to the right position - - double TEACHY_ADVANCES = 0; - bool should_use_teachy_tv = USE_TEACHY_TV && (INGAME_ADVANCES > TEACHY_TV_BUFFER); // don't use Teachy TV for short in-game advance targets - if (should_use_teachy_tv) { - TEACHY_ADVANCES = std::floor((INGAME_ADVANCES - TEACHY_TV_BUFFER + 7500) / 313) * 313; - } + double seed_bump = SEED_BUMPS[advance_history.results.size() % 5]; + calibrations.seed_offset += seed_bump; - 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 ? 14067 : 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"); - } + uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES; - check_timings(env.console, TARGET, CALIBRATED_SEED_DELAY, CONTINUE_SCREEN_DELAY, INGAME_DELAY, safari_zone); + RngTimings timings = prepare_timings( + env.console, TARGET, + SEED_DELAY, CONTINUE_SCREEN_FRAMES, ingame_advances, + USE_TEACHY_TV, calibrations, + FIXED_SEED_OFFSET, FIXED_ADVANCES_OFFSET + ); 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 + SEED_BUTTON, EXTRA_BUTTON, timings, + safari_zone, PROFILE ); stats.resets++; @@ -885,76 +477,81 @@ void WildRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& break; } - bool failed = auto_catch(env, context, stats, MAX_BALL_THROWS, safari_zone); - if (failed){ + int balls_thrown = auto_catch(env.console, context, MAX_BALL_THROWS, safari_zone); + if (balls_thrown < 0){ + stats.errors++; + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_catch() encountered an error." + ); + continue; + }else if(balls_thrown == 0){ env.log("Failed catch."); continue; } - AdvObservedPokemon pokemon = read_summary(env, context, SPECIES_LIST, safari_zone); + go_to_summary(env.console, context, 0, safari_zone ? StartMenuContext::SAFARI_ZONE : StartMenuContext::STANDARD); + AdvObservedPokemon pokemon = read_summary(env.console, context, LANGUAGE, SPECIES_LIST); RngStats species_stats; try{ - species_stats = stats_data.get_throw(pokemon.species); + species_stats = STATS_DATA.get_throw(pokemon.species); }catch (const InternalProgramError& err){ env.log(err.message()); env.log("Failed to load base stats."); continue; } - BaseStats BASE_STATS = species_stats.base_stats; - int16_t GENDER_THRESHOLD = species_stats.gender_threshold; + 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); + 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); + 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, + calibrations.seed_offset * FRLG_FRAME_DURATION, + calibrations.csf_offset, + calibrations.ingame_offset, 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."); - if (search_hits.size() == 0){ - failed_searches++; - }else{ - failed_searches = 0; - } - continue; - } + ); + bool finished = update_history(env.console, advance_history, calibration_history, MAX_HISTORY_LENGTH, calibrations, search_hits, 1); + finished = finished || all_indistinguishable(search_hits, searcher, gender_threshold, SUPER_ROD); for (uint64_t i=0; i& SPECIES_LIST, - bool safari_zone - ); - - bool auto_catch( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - WildRng_Descriptor::Stats& stats, - const uint64_t& MAX_BALL_THROWS, - bool safari_zone - ); - - bool use_rare_candy( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - WildRng_Descriptor::Stats& stats, - AdvObservedPokemon& pokemon, - AdvRngFilters& filters, - const BaseStats& BASE_STATS, - bool safari_zone, - bool first - ); OCR::LanguageOCROption LANGUAGE;