Skip to content

Commit 141b11d

Browse files
authored
Separate out common patterns in FRLG RNG programs (#1291)
* separate out repeated and reusable patterns in FRLG RNG programs * add egg histories constructor
1 parent 82a2edc commit 141b11d

19 files changed

Lines changed: 854 additions & 321 deletions

SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,53 @@ void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& conte
797797
}
798798
}
799799

800+
void use_sweet_scent_from_overworld(ConsoleHandle& console, ProControllerContext& context, int from_last){
801+
uint16_t errors = 0;
802+
803+
while (true){
804+
if (errors > 5){
805+
OperationFailedException::fire(
806+
ErrorReport::SEND_ERROR_REPORT,
807+
"use_teleport_from_overworld(): Failed to use Teleport 5 times in a row.",
808+
console
809+
);
810+
}
811+
812+
open_party_menu_from_overworld(console, context);
813+
// navigate to last party slot
814+
for (int i=0; i<(2+from_last); i++){
815+
pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms);
816+
}
817+
818+
PartySelectionWatcher sweetscent_selected(COLOR_RED);
819+
820+
context.wait_for_all_requests();
821+
int ret = run_until<ProControllerContext>(
822+
console, context,
823+
[](ProControllerContext& context){
824+
pbf_press_button(context, BUTTON_A, 200ms, 1800ms);
825+
},
826+
{ sweetscent_selected }
827+
);
828+
829+
if (ret < 0){
830+
console.log("Failed to select Sweet Scent user.");
831+
errors++;
832+
pbf_mash_button(context, BUTTON_B, 3000ms);
833+
continue;
834+
}
835+
836+
// select Sweet Scent (2nd option, but maybe HMs could change this)
837+
pbf_move_left_joystick(context, {0, -1}, 200ms, 300ms);
838+
pbf_press_button(context, BUTTON_A, 200ms, 1800ms);
839+
pbf_press_button(context, BUTTON_A, 200ms, 800ms);
840+
841+
context.wait_for_all_requests();
842+
console.log("Used Sweet Scent.");
843+
return;
844+
}
845+
}
846+
800847
void use_teleport_from_overworld(ConsoleHandle& console, ProControllerContext& context){
801848
uint16_t errors = 0;
802849

SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ namespace NintendoSwitch{
1919
using ProControllerContext = ControllerContext<ProController>;
2020
namespace PokemonFRLG{
2121

22+
using namespace std::chrono_literals;
23+
24+
2225
enum class BattleResult{
2326
opponentfainted,
2427
playerfainted,
@@ -80,6 +83,10 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext
8083
// Starting from the start menu, a sub-screen of the start menu, or the overworld, navigate to the bag
8184
void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD);
8285

86+
// Uses Sweet Scent, assuming that the specified party member has it learned
87+
// The last argument is the distance of the Sweet Scent user from the last party slot
88+
void use_sweet_scent_from_overworld(ConsoleHandle& console, ProControllerContext& context, int from_last = 0);
89+
8390
// Uses Teleport to return to a PokeCenter.
8491
// Assumes that Teleport is usable and the last party member has it learned
8592
void use_teleport_from_overworld(ConsoleHandle& console, ProControllerContext& context);

SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp

Lines changed: 19 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "PokemonFRLG_RngNavigation.h"
2020
#include "PokemonFRLG_HardReset.h"
2121
#include "PokemonFRLG_RngCalibration.h"
22+
#include "PokemonFRLG_RngLoopRoutines.h"
2223
#include "PokemonFRLG_GiftRng.h"
2324

2425
namespace PokemonAutomation{
@@ -227,10 +228,6 @@ GiftRng::GiftRng()
227228

228229

229230

230-
bool GiftRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){
231-
return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES);
232-
}
233-
234231
void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){
235232
/*
236233
* Settings: Text Speed fast
@@ -324,8 +321,6 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext&
324321

325322
static const uint64_t CONTINUE_SCREEN_FRAMES = 200;
326323

327-
static const double SEED_BUMPS[] = {0, 1, -1, 2, -2};
328-
329324
const uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 4096 : 1024;
330325

331326
const uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10;
@@ -344,24 +339,14 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext&
344339
AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1);
345340
AdvPokemonResult target_result = searcher.generate_pokemon();
346341
RNG_TARGET.set_target(target_result, GENDER_THRESHOLD);
347-
env.log("Target PID: " + to_hex_string(target_result.pid));
348-
env.log("Target Nature: " + nature_to_string(target_result.nature));
349-
env.log("Target IVs:");
350-
env.log(" HP: " + std::to_string(target_result.ivs.hp));
351-
env.log(" Atk: " + std::to_string(target_result.ivs.attack));
352-
env.log(" Def: " + std::to_string(target_result.ivs.defense));
353-
env.log(" SpA: " + std::to_string(target_result.ivs.spatk));
354-
env.log(" SpD: " + std::to_string(target_result.ivs.spdef));
355-
env.log(" Spe: " + std::to_string(target_result.ivs.speed));
342+
log_target_pokemon(env.console, target_result);
356343

357344
RngCalibrations calibrations = {
358345
RNG_CALIBRATION.seed_calibration / FRLG_FRAME_DURATION,
359346
RNG_CALIBRATION.csf_calibration,
360347
RNG_CALIBRATION.advances_calibration
361348
};
362-
env.log("Initial Seed calibration (frames): " + std::to_string(calibrations.seed_offset));
363-
env.log("Initial CSF calibration (frames): " + std::to_string(calibrations.csf_offset));
364-
env.log("Initial In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset));
349+
log_calibrations(env.console, calibrations, true);
365350

366351
Milliseconds launch_delay = INITIAL_LAUNCH_DELAY;
367352

@@ -373,7 +358,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext&
373358
while (true){
374359
if (calibration_history.results.size() > 0){
375360
env.log("Checking for nonshiny target hit...");
376-
if (have_hit_target(env, TARGET_SEED, calibration_history.results.back())){
361+
if (have_hit_target(TARGET_SEED, ADVANCES, calibration_history.results.back())){
377362
env.log("Target Hit!");
378363
stats.nonshiny++;
379364
break;
@@ -410,8 +395,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext&
410395
}
411396

412397
// if previous resets had uncertain advances, slightly modify the seed delay to try to hit a different target
413-
double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5];
414-
calibrations.seed_offset += seed_bump;
398+
apply_seed_bump(calibrations, uncertain_history);
415399

416400
uint64_t ingame_advances = ADVANCES - CONTINUE_SCREEN_FRAMES;
417401

@@ -459,60 +443,23 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext&
459443
AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS);
460444
RNG_FILTERS.set(filters);
461445

462-
std::vector<AdvRngState> search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD);
463-
RNG_CALIBRATION.set_hits(search_hits);
464-
bool force_finish = (
465-
(MAX_RARE_CANDIES == 0)
466-
|| all_equal(search_hits)
467-
|| all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD)
468-
);
469-
bool finished = update_history(
470-
env.console, uncertain_history, calibration_history, MAX_HISTORY_LENGTH,
471-
calibrations, search_hits, 1, 2, force_finish
472-
);
473-
474-
for (uint64_t i=0; i<MAX_RARE_CANDIES; i++){
475-
if (finished){
476-
break;
477-
}
478-
479-
bool failed = use_rare_candy(env.console, context, LANGUAGE, pokemon, filters, BASE_STATS, AdvRngMethod::Method1, false, i == 0);
480-
if (failed){
481-
update_history(
482-
env.console, uncertain_history, calibration_history,
483-
MAX_HISTORY_LENGTH, calibrations, search_hits, 1, 2, true
484-
);
485-
stats.errors++;
486-
send_program_recoverable_error_notification(
487-
env, NOTIFICATION_ERROR_RECOVERABLE,
488-
"Failed to use Rare Candy."
489-
);
446+
std::vector<AdvRngState> search_hits = refine_calibration_with_rare_candy(
447+
env, context, LANGUAGE, pokemon, filters, BASE_STATS,
448+
uncertain_history, calibration_history, calibrations,
449+
MAX_HISTORY_LENGTH, MAX_RARE_CANDIES, AdvRngMethod::Method1, false,
450+
stats.errors, NOTIFICATION_ERROR_RECOVERABLE,
451+
[&](AdvRngFilters& f){
452+
return get_search_results(env.console, searcher, f, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD);
453+
},
454+
[&](const std::vector<AdvRngState>& h){ RNG_CALIBRATION.set_hits(h); },
455+
[&](const AdvRngFilters& f){ RNG_FILTERS.set(f); },
456+
[&](const std::vector<AdvRngState>& h){
457+
return all_equal(h) || all_indistinguishable(h, searcher, GENDER_THRESHOLD);
490458
}
491-
RNG_FILTERS.set(filters);
492-
493-
search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD);
494-
RNG_CALIBRATION.set_hits(search_hits);
495-
496-
force_finish = (
497-
failed
498-
|| (i == (MAX_RARE_CANDIES - 1))
499-
|| all_equal(search_hits)
500-
|| all_indistinguishable(search_hits, searcher, GENDER_THRESHOLD)
501-
);
502-
finished = update_history(
503-
env.console, uncertain_history,
504-
calibration_history, MAX_HISTORY_LENGTH,
505-
calibrations, search_hits,
506-
1, 2, force_finish
507-
);
508-
}
459+
);
509460

510461
env.log("RNG search finished.");
511-
if (search_hits.size() == 0){
512-
failed_searches++;
513-
}else{
514-
failed_searches = 0;
515-
}
462+
update_failed_searches(failed_searches, search_hits);
516463

517464
}
518465

SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ class GiftRng : public SingleSwitchProgramInstance{
4444

4545
private:
4646

47-
bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit);
48-
4947
SectionDividerOption m_calibration_displays;
5048
RngTargetDisplay RNG_TARGET;
5149
RngFilterDisplay RNG_FILTERS;

SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,35 @@ std::string to_hex_string(const uint32_t& val){
8585
return s.str();
8686
}
8787

88+
bool have_hit_target(uint32_t target_seed, uint64_t target_advances, const AdvRngState& hit){
89+
return (hit.seed == target_seed) && (hit.advance == target_advances);
90+
}
91+
92+
void apply_seed_bump(RngCalibrations& calibrations, const RngUncertainHistory& uncertain_history){
93+
static const double SEED_BUMPS[] = {0, 1, -1, 2, -2};
94+
double seed_bump = SEED_BUMPS[uncertain_history.results.size() % 5];
95+
calibrations.seed_offset += seed_bump;
96+
}
97+
98+
void log_target_pokemon(ConsoleHandle& console, const AdvPokemonResult& result, bool normal_method_note){
99+
console.log("Target PID: " + to_hex_string(result.pid));
100+
console.log("Target Nature: " + nature_to_string(result.nature));
101+
console.log(normal_method_note ? "Target IVs (assuming Normal method):" : "Target IVs:");
102+
console.log(" HP: " + std::to_string(result.ivs.hp));
103+
console.log(" Atk: " + std::to_string(result.ivs.attack));
104+
console.log(" Def: " + std::to_string(result.ivs.defense));
105+
console.log(" SpA: " + std::to_string(result.ivs.spatk));
106+
console.log(" SpD: " + std::to_string(result.ivs.spdef));
107+
console.log(" Spe: " + std::to_string(result.ivs.speed));
108+
}
109+
110+
void log_calibrations(ConsoleHandle& console, const RngCalibrations& calibrations, bool initial){
111+
std::string prefix = initial ? "Initial " : "";
112+
console.log(prefix + "Seed calibration (frames): " + std::to_string(calibrations.seed_offset));
113+
console.log(prefix + "CSF calibration (frames): " + std::to_string(calibrations.csf_offset));
114+
console.log(prefix + "In-game calibration (frames x2): " + std::to_string(calibrations.ingame_offset));
115+
}
116+
88117
RngTimings prepare_timings(
89118
ConsoleHandle& console,
90119
PokemonFRLG_RngTarget target,

SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,31 @@ int16_t seed_position_in_list(uint16_t seed, std::vector<uint16_t> list);
5555
std::string to_hex_string(const uint16_t& val);
5656
std::string to_hex_string(const uint32_t& val);
5757

58+
59+
// true if a search hit matches the target seed and advance
60+
bool have_hit_target(uint32_t target_seed, uint64_t target_advances, const AdvRngState& hit);
61+
62+
// if previous resets had uncertain advances, slightly nudge the seed offset to try
63+
// to land on a different target. Cycles through {0, +1, -1, +2, -2} frames.
64+
void apply_seed_bump(RngCalibrations& calibrations, const RngUncertainHistory& uncertain_history);
65+
66+
// increment the failed-search counter when a search returned no hits, otherwise reset it.
67+
// templated so it accepts both plain hit vectors and the egg searcher's pair vectors.
68+
template <typename HitType>
69+
void update_failed_searches(uint16_t& failed_searches, const std::vector<HitType>& search_hits){
70+
if (search_hits.size() == 0){
71+
failed_searches++;
72+
}else{
73+
failed_searches = 0;
74+
}
75+
}
76+
77+
// log the PID, nature, and IVs of a target pokemon result
78+
void log_target_pokemon(ConsoleHandle& console, const AdvPokemonResult& result, bool normal_method_note = false);
79+
80+
// log the three calibration offsets (seed/CSF/in-game), in frames
81+
void log_calibrations(ConsoleHandle& console, const RngCalibrations& calibrations, bool initial = false);
82+
5883
RngTimings prepare_timings(
5984
ConsoleHandle& console,
6085
PokemonFRLG_RngTarget target,

0 commit comments

Comments
 (0)