From b0d5be71e65716ac6846986b9759190266fc6bab Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 16 Jul 2026 12:38:26 -0700 Subject: [PATCH 01/20] turn run_batch() into state machine --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 304 +++++++++++------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 43 ++- 2 files changed, 234 insertions(+), 113 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 1f9a04ba2c..d383743c36 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -299,119 +299,88 @@ bool EggAutonomous::run_batch( env.update_stats(); send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); + EggAutoPhase phase = EggAutoPhase::BIKE_LOOP; if (m_player_at_loop_start == false){ // reset position - const bool fly_from_overworld = true; // fly from menu - call_flying_taxi(env, context, fly_from_overworld); + phase = EggAutoPhase::FLY_RESET; } - size_t bike_loop_count = 0; + size_t total_bike_loop_count = 0; const size_t MAX_BIKE_LOOP_COUNT = 100; + size_t num_loops_since_last_fetch_attempt = 0; + size_t num_eggs_hatched = 0; m_player_at_loop_start = false; - + // Each iteration in the while-loop is made by: // - bike loops of LOOPS_PER_FETCH times. Bike loops begin at lady or nursery front door, end at lady. // - if not enough eggs fetched, talk to lady to try fetching an egg. while (num_eggs_hatched < 5 || m_num_eggs_retrieved < 5){ - // Detect when Y-Comm icon disappears. This is the time an egg is hatching - const bool y_comm_visible_when_egg_hatching = false; - YCommIconWatcher egg_hatching_detector(COLOR_RED, y_comm_visible_when_egg_hatching); - bool restart_bike_loop = false; - for (size_t i_bike_loop = 0; i_bike_loop < this->LOOPS_PER_FETCH && bike_loop_count < MAX_BIKE_LOOP_COUNT;){ - context.wait_for_all_requests(); - // +1 here because video overlay is for general users. General users start counts at 1, while us programmers start count at 0. - if (restart_bike_loop){ - env.console.overlay().add_log("Restart loop " + std::to_string(bike_loop_count+1), COLOR_WHITE); - restart_bike_loop = false; - }else{ - env.console.overlay().add_log("Loop " + std::to_string(bike_loop_count+1), COLOR_WHITE); + // NOTE: the egg hatching detector cannot be constantly running, + // since it only detects the black dialog box, and speaking to the lady will also produce a black dialog box. + // therefore, each phase has its own egg hatching detector, so it can be more easily turned on and off. + + switch(phase){ + case EggAutoPhase::BIKE_LOOP:{ + env.console.overlay().add_log("Loop " + std::to_string(total_bike_loop_count+1), COLOR_WHITE); + env.console.log("Bike Loop " + std::to_string(total_bike_loop_count+1)); + bool hatch_detected = run_bike_loop(env, context); + if (hatch_detected){ + phase = EggAutoPhase::HATCHING; + continue; } - int ret = run_until( - env.console, context, - [](ProControllerContext& context){ - travel_to_spin_location(context); - travel_back_to_lady(context); - }, - {{egg_hatching_detector}} - ); - if (ret < 0){ // we are at nursery lady; no egg hatching detected - ++i_bike_loop; - ++bike_loop_count; - continue; + // done Bike loop + ++num_loops_since_last_fetch_attempt; + ++total_bike_loop_count; + if (total_bike_loop_count >= MAX_BIKE_LOOP_COUNT){ + exceed_bike_loop_limit(env, context, MAX_BIKE_LOOP_COUNT); } - // Egg hatching - do{ - ++num_eggs_hatched; - stats.m_hatched++; - env.update_stats(); - wait_for_egg_hatched(env, context, stats, num_eggs_hatched); - if (num_eggs_hatched == 5){ - // We hatched all five eggs. No more eggs can hatch. Go to next loop - break; + if (num_loops_since_last_fetch_attempt < LOOPS_PER_FETCH){ + phase = EggAutoPhase::BIKE_LOOP; // repeat bike loop + }else{ + if (m_num_eggs_retrieved < 5){ + phase = EggAutoPhase::FETCH_EGG; + }else{ + // done retrieving eggs + // resume hatching eggs if needed + phase = EggAutoPhase::BIKE_LOOP; } - // Now we see if we can hatch one more egg. - ret = run_until( - env.console, context, - [](ProControllerContext& context){ - // Try move a little to hatch more: - // We move toward lower-left so that it wont hit the lady or enter the Nursory. - // Add 1 second of settle time to stop moving. - // We need to not be moving before trying to fly or an egg will hatch during - // that sequence when we cannot handle it. - pbf_move_left_joystick(context, {-1, -1}, 800ms, 1000ms); - }, - {{egg_hatching_detector}} - ); - }while (ret == 0); - // now no more hatching in this bike loop - // We either cannot find a consecutive hatch any more or we already hatch five of them - - if (num_eggs_hatched == 5 && m_num_eggs_retrieved == 5){ - m_player_at_loop_start = false; - break; } - - // Now we either cannot find a consecutive hatch any more, or we already hatch five for them, but - // we still need to fetch more eggs - - // Use fly to reset the location because now we don't know where the player character is. + continue; + } + case EggAutoPhase::HATCHING:{ + env.console.log("Hatching egg."); + num_eggs_hatched = hatch_routine(env, context, stats, num_eggs_hatched); + phase = EggAutoPhase::FLY_RESET; + continue; + } + case EggAutoPhase::FLY_RESET:{ + env.console.log("Call flying taxi to reset position."); const bool fly_from_overworld = true; - call_flying_taxi(env, context, fly_from_overworld); - restart_bike_loop = true; - // We don't update i_bike_loop here because we haven't finished one full bike loop due to egg hatching - } // end one bike loop - - if (bike_loop_count >= MAX_BIKE_LOOP_COUNT){ - env.log("Reached max number of bike loops " + std::to_string(MAX_BIKE_LOOP_COUNT)); - env.console.overlay().add_log("Error: max loops " + std::to_string(MAX_BIKE_LOOP_COUNT), COLOR_WHITE); - env.log("Take a screenshot of party to debug."); - // Now take a photo at the player's party for dumping debug info: - // Enter Rotom Phone menu - pbf_press_button(context, BUTTON_X, 160ms, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0); - // Select Pokemon App - navigate_to_menu_app(env.console, context, POKEMON_APP_INDEX); - // From menu enter Pokemon App - ssf_press_button(context, BUTTON_A, GameSettings::instance().MENU_TO_POKEMON_DELAY0, EGG_BUTTON_HOLD_DELAY); - context.wait_for_all_requests(); - - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Max number of loops reached. Not enough eggs in party?", - env.console - ); + bool hatch_detected = call_flying_taxi(env, context, fly_from_overworld); + if (hatch_detected){ + phase = EggAutoPhase::HATCHING; + }else{ + phase = EggAutoPhase::BIKE_LOOP; + } + continue; } - - context.wait_for_all_requests(); - if (m_num_eggs_retrieved < 5){ - // Update num_eggs_retrieved - m_num_eggs_retrieved = talk_to_lady_to_fetch_egg(env, context, stats, m_num_eggs_retrieved); - if (num_eggs_hatched == 5 && m_num_eggs_retrieved == 5){ - m_player_at_loop_start = true; - break; + case EggAutoPhase::FETCH_EGG:{ + env.console.log("Talk to lady to fetch egg."); + EggFetchResult fetch_result = talk_to_lady_to_fetch_egg(env, context, stats, m_num_eggs_retrieved); + if (fetch_result.hatch_detected){ + phase = EggAutoPhase::HATCHING; + }else{ + m_num_eggs_retrieved = fetch_result.num_eggs_retrieved; + + // if spoke to lady, back to the bike loop regardless of the success of the egg fetch + num_loops_since_last_fetch_attempt = 0; + phase = EggAutoPhase::BIKE_LOOP; } + continue; + } } } @@ -452,6 +421,88 @@ bool EggAutonomous::run_batch( return false; } +bool EggAutonomous::run_bike_loop( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + + // use Black dialog box detector as proxy for detecting when an egg is hatching + BlackDialogBoxWatcher2 egg_hatching_detector; + int ret = run_until( + env.console, context, + [](ProControllerContext& context){ + travel_to_spin_location(context); + travel_back_to_lady(context); + }, + {{egg_hatching_detector}} + ); + + bool hatch_detected = ret == 0; + if (hatch_detected){ + env.console.log("Hatching detected during bike loop."); + } + return hatch_detected; +} + +void EggAutonomous::exceed_bike_loop_limit( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + size_t max_bike_loop_count +){ + env.console.log("Reached max number of bike loops " + std::to_string(max_bike_loop_count)); + env.console.overlay().add_log("Error: max loops " + std::to_string(max_bike_loop_count), COLOR_WHITE); + env.console.log("Take a screenshot of party to debug."); + // Now take a photo at the player's party for dumping debug info: + // Enter Rotom Phone menu + pbf_press_button(context, BUTTON_X, 160ms, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0); + // Select Pokemon App + navigate_to_menu_app(env.console, context, POKEMON_APP_INDEX); + // From menu enter Pokemon App + ssf_press_button(context, BUTTON_A, GameSettings::instance().MENU_TO_POKEMON_DELAY0, EGG_BUTTON_HOLD_DELAY); + context.wait_for_all_requests(); + + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Max number of loops reached. Not enough eggs in party?", + env.console + ); + +} + +size_t EggAutonomous::hatch_routine( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EggAutonomous_Descriptor::Stats& stats, + size_t num_eggs_hatched +){ + // use Black dialog box detector as proxy for detecting when an egg is hatching + BlackDialogBoxWatcher2 egg_hatching_detector; + + int ret = -1; + do{ + ++num_eggs_hatched; + stats.m_hatched++; + env.update_stats(); + wait_for_egg_hatched(env, context, stats, num_eggs_hatched); + + // Now we see if we can hatch one more egg. + ret = run_until( + env.console, context, + [](ProControllerContext& context){ + // Try move a little to hatch more: + // We move toward lower-left so that it wont hit the lady or enter the Nursory. + // Add 1 second of settle time to stop moving. + // We need to not be moving before trying to fly or an egg will hatch during + // that sequence when we cannot handle it. + pbf_move_left_joystick(context, {-1, -1}, 800ms, 1000ms); + }, + {{egg_hatching_detector}} + ); + }while (ret == 0); + + return num_eggs_hatched; +} + void EggAutonomous::save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ context.wait_for_all_requests(); env.log("Save game."); @@ -462,25 +513,40 @@ void EggAutonomous::save_game(SingleSwitchProgramEnvironment& env, ProController mash_B_until_y_comm_icon(env, context, "Cannot detect end of saving game."); } -void EggAutonomous::call_flying_taxi( +bool EggAutonomous::call_flying_taxi( SingleSwitchProgramEnvironment& env, ProControllerContext& context, bool fly_from_overworld ){ - context.wait_for_all_requests(); - env.console.overlay().add_log("Call Flying Taxi", COLOR_WHITE); - if (fly_from_overworld){ - // Open menu - env.log("Fly from overworld to reset position"); - ssf_press_button(context, BUTTON_X, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0, 160ms); - }else{ - env.log("Fly from menu to reset position"); - } - navigate_to_menu_app(env.console, context, TOWN_MAP_APP_INDEX); + BlackDialogBoxWatcher2 egg_hatching_detector; + int ret = run_until( + env.console, context, + [&](ProControllerContext& context){ + context.wait_for_all_requests(); + env.console.overlay().add_log("Call Flying Taxi", COLOR_WHITE); + if (fly_from_overworld){ + // Open menu + env.log("Fly from overworld to reset position"); + ssf_press_button(context, BUTTON_X, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0, 160ms); + }else{ + env.log("Fly from menu to reset position"); + } + + navigate_to_menu_app(env.console, context, TOWN_MAP_APP_INDEX); + }, + {{egg_hatching_detector}} + ); - fly_home(context, false); - mash_B_until_y_comm_icon(env, context, "Cannot detect end of flying taxi animation."); + bool hatch_detected = ret == 0; + if (hatch_detected){ + env.console.log("Hatching detected while trying to call flying taxi."); + }else { + fly_home(context, false); + mash_B_until_y_comm_icon(env, context, "Cannot detect end of flying taxi animation."); + } + + return hatch_detected; } void EggAutonomous::wait_for_egg_hatched( @@ -508,7 +574,7 @@ void EggAutonomous::wait_for_egg_hatched( } } -size_t EggAutonomous::talk_to_lady_to_fetch_egg( +EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EggAutonomous_Descriptor::Stats& stats, @@ -520,6 +586,7 @@ size_t EggAutonomous::talk_to_lady_to_fetch_egg( // collect_egg(context); RetrieveEggArrowFinder egg_arrow_detector(env.console); CheckNurseryArrowFinder no_egg_arrow_detector(env.console); + BlackDialogBoxWatcher2 egg_hatching_detector; int ret = run_until( env.console, context, @@ -532,6 +599,7 @@ size_t EggAutonomous::talk_to_lady_to_fetch_egg( { egg_arrow_detector, no_egg_arrow_detector, + egg_hatching_detector, } ); @@ -566,13 +634,26 @@ size_t EggAutonomous::talk_to_lady_to_fetch_egg( }, {{dialog_over_detector}} ); - return num_eggs_retrieved; + return EggFetchResult{ + .num_eggs_retrieved = num_eggs_retrieved, + .hatch_detected = false + }; // break; + case 2: + env.log("Hatching detected while trying to talk to lady to fetch egg."); + + return EggFetchResult{ + .num_eggs_retrieved = num_eggs_retrieved, + .hatch_detected = true + }; default: env.log("Daycare lady not found."); env.console.overlay().add_log("No daycare lady", COLOR_WHITE); - return num_eggs_retrieved; + return EggFetchResult{ + .num_eggs_retrieved = num_eggs_retrieved, + .hatch_detected = false + }; // OperationFailedException::fire( // ErrorReport::SEND_ERROR_REPORT, // "Cannot detect dialog selection arrow when talking to Nursery lady.", @@ -589,7 +670,10 @@ size_t EggAutonomous::talk_to_lady_to_fetch_egg( ); } - return num_eggs_retrieved; + return EggFetchResult{ + .num_eggs_retrieved = num_eggs_retrieved, + .hatch_detected = false + }; } // After all five eggs hatched and another five eggs deposit into the first column of the box, diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index a4790bafac..f483d5c912 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -22,6 +22,19 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonSwSh{ +enum class EggAutoPhase{ + BIKE_LOOP, + HATCHING, + FLY_RESET, + FETCH_EGG, + +}; + +struct EggFetchResult{ + size_t num_eggs_retrieved; + bool hatch_detected; +}; + class EggAutonomous_Descriptor : public SingleSwitchProgramDescriptor{ public: @@ -50,12 +63,34 @@ class EggAutonomous : public SingleSwitchProgramInstance{ EggAutonomous_Descriptor::Stats& stats ); + + // return true if egg hatching detected during the bike loop + bool run_bike_loop( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context + ); + + void exceed_bike_loop_limit( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + size_t max_bike_loop_count + ); + + // Return updated `num_eggs_hatched` to reflect change in hatched eggs. + size_t hatch_routine( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EggAutonomous_Descriptor::Stats& stats, + size_t num_eggs_hatched + ); + void save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context); // Call flying taxi to reset player character position to Nursery front door. // fly_from_overworld: if true, the game is in the overworld while calling this function. If false, the game is in the menu. // Note: the cursor in the menu must already be at Town Map. - void call_flying_taxi( + // return true if egg hatching detected while trying to open Rotom phone menu + bool call_flying_taxi( SingleSwitchProgramEnvironment& env, ProControllerContext& context, bool fly_from_overworld @@ -71,8 +106,10 @@ class EggAutonomous : public SingleSwitchProgramInstance{ ); // Call this function when standing in front of the lady to fetch one egg. - // Return updated `num_eggs_retrieved` to reflect change in fetched eggs. - size_t talk_to_lady_to_fetch_egg( + // return EggFetchResult, which is a struct of the following: + // - num_eggs_retrieved: updated `num_eggs_retrieved` that reflects the change in fetched eggs. + // - hatch_detected: boolean that is true if hatch was detected. + EggFetchResult talk_to_lady_to_fetch_egg( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EggAutonomous_Descriptor::Stats& stats, From 87d692c24088c723f2edbf6a4cb9a8561251f745 Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 16 Jul 2026 22:02:39 -0700 Subject: [PATCH 02/20] add NUM_EGGS_IN_PARTY, which affects number of eggs fetched on the first batch --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 34 +++++++++++++++---- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 11 +++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index d383743c36..ec485b82c9 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -105,7 +105,20 @@ EggAutonomous::EggAutonomous() 1, 1 ) , NUM_EGGS_IN_COLUMN( - "Num Eggs in Column:
How many eggs already deposited in the first column in Box 1.", + "Num Eggs in Column 1:
How many eggs already deposited in the first column in Box 1.", + { + {0, "0", "0"}, + {1, "1", "1"}, + {2, "2", "2"}, + {3, "3", "3"}, + {4, "4", "4"}, + {5, "5", "5"}, + }, + LockMode::LOCK_WHILE_RUNNING, + 0 + ) + , NUM_EGGS_IN_PARTY( + "Num Eggs in Party:
Number of eggs in your party.", { {0, "0", "0"}, {1, "1", "1"}, @@ -180,6 +193,7 @@ EggAutonomous::EggAutonomous() PA_ADD_OPTION(MAX_KEEPERS); PA_ADD_OPTION(LOOPS_PER_FETCH); PA_ADD_OPTION(NUM_EGGS_IN_COLUMN); + PA_ADD_OPTION(NUM_EGGS_IN_PARTY); PA_ADD_OPTION(AUTO_SAVING); PA_ADD_OPTION(FILTERS0); PA_ADD_OPTION(NOTIFICATIONS); @@ -217,8 +231,10 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo if (AUTO_SAVING == AutoSave::AfterStartAndKeep){ save_game(env, context); m_num_eggs_in_storage_when_game_saved = static_cast(NUM_EGGS_IN_COLUMN.current_value()); + m_num_eggs_in_party_when_game_saved = static_cast(NUM_EGGS_IN_PARTY.current_value()); } m_num_eggs_retrieved = static_cast(NUM_EGGS_IN_COLUMN.current_value()); + m_num_eggs_in_party_at_batch_start = static_cast(NUM_EGGS_IN_PARTY.current_value()); m_num_pokemon_kept = 0; @@ -278,6 +294,7 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo m_player_at_loop_start = false; m_num_eggs_retrieved = m_num_eggs_in_storage_when_game_saved; + m_num_eggs_in_party_at_batch_start = m_num_eggs_in_party_when_game_saved; } } @@ -299,6 +316,8 @@ bool EggAutonomous::run_batch( env.update_stats(); send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); + size_t total_eggs_to_fetch = 10 - m_num_eggs_retrieved - m_num_eggs_in_party_at_batch_start; + EggAutoPhase phase = EggAutoPhase::BIKE_LOOP; if (m_player_at_loop_start == false){ // reset position phase = EggAutoPhase::FLY_RESET; @@ -314,7 +333,7 @@ bool EggAutonomous::run_batch( // Each iteration in the while-loop is made by: // - bike loops of LOOPS_PER_FETCH times. Bike loops begin at lady or nursery front door, end at lady. // - if not enough eggs fetched, talk to lady to try fetching an egg. - while (num_eggs_hatched < 5 || m_num_eggs_retrieved < 5){ + while (num_eggs_hatched < 5 || m_num_eggs_retrieved < total_eggs_to_fetch){ // NOTE: the egg hatching detector cannot be constantly running, // since it only detects the black dialog box, and speaking to the lady will also produce a black dialog box. @@ -340,7 +359,7 @@ bool EggAutonomous::run_batch( if (num_loops_since_last_fetch_attempt < LOOPS_PER_FETCH){ phase = EggAutoPhase::BIKE_LOOP; // repeat bike loop }else{ - if (m_num_eggs_retrieved < 5){ + if (m_num_eggs_retrieved < total_eggs_to_fetch){ phase = EggAutoPhase::FETCH_EGG; }else{ // done retrieving eggs @@ -369,7 +388,7 @@ bool EggAutonomous::run_batch( } case EggAutoPhase::FETCH_EGG:{ env.console.log("Talk to lady to fetch egg."); - EggFetchResult fetch_result = talk_to_lady_to_fetch_egg(env, context, stats, m_num_eggs_retrieved); + EggFetchResult fetch_result = talk_to_lady_to_fetch_egg(env, context, stats, m_num_eggs_retrieved, total_eggs_to_fetch); if (fetch_result.hatch_detected){ phase = EggAutoPhase::HATCHING; }else{ @@ -395,6 +414,7 @@ bool EggAutonomous::run_batch( return true; } m_num_eggs_retrieved = 0; + m_num_eggs_in_party_at_batch_start = 5; // after process_hatched_pokemon(), the player location is at the start of bike loop m_player_at_loop_start = true; @@ -415,6 +435,7 @@ bool EggAutonomous::run_batch( if (save){ save_game(env, context); m_num_eggs_in_storage_when_game_saved = 0; + m_num_eggs_in_party_when_game_saved = 5; } context.wait_for_all_requests(); @@ -578,7 +599,8 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EggAutonomous_Descriptor::Stats& stats, - size_t num_eggs_retrieved + size_t num_eggs_retrieved, + size_t total_eggs_to_fetch ){ env.log("Fetching egg"); stats.m_fetch_attempts++; @@ -609,7 +631,7 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( case 0: ++num_eggs_retrieved; env.log("Found egg"); - env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/5", COLOR_WHITE); + env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); stats.m_fetch_success++; env.update_stats(); // Press A to get the egg diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index f483d5c912..f50055c470 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -113,7 +113,8 @@ class EggAutonomous : public SingleSwitchProgramInstance{ SingleSwitchProgramEnvironment& env, ProControllerContext& context, EggAutonomous_Descriptor::Stats& stats, - size_t num_eggs_retrieved + size_t num_eggs_retrieved, + size_t num_eggs_to_fetch ); // After all five eggs hatched and another five eggs deposit into the first column of the box, @@ -149,6 +150,7 @@ class EggAutonomous : public SingleSwitchProgramInstance{ SimpleIntegerOption MAX_KEEPERS; SimpleIntegerOption LOOPS_PER_FETCH; IntegerEnumDropdownOption NUM_EGGS_IN_COLUMN; + IntegerEnumDropdownOption NUM_EGGS_IN_PARTY; enum class AutoSave{ NoAutoSave, @@ -174,9 +176,16 @@ class EggAutonomous : public SingleSwitchProgramInstance{ // How many eggs have been placed behind a game save. // This is used so that if we recover from an error, we know how many eggs are in storage. size_t m_num_eggs_in_storage_when_game_saved = 0; + + // This is used so that if we recover from an error, we know how many eggs are in the party + size_t m_num_eggs_in_party_when_game_saved = 0; + // How many eggs are already deposited to storage so far. size_t m_num_eggs_retrieved = 0; + // number of eggs in party at the start of run_batch() + size_t m_num_eggs_in_party_at_batch_start = 0; + // Is player's location at the bike loop start bool m_player_at_loop_start = false; }; From d18348879e32e9a793e54dad82bb0bdbfaef4c2c Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 16 Jul 2026 22:45:40 -0700 Subject: [PATCH 03/20] add SwSh::save_game() --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 46 +++---------------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 10 +--- 2 files changed, 8 insertions(+), 48 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index ec485b82c9..783c6a7473 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -229,7 +229,7 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo if (AUTO_SAVING == AutoSave::AfterStartAndKeep){ - save_game(env, context); + save_game(env.console, context); m_num_eggs_in_storage_when_game_saved = static_cast(NUM_EGGS_IN_COLUMN.current_value()); m_num_eggs_in_party_when_game_saved = static_cast(NUM_EGGS_IN_PARTY.current_value()); } @@ -248,7 +248,7 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo if (TOUCH_DATE_INTERVAL.ok_to_touch_now()){ env.log("Touching date to prevent rollover."); env.console.overlay().add_log("Touching date", COLOR_WHITE); - pbf_press_button(context, BUTTON_HOME, 160ms, GameSettings::instance().GAME_TO_HOME_DELAY_SAFE0); + go_home(env.console, context); touch_date_from_home(env.console, context, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0); resume_game_no_interact(env.console, context, ConsoleSettings::instance().TOLERATE_SYSTEM_UPDATE_MENU_FAST); } @@ -285,7 +285,7 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo env.console ); } - ssf_press_button(context, BUTTON_HOME, GameSettings::instance().GAME_TO_HOME_DELAY_SAFE0, 160ms); + go_home(env.console, context); env.console.overlay().add_log("Reset game", COLOR_WHITE); reset_game_from_home_with_inference( env.console, context, @@ -433,7 +433,7 @@ bool EggAutonomous::run_batch( } if (save){ - save_game(env, context); + save_game(env.console, context); m_num_eggs_in_storage_when_game_saved = 0; m_num_eggs_in_party_when_game_saved = 5; } @@ -475,7 +475,7 @@ void EggAutonomous::exceed_bike_loop_limit( env.console.log("Take a screenshot of party to debug."); // Now take a photo at the player's party for dumping debug info: // Enter Rotom Phone menu - pbf_press_button(context, BUTTON_X, 160ms, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0); + menus_to_mainmenu(env.console, context); // Select Pokemon App navigate_to_menu_app(env.console, context, POKEMON_APP_INDEX); // From menu enter Pokemon App @@ -524,15 +524,6 @@ size_t EggAutonomous::hatch_routine( return num_eggs_hatched; } -void EggAutonomous::save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - context.wait_for_all_requests(); - env.log("Save game."); - env.console.overlay().add_log("Save game", COLOR_WHITE); - pbf_press_button(context, BUTTON_X, 80ms, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0); - pbf_press_button(context, BUTTON_R, 80ms, 2000ms); - pbf_mash_button(context, BUTTON_A, 500ms); - mash_B_until_y_comm_icon(env, context, "Cannot detect end of saving game."); -} bool EggAutonomous::call_flying_taxi( SingleSwitchProgramEnvironment& env, @@ -549,7 +540,7 @@ bool EggAutonomous::call_flying_taxi( if (fly_from_overworld){ // Open menu env.log("Fly from overworld to reset position"); - ssf_press_button(context, BUTTON_X, GameSettings::instance().OVERWORLD_TO_MENU_DELAY0, 160ms); + menus_to_mainmenu(env.console, context); }else{ env.log("Fly from menu to reset position"); } @@ -564,7 +555,7 @@ bool EggAutonomous::call_flying_taxi( env.console.log("Hatching detected while trying to call flying taxi."); }else { fly_home(context, false); - mash_B_until_y_comm_icon(env, context, "Cannot detect end of flying taxi animation."); + mash_B_until_y_comm_icon(env.console, context, "Cannot detect end of flying taxi animation."); } return hatch_detected; @@ -960,29 +951,6 @@ bool EggAutonomous::process_hatched_pokemon( return false; } -void EggAutonomous::mash_B_until_y_comm_icon( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - const std::string& error_msg -){ - context.wait_for_all_requests(); - const bool y_comm_visible = true; - YCommIconWatcher y_comm_detector(COLOR_RED, y_comm_visible); - int ret = run_until( - env.console, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 10s); - }, - {y_comm_detector} - ); - if (ret != 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - error_msg + " No Y-Comm mark found.", - env.console - ); - } -} diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index f50055c470..b22ae7badf 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -84,8 +84,6 @@ class EggAutonomous : public SingleSwitchProgramInstance{ size_t num_eggs_hatched ); - void save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - // Call flying taxi to reset player character position to Nursery front door. // fly_from_overworld: if true, the game is in the overworld while calling this function. If false, the game is in the menu. // Note: the cursor in the menu must already be at Town Map. @@ -131,13 +129,7 @@ class EggAutonomous : public SingleSwitchProgramInstance{ bool need_taxi ); - // Used to wait until Y-Comm icon shows up. - // Throw error if it does not find it after 10 sec. - void mash_B_until_y_comm_icon( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - const std::string& error_msg - ); + StartInGripOrGameOption START_LOCATION; TouchDateIntervalOption TOUCH_DATE_INTERVAL; From d9f5c585b2c91ba61901a46dc2c61522db9a3b06 Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 16 Jul 2026 23:09:03 -0700 Subject: [PATCH 04/20] add watchers to confirm starting conditions for routines --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 71 +++++++++++++++++-- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 783c6a7473..065577aa40 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -353,6 +353,7 @@ bool EggAutonomous::run_batch( ++num_loops_since_last_fetch_attempt; ++total_bike_loop_count; if (total_bike_loop_count >= MAX_BIKE_LOOP_COUNT){ + // throw exception exceed_bike_loop_limit(env, context, MAX_BIKE_LOOP_COUNT); } @@ -446,9 +447,30 @@ bool EggAutonomous::run_bike_loop( SingleSwitchProgramEnvironment& env, ProControllerContext& context ){ - - // use Black dialog box detector as proxy for detecting when an egg is hatching + // confirm we are starting in the overworld BlackDialogBoxWatcher2 egg_hatching_detector; + YCommIconWatcher y_comm_detector(COLOR_RED, true); + int ret0 = wait_until( + env.console, context, + std::chrono::seconds(5), + { + egg_hatching_detector, + y_comm_detector, + } + ); + if (ret0 == 0){ + env.console.log("Hatching detected at start of bike loop."); + return true; + } + if (ret0 < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "run_bike_loop: We expected to start in the overworld, but overworld not detected.", + env.console + ); + } + + int ret = run_until( env.console, context, [](ProControllerContext& context){ @@ -496,8 +518,22 @@ size_t EggAutonomous::hatch_routine( EggAutonomous_Descriptor::Stats& stats, size_t num_eggs_hatched ){ - // use Black dialog box detector as proxy for detecting when an egg is hatching + // confirm we are starting with the hatching screen BlackDialogBoxWatcher2 egg_hatching_detector; + int ret0 = wait_until( + env.console, context, + std::chrono::seconds(5), + { + egg_hatching_detector, + } + ); + if (ret0 < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "hatch_routine: We expected to see a hatching egg, but no hatching detected.", + env.console + ); + } int ret = -1; do{ @@ -596,10 +632,35 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( env.log("Fetching egg"); stats.m_fetch_attempts++; env.update_stats(); - // collect_egg(context); + + // confirm we are starting in the overworld + BlackDialogBoxWatcher2 egg_hatching_detector; + YCommIconWatcher y_comm_detector(COLOR_RED, true); + int ret0 = wait_until( + env.console, context, + std::chrono::seconds(5), + { + egg_hatching_detector, + y_comm_detector, + } + ); + if (ret0 == 0){ + env.console.log("Hatching detected at start of talking to lady."); + EggFetchResult{ + .num_eggs_retrieved = num_eggs_retrieved, + .hatch_detected = true + }; + } + if (ret0 < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "talk_to_lady_to_fetch_egg: We expected to start in the overworld, but overworld not detected.", + env.console + ); + } + RetrieveEggArrowFinder egg_arrow_detector(env.console); CheckNurseryArrowFinder no_egg_arrow_detector(env.console); - BlackDialogBoxWatcher2 egg_hatching_detector; int ret = run_until( env.console, context, From e719737f1f9bcc565beccb1e3248799d5f0c95bb Mon Sep 17 00:00:00 2001 From: jw098 Date: Fri, 17 Jul 2026 20:17:54 -0700 Subject: [PATCH 05/20] refactor talk_to_lady_to_fetch_egg --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 190 +++++++++--------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 6 +- 2 files changed, 94 insertions(+), 102 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 065577aa40..12cedce952 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -389,11 +389,17 @@ bool EggAutonomous::run_batch( } case EggAutoPhase::FETCH_EGG:{ env.console.log("Talk to lady to fetch egg."); - EggFetchResult fetch_result = talk_to_lady_to_fetch_egg(env, context, stats, m_num_eggs_retrieved, total_eggs_to_fetch); + EggFetchResult fetch_result = talk_to_lady_to_fetch_egg(env, context, stats); if (fetch_result.hatch_detected){ phase = EggAutoPhase::HATCHING; }else{ - m_num_eggs_retrieved = fetch_result.num_eggs_retrieved; + if(fetch_result.found_egg){ + m_num_eggs_retrieved++; + env.log("Found egg " + std::to_string(m_num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); + env.console.overlay().add_log("Found egg " + std::to_string(m_num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); + stats.m_fetch_success++; + env.update_stats(); + } // if spoke to lady, back to the bike loop regardless of the success of the egg fetch num_loops_since_last_fetch_attempt = 0; @@ -625,118 +631,106 @@ void EggAutonomous::wait_for_egg_hatched( EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EggAutonomous_Descriptor::Stats& stats, - size_t num_eggs_retrieved, - size_t total_eggs_to_fetch + EggAutonomous_Descriptor::Stats& stats ){ env.log("Fetching egg"); stats.m_fetch_attempts++; env.update_stats(); - // confirm we are starting in the overworld - BlackDialogBoxWatcher2 egg_hatching_detector; - YCommIconWatcher y_comm_detector(COLOR_RED, true); - int ret0 = wait_until( - env.console, context, - std::chrono::seconds(5), - { - egg_hatching_detector, - y_comm_detector, + YCommIconWatcher overworld; + RetrieveEggArrowFinder egg_arrow_detector(env.console); + CheckNurseryArrowFinder no_egg_arrow_detector(env.console); + WhiteDialogBoxWatcher white_dialog; + BlackDialogBoxWatcher2 black_dialog; + + size_t seen_overworld = 0; + bool egg_status_known = false; + bool found_egg = false; + + WallClock deadline = current_time() + std::chrono::minutes(2); + while(!egg_status_known && current_time() < deadline){ + context.wait_for_all_requests(); + int ret = wait_until( + env.console, context, + std::chrono::seconds(30), + { + overworld, + egg_arrow_detector, + black_dialog, + white_dialog, + no_egg_arrow_detector, + } + ); + switch (ret){ + case 0: // overworld + env.log("Detected Overworld...", COLOR_BLUE); + seen_overworld++; + if (seen_overworld > 10){ + // No NPC found + env.log("Stuck in Overworld. Daycare lady not found.", COLOR_BLUE); + return EggFetchResult{ + .found_egg = false, + .hatch_detected = false + }; + } + pbf_press_button(context, BUTTON_A, 160ms, 100ms); + continue; + case 1: // egg_arrow_detector + env.log("Found egg"); + found_egg = true; + // Press A to get the egg + ssf_press_button(context, BUTTON_A, 320ms, 160ms); + continue; + case 2: // black_dialog + if (found_egg){ + env.log("Received egg"); + egg_status_known = true; // break the loop. then mash B + + }else{ // this black dialog might actually be a hatching egg. + env.log("Hatching detected while trying to talk to lady to fetch egg."); + return EggFetchResult{ + .found_egg = false, + .hatch_detected = true + }; + } + continue; + case 3: // white_dialog + env.log("Detected dialog box...", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 160ms, 40ms); + continue; + case 4: // no_egg_arrow_detector + env.log("No egg"); + env.console.overlay().add_log("No egg", COLOR_WHITE); + found_egg = false; + egg_status_known = true; // break the loop. then mash B + continue; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "talk_to_lady_to_fetch_egg(): No recognized state after 30 seconds.", + env.console + ); } - ); - if (ret0 == 0){ - env.console.log("Hatching detected at start of talking to lady."); - EggFetchResult{ - .num_eggs_retrieved = num_eggs_retrieved, - .hatch_detected = true - }; } - if (ret0 < 0){ + + if (!egg_status_known){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "talk_to_lady_to_fetch_egg: We expected to start in the overworld, but overworld not detected.", + "talk_to_lady_to_fetch_egg(): Unable to speak to lady after 2 minutes.", env.console ); } - RetrieveEggArrowFinder egg_arrow_detector(env.console); - CheckNurseryArrowFinder no_egg_arrow_detector(env.console); - - int ret = run_until( + // we know if the egg was found or not + // mash b to return to overworld + int ret2 = run_until( env.console, context, [](ProControllerContext& context){ - for (size_t i_hatched = 0; i_hatched < 2; i_hatched++){ - pbf_press_button(context, BUTTON_A, 160ms, 1200ms); - } - pbf_wait(context, 1600ms); + pbf_mash_button(context, BUTTON_B, 30s); }, - { - egg_arrow_detector, - no_egg_arrow_detector, - egg_hatching_detector, - } + {{overworld}} ); - - const bool y_comm_visible_at_end_of_dialog = true; - YCommIconWatcher dialog_over_detector(COLOR_RED, y_comm_visible_at_end_of_dialog); - switch (ret){ - case 0: - ++num_eggs_retrieved; - env.log("Found egg"); - env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); - stats.m_fetch_success++; - env.update_stats(); - // Press A to get the egg - ssf_press_button(context, BUTTON_A, 320ms, 160ms); - - ret = run_until( - env.console, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 30s); - }, - {{dialog_over_detector}} - ); - break; - - case 1: - env.log("No egg"); - env.console.overlay().add_log("No egg", COLOR_WHITE); - run_until( - env.console, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 5s); - }, - {{dialog_over_detector}} - ); - return EggFetchResult{ - .num_eggs_retrieved = num_eggs_retrieved, - .hatch_detected = false - }; -// break; - case 2: - env.log("Hatching detected while trying to talk to lady to fetch egg."); - - return EggFetchResult{ - .num_eggs_retrieved = num_eggs_retrieved, - .hatch_detected = true - }; - - default: - env.log("Daycare lady not found."); - env.console.overlay().add_log("No daycare lady", COLOR_WHITE); - return EggFetchResult{ - .num_eggs_retrieved = num_eggs_retrieved, - .hatch_detected = false - }; -// OperationFailedException::fire( -// ErrorReport::SEND_ERROR_REPORT, -// "Cannot detect dialog selection arrow when talking to Nursery lady.", -// env.console -// ); - } - - // If dialog over is not detected: - if (ret < 0){ + if (ret2 < 0){ // If dialog over is not detected: OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Cannot detect end of Nursery lady dialog. No Y-Comm mark found.", @@ -745,7 +739,7 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( } return EggFetchResult{ - .num_eggs_retrieved = num_eggs_retrieved, + .found_egg = found_egg, .hatch_detected = false }; } diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index b22ae7badf..3b06967e50 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -31,7 +31,7 @@ enum class EggAutoPhase{ }; struct EggFetchResult{ - size_t num_eggs_retrieved; + bool found_egg; bool hatch_detected; }; @@ -110,9 +110,7 @@ class EggAutonomous : public SingleSwitchProgramInstance{ EggFetchResult talk_to_lady_to_fetch_egg( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EggAutonomous_Descriptor::Stats& stats, - size_t num_eggs_retrieved, - size_t num_eggs_to_fetch + EggAutonomous_Descriptor::Stats& stats ); // After all five eggs hatched and another five eggs deposit into the first column of the box, From 5d911cfbd9f28b0fc907b2519f23d46573c0e148 Mon Sep 17 00:00:00 2001 From: jw098 Date: Fri, 17 Jul 2026 20:26:38 -0700 Subject: [PATCH 06/20] refactor run_bike_loop --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 12cedce952..bbc696f2e7 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -453,44 +453,38 @@ bool EggAutonomous::run_bike_loop( SingleSwitchProgramEnvironment& env, ProControllerContext& context ){ - // confirm we are starting in the overworld - BlackDialogBoxWatcher2 egg_hatching_detector; - YCommIconWatcher y_comm_detector(COLOR_RED, true); - int ret0 = wait_until( - env.console, context, - std::chrono::seconds(5), - { - egg_hatching_detector, - y_comm_detector, - } - ); - if (ret0 == 0){ - env.console.log("Hatching detected at start of bike loop."); - return true; - } - if (ret0 < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "run_bike_loop: We expected to start in the overworld, but overworld not detected.", - env.console - ); - } - - + YCommIconWatcher no_overworld(COLOR_RED, false); int ret = run_until( env.console, context, [](ProControllerContext& context){ travel_to_spin_location(context); travel_back_to_lady(context); }, - {{egg_hatching_detector}} + {{no_overworld}} ); - bool hatch_detected = ret == 0; - if (hatch_detected){ - env.console.log("Hatching detected during bike loop."); + if (ret == 0){ // no overworld detected. Check if we find egg hatching. + BlackDialogBoxWatcher2 egg_hatching_detector; + int ret2 = wait_until( + env.console, context, + std::chrono::seconds(10), + { + egg_hatching_detector, + } + ); + if (ret2 == 0){ + env.console.log("Hatching detected during bike loop."); + return true; + }else{ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "run_bike_loop: No recognized state after 10 seconds.", + env.console + ); + } } - return hatch_detected; + + return false; } void EggAutonomous::exceed_bike_loop_limit( From 98fb1dba46e5a4cf8ebc4dc12ac293972658a884 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sat, 18 Jul 2026 11:45:44 -0700 Subject: [PATCH 07/20] fix bike loop routine --- .../Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index bbc696f2e7..c00fb31891 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -457,8 +457,8 @@ bool EggAutonomous::run_bike_loop( int ret = run_until( env.console, context, [](ProControllerContext& context){ - travel_to_spin_location(context); - travel_back_to_lady(context); + travel_to_spin_location2(context); + travel_back_to_lady2(context); }, {{no_overworld}} ); From eb2a8a0ad53690d22195087aaf3369a80f5bbd4b Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 19 Jul 2026 09:47:12 -0700 Subject: [PATCH 08/20] add EmptySlotDetector. add check_box() --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 110 ++++++++++++++++++ .../EggPrograms/PokemonSwSh_EggAutonomous.h | 17 +++ 2 files changed, 127 insertions(+) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index c00fb31891..e333f8de64 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -18,6 +18,7 @@ #include "Pokemon/Pokemon_Notification.h" #include "Pokemon/Pokemon_Strings.h" #include "PokemonSwSh/Commands/PokemonSwSh_Commands_DateSpam.h" +#include "PokemonSwSh/Inference/PokemonSwSh_BoxEmptySlotDetector.h" #include "PokemonSwSh/Inference/PokemonSwSh_BoxGenderDetector.h" #include "PokemonSwSh/Inference/PokemonSwSh_BoxShinySymbolDetector.h" #include "PokemonSwSh/Inference/PokemonSwSh_DialogBoxDetector.h" @@ -1002,6 +1003,115 @@ bool EggAutonomous::process_hatched_pokemon( +size_t check_box(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + context.wait_for(500ms); + + auto screen = stream.video().snapshot(); + check_box_filled(stream, screen); + check_non_egg_lead(stream, screen); + size_t eggs_in_party = count_eggs_in_party(stream, screen); + size_t eggs_in_col_0_box = count_eggs_in_first_box_column(stream, screen); + + + return eggs_in_party + eggs_in_col_0_box; +} + + +void check_box_filled(VideoStream& stream, const ImageViewRGB32& screen){ + for (uint8_t row = 0; row < 5; row++){ + for (uint8_t column = 1; column < 6; column++){ + BoxEmptySlotDetector slot(SlotLocation::BOX, row, column); + bool is_empty = slot.detect(screen); + // stream.log("row " + std::to_string(row) + " col " + std::to_string(column) + (is_empty ? " is_empty" : " not empty")); + if (is_empty){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "check_box_filled: Box is not filled.", + stream + ); + } + + } + } +} + +void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen){ + BoxEmptySlotDetector slot(SlotLocation::PARTY, 0, 0); + bool is_empty = slot.detect(screen); + if (is_empty){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "check_non_egg_lead: Detected an empty lead slot in party. This shouldn't be possible.", + stream + ); + } + + BoxEggDetector egg(SlotLocation::PARTY, 0); + bool is_egg = egg.detect(screen); + if (is_egg){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "check_non_egg_lead: Detected an egg in lead slot of party.", + stream + ); + } +} + +size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen){ + size_t num_empty = 0; + size_t num_eggs = 0; + for (uint8_t row = 1; row < 6; row++){ + BoxEmptySlotDetector slot(SlotLocation::PARTY, row, 0); + bool is_empty = slot.detect(screen); + if (is_empty) { num_empty++; } + + BoxEggDetector egg(SlotLocation::PARTY, row); + bool is_egg = egg.detect(screen); + if (is_egg) { num_eggs++; } + } + + if (num_empty + num_eggs != 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "count_eggs_in_party: Total number of eggs and empty slots in the party don't add up to 5. " + "Ensure your only non-egg Pokemon is in the lead. " + "The rest of the slots in the team should either be empty or an egg.", + stream + ); + } + + return num_eggs; +} + +size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen){ + + size_t num_empty = 0; + size_t num_eggs = 0; + for (uint8_t row = 0; row < 5; row++){ + BoxEmptySlotDetector slot(SlotLocation::BOX, row, 0); + bool is_empty = slot.detect(screen); + if (is_empty) { num_empty++; } + + BoxEggDetector egg(SlotLocation::BOX, row); + bool is_egg = egg.detect(screen); + if (is_egg) { num_eggs++; } + } + + if (num_empty + num_eggs != 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "count_eggs_in_first_box_column: Total number of eggs and empty slots in the first box column don't add up to 5. " + "Ensure thre are no non-egg Pokemon in the first box column.", + stream + ); + } + + return num_eggs; +} + + + } diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index 3b06967e50..52df74a9c7 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -180,6 +180,23 @@ class EggAutonomous : public SingleSwitchProgramInstance{ bool m_player_at_loop_start = false; }; + +// Starting within the box, confirm that the lead is not an egg, and that the box is full except for the first column +// return number of eggs in both the party and the first box column +size_t check_box(VideoStream& stream, ProControllerContext& context); + +// ensure that all rows/columns are filled except the first column +// ASSUMES: the cursor should NOT be on the box, with the exception that it can be at the top row. +// This is because it pop-up text otherwise covers up the box slot, preventing proper detection +void check_box_filled(VideoStream& stream, const ImageViewRGB32& screen); + +// ensure that the lead pokemon in the party is not an egg +void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen); + +size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen); + +size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen); + } } From 0aef9edd6898668fbd61ab2f1a7d4c69e9299b1a Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 19 Jul 2026 10:26:25 -0700 Subject: [PATCH 09/20] run check_box() at the start of the program, and after every process_hatched_pokemon() --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 76 +++++++++---------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 39 +++++----- 2 files changed, 60 insertions(+), 55 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index e333f8de64..cb77b44fae 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -105,32 +105,6 @@ EggAutonomous::EggAutonomous() LockMode::LOCK_WHILE_RUNNING, 1, 1 ) - , NUM_EGGS_IN_COLUMN( - "Num Eggs in Column 1:
How many eggs already deposited in the first column in Box 1.", - { - {0, "0", "0"}, - {1, "1", "1"}, - {2, "2", "2"}, - {3, "3", "3"}, - {4, "4", "4"}, - {5, "5", "5"}, - }, - LockMode::LOCK_WHILE_RUNNING, - 0 - ) - , NUM_EGGS_IN_PARTY( - "Num Eggs in Party:
Number of eggs in your party.", - { - {0, "0", "0"}, - {1, "1", "1"}, - {2, "2", "2"}, - {3, "3", "3"}, - {4, "4", "4"}, - {5, "5", "5"}, - }, - LockMode::LOCK_WHILE_RUNNING, - 0 - ) , AUTO_SAVING( "Auto-Saving:
Automatically save the game to recover from crashes and allow eggs to be unhatched.
" "(Unhatching eggs can be useful for obtaining breeding parents by rehatching a perfect egg in a game with a different language.)

