From 6bd1bda76584bb3ce706e13ab5a273685b676715 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Sun, 21 Jun 2026 11:05:22 -0500 Subject: [PATCH] FRLG Fishing refactor to match other programs and move fishing encounnter logic to Navigation. --- .../PokemonFRLG/PokemonFRLG_Navigation.cpp | 38 +++ .../PokemonFRLG/PokemonFRLG_Navigation.h | 4 + .../PokemonFRLG_ShinyHunt-Fishing.cpp | 288 ++---------------- .../PokemonFRLG_ShinyHunt-Fishing.h | 6 +- 4 files changed, 68 insertions(+), 268 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index 9743df85d8..9db7452206 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -1184,6 +1184,44 @@ int grass_spin(ConsoleHandle& console, ProControllerContext& context, bool leftr return encounter_shiny ? 1 : 0; } +int fish_encounter(ConsoleHandle& console, ProControllerContext& context, Seconds timeout){ + WhiteDialogWatcher fishing_dialog(COLOR_RED); + BlackScreenWatcher battle_entered(COLOR_RED); + AdvanceBattleDialogWatcher battle_dialog(COLOR_RED); + BattleMenuWatcher battle_menu(COLOR_RED); + + context.wait_for_all_requests(); + console.log("Starting fish encounter."); + WallClock start = current_time(); + + while (true){ + if (current_time() - start > timeout){ + console.log("No pokemon hooked after timeout."); + return -1; + } + + pbf_press_button(context, BUTTON_MINUS, 200ms, 200ms); + context.wait_for_all_requests(); + + int ret = wait_until( + console, context, + std::chrono::milliseconds(2000), + { fishing_dialog, battle_entered, battle_dialog, battle_menu } + ); + + if (ret == 0){ + console.log("Fishing dialog detected."); + pbf_press_button(context, BUTTON_B, 200ms, 200ms); + context.wait_for_all_requests(); + } else if (ret == 1 || ret == 2 || ret == 3){ + console.log("Battle entered."); + break; + } + } + + bool encounter_shiny = handle_encounter(console, context, true); + return encounter_shiny ? 1 : 0; +} } } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index b5a3ac3177..c138145a58 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -113,6 +113,10 @@ void heal_at_pokecenter(ConsoleHandle& console, ProControllerContext& context); // returns -1 if no encounter is triggered, 0 if a non-shiny is encounter, and 1 if a shiny is encountered int grass_spin(ConsoleHandle& console, ProControllerContext& context, bool leftright, Seconds timeout = 60s); +// Trigger encounters by fishing with a registered rod. The player must be facing water. +// returns -1 if no encounter is triggered, 0 if a non-shiny is encounter, and 1 if a shiny is encountered +int fish_encounter(ConsoleHandle& console, ProControllerContext& context, Seconds timeout = 300s); + // Go to home to check that scaling is 100%. Then resume game. void home_black_border_check(ConsoleHandle& console, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.cpp index 6204e8fec5..2694d5cc17 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.cpp @@ -6,38 +6,23 @@ * This program automates fishing encounters in Pokemon FireRed/LeafGreen * and stops when a shiny Pokemon is detected. * - * Core Features: - * - Adaptive timing adjusts A-button press timing to maximize encounter rate - * - Fail-safe prevents soft-lock if dialog detection stalls - * - Automatic flee from non-shiny encounters - * - Initial autosave to protect progress - * - Error tracking for debugging stability issues - * * Assumptions: * - Fishing rod is registered to the SELECT button * - Player is facing fishable water * - Lead Pokemon can always flee (Smoke Ball recommended) * - Text speed is set to FAST */ -#include + +#include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "Pokemon/Pokemon_Strings.h" -#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" -#include "PokemonFRLG/Inference/PokemonFRLG_SelectionArrowDetector.h" -#include "PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" -#include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" #include "PokemonFRLG_ShinyHunt-Fishing.h" -using namespace std::chrono_literals; - namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonFRLG { @@ -56,7 +41,6 @@ ShinyHuntFishing_Descriptor::ShinyHuntFishing_Descriptor() ) {} -/*Tracks runtime statistics displayed in UI.*/ struct ShinyHuntFishing_Descriptor::Stats : public StatsTracker { Stats() : encounters(m_stats["Encounters"]) @@ -65,7 +49,7 @@ struct ShinyHuntFishing_Descriptor::Stats : public StatsTracker { { m_display_order.emplace_back("Encounters"); m_display_order.emplace_back("Shinies"); - m_display_order.emplace_back("Errors"); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); } std::atomic& encounters; std::atomic& shinies; @@ -76,51 +60,25 @@ ShinyHuntFishing_Descriptor::make_stats() const {return std::make_unique( ShinyHuntFishing::ShinyHuntFishing() : SingleSwitchProgramInstance() - - // Adaptive delay before pressing A after casting rod. - // Adjusted dynamically to maximize encounter success. - , BITE_WAIT_MS( - "Bite Wait (ms)
" - "Adaptive delay before pressing A.", - LockMode::UNLOCK_WHILE_RUNNING, - 3000, - 2400, - 4200 - ) - - // Return to HOME menu after shiny is found. , GO_HOME_WHEN_DONE(true) - // Notification triggered when shiny detected. + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) , NOTIFICATION_SHINY("Shiny found", true, true, ImageAttachmentMode::JPG, {"Notifs", "Showcase"}) , NOTIFICATIONS({ + &NOTIFICATION_STATUS_UPDATE, &NOTIFICATION_SHINY, &NOTIFICATION_PROGRAM_FINISH, }) { - PA_ADD_OPTION(BITE_WAIT_MS); PA_ADD_OPTION(GO_HOME_WHEN_DONE); PA_ADD_OPTION(NOTIFICATIONS); } -/* -=============================================================================== -Program Logic -=============================================================================== -*/ - void ShinyHuntFishing::program( SingleSwitchProgramEnvironment& env, ProControllerContext& context ){ - auto& stats = - env.current_stats(); + ShinyHuntFishing_Descriptor::Stats& stats = env.current_stats(); - /* - --------------------------------------------------------------------------- - Ensure game is properly detected and scaled. - Prevents capture card resolution mismatch issues. - --------------------------------------------------------------------------- - */ home_black_border_check(env.console, context); /* @@ -131,206 +89,27 @@ void ShinyHuntFishing::program( */ env.log("FRLG Fishing shiny hunt started."); - /* - Ensure we begin in overworld state. - Clears any open dialog boxes. - */ - pbf_mash_button(context, BUTTON_B, 1500ms); - - /* - Perform autosave at program start. - Protects progress if crash occurs later. - */ - env.log("Performing initial autosave."); - save_game_to_overworld(env.console, context); - - AdvanceWhiteDialogWatcher advance_dialog(COLOR_RED); - WhiteDialogWatcher white_dialog(COLOR_RED); - BlackScreenWatcher battle_entered(COLOR_RED); - BattleDialogWatcher battle_dialog(COLOR_RED); - BattleMenuWatcher battle_menu(COLOR_RED); - - /* - --------------------------------------------------------------------------- - Adaptive timing variables - Adjusts A press timing to match bite window. - --------------------------------------------------------------------------- - */ - int bite_wait_ms = BITE_WAIT_MS; - - const int min_wait = 2400; - const int max_wait = 4200; - const int adjustment_step = 40; - - int failed_attempts = 0; - - /* - =========================================================================== - Main Fishing Loop - =========================================================================== - */ while (true){ - /* - Cast fishing rod. - Rod must be registered to SELECT. - */ - pbf_press_button(context, BUTTON_MINUS, 20ms, 1000ms); + send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); + int fish_result = fish_encounter(env.console, context); - /* - Wait for one of the following: - 0 -> dialog with red arrow ("Oh! A bite!") - 1 -> white dialog box appears - 2 -> battle transition begins - If timeout occurs, run_until returns negative. - */ - int ret = - wait_until( - env.console, - context, - std::chrono::milliseconds(bite_wait_ms), - { - advance_dialog, - white_dialog, - battle_entered - } - ); - - /* - Advance dialog text as quickly as possible. - Prevents missing encounter trigger window. - */ - if (ret == 0){ - env.log("Advance dialog detected (red arrow)."); - pbf_mash_button(context, BUTTON_B, 800ms); - } else if (ret == 1){ - env.log("White dialog detected."); - pbf_mash_button(context, BUTTON_B, 800ms); - } else if (ret == 2){ - env.log("Battle transition detected."); - }else{ - env.log("No bite detected."); - continue; - } - - /* - Confirm battle has started. - Multiple detectors used to improve reliability: - - black screen fade - - teal dialog box - - FIGHT menu appearance - */ - int battle_ret = - wait_until( - env.console, - context, - 4000ms, - { - battle_entered, - battle_dialog, - battle_menu - } - ); - - /* - ----------------------------------------------------------------------- - Dialog fail-safe - Sometimes dialog detection may stall. - If dialog persists too long, mash A to force progression. - ----------------------------------------------------------------------- - */ - constexpr auto DIALOG_FAILSAFE_TIMEOUT = - std::chrono::seconds(8); - bool dialog_seen = false; - WallClock dialog_start; - if (ret == 0 || ret == 1){ - dialog_seen = true; - dialog_start = current_time(); - } - if ( - dialog_seen && - current_time() - dialog_start > - DIALOG_FAILSAFE_TIMEOUT - ){ - env.log("Dialog persisted too long. " - "Triggering fail-safe." + switch (fish_result){ + case -1: + stats.errors++; + env.update_stats(); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "program(): No fish hooked after 5 minutes. Please ensure you are facing water with a rod registered.", + env.console ); - pbf_mash_button(context, BUTTON_A, 1500ms); - - /*Attempt to recover battle state.*/ - battle_ret = - wait_until( - env.console, - context, - 2000ms, - { - battle_entered, - battle_dialog, - battle_menu - } - ); - - /*Count error only if fail-safe did not recover battle.*/ - if (battle_ret < 0){ - env.log("Fail-safe recovery failed."); - stats.errors++; - env.update_stats(); - } - } - - /* - ----------------------------------------------------------------------- - No encounter triggered - Adjust timing upward to improve bite sync. - ----------------------------------------------------------------------- - */ - if (battle_ret < 0){ - failed_attempts++; - - /* - Increase delay gradually. - Prevents over-adjusting due to random variance. - */ - if (failed_attempts >= 3){ - bite_wait_ms += adjustment_step; - if (bite_wait_ms > max_wait){ - bite_wait_ms = max_wait; - } - BITE_WAIT_MS.set(bite_wait_ms); - failed_attempts = 0; - } + break; + case 0: + stats.encounters++; + env.update_stats(); + flee_battle(env.console, context); + context.wait_for_all_requests(); continue; - } - - /* - ----------------------------------------------------------------------- - Encounter triggered successfully - ----------------------------------------------------------------------- - */ - stats.encounters++; - env.update_stats(); - failed_attempts = 0; - - /* - Slightly reduce delay to improve efficiency. - */ - bite_wait_ms -= adjustment_step; - if (bite_wait_ms < min_wait){ - bite_wait_ms = min_wait; - } - BITE_WAIT_MS.set(bite_wait_ms); - - /* - Detect shiny animation/audio. - handle_encounter waits for Pokemon to appear. - */ - bool shiny = handle_encounter(env.console, context, true); - - /* - ----------------------------------------------------------------------- - Shiny detected - ----------------------------------------------------------------------- - */ - if (shiny){ + case 1: stats.shinies++; env.update_stats(); send_program_notification( @@ -345,27 +124,10 @@ void ShinyHuntFishing::program( ); break; } - /* - ----------------------------------------------------------------------- - Non-shiny encounter - Assumes fleeing always succeeds. - ----------------------------------------------------------------------- - */ - flee_battle(env.console, context); - context.wait_for_all_requests(); } - /*Return to HOME menu after shiny found.*/ - if (GO_HOME_WHEN_DONE){ - pbf_press_button( - context, - BUTTON_HOME, - 200ms, - 1000ms - ); - } - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH - ); + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + GO_HOME_WHEN_DONE.run_end_of_program(context); } } // namespace PokemonFRLG } // namespace NintendoSwitch diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h index 878fdf2559..5f133de868 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/ShinyHunting/PokemonFRLG_ShinyHunt-Fishing.h @@ -8,8 +8,6 @@ #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "CommonFramework/ProgramStats/StatsTracking.h" -#include "Common/Cpp/Options/SimpleIntegerOption.h" - #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" #include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" @@ -41,12 +39,10 @@ class ShinyHuntFishing : public SingleSwitchProgramInstance { ) override {} private: - SimpleIntegerOption BITE_WAIT_MS; - GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; EventNotificationOption NOTIFICATION_SHINY; - EventNotificationsOption NOTIFICATIONS; };