diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index daed1a79d..741a10173 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -1459,7 +1459,9 @@ file(GLOB MAIN_SOURCES Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_09.cpp Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_09.h Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_10.cpp - Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_10.h + Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_10.h + Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.cpp + Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.h Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.h Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.cpp diff --git a/SerialPrograms/SerialPrograms.pro b/SerialPrograms/SerialPrograms.pro index b60b57694..02af50cb2 100644 --- a/SerialPrograms/SerialPrograms.pro +++ b/SerialPrograms/SerialPrograms.pro @@ -729,6 +729,7 @@ SOURCES += \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_08.cpp \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_09.cpp \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_10.cpp \ + Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.cpp \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.cpp \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.cpp \ Source/PokemonSV/Programs/AutoStory/PokemonSV_MenuOption.cpp \ @@ -1832,6 +1833,7 @@ HEADERS += \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_08.h \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_09.h \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_10.h \ + Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.h \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory.h \ Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.h \ Source/PokemonSV/Programs/AutoStory/PokemonSV_MenuOption.h \ diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.cpp b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.cpp index 13b80350d..f14fa33ea 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.cpp @@ -8,6 +8,7 @@ #include "Common/Qt/StringToolsQt.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/FatalProgramException.h" +#include "CommonFramework/InferenceInfra/InferenceRoutines.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/ImageTools/ImageFilter.h" @@ -18,6 +19,9 @@ #include "CommonFramework/OCR/OCR_RawOCR.h" #include "CommonFramework/OCR/OCR_NumberReader.h" #include "CommonFramework/OCR/OCR_StringNormalization.h" +#include "PokemonSwSh/Commands/PokemonSwSh_Commands_DateSpam.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" +#include "NintendoSwitch/Inference/NintendoSwitch_DetectHome.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch_DateReader.h" @@ -568,6 +572,54 @@ void DateReader::set_date( ); } +void change_date( + SingleSwitchProgramEnvironment& env, BotBaseContext& context, + const DateTime& date +){ + while (true){ + context.wait_for_all_requests(); + + HomeWatcher home; + DateChangeWatcher date_reader; + int ret = wait_until( + env.console, context, std::chrono::seconds(120), + { + home, + date_reader + } + ); + switch (ret){ + case 0: + home_to_date_time(context, true, false); + pbf_press_button(context, BUTTON_A, 10, 30); + context.wait_for_all_requests(); + continue; + case 1:{ + env.log("Detected date change."); + + // Set the date + VideoOverlaySet overlays(env.console.overlay()); + date_reader.make_overlays(overlays); + date_reader.set_date(env.program_info(), env.console, context, date); + + // Commit the date. + pbf_press_button(context, BUTTON_A, 20, 30); + + // Re-enter the game. + pbf_press_button(context, BUTTON_HOME, 20, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY); + + return; + } + default: + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + env.logger(), + "Failed to set date" + ); + } + } +} + diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.h b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.h index 477ee1fb7..834dadbe1 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.h +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_DateReader.h @@ -11,6 +11,7 @@ #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/InferenceInfra/VisualInferenceCallback.h" #include "CommonFramework/Inference/VisualDetector.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" namespace PokemonAutomation{ struct ProgramInfo; @@ -101,6 +102,14 @@ class DateChangeWatcher : public DetectorToFinder{ {} }; +// starting from Home screen, change the date to the desired date +// then go back to the home screen +void change_date( + SingleSwitchProgramEnvironment& env, + BotBaseContext& context, + const DateTime& date +); + } diff --git a/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.cpp index ccfeabde7..75591bc34 100644 --- a/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.cpp @@ -12,16 +12,21 @@ #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/Tools/StatsTracking.h" #include "CommonFramework/ImageTools/SolidColorTest.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageFilter.h" +#include "CommonFramework/OCR/OCR_NumberReader.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "NintendoSwitch/Programs/NintendoSwitch_SnapshotDumper.h" #include "PokemonSwSh/Inference/PokemonSwSh_IvJudgeReader.h" #include "PokemonSV/Inference/Battles/PokemonSV_NormalBattleMenus.h" #include "PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h" #include "PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.h" -// #include "PokemonSV/Inference/Overworld/PokemonSV_StationaryOverworldWatcher.h" +#include "PokemonSV/Inference/Overworld/PokemonSV_StationaryOverworldWatcher.h" #include "PokemonSV/Inference/PokemonSV_MainMenuDetector.h" #include "PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.h" +#include "PokemonSV/PokemonSV_Settings.h" #include "PokemonSV/Programs/PokemonSV_Navigation.h" #include "PokemonSV/Programs/PokemonSV_GameEntry.h" #include "PokemonSV/Programs/PokemonSV_SaveGame.h" @@ -380,7 +385,7 @@ bool confirm_marker_present( int ret = wait_until( console, context, - std::chrono::seconds(10), + std::chrono::seconds(5), {marker, battle} ); switch (ret){ @@ -454,9 +459,11 @@ void overworld_navigation( } } } + context.wait_for_all_requests(); if (should_realign){ try { realign_player(info, console, context, PlayerRealignMode::REALIGN_OLD_MARKER); + }catch (UnexpectedBattleException&){ pbf_wait(context, 30 * TICKS_PER_SECOND); // catch exception to allow the battle callback to take over. } @@ -479,10 +486,9 @@ void overworld_navigation( if (auto_heal){ auto_heal_from_menu_or_overworld(info, console, context, 0, true); } - + context.wait_for_all_requests(); try { realign_player(info, console, context, PlayerRealignMode::REALIGN_OLD_MARKER); - if (!confirm_marker_present(info, console, context)){ // if marker not present, don't keep walking forward. return; @@ -682,7 +688,7 @@ void do_action_and_monitor_for_battles( {battle_menu} ); if (ret == 0){ // battle detected - throw OperationFailedException( + throw UnexpectedBattleException( ErrorReport::SEND_ERROR_REPORT, console, "do_action_and_monitor_for_battles(): Detected battle. Failed to complete action.", true @@ -713,52 +719,65 @@ void handle_unexpected_battles( } } -// void handle_when_stationary_in_overworld( -// const ProgramInfo& info, -// ConsoleHandle& console, -// BotBaseContext& context, -// std::function< -// void(const ProgramInfo& info, -// ConsoleHandle& console, -// BotBaseContext& context) -// >&& action, -// std::function< -// void(const ProgramInfo& info, -// ConsoleHandle& console, -// BotBaseContext& context) -// >&& recovery_action, -// size_t seconds_stationary, -// uint16_t minutes_timeout -// ){ -// StationaryOverworldWatcher stationary_overworld(COLOR_RED, {0.865, 0.82, 0.08, 0.1}, seconds_stationary); -// WallClock start = current_time(); -// while (true){ -// if (current_time() - start > std::chrono::minutes(minutes_timeout)){ -// throw OperationFailedException( -// ErrorReport::SEND_ERROR_REPORT, console, -// "handle_when_stationary_in_overworld(): Failed to complete action after 5 minutes.", -// true -// ); -// } - -// int ret = run_until( -// console, context, -// [&](BotBaseContext& context){ -// context.wait_for_all_requests(); -// action(info, console, context); -// }, -// {stationary_overworld} -// ); -// if (ret < 0){ -// // successfully completed action without being stuck in a position where the overworld is stationary. -// return; -// }else if (ret == 0){ -// // if stationary in overworld, run recovery action then try action again -// context.wait_for_all_requests(); -// recovery_action(info, console, context); -// } -// } -// } +void handle_when_stationary_in_overworld( + const ProgramInfo& info, + ConsoleHandle& console, + BotBaseContext& context, + std::function< + void(const ProgramInfo& info, + ConsoleHandle& console, + BotBaseContext& context) + >&& action, + std::function< + void(const ProgramInfo& info, + ConsoleHandle& console, + BotBaseContext& context) + >&& recovery_action, + size_t seconds_stationary, + uint16_t minutes_timeout, + size_t max_failures +){ + + WallClock start = current_time(); + size_t num_failures = 0; + while (true){ + if (current_time() - start > std::chrono::minutes(minutes_timeout)){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, console, + "handle_when_stationary_in_overworld(): Failed to complete action after " + std::to_string(minutes_timeout) + " minutes.", + true + ); + } + StationaryOverworldWatcher stationary_overworld(COLOR_RED, {0.865, 0.825, 0.08, 0.1}, seconds_stationary); + + int ret = run_until( + console, context, + [&](BotBaseContext& context){ + context.wait_for_all_requests(); + action(info, console, context); + }, + {stationary_overworld} + ); + if (ret < 0){ + // successfully completed action without being stuck in a position where the overworld is stationary. + return; + }else if (ret == 0){ + // if stationary in overworld, run recovery action then try action again + console.log("Detected stationary overworld."); + num_failures++; + if (num_failures > max_failures){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, console, + "handle_when_stationary_in_overworld(): Failed to complete action within " + std::to_string(max_failures) + " attempts.", + true + ); + } + context.wait_for_all_requests(); + recovery_action(info, console, context); + } + } +} + void wait_for_gradient_arrow( const ProgramInfo& info, @@ -860,6 +879,8 @@ bool check_ride_active(const ProgramInfo& info, ConsoleHandle& console, BotBaseC } void get_on_ride(const ProgramInfo& info, ConsoleHandle& console, BotBaseContext& context){ + pbf_press_button(context, BUTTON_PLUS, 20, 20); + WallClock start = current_time(); while (!check_ride_active(info, console, context)){ if (current_time() - start > std::chrono::minutes(3)){ @@ -1065,6 +1086,28 @@ void move_cursor_towards_flypoint_and_go_there( } + +void check_num_sunflora_found(SingleSwitchProgramEnvironment& env, BotBaseContext& context, int expected_number){ + context.wait_for_all_requests(); + VideoSnapshot screen = env.console.video().snapshot(); + ImageFloatBox num_sunflora_box = {0.27, 0.02, 0.04, 0.055}; + int number = OCR::read_number_waterfill(env.console, extract_box_reference(screen, num_sunflora_box), 0xff000000, 0xff808080); + + if (number != expected_number){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + env.logger(), + "The number of sunflora found is different than expected." + ); + }else{ + env.console.log("Number of sunflora found: " + std::to_string(number)); + } + + +} + + + } } } diff --git a/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.h b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.h index 02cc8970a..8823ab86d 100644 --- a/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStoryTools.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonSV_AutoStoryTools_H #define PokemonAutomation_PokemonSV_AutoStoryTools_H +#include "Common/Cpp/DateTime.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" #include "CommonFramework/Tools/StatsTracking.h" #include "CommonFramework/Language.h" @@ -191,6 +192,8 @@ void handle_unexpected_battles( // if stationary in overworld for an amount of time (seconds_stationary), run `recovery_action` then try `action` again // return once successfully completed `action` // throw exception if fails to complete `action` within a certain amount of time (minutes_timeout). +// NOTE: if using this function to wrap overworld_navigation(), keep in mind that +// confirm_marker_present() will keep the player still for 5 seconds before moving. Therefore, seconds_stationary should be greater than 5 seconds in this case. void handle_when_stationary_in_overworld( const ProgramInfo& info, ConsoleHandle& console, @@ -205,8 +208,9 @@ void handle_when_stationary_in_overworld( ConsoleHandle& console, BotBaseContext& context) >&& recovery_action, - size_t seconds_stationary = 5, - uint16_t minutes_timeout = 5 + size_t seconds_stationary = 6, + uint16_t minutes_timeout = 5, + size_t max_attempts = 2 ); void wait_for_gradient_arrow( @@ -283,6 +287,9 @@ void move_cursor_towards_flypoint_and_go_there( MoveCursor move_cursor_near_flypoint ); + +void check_num_sunflora_found(SingleSwitchProgramEnvironment& env, BotBaseContext& context, int expected_number); + } } } diff --git a/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.cpp b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.cpp new file mode 100644 index 000000000..14941c8b7 --- /dev/null +++ b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.cpp @@ -0,0 +1,630 @@ +/* AutoStory + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include "Common/Cpp/DateTime.h" +#include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/Exceptions/FatalProgramException.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OliveActionFailedException.h" +#include "CommonFramework/InferenceInfra/InferenceRoutines.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/Tools/StatsTracking.h" +#include "CommonFramework/Tools/VideoResolutionCheck.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Routines.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "NintendoSwitch/Inference/NintendoSwitch_DateReader.h" +#include "PokemonSwSh/Commands/PokemonSwSh_Commands_DateSpam.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonSwSh/Inference/PokemonSwSh_IvJudgeReader.h" +#include "PokemonSV/Programs/PokemonSV_GameEntry.h" +#include "PokemonSV/Programs/PokemonSV_SaveGame.h" +#include "PokemonSV/PokemonSV_Settings.h" +#include "PokemonSV/Inference/Overworld/PokemonSV_DirectionDetector.h" +#include "PokemonSV/Inference/Overworld/PokemonSV_OliveDetector.h" +#include "PokemonSV/Inference/Overworld/PokemonSV_NoMinimapDetector.h" +#include "PokemonSV_AutoStoryTools.h" +#include "PokemonSV_AutoStory_Segment_11.h" + +//#include +//using std::cout; +//using std::endl; +//#include +//#include + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSV{ + +using namespace Pokemon; + + + + +std::string AutoStory_Segment_11::name() const{ + return "10.2: Cortondo Gym - Gym challenge"; +} + +std::string AutoStory_Segment_11::start_text() const{ + return "Start: At Cortondo East Pokecenter."; +} + +std::string AutoStory_Segment_11::end_text() const{ + return "End: Beat Cortondo Gym challenge. At Cortondo West Pokecenter."; +} + +void AutoStory_Segment_11::run_segment(SingleSwitchProgramEnvironment& env, BotBaseContext& context, AutoStoryOptions options) const{ + AutoStoryStats& stats = env.current_stats(); + + context.wait_for_all_requests(); + env.console.overlay().add_log("Start Segment 10.2: Cortondo Gym - Gym challenge", COLOR_ORANGE); + + checkpoint_24(env, context, options.notif_status_update); + checkpoint_25(env, context, options.notif_status_update); + checkpoint_26(env, context, options.notif_status_update); + checkpoint_27(env, context, options.notif_status_update); + + context.wait_for_all_requests(); + env.console.log("End Segment 10.2: Cortondo Gym - Gym challenge", COLOR_GREEN); + stats.m_segment++; + env.update_stats(); + +} + +void checkpoint_24( + SingleSwitchProgramEnvironment& env, + BotBaseContext& context, + EventNotificationOption& notif_status_update +){ + AutoStoryStats& stats = env.current_stats(); + bool first_attempt = true; + while (true){ + try{ + if (first_attempt){ + checkpoint_save(env, context, notif_status_update); + first_attempt = false; + } + context.wait_for_all_requests(); + DirectionDetector direction; + do_action_and_monitor_for_battles(env.program_info(), env.console, context, + [&](const ProgramInfo& info, ConsoleHandle& console, BotBaseContext& context){ + direction.change_direction(env.program_info(), env.console, context, 2.71); + pbf_move_left_joystick(context, 128, 0, 375, 100); + direction.change_direction(env.program_info(), env.console, context, 1.26); + pbf_move_left_joystick(context, 128, 0, 1750, 100); + }); + + direction.change_direction(env.program_info(), env.console, context, 2.73); + pbf_move_left_joystick(context, 128, 0, 200, 100); + pbf_wait(context, 5 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + handle_when_stationary_in_overworld(env.program_info(), env.console, context, + [&](const ProgramInfo& info, ConsoleHandle& console, BotBaseContext& context){ + walk_forward_until_dialog(env.program_info(), env.console, context, NavigationMovementMode::DIRECTIONAL_ONLY, 20); + }, + [&](const ProgramInfo& info, ConsoleHandle& console, BotBaseContext& context){ + pbf_move_left_joystick(context, 0, 0, 100, 20); + }, + 5, 5 + ); + // enter gym building. talk go Nemona + mash_button_till_overworld(env.console, context, BUTTON_A, 360); + // talk to receptionist + walk_forward_until_dialog(env.program_info(), env.console, context, NavigationMovementMode::DIRECTIONAL_SPAM_A, 10); + clear_dialog(env.console, context, ClearDialogMode::STOP_OVERWORLD, 60, {CallbackEnum::OVERWORLD}); + + pbf_move_left_joystick(context, 128, 255, 500, 100); + pbf_wait(context, 3 * TICKS_PER_SECOND); + // wait for overworld after leaving gym + wait_for_overworld(env.program_info(), env.console, context, 30); + + pbf_move_left_joystick(context, 128, 0, 450, 100); + direction.change_direction(env.program_info(), env.console, context, 1.26); + pbf_move_left_joystick(context, 128, 0, 1600, 100); + fly_to_overlapping_flypoint(env.program_info(), env.console, context); + + + break; + }catch(...){ + context.wait_for_all_requests(); + env.console.log("Resetting from checkpoint."); + reset_game(env.program_info(), env.console, context); + stats.m_reset++; + env.update_stats(); + } + } + +} + + +void checkpoint_25( + SingleSwitchProgramEnvironment& env, + BotBaseContext& context, + EventNotificationOption& notif_status_update +){ + AutoStoryStats& stats = env.current_stats(); + bool first_attempt = true; + while (true){ + try{ + if (first_attempt){ + checkpoint_save(env, context, notif_status_update); + first_attempt = false; + } + context.wait_for_all_requests(); + + // section 1. align to Olive roll NPC + realign_player(env.program_info(), env.console, context, PlayerRealignMode::REALIGN_NEW_MARKER, 157, 0, 40); + overworld_navigation(env.program_info(), env.console, context, + NavigationStopCondition::STOP_MARKER, NavigationMovementMode::DIRECTIONAL_ONLY, + 128, 0, 40, 10); + + // section 1.1. keep walking forward and talk to Olive roll NPC + do_action_and_monitor_for_battles(env.program_info(), env.console, context, + [&](const ProgramInfo& info, ConsoleHandle& console, BotBaseContext& context){ + walk_forward_until_dialog(env.program_info(), env.console, context, NavigationMovementMode::DIRECTIONAL_SPAM_A, 10); + } + ); + mash_button_till_overworld(env.console, context, BUTTON_A); + + // section 2 + pbf_move_left_joystick(context, 128, 0, 1300, 100); + + // section 3 + DirectionDetector direction; + direction.change_direction(env.program_info(), env.console, context, 6.0); + pbf_move_left_joystick(context, 128, 0, 700, 100); + + // section 4. align to corner + direction.change_direction(env.program_info(), env.console, context, 4.69); + pbf_move_left_joystick(context, 128, 0, 150, 100); + + // section 5. battle first NPC + direction.change_direction(env.program_info(), env.console, context, 1.485); + walk_forward_until_dialog(env.program_info(), env.console, context, NavigationMovementMode::DIRECTIONAL_SPAM_A, 10, 128, 20); + clear_dialog(env.console, context, ClearDialogMode::STOP_BATTLE, 60, {CallbackEnum::BATTLE, CallbackEnum::DIALOG_ARROW}); + run_battle_press_A(env.console, context, BattleStopCondition::STOP_DIALOG); + mash_button_till_overworld(env.console, context, BUTTON_A); + + // section 6 + direction.change_direction(env.program_info(), env.console, context, 5.95); + pbf_move_left_joystick(context, 128, 0, 1000, 100); + + // section 7 + direction.change_direction(env.program_info(), env.console, context, 1.327); + pbf_move_left_joystick(context, 128, 0, 700, 100); + + // section 8 + direction.change_direction(env.program_info(), env.console, context, 6.106); + pbf_move_left_joystick(context, 128, 0, 200, 100); + + // section 9. battle second NPC + direction.change_direction(env.program_info(), env.console, context, 4.275); + walk_forward_until_dialog(env.program_info(), env.console, context, NavigationMovementMode::DIRECTIONAL_SPAM_A, 10, 128, 20); + clear_dialog(env.console, context, ClearDialogMode::STOP_BATTLE, 60, {CallbackEnum::BATTLE, CallbackEnum::DIALOG_ARROW}); + run_battle_press_A(env.console, context, BattleStopCondition::STOP_DIALOG); + mash_button_till_overworld(env.console, context, BUTTON_A); + + // section 10. leave Olive roll + pbf_mash_button(context, BUTTON_Y, 100); + clear_dialog(env.console, context, ClearDialogMode::STOP_PROMPT, 60, {CallbackEnum::PROMPT_DIALOG}); + clear_dialog(env.console, context, ClearDialogMode::STOP_TIMEOUT, 15, {CallbackEnum::PROMPT_DIALOG}); + wait_for_overworld(env.program_info(), env.console, context); + enter_menu_from_overworld(env.program_info(), env.console, context, -1); + + break; + }catch(...){ + context.wait_for_all_requests(); + env.console.log("Resetting from checkpoint."); + reset_game(env.program_info(), env.console, context); + stats.m_reset++; + env.update_stats(); + } + } + +} + +void checkpoint_26( + SingleSwitchProgramEnvironment& env, + BotBaseContext& context, + EventNotificationOption& notif_status_update +){ + AutoStoryStats& stats = env.current_stats(); + bool first_attempt = true; + while (true){ + try{ + if (first_attempt){ + checkpoint_save(env, context, notif_status_update); + first_attempt = false; + } + context.wait_for_all_requests(); + + // change the time of day: close game, change time to 5:45 am. + pbf_press_button(context, BUTTON_HOME, 20, GameSettings::instance().GAME_TO_HOME_DELAY); + change_date(env, context, {2025, 1, 1, 5, 45, 0}); + reset_game_from_home(env.program_info(), env.console, context); + + // talk to Olive roll NPC + walk_forward_until_dialog(env.program_info(), env.console, context, NavigationMovementMode::DIRECTIONAL_SPAM_A, 10, 128, 20); + mash_button_till_overworld(env.console, context, BUTTON_A); + + // section 1 + pbf_move_left_joystick(context, 128, 0, 400, 50); + OliveDetector green(env.console); + size_t MAX_ATTEMPTS_SECTION_1 = 2; + uint16_t ticks_to_walk_for_section1 = 650; + uint16_t push_strength_section_1 = 75; + for (size_t i = 0; i < MAX_ATTEMPTS_SECTION_1; i++){ + try{ + green.push_olive_forward(env.program_info(), env.console, context, 4.38, ticks_to_walk_for_section1, push_strength_section_1); + // green.align_to_olive(env.program_info(), env.console, context, 4.38); + green.walk_up_to_olive(env.program_info(), env.console, context, 4.38); + break; + }catch (OliveActionFailedException& e){ + if (e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // if olive is stuck, we might have pushed the olive all the way to the end. so we can try moving on. + break; + } + + if (i >= MAX_ATTEMPTS_SECTION_1-1){ + throw e; + } + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED || e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE){ + // may have walked past olive + pbf_move_left_joystick(context, 128, 255, 200, 50); + pbf_wait(context, 5 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + ticks_to_walk_for_section1 = 100; + push_strength_section_1 = 50; + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE + throw e; + } + + } + } + + + // section 1b. realign using fence corner + DirectionDetector direction; + direction.change_direction(env.program_info(), env.console, context, 2.74); + pbf_move_left_joystick(context, 128, 0, 200, 50); + direction.change_direction(env.program_info(), env.console, context, 4.328); + pbf_move_left_joystick(context, 128, 0, 200, 50); + direction.change_direction(env.program_info(), env.console, context, 1.22); + pbf_move_left_joystick(context, 128, 0, 75, 50); + + // section 2.1 nudge olive straight + size_t MAX_ATTEMPTS_SECTION_2_1 = 2; + for (size_t i = 0; i < MAX_ATTEMPTS_SECTION_2_1; i++){ + try{ + green.push_olive_forward(env.program_info(), env.console, context, 5.95, 50, 50); + break; + }catch (OliveActionFailedException& e){ + if (i >= MAX_ATTEMPTS_SECTION_2_1-1){ + throw e; + } + + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED){ + pbf_move_left_joystick(context, 128, 255, 250, 50); + pbf_move_left_joystick(context, 128, 0, 50, 50); // move forward slight in case the olive is undetectable since it's right in front of the character + pbf_wait(context, 5 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + }else if (e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE || e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // try moving back and then ramming forward + pbf_move_left_joystick(context, 128, 255, 50, 50); + pbf_move_left_joystick(context, 128, 0, 150, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + break; // then move on to next section + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE, + throw e; + } + + } + } + + pbf_move_left_joystick(context, 128, 255, 100, 50); + green.align_to_olive(env.program_info(), env.console, context, 5.95); + pbf_move_left_joystick(context, 128, 255, 500, 50); + + // section 2.2 push at angle towards outer fence + size_t MAX_ATTEMPTS_SECTION_2_2 = 2; + // uint16_t ticks_walked_section2_2 = 0; + for (size_t i = 0; i < MAX_ATTEMPTS_SECTION_2_2; i++){ + try{ + green.push_olive_forward(env.program_info(), env.console, context, 5.8, 250, 50); + break; + }catch (OliveActionFailedException& e){ + if (i >= MAX_ATTEMPTS_SECTION_2_2-1){ + throw e; + } + + if (e.m_fail_reason == OliveFail::OLIVE_STUCK){ // olive possibly stuck on fence + pbf_move_left_joystick(context, 128, 255, 20, 50); + pbf_move_left_joystick(context, 0, 128, 100, 50); + pbf_move_left_joystick(context, 128, 0, 200, 50); + // push olive parallel to fence + green.align_to_olive(env.program_info(), env.console, context, 4.28, 20); + green.walk_up_to_olive(env.program_info(), env.console, context, 4.28); + green.push_olive_forward(env.program_info(), env.console, context, 4.28, 100); + green.align_to_olive(env.program_info(), env.console, context, 4.28, 20); + green.walk_up_to_olive(env.program_info(), env.console, context, 4.28); + // back off + pbf_move_left_joystick(context, 128, 255, 50, 50); + // realign using fence corner + direction.change_direction(env.program_info(), env.console, context, 2.74); + pbf_move_left_joystick(context, 128, 0, 400, 50); + direction.change_direction(env.program_info(), env.console, context, 4.328); + pbf_move_left_joystick(context, 128, 0, 400, 50); + direction.change_direction(env.program_info(), env.console, context, 1.22); + pbf_move_left_joystick(context, 128, 0, 100, 50); + }else if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED){ + pbf_move_left_joystick(context, 128, 255, 200, 50); + pbf_wait(context, 5 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + + }else if (e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE){ + // try moving back and then ramming forward + pbf_move_left_joystick(context, 128, 255, 50, 50); + pbf_move_left_joystick(context, 128, 0, 150, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE, + // continue trying to push the olive. at a different angle + break; + } + } + } + + pbf_move_left_joystick(context, 128, 255, 100, 50); + green.align_to_olive(env.program_info(), env.console, context, 5.95); + pbf_move_left_joystick(context, 128, 255, 500, 50); + + // section 2.3 push olive past first NPC + uint16_t ticks_to_walk_for_section2_3 = 950; + size_t MAX_ATTEMPTS = 2; + for (size_t i = 0; i < MAX_ATTEMPTS; i++){ + try{ + green.push_olive_forward(env.program_info(), env.console, context, 5.95, ticks_to_walk_for_section2_3, 75, 10); + // green.align_to_olive(env.program_info(), env.console, context, 5.95, 10); + green.walk_up_to_olive(env.program_info(), env.console, context, 5.95, 10); + break; + }catch (OliveActionFailedException& e){ + if (e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // if olive is stuck, we might have pushed the olive all the way to the end. so we can try moving on. + break; + } + + if (i >= MAX_ATTEMPTS-1){ + throw e; + } + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED || e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE){ + // may have walked past olive + pbf_move_left_joystick(context, 128, 255, 200, 50); + pbf_wait(context, 5 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + // ticks_to_walk_for_section2_3 = 500; + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE + throw e; + } + + } + } + + // section 2b. realign using fence corner + direction.change_direction(env.program_info(), env.console, context, 4.5); + pbf_move_left_joystick(context, 128, 0, 200, 50); + direction.change_direction(env.program_info(), env.console, context, 5.86); + pbf_move_left_joystick(context, 128, 0, 300, 50); + direction.change_direction(env.program_info(), env.console, context, 2.76); + pbf_move_left_joystick(context, 128, 0, 50, 50); + + // section 3.1 push olive across the hump + uint16_t ticks_to_walk_for_section3_1 = 350; + uint16_t ticks_walked_section3_1 = 0; + for (size_t i = 0; i < MAX_ATTEMPTS; i++){ + try{ + ticks_walked_section3_1 = green.push_olive_forward(env.program_info(), env.console, context, 1.27, ticks_to_walk_for_section3_1, 125); + break; + }catch (OliveActionFailedException& e){ + // may have failed to push the olive past the hump. and walked past it + if (i >= MAX_ATTEMPTS-1){ + throw e; + } + + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED){ + pbf_move_left_joystick(context, 128, 255, 250, 50); + pbf_move_left_joystick(context, 128, 0, 50, 50); // move forward slight in case the olive is undetectable since it's right in front of the character + pbf_wait(context, 5 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + ticks_to_walk_for_section3_1 = 200; + }else if (e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE || e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // try moving back and then ramming forward + pbf_move_left_joystick(context, 128, 255, 50, 50); + pbf_move_left_joystick(context, 128, 0, 150, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + ticks_to_walk_for_section3_1 = 200; + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE, + throw e; + } + + } + } + + + // section 3.2 push olive across the hump and into fence + uint16_t total_ticks_to_walk_for_section3 = 550; + uint16_t ticks_to_walk_for_section3_2 = 0; + if (ticks_walked_section3_1 < total_ticks_to_walk_for_section3){ + ticks_to_walk_for_section3_2 = (total_ticks_to_walk_for_section3 - ticks_walked_section3_1); + } + for (size_t i = 0; i < MAX_ATTEMPTS; i++){ + try{ + green.push_olive_forward(env.program_info(), env.console, context, 1.27, ticks_to_walk_for_section3_2, 125); + pbf_move_left_joystick(context, 128, 255, 100, 50); + green.align_to_olive(env.program_info(), env.console, context, 1.27); + green.walk_up_to_olive(env.program_info(), env.console, context, 1.27); + break; + }catch (OliveActionFailedException& e){ + // may have failed to push the olive past the hump. and walked past it + if (i >= MAX_ATTEMPTS-1){ + throw e; + } + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED){ + pbf_move_left_joystick(context, 128, 255, 200, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + ticks_to_walk_for_section3_2 += 200; + }else if (e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE || e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // try moving back and then ramming forward + pbf_move_left_joystick(context, 128, 255, 50, 50); + pbf_move_left_joystick(context, 128, 0, 150, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE, + throw e; + } + + } + } + + + // section 3b. realign using fence. + direction.change_direction(env.program_info(), env.console, context, 3.0); + pbf_move_left_joystick(context, 128, 0, 75, 50); + direction.change_direction(env.program_info(), env.console, context, 1.17); + pbf_move_left_joystick(context, 128, 0, 100, 50); + + // section 4.1 last stretch. nudge the olive + for (size_t i = 0; i < MAX_ATTEMPTS; i++){ + try{ + green.align_to_olive(env.program_info(), env.console, context, 6.0); + green.walk_up_to_olive(env.program_info(), env.console, context, 6.0); + green.push_olive_forward(env.program_info(), env.console, context, 6.0, 50, 50); + break; + }catch (OliveActionFailedException& e){ + // may have failed to push the olive. and walked past it + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED){ + pbf_move_left_joystick(context, 128, 255, 200, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + }else if (e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE || e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // try moving back and then ramming forward + pbf_move_left_joystick(context, 128, 255, 50, 50); + pbf_move_left_joystick(context, 128, 0, 100, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE, + throw e; + } + } + } + + // section 4.2 past second NPC and into the finish line + NoMinimapWatcher no_minimap(env.console, COLOR_RED, Milliseconds(5000)); + size_t MAX_ATTEMPTS_SECTION_4 = 3; + int ret = run_until( + env.console, context, + [&](BotBaseContext& context){ + for (size_t i = 0; i < MAX_ATTEMPTS_SECTION_4; i++){ + try{ + green.push_olive_forward(env.program_info(), env.console, context, 6.0, 250); + green.push_olive_forward(env.program_info(), env.console, context, 5.8, 100); + green.push_olive_forward(env.program_info(), env.console, context, 6.0, 200); + green.push_olive_forward(env.program_info(), env.console, context, 6.1, 200); + break; + }catch (OliveActionFailedException& e){ + // may have failed to push the olive. and walked past it + if (e.m_fail_reason == OliveFail::NO_OLIVE_DETECTED){ + pbf_move_left_joystick(context, 128, 255, 200, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + }else if (e.m_fail_reason == OliveFail::FAILED_WALK_TO_OLIVE || e.m_fail_reason == OliveFail::OLIVE_STUCK){ + // try moving back and then ramming forward + pbf_move_left_joystick(context, 128, 255, 50, 50); + pbf_move_left_joystick(context, 128, 0, 150, 50); + pbf_wait(context, 7 * TICKS_PER_SECOND); + context.wait_for_all_requests(); + // then push angled towards the right + green.push_olive_forward(env.program_info(), env.console, context, 5.8, 100); + }else{ // FAILED_PUSH_OLIVE_TOTAL_DISTANCE, + throw e; + } + } + } + }, + {no_minimap} + ); + if (ret < 0){ + throw OliveActionFailedException( + ErrorReport::SEND_ERROR_REPORT, + env.logger(), + "Failed to finish Olive roll in the last stretch." + ); + } + env.log("No minimap seen. Likely finished the Olive roll."); + + // section 8. finish olive roll + mash_button_till_overworld(env.console, context, BUTTON_A, 360); + + // fix the time + pbf_press_button(context, BUTTON_HOME, 10, GameSettings::instance().GAME_TO_HOME_DELAY); + home_to_date_time(context, false, false); + pbf_press_button(context, BUTTON_A, 20, 105); + pbf_press_button(context, BUTTON_A, 20, 105); + pbf_press_button(context, BUTTON_HOME, 20, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY); + resume_game_from_home(env.console, context); + + enter_menu_from_overworld(env.program_info(), env.console, context, -1); + + break; + }catch (...){ + context.wait_for_all_requests(); + env.console.log("Resetting from checkpoint."); + // reset_game(env.program_info(), env.console, context); // the checkpoint itself already resets the game, so need to reset twice + stats.m_reset++; + env.update_stats(); + } + } + +} + +void checkpoint_27( + SingleSwitchProgramEnvironment& env, + BotBaseContext& context, + EventNotificationOption& notif_status_update +){ + AutoStoryStats& stats = env.current_stats(); + bool first_attempt = true; + while (true){ + try{ + if (first_attempt){ + checkpoint_save(env, context, notif_status_update); + first_attempt = false; + } + context.wait_for_all_requests(); + move_cursor_towards_flypoint_and_go_there(env.program_info(), env.console, context, {ZoomChange::KEEP_ZOOM, 255, 128, 40}); + + break; + }catch (...){ + context.wait_for_all_requests(); + env.console.log("Resetting from checkpoint."); + reset_game(env.program_info(), env.console, context); + stats.m_reset++; + env.update_stats(); + } + } + +} + + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.h b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.h new file mode 100644 index 000000000..46387b1d8 --- /dev/null +++ b/SerialPrograms/Source/PokemonSV/Programs/AutoStory/PokemonSV_AutoStory_Segment_11.h @@ -0,0 +1,56 @@ +/* Autostory + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_PokemonSV_AutoStory_Segment_11_H +#define PokemonAutomation_PokemonSV_AutoStory_Segment_11_H + +#include +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "CommonFramework/Options/LanguageOCROption.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "Common/NintendoSwitch/NintendoSwitch_ControllerDefs.h" +#include "PokemonSV/Programs/PokemonSV_Navigation.h" +#include "PokemonSV_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSV{ + +class AutoStory_Segment_11 : public AutoStory_Segment{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_segment( + SingleSwitchProgramEnvironment& env, + BotBaseContext& context, + AutoStoryOptions options) const override; +}; + + +// start: At Cortondo East Pokecenter +// end: Spoke to Cortondo Gym reception. At Cortondo West Pokecenter. +void checkpoint_24(SingleSwitchProgramEnvironment& env, BotBaseContext& context, EventNotificationOption& notif_status_update); + +// start: Spoke to Cortondo Gym reception. At Cortondo West Pokecenter. +// end: Defeated the trainers at Olive Roll, but left Olive unmoved. Then backed out, standing in front of the Olive Roll NPC. +void checkpoint_25(SingleSwitchProgramEnvironment& env, BotBaseContext& context, EventNotificationOption& notif_status_update); + +// start: Defeated the trainers at Olive Roll, but left Olive unmoved. Then backed out, standing in front of the Olive Roll NPC. +// end: Completed Olive roll gym challenge. +void checkpoint_26(SingleSwitchProgramEnvironment& env, BotBaseContext& context, EventNotificationOption& notif_status_update); + +// start: Completed Olive roll gym challenge. +// end: At Cortondo East Pokecenter. +void checkpoint_27(SingleSwitchProgramEnvironment& env, BotBaseContext& context, EventNotificationOption& notif_status_update); + + + +} +} +} +#endif