" @@ -193,8 +167,6 @@ EggAutonomous::EggAutonomous() PA_ADD_OPTION(LANGUAGE); PA_ADD_OPTION(MAX_KEEPERS); PA_ADD_OPTION(LOOPS_PER_FETCH); - PA_ADD_OPTION(NUM_EGGS_IN_COLUMN); - PA_ADD_OPTION(NUM_EGGS_IN_PARTY); PA_ADD_OPTION(AUTO_SAVING); PA_ADD_OPTION(FILTERS0); PA_ADD_OPTION(NOTIFICATIONS); @@ -228,14 +200,20 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo return; } + menus_to_boxsystem(env.console, context); + EggQuantity egg_quantity = check_box(env.console, context); + size_t num_eggs_in_column_0 = egg_quantity.eggs_in_column_0; + size_t num_eggs_in_party = egg_quantity.eggs_in_party; + env.log("Starting with " + std::to_string(num_eggs_in_party) + " eggs in the party and " + + std::to_string(num_eggs_in_column_0) + " eggs in the first box column."); if (AUTO_SAVING == AutoSave::AfterStartAndKeep){ save_game(env.console, context); - m_num_eggs_in_storage_when_game_saved = static_cast(NUM_EGGS_IN_COLUMN.current_value()); - m_num_eggs_in_party_when_game_saved = static_cast(NUM_EGGS_IN_PARTY.current_value()); + m_num_eggs_in_storage_when_game_saved = num_eggs_in_column_0; + m_num_eggs_in_party_when_game_saved = num_eggs_in_party; } - m_num_eggs_retrieved = static_cast(NUM_EGGS_IN_COLUMN.current_value()); - m_num_eggs_in_party_at_batch_start = static_cast(NUM_EGGS_IN_PARTY.current_value()); + m_num_eggs_retrieved = num_eggs_in_column_0; + m_num_eggs_in_party_at_batch_start = num_eggs_in_party; m_num_pokemon_kept = 0; @@ -972,6 +950,28 @@ bool EggAutonomous::process_hatched_pokemon( // Press A to finish dropping the egg column ssf_press_button_ptv(context, BUTTON_A, BOX_PICKUP_DROP_DELAY, EGG_BUTTON_HOLD_DELAY); + // After processing the box: + // Confirm that Box Column 0 is empty, and the party is full of eggs + EggQuantity egg_quantity = check_box(env.console, context); + size_t num_eggs_in_column_0 = egg_quantity.eggs_in_column_0; + size_t num_eggs_in_party = egg_quantity.eggs_in_party; + + if (num_eggs_in_column_0 != 0 ){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "process_hatched_pokemon: Did not end up with an empty first box column, after processing.", + env.console + ); + } + + if (num_eggs_in_party != 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "process_hatched_pokemon: Did not end up with a party full of 5 eggs, after processing.", + env.console + ); + } + // Back out to menu. menus_to_mainmenu(env.console, context); @@ -1003,7 +1003,7 @@ bool EggAutonomous::process_hatched_pokemon( -size_t check_box(VideoStream& stream, ProControllerContext& context){ +EggQuantity EggAutonomous::check_box(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); context.wait_for(500ms); @@ -1014,11 +1014,11 @@ size_t check_box(VideoStream& stream, ProControllerContext& context){ size_t eggs_in_col_0_box = count_eggs_in_first_box_column(stream, screen); - return eggs_in_party + eggs_in_col_0_box; + return {eggs_in_party, eggs_in_col_0_box}; } -void check_box_filled(VideoStream& stream, const ImageViewRGB32& screen){ +void EggAutonomous::check_box_filled(VideoStream& stream, const ImageViewRGB32& screen){ for (uint8_t row = 0; row < 5; row++){ for (uint8_t column = 1; column < 6; column++){ BoxEmptySlotDetector slot(SlotLocation::BOX, row, column); @@ -1036,7 +1036,7 @@ void check_box_filled(VideoStream& stream, const ImageViewRGB32& screen){ } } -void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen){ +void EggAutonomous::check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen){ BoxEmptySlotDetector slot(SlotLocation::PARTY, 0, 0); bool is_empty = slot.detect(screen); if (is_empty){ @@ -1058,7 +1058,7 @@ void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen){ } } -size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen){ +size_t EggAutonomous::count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen){ size_t num_empty = 0; size_t num_eggs = 0; for (uint8_t row = 1; row < 6; row++){ @@ -1084,7 +1084,7 @@ size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen){ return num_eggs; } -size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen){ +size_t EggAutonomous::count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen){ size_t num_empty = 0; size_t num_eggs = 0; diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index 52df74a9c7..b754bd722a 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -35,6 +35,11 @@ struct EggFetchResult{ bool hatch_detected; }; +struct EggQuantity{ + size_t eggs_in_party; + size_t eggs_in_column_0; +}; + class EggAutonomous_Descriptor : public SingleSwitchProgramDescriptor{ public: @@ -127,6 +132,23 @@ class EggAutonomous : public SingleSwitchProgramInstance{ bool need_taxi ); + // Starting within the box, confirm that the lead is not an egg, and that the box is full except for the first column + // return quantity of eggs in both the party and the first box column + EggQuantity check_box(VideoStream& stream, ProControllerContext& context); + + // ensure that all rows/columns are filled except the first column + // ASSUMES: the cursor should NOT be on the box, with the exception that it can be at the top row. + // This is because it pop-up text otherwise covers up the box slot, preventing proper detection + void check_box_filled(VideoStream& stream, const ImageViewRGB32& screen); + + // ensure that the lead pokemon in the party is not an egg + void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen); + + size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen); + + size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen); + + StartInGripOrGameOption START_LOCATION; @@ -139,8 +161,6 @@ class EggAutonomous : public SingleSwitchProgramInstance{ SimpleIntegerOption MAX_KEEPERS; SimpleIntegerOption LOOPS_PER_FETCH; - IntegerEnumDropdownOption NUM_EGGS_IN_COLUMN; - IntegerEnumDropdownOption NUM_EGGS_IN_PARTY; enum class AutoSave{ NoAutoSave, @@ -181,21 +201,6 @@ class EggAutonomous : public SingleSwitchProgramInstance{ }; -// Starting within the box, confirm that the lead is not an egg, and that the box is full except for the first column -// return number of eggs in both the party and the first box column -size_t check_box(VideoStream& stream, ProControllerContext& context); - -// ensure that all rows/columns are filled except the first column -// ASSUMES: the cursor should NOT be on the box, with the exception that it can be at the top row. -// This is because it pop-up text otherwise covers up the box slot, preventing proper detection -void check_box_filled(VideoStream& stream, const ImageViewRGB32& screen); - -// ensure that the lead pokemon in the party is not an egg -void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen); - -size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen); - -size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen); } From 658a0cffcb6985ec1ca87036ecd749e97cd39cf0 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 19 Jul 2026 17:15:26 -0700 Subject: [PATCH 10/20] fix initialization of num_eggs_retrieved and total_eggs_to_fetch --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 23 +++++++++++-------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index cb77b44fae..544716e587 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -212,7 +212,7 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo m_num_eggs_in_storage_when_game_saved = num_eggs_in_column_0; m_num_eggs_in_party_when_game_saved = num_eggs_in_party; } - m_num_eggs_retrieved = num_eggs_in_column_0; + num_eggs_in_column_0_at_batch_start = num_eggs_in_column_0; m_num_eggs_in_party_at_batch_start = num_eggs_in_party; m_num_pokemon_kept = 0; @@ -272,7 +272,7 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo ); m_player_at_loop_start = false; - m_num_eggs_retrieved = m_num_eggs_in_storage_when_game_saved; + num_eggs_in_column_0_at_batch_start = m_num_eggs_in_storage_when_game_saved; m_num_eggs_in_party_at_batch_start = m_num_eggs_in_party_when_game_saved; } } @@ -295,7 +295,8 @@ bool EggAutonomous::run_batch( env.update_stats(); send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); - size_t total_eggs_to_fetch = 10 - m_num_eggs_retrieved - m_num_eggs_in_party_at_batch_start; + size_t total_eggs_to_fetch = 10 - num_eggs_in_column_0_at_batch_start - m_num_eggs_in_party_at_batch_start; + size_t num_eggs_retrieved = 0; EggAutoPhase phase = EggAutoPhase::BIKE_LOOP; if (m_player_at_loop_start == false){ // reset position @@ -312,7 +313,9 @@ bool EggAutonomous::run_batch( // Each iteration in the while-loop is made by: // - bike loops of LOOPS_PER_FETCH times. Bike loops begin at lady or nursery front door, end at lady. // - if not enough eggs fetched, talk to lady to try fetching an egg. - while (num_eggs_hatched < 5 || m_num_eggs_retrieved < total_eggs_to_fetch){ + while (num_eggs_hatched < 5 || num_eggs_retrieved < total_eggs_to_fetch){ + env.log("Eggs hatched: " + std::to_string(num_eggs_hatched) + + ". Eggs fetched: " + std::to_string(num_eggs_retrieved)); // NOTE: the egg hatching detector cannot be constantly running, // since it only detects the black dialog box, and speaking to the lady will also produce a black dialog box. @@ -339,7 +342,7 @@ bool EggAutonomous::run_batch( if (num_loops_since_last_fetch_attempt < LOOPS_PER_FETCH){ phase = EggAutoPhase::BIKE_LOOP; // repeat bike loop }else{ - if (m_num_eggs_retrieved < total_eggs_to_fetch){ + if (num_eggs_retrieved < total_eggs_to_fetch){ phase = EggAutoPhase::FETCH_EGG; }else{ // done retrieving eggs @@ -352,6 +355,8 @@ bool EggAutonomous::run_batch( case EggAutoPhase::HATCHING:{ env.console.log("Hatching egg."); num_eggs_hatched = hatch_routine(env, context, stats, num_eggs_hatched); + env.log("Hatched eggs " + std::to_string(num_eggs_hatched) + "/5", COLOR_WHITE); + env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); phase = EggAutoPhase::FLY_RESET; continue; } @@ -373,9 +378,9 @@ bool EggAutonomous::run_batch( phase = EggAutoPhase::HATCHING; }else{ if(fetch_result.found_egg){ - m_num_eggs_retrieved++; - env.log("Found egg " + std::to_string(m_num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); - env.console.overlay().add_log("Found egg " + std::to_string(m_num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); + num_eggs_retrieved++; + env.log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); + env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); stats.m_fetch_success++; env.update_stats(); } @@ -399,7 +404,7 @@ bool EggAutonomous::run_batch( // While checking hatched pokemon, we find that We need to stop the program: return true; } - m_num_eggs_retrieved = 0; + num_eggs_in_column_0_at_batch_start = 0; m_num_eggs_in_party_at_batch_start = 5; // after process_hatched_pokemon(), the player location is at the start of bike loop m_player_at_loop_start = true; diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index b754bd722a..a1ecec25ed 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -190,8 +190,8 @@ class EggAutonomous : public SingleSwitchProgramInstance{ // This is used so that if we recover from an error, we know how many eggs are in the party size_t m_num_eggs_in_party_when_game_saved = 0; - // How many eggs are already deposited to storage so far. - size_t m_num_eggs_retrieved = 0; + // number of eggs in box column 0 at the start of run_batch() + size_t num_eggs_in_column_0_at_batch_start = 0; // number of eggs in party at the start of run_batch() size_t m_num_eggs_in_party_at_batch_start = 0; From e3e413f8e1ee1d41846d81db40dd9bf96dd4f53b Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 19 Jul 2026 17:44:37 -0700 Subject: [PATCH 11/20] add recovery routine if never spoke to lady. reset position. --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 22 +++++++++++++------ .../EggPrograms/PokemonSwSh_EggAutonomous.h | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 544716e587..edf79643a3 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -385,9 +385,14 @@ bool EggAutonomous::run_batch( env.update_stats(); } - // if spoke to lady, back to the bike loop regardless of the success of the egg fetch - num_loops_since_last_fetch_attempt = 0; - phase = EggAutoPhase::BIKE_LOOP; + if (!fetch_result.spoke_to_lady){ // never spoke to lady + phase = EggAutoPhase::FLY_RESET; + }else{ + // if spoke to lady, back to the bike loop regardless of the success of the egg fetch + num_loops_since_last_fetch_attempt = 0; + phase = EggAutoPhase::BIKE_LOOP; + } + } continue; } @@ -648,7 +653,8 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( env.log("Stuck in Overworld. Daycare lady not found.", COLOR_BLUE); return EggFetchResult{ .found_egg = false, - .hatch_detected = false + .hatch_detected = false, + .spoke_to_lady = false }; } pbf_press_button(context, BUTTON_A, 160ms, 100ms); @@ -668,7 +674,8 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( env.log("Hatching detected while trying to talk to lady to fetch egg."); return EggFetchResult{ .found_egg = false, - .hatch_detected = true + .hatch_detected = true, + .spoke_to_lady = true }; } continue; @@ -694,7 +701,7 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( if (!egg_status_known){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "talk_to_lady_to_fetch_egg(): Unable to speak to lady after 2 minutes.", + "talk_to_lady_to_fetch_egg(): Caught in loop. Unable to speak to lady after 2 minutes.", env.console ); } @@ -718,7 +725,8 @@ EggFetchResult EggAutonomous::talk_to_lady_to_fetch_egg( return EggFetchResult{ .found_egg = found_egg, - .hatch_detected = false + .hatch_detected = false, + .spoke_to_lady = true }; } diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index a1ecec25ed..feebf7a332 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -33,6 +33,7 @@ enum class EggAutoPhase{ struct EggFetchResult{ bool found_egg; bool hatch_detected; + bool spoke_to_lady; }; struct EggQuantity{ From fe99073fa07b4ca32d8ec8e03c38cea7d4574528 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 19 Jul 2026 17:58:41 -0700 Subject: [PATCH 12/20] update logging --- .../Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index edf79643a3..c7193d62e5 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -355,7 +355,7 @@ bool EggAutonomous::run_batch( case EggAutoPhase::HATCHING:{ env.console.log("Hatching egg."); num_eggs_hatched = hatch_routine(env, context, stats, num_eggs_hatched); - env.log("Hatched eggs " + std::to_string(num_eggs_hatched) + "/5", COLOR_WHITE); + env.log("Hatched eggs " + std::to_string(num_eggs_hatched) + "/5"); env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); phase = EggAutoPhase::FLY_RESET; continue; @@ -379,7 +379,7 @@ bool EggAutonomous::run_batch( }else{ if(fetch_result.found_egg){ num_eggs_retrieved++; - env.log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); + env.log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch)); env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); stats.m_fetch_success++; env.update_stats(); From 2ed662655ec70b9c185d9f141d8ed30744d5b522 Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 20 Jul 2026 12:27:44 -0700 Subject: [PATCH 13/20] check eggs in box, before process_hatched_pokemon() --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index c7193d62e5..bd0ec82ebd 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -748,6 +748,31 @@ bool EggAutonomous::process_hatched_pokemon( menus_to_boxsystem(env.console, context); + // Before processing the box: + // Confirm that Box Column 0 has 5 eggs, and the party has no eggs + context.wait_for_all_requests(); + context.wait_for(500ms); + auto screen0 = env.console.video().snapshot(); + size_t num_eggs_in_column_0_before = count_eggs_in_first_box_column(env.console, screen0); + size_t num_eggs_in_party_before = count_eggs_in_party(env.console, screen0); + + if (num_eggs_in_column_0_before != 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "process_hatched_pokemon: Did not start with an 5 eggs in the first box column, before processing.", + env.console + ); + } + + if (num_eggs_in_party_before != 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "process_hatched_pokemon: Did not start with a party without eggs, before processing, since they should all be hatched.", + env.console + ); + } + + const Milliseconds BOX_CHANGE_DELAY = GameSettings::instance().BOX_CHANGE_DELAY0; const Milliseconds BOX_PICKUP_DROP_DELAY = GameSettings::instance().BOX_PICKUP_DROP_DELAY0; @@ -966,10 +991,10 @@ bool EggAutonomous::process_hatched_pokemon( // After processing the box: // Confirm that Box Column 0 is empty, and the party is full of eggs EggQuantity egg_quantity = check_box(env.console, context); - size_t num_eggs_in_column_0 = egg_quantity.eggs_in_column_0; - size_t num_eggs_in_party = egg_quantity.eggs_in_party; + size_t num_eggs_in_column_0_after = egg_quantity.eggs_in_column_0; + size_t num_eggs_in_party_after = egg_quantity.eggs_in_party; - if (num_eggs_in_column_0 != 0 ){ + if (num_eggs_in_column_0_after != 0 ){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "process_hatched_pokemon: Did not end up with an empty first box column, after processing.", @@ -977,7 +1002,7 @@ bool EggAutonomous::process_hatched_pokemon( ); } - if (num_eggs_in_party != 5){ + if (num_eggs_in_party_after != 5){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "process_hatched_pokemon: Did not end up with a party full of 5 eggs, after processing.", From f7a40d1633bdfa437cb1d0223d95f508dbbb01f7 Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 23 Jul 2026 22:00:50 -0700 Subject: [PATCH 14/20] Change box view to judge or stats --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index bd0ec82ebd..6e7035282f 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -776,7 +776,18 @@ bool EggAutonomous::process_hatched_pokemon( const Milliseconds BOX_CHANGE_DELAY = GameSettings::instance().BOX_CHANGE_DELAY0; const Milliseconds BOX_PICKUP_DROP_DELAY = GameSettings::instance().BOX_PICKUP_DROP_DELAY0; + // select top Pokemon in party box_scroll(context, DPAD_LEFT); + + // Change box view to judge or stats + Language language = LANGUAGE; + if (language == Language::None){ + change_view_to_stats_or_judge(env.console, context); + }else{ + change_view_to_judge(env.console, context, language); + } + + // select the first egg box_scroll(context, DPAD_DOWN); context.wait_for_all_requests(); From d3d581db5c03b7ae23c5b1e54860eac409cc6ea0 Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 23 Jul 2026 22:38:10 -0700 Subject: [PATCH 15/20] add parameter for number of expected eggs/empty slots --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 49 +++++++++++-------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 6 ++- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 6e7035282f..6140b79608 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -753,8 +753,8 @@ bool EggAutonomous::process_hatched_pokemon( context.wait_for_all_requests(); context.wait_for(500ms); auto screen0 = env.console.video().snapshot(); - size_t num_eggs_in_column_0_before = count_eggs_in_first_box_column(env.console, screen0); - size_t num_eggs_in_party_before = count_eggs_in_party(env.console, screen0); + size_t num_eggs_in_column_0_before = count_eggs_in_first_box_column(env.console, screen0, 5); + size_t num_eggs_in_party_before = count_eggs_in_party(env.console, screen0, 0); if (num_eggs_in_column_0_before != 5){ OperationFailedException::fire( @@ -1059,8 +1059,8 @@ EggQuantity EggAutonomous::check_box(VideoStream& stream, ProControllerContext& auto screen = stream.video().snapshot(); check_box_filled(stream, screen); check_non_egg_lead(stream, screen); - size_t eggs_in_party = count_eggs_in_party(stream, screen); - size_t eggs_in_col_0_box = count_eggs_in_first_box_column(stream, screen); + size_t eggs_in_party = count_eggs_in_party(stream, screen, 5); + size_t eggs_in_col_0_box = count_eggs_in_first_box_column(stream, screen, 5); return {eggs_in_party, eggs_in_col_0_box}; @@ -1107,7 +1107,7 @@ void EggAutonomous::check_non_egg_lead(VideoStream& stream, const ImageViewRGB32 } } -size_t EggAutonomous::count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen){ +size_t EggAutonomous::count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty){ size_t num_empty = 0; size_t num_eggs = 0; for (uint8_t row = 1; row < 6; row++){ @@ -1120,20 +1120,24 @@ size_t EggAutonomous::count_eggs_in_party(VideoStream& stream, const ImageViewRG if (is_egg) { num_eggs++; } } - if (num_empty + num_eggs != 5){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "count_eggs_in_party: Total number of eggs and empty slots in the party don't add up to 5. " - "Ensure your only non-egg Pokemon is in the lead. " - "The rest of the slots in the team should either be empty or an egg.", - stream - ); + if (expected_eggs_plus_empty.has_value()){ + size_t expected = expected_eggs_plus_empty.value(); + if (num_empty + num_eggs != expected){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "count_eggs_in_party: Total number of eggs and empty slots in the party don't add up to the expected number. " + "During setup, ensure your only non-egg Pokemon is in the lead. " + "The rest of the slots in the team should either be empty or an egg.", + stream + ); + } } + return num_eggs; } -size_t EggAutonomous::count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen){ +size_t EggAutonomous::count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty){ size_t num_empty = 0; size_t num_eggs = 0; @@ -1147,13 +1151,16 @@ size_t EggAutonomous::count_eggs_in_first_box_column(VideoStream& stream, const if (is_egg) { num_eggs++; } } - if (num_empty + num_eggs != 5){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "count_eggs_in_first_box_column: Total number of eggs and empty slots in the first box column don't add up to 5. " - "Ensure thre are no non-egg Pokemon in the first box column.", - stream - ); + if (expected_eggs_plus_empty.has_value()){ + size_t expected = expected_eggs_plus_empty.value(); + if (num_empty + num_eggs != expected){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "count_eggs_in_first_box_column: Total number of eggs and empty slots in the first box column don't add up to the expected number. " + "During setup, ensure there are no non-egg Pokemon in the first box column.", + stream + ); + } } return num_eggs; diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index feebf7a332..848bcf18b1 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonSwSh_EggAutonomous_H #define PokemonAutomation_PokemonSwSh_EggAutonomous_H +#include #include "Common/Cpp/Options/BooleanCheckBoxOption.h" #include "Common/Cpp/Options/SimpleIntegerOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" @@ -134,6 +135,7 @@ class EggAutonomous : public SingleSwitchProgramInstance{ ); // Starting within the box, confirm that the lead is not an egg, and that the box is full except for the first column + // this is run before each batch // return quantity of eggs in both the party and the first box column EggQuantity check_box(VideoStream& stream, ProControllerContext& context); @@ -145,9 +147,9 @@ class EggAutonomous : public SingleSwitchProgramInstance{ // ensure that the lead pokemon in the party is not an egg void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen); - size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen); + size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty); - size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen); + size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty); From 0a2659b76c670b1497ecce240b74f50dabce823f Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 23 Jul 2026 22:42:18 -0700 Subject: [PATCH 16/20] update logging --- .../Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 6140b79608..1872c99141 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -315,7 +315,7 @@ bool EggAutonomous::run_batch( // - if not enough eggs fetched, talk to lady to try fetching an egg. while (num_eggs_hatched < 5 || num_eggs_retrieved < total_eggs_to_fetch){ env.log("Eggs hatched: " + std::to_string(num_eggs_hatched) + - ". Eggs fetched: " + std::to_string(num_eggs_retrieved)); + "/5. Eggs fetched: " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch)); // NOTE: the egg hatching detector cannot be constantly running, // since it only detects the black dialog box, and speaking to the lady will also produce a black dialog box. @@ -345,6 +345,7 @@ bool EggAutonomous::run_batch( if (num_eggs_retrieved < total_eggs_to_fetch){ phase = EggAutoPhase::FETCH_EGG; }else{ + env.log("Done retrieving eggs. Resume bike loop to hatch eggs."); // done retrieving eggs // resume hatching eggs if needed phase = EggAutoPhase::BIKE_LOOP; @@ -355,7 +356,7 @@ bool EggAutonomous::run_batch( case EggAutoPhase::HATCHING:{ env.console.log("Hatching egg."); num_eggs_hatched = hatch_routine(env, context, stats, num_eggs_hatched); - env.log("Hatched eggs " + std::to_string(num_eggs_hatched) + "/5"); + // env.log("Hatched eggs " + std::to_string(num_eggs_hatched) + "/5"); env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); phase = EggAutoPhase::FLY_RESET; continue; @@ -379,7 +380,7 @@ bool EggAutonomous::run_batch( }else{ if(fetch_result.found_egg){ num_eggs_retrieved++; - env.log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch)); + // env.log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch)); env.console.overlay().add_log("Found egg " + std::to_string(num_eggs_retrieved) + "/" + std::to_string(total_eggs_to_fetch), COLOR_WHITE); stats.m_fetch_success++; env.update_stats(); From 94fe6f11cbe4dd0422f302475d4867281ad6cc7d Mon Sep 17 00:00:00 2001 From: jw098 Date: Thu, 23 Jul 2026 23:08:03 -0700 Subject: [PATCH 17/20] update comments --- .../Programs/EggPrograms/PokemonSwSh_EggAutonomous.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index 848bcf18b1..0db89fcdd6 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -112,8 +112,9 @@ class EggAutonomous : public SingleSwitchProgramInstance{ // Call this function when standing in front of the lady to fetch one egg. // return EggFetchResult, which is a struct of the following: - // - num_eggs_retrieved: updated `num_eggs_retrieved` that reflects the change in fetched eggs. + // - found_egg: boolean that is true if an egg was retrieved from the lady // - hatch_detected: boolean that is true if hatch was detected. + // - spoke_to_lady: boolean that is true if we spoke to the lady EggFetchResult talk_to_lady_to_fetch_egg( SingleSwitchProgramEnvironment& env, ProControllerContext& context, From c61cb213f918bfc9e4a56fee1810355fe1281781 Mon Sep 17 00:00:00 2001 From: jw098 Date: Fri, 24 Jul 2026 17:08:25 -0700 Subject: [PATCH 18/20] change view to judge at beginning of program --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index 1872c99141..dd0a8fdc35 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -207,6 +207,16 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo env.log("Starting with " + std::to_string(num_eggs_in_party) + " eggs in the party and " + std::to_string(num_eggs_in_column_0) + " eggs in the first box column."); + // select top Pokemon in party + box_scroll(context, DPAD_LEFT); + // Change box view to judge or stats + Language language = LANGUAGE; + if (language == Language::None){ + change_view_to_stats_or_judge(env.console, context); + }else{ + change_view_to_judge(env.console, context, language); + } + if (AUTO_SAVING == AutoSave::AfterStartAndKeep){ save_game(env.console, context); m_num_eggs_in_storage_when_game_saved = num_eggs_in_column_0; @@ -779,15 +789,6 @@ bool EggAutonomous::process_hatched_pokemon( // select top Pokemon in party box_scroll(context, DPAD_LEFT); - - // Change box view to judge or stats - Language language = LANGUAGE; - if (language == Language::None){ - change_view_to_stats_or_judge(env.console, context); - }else{ - change_view_to_judge(env.console, context, language); - } - // select the first egg box_scroll(context, DPAD_DOWN); From a4cc18db5e3825d02a95ee21c473bd5677a58319 Mon Sep 17 00:00:00 2001 From: jw098 Date: Fri, 24 Jul 2026 17:38:00 -0700 Subject: [PATCH 19/20] counting eggs and empty slots are split into separate functions --- .../EggPrograms/PokemonSwSh_EggAutonomous.cpp | 132 ++++++++++-------- .../EggPrograms/PokemonSwSh_EggAutonomous.h | 13 +- 2 files changed, 75 insertions(+), 70 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index dd0a8fdc35..f09bf8422b 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -201,9 +201,31 @@ void EggAutonomous::program(SingleSwitchProgramEnvironment& env, ProControllerCo } menus_to_boxsystem(env.console, context); - EggQuantity egg_quantity = check_box(env.console, context); - size_t num_eggs_in_column_0 = egg_quantity.eggs_in_column_0; - size_t num_eggs_in_party = egg_quantity.eggs_in_party; + check_box(env.console, context); + context.wait_for_all_requests(); + context.wait_for(500ms); + auto screen = env.console.video().snapshot(); + size_t num_eggs_in_column_0 = count_eggs_in_first_box_column(env.console, screen); + size_t num_empty_slots_in_column_0 = count_empty_slots_in_first_box_column(env.console, screen); + size_t num_eggs_in_party = count_eggs_in_party(env.console, screen); + size_t num_empty_slots_in_party = count_empty_slots_in_party(env.console, screen); + if (num_eggs_in_column_0 + num_empty_slots_in_column_0 != 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Total number of eggs and empty slots in the first box column don't add up to 5. " + "During setup, ensure there are no non-egg Pokemon in the first box column.", + env.console + ); + } + if (num_eggs_in_party + num_empty_slots_in_party != 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Total number of eggs and empty slots in the party don't add up to 5. " + "During setup, ensure there is only one Pokemon in the lead slot, and no other Pokemon.", + env.console + ); + } + env.log("Starting with " + std::to_string(num_eggs_in_party) + " eggs in the party and " + std::to_string(num_eggs_in_column_0) + " eggs in the first box column."); @@ -764,21 +786,21 @@ bool EggAutonomous::process_hatched_pokemon( context.wait_for_all_requests(); context.wait_for(500ms); auto screen0 = env.console.video().snapshot(); - size_t num_eggs_in_column_0_before = count_eggs_in_first_box_column(env.console, screen0, 5); - size_t num_eggs_in_party_before = count_eggs_in_party(env.console, screen0, 0); - - if (num_eggs_in_column_0_before != 5){ + size_t num_eggs_in_column_0_before = count_eggs_in_first_box_column(env.console, screen0); + size_t num_empty_slots_in_column_0_before = count_empty_slots_in_first_box_column(env.console, screen0); + size_t num_eggs_in_party_before = count_eggs_in_party(env.console, screen0); + size_t num_empty_slots_in_party_before = count_empty_slots_in_party(env.console, screen0); + if (num_eggs_in_column_0_before != 5 || num_empty_slots_in_column_0_before != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "process_hatched_pokemon: Did not start with an 5 eggs in the first box column, before processing.", + "process_hatched_pokemon: Before processing, we expected 5 eggs in the first box column.", env.console ); } - - if (num_eggs_in_party_before != 0){ + if (num_eggs_in_party_before != 0 || num_empty_slots_in_party_before != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "process_hatched_pokemon: Did not start with a party without eggs, before processing, since they should all be hatched.", + "process_hatched_pokemon: Before processing, we expected a party without eggs, since they should all be hatched.", env.console ); } @@ -1003,22 +1025,26 @@ bool EggAutonomous::process_hatched_pokemon( // After processing the box: // Confirm that Box Column 0 is empty, and the party is full of eggs - EggQuantity egg_quantity = check_box(env.console, context); - size_t num_eggs_in_column_0_after = egg_quantity.eggs_in_column_0; - size_t num_eggs_in_party_after = egg_quantity.eggs_in_party; - - if (num_eggs_in_column_0_after != 0 ){ + check_box(env.console, context); + context.wait_for_all_requests(); + context.wait_for(500ms); + auto screen = env.console.video().snapshot(); + size_t num_eggs_in_column_0_after = count_eggs_in_first_box_column(env.console, screen); + size_t num_empty_slots_in_column_0_after = count_empty_slots_in_first_box_column(env.console, screen); + size_t num_eggs_in_party_after = count_eggs_in_party(env.console, screen); + size_t num_empty_slots_in_party_after = count_empty_slots_in_party(env.console, screen); + if (num_eggs_in_column_0_after != 0 || num_empty_slots_in_column_0_after != 5){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "process_hatched_pokemon: Did not end up with an empty first box column, after processing.", + "process_hatched_pokemon: After processing, we expected an empty first box column.", env.console ); } - if (num_eggs_in_party_after != 5){ + if (num_eggs_in_party_after != 5 || num_empty_slots_in_party_after != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "process_hatched_pokemon: Did not end up with a party full of 5 eggs, after processing.", + "process_hatched_pokemon: After processing, we expected a party full of 5 eggs.", env.console ); } @@ -1054,18 +1080,13 @@ bool EggAutonomous::process_hatched_pokemon( -EggQuantity EggAutonomous::check_box(VideoStream& stream, ProControllerContext& context){ +void EggAutonomous::check_box(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); context.wait_for(500ms); auto screen = stream.video().snapshot(); check_box_filled(stream, screen); check_non_egg_lead(stream, screen); - size_t eggs_in_party = count_eggs_in_party(stream, screen, 5); - size_t eggs_in_col_0_box = count_eggs_in_first_box_column(stream, screen, 5); - - - return {eggs_in_party, eggs_in_col_0_box}; } @@ -1109,63 +1130,50 @@ void EggAutonomous::check_non_egg_lead(VideoStream& stream, const ImageViewRGB32 } } -size_t EggAutonomous::count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty){ - size_t num_empty = 0; +size_t EggAutonomous::count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen){ size_t num_eggs = 0; - for (uint8_t row = 1; row < 6; row++){ - BoxEmptySlotDetector slot(SlotLocation::PARTY, row, 0); - bool is_empty = slot.detect(screen); - if (is_empty) { num_empty++; } - + for (uint8_t row = 0; row < 6; row++){ BoxEggDetector egg(SlotLocation::PARTY, row); bool is_egg = egg.detect(screen); if (is_egg) { num_eggs++; } } - if (expected_eggs_plus_empty.has_value()){ - size_t expected = expected_eggs_plus_empty.value(); - if (num_empty + num_eggs != expected){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "count_eggs_in_party: Total number of eggs and empty slots in the party don't add up to the expected number. " - "During setup, ensure your only non-egg Pokemon is in the lead. " - "The rest of the slots in the team should either be empty or an egg.", - stream - ); - } - } - - return num_eggs; } -size_t EggAutonomous::count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty){ - +size_t EggAutonomous::count_empty_slots_in_party(VideoStream& stream, const ImageViewRGB32& screen){ size_t num_empty = 0; - size_t num_eggs = 0; - for (uint8_t row = 0; row < 5; row++){ - BoxEmptySlotDetector slot(SlotLocation::BOX, row, 0); + for (uint8_t row = 0; row < 6; row++){ + BoxEmptySlotDetector slot(SlotLocation::PARTY, row, 0); bool is_empty = slot.detect(screen); if (is_empty) { num_empty++; } + } + + return num_empty; +} +size_t EggAutonomous::count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen){ + + size_t num_eggs = 0; + for (uint8_t row = 0; row < 5; row++){ BoxEggDetector egg(SlotLocation::BOX, row); bool is_egg = egg.detect(screen); if (is_egg) { num_eggs++; } } - if (expected_eggs_plus_empty.has_value()){ - size_t expected = expected_eggs_plus_empty.value(); - if (num_empty + num_eggs != expected){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "count_eggs_in_first_box_column: Total number of eggs and empty slots in the first box column don't add up to the expected number. " - "During setup, ensure there are no non-egg Pokemon in the first box column.", - stream - ); - } + return num_eggs; +} + +size_t EggAutonomous::count_empty_slots_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen){ + + size_t num_empty = 0; + for (uint8_t row = 0; row < 5; row++){ + BoxEmptySlotDetector slot(SlotLocation::BOX, row, 0); + bool is_empty = slot.detect(screen); + if (is_empty) { num_empty++; } } - return num_eggs; + return num_empty; } diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h index 0db89fcdd6..3938963cce 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.h @@ -37,10 +37,6 @@ struct EggFetchResult{ bool spoke_to_lady; }; -struct EggQuantity{ - size_t eggs_in_party; - size_t eggs_in_column_0; -}; class EggAutonomous_Descriptor : public SingleSwitchProgramDescriptor{ @@ -137,8 +133,7 @@ class EggAutonomous : public SingleSwitchProgramInstance{ // Starting within the box, confirm that the lead is not an egg, and that the box is full except for the first column // this is run before each batch - // return quantity of eggs in both the party and the first box column - EggQuantity check_box(VideoStream& stream, ProControllerContext& context); + void check_box(VideoStream& stream, ProControllerContext& context); // ensure that all rows/columns are filled except the first column // ASSUMES: the cursor should NOT be on the box, with the exception that it can be at the top row. @@ -148,9 +143,11 @@ class EggAutonomous : public SingleSwitchProgramInstance{ // ensure that the lead pokemon in the party is not an egg void check_non_egg_lead(VideoStream& stream, const ImageViewRGB32& screen); - size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty); + size_t count_eggs_in_party(VideoStream& stream, const ImageViewRGB32& screen); + size_t count_empty_slots_in_party(VideoStream& stream, const ImageViewRGB32& screen); - size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen, std::optional expected_eggs_plus_empty); + size_t count_eggs_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen); + size_t count_empty_slots_in_first_box_column(VideoStream& stream, const ImageViewRGB32& screen); From 78b05b511a4ef542050fb3e087f8e3c1dfe2f08c Mon Sep 17 00:00:00 2001 From: jw098 Date: Fri, 24 Jul 2026 18:37:54 -0700 Subject: [PATCH 20/20] update error message --- .../Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp index f09bf8422b..1878b25cba 100644 --- a/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp +++ b/SerialPrograms/Source/PokemonSwSh/Programs/EggPrograms/PokemonSwSh_EggAutonomous.cpp @@ -800,7 +800,7 @@ bool EggAutonomous::process_hatched_pokemon( if (num_eggs_in_party_before != 0 || num_empty_slots_in_party_before != 0){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "process_hatched_pokemon: Before processing, we expected a party without eggs, since they should all be hatched.", + "process_hatched_pokemon: Before processing, we expected a party without eggs (and no empty slots), since they should all be hatched.", env.console ); }