From 8711f8a3557b58e8acecb73c606ffecfb6e45f9b Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Fri, 20 Feb 2026 21:44:28 +0100 Subject: [PATCH 01/14] Updated draft --- .../PokemonSwSh_MaxLair_StateMachine.cpp | 17 +++-- .../PokemonSwSh_MaxLair_BossFinder.cpp | 3 + .../MaxLair/PokemonSwSh_MaxLair_Standard.cpp | 3 + .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 76 ++++++++++++++++++- .../PokemonSwSh_MaxLair_Run_Entrance.h | 3 +- 5 files changed, 93 insertions(+), 9 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp index fed71fe8aa..e08c712fc6 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp @@ -182,16 +182,19 @@ StateMachineAction run_state_iteration( global_state, decider, entrance ); - case 7: + case 7: { console.log("Current State: Entrance"); + std::string boss_slug = global_state.infer_actual_state(console_index).boss; run_entrance( - runtime, - env, console_index, - console, context, - save_path, - global_state - ); + runtime, + env, console_index, + console, context, + boss_slug, + decider, + global_state + ); return StateMachineAction::DONE_WITH_ADVENTURE; + } case 8: console.log("Current State: Frozen Screen", COLOR_RED); // pbf_mash_button(context, BUTTON_B, 1000ms); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp index 1b503f2437..f83aeee6ff 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp @@ -151,6 +151,9 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ } throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); } + virtual bool save_path(const std::string& boss_slug) const override { + return false; + } private: diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp index 53280e6c16..6696ff4890 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp @@ -166,6 +166,9 @@ class EndBattleDecider_Standard : public EndBattleDecider{ } return actions.no_shinies; } + virtual bool save_path(const std::string& boss_slug) const override { + return false; + } private: const MaxLairStandard_ConsoleOptions& console(size_t index) const{ diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index a86a977f62..41f9730b01 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -8,6 +8,7 @@ #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonSwSh/MaxLair/Inference/PokemonSwSh_MaxLair_Detect_PokemonReader.h" #include "PokemonSwSh_MaxLair_Run_Entrance.h" namespace PokemonAutomation{ @@ -15,12 +16,60 @@ namespace NintendoSwitch{ namespace PokemonSwSh{ namespace MaxLairInternal{ +std::vector read_saved_name( + Logger& logger, + Language language, + const ImageViewRGB32& screen, + const ImageFloatBox& box +) { + auto cropped = extract_box_reference(screen, box); + OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( + logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), + 0.01, 0.50, 2.0 + ); + if (result.results.empty()) { + return ""; + } + + return result.results.begin()->second.token; + +} + +// Read the three currently saved paths (if any) from the entrance screen. +// Returns a vector of three slugs (empty strings for empty slots). +std::vector read_saved_paths( + VideoStream& stream, + Language language +) { + auto snapshot = stream.video().snapshot(); + if (!snapshot) return { "", "", "" }; + + const double width = snapshot->width(); + const double height = snapshot->height(); + + // To be adjusted + const ImageFloatBox name_region(0.30, 0.75, 0.40, 0.05); + + std::vector slugs; + for (int i = 0; i < 3; ++i) { + ImageFloatBox box( + name_region.x + i * (name_region.width / 3.0), + name_region.y, + name_region.width / 3.0, + name_region.height + ); + std::string slug = read_saved_name(stream.logger(), language, *snapshot, box); + slugs.push_back(slug); + } + return slugs; +} void run_entrance( AdventureRuntime& runtime, ProgramEnvironment& env, size_t console_index, VideoStream& stream, ProControllerContext& context, - bool save_path, + const std::string& boss_slug, + const EndBattleDecider& decider, GlobalStateTracker& state_tracker ){ GlobalState& state = state_tracker[console_index]; @@ -35,6 +84,31 @@ void run_entrance( runtime.path_stats.clear(); } } + + // Read the three currently saved paths (if any) + Language language = runtime.console_settings[console_index].language; + std::vector saved = read_saved_paths(stream, language); + + // Determine whether we should save this boss + bool should_save = false; + if (!boss_slug.empty()) { + // Check if already saved + bool already_saved = false; + for (const auto& s : saved) { + if (s == boss_slug) { + already_saved = true; + break; + } + } + // Count non‑empty slots + size_t occupied = 0; + for (const auto& s : saved) { + if (!s.empty()) ++occupied; + } + if (!already_saved && occupied < 3) { + should_save = decider.save_path(boss_slug); + } + } OverlayBoxScope box(stream.overlay(), {0.782, 0.850, 0.030, 0.050}); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h index 14c0d8a90c..b2399a84bf 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h @@ -22,7 +22,8 @@ void run_entrance( AdventureRuntime& runtime, ProgramEnvironment& env, size_t console_index, VideoStream& stream, ProControllerContext& context, - bool save_path, + const std::string& boss_slug, + const EndBattleDecider& decider, GlobalStateTracker& state_tracker ); From f581b9c4fa52fccf5062fd098c3abf58846d6e7f Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 21 Feb 2026 16:47:44 +0100 Subject: [PATCH 02/14] Updated draft for adding saved paths on the go --- .../Options/PokemonSwSh_MaxLair_Options.h | 1 + ...PokemonSwSh_MaxLair_Options_BossAction.cpp | 1 + .../PokemonSwSh_MaxLair_Options_BossAction.h | 2 + .../PokemonSwSh_MaxLair_BossFinder.cpp | 7 +++- .../PokemonSwSh_MaxLair_StrongBoss.cpp | 3 ++ .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 41 ++++++++++++------- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h index 6b4e8335cf..3821077b26 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h @@ -59,6 +59,7 @@ class EndBattleDecider{ const PathStats& path_stats, bool any_shiny, bool boss_is_shiny ) const = 0; + virtual bool save_path(const std::string& boss_slug) const = 0; }; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp index 61d467e4c6..d580e2698d 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp @@ -49,6 +49,7 @@ BossActionRow::BossActionRow(std::string slug, const std::string& name_slug, con BossAction::CATCH_AND_STOP_IF_SHINY ) , ball(LockMode::UNLOCK_WHILE_RUNNING, "poke-ball") + , save_on_the_go(LockMode::UNLOCK_WHILE_RUNNING, false) { PA_ADD_STATIC(pokemon); add_option(action, "Action"); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h index 3e228c5089..391087fa19 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h @@ -11,6 +11,7 @@ #include "Common/Cpp/Options/StaticTableOption.h" #include "CommonFramework/Options/LabelCellOption.h" #include "PokemonSwSh/Options/PokemonSwSh_BallSelectOption.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -31,6 +32,7 @@ class BossActionRow : public StaticTableRow{ LabelCellOption pokemon; EnumDropdownCell action; PokemonBallSelectCell ball; + BooleanCheckBoxCell save_on_the_go; }; class BossActionTable : public StaticTableOption{ diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp index f83aeee6ff..dacf378c78 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp @@ -152,7 +152,12 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); } virtual bool save_path(const std::string& boss_slug) const override { - return false; + try { + const BossActionRow& row = get_filter(boss_slug); + return row.save_on_the_go; + } catch (...) { + return false; + } } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index d8b2e2def5..060d21239f 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,6 +215,9 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } + virtual bool save_path(const std::string& boss_slug) const override { + return true; + } private: const MaxLairStrongBoss_ConsoleOptions& console(size_t index) const{ diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index 41f9730b01..a5bb312d51 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -8,6 +8,7 @@ #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Inference/Pokemon_NameReader.h" #include "PokemonSwSh/MaxLair/Inference/PokemonSwSh_MaxLair_Detect_PokemonReader.h" #include "PokemonSwSh_MaxLair_Run_Entrance.h" @@ -16,27 +17,27 @@ namespace NintendoSwitch{ namespace PokemonSwSh{ namespace MaxLairInternal{ -std::vector read_saved_name( +using namespace Pokemon; + +namespace { +std::string read_saved_name( Logger& logger, Language language, - const ImageViewRGB32& screen, + const ImageViewRGB32& image, const ImageFloatBox& box -) { - auto cropped = extract_box_reference(screen, box); + ) { + auto cropped = extract_box_reference(image, box); OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( - logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), - 0.01, 0.50, 2.0 - ); + logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), +0.01, 0.50, 2.0 + ); if (result.results.empty()) { return ""; } return result.results.begin()->second.token; - } -// Read the three currently saved paths (if any) from the entrance screen. -// Returns a vector of three slugs (empty strings for empty slots). std::vector read_saved_paths( VideoStream& stream, Language language @@ -44,6 +45,9 @@ std::vector read_saved_paths( auto snapshot = stream.video().snapshot(); if (!snapshot) return { "", "", "" }; + const ImageRGB32* img_ptr = snapshot.get(); + const ImageViewRGB32& screen = *img_ptr; + const double width = snapshot->width(); const double height = snapshot->height(); @@ -51,18 +55,24 @@ std::vector read_saved_paths( const ImageFloatBox name_region(0.30, 0.75, 0.40, 0.05); std::vector slugs; - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < 3; i++) { ImageFloatBox box( name_region.x + i * (name_region.width / 3.0), name_region.y, name_region.width / 3.0, name_region.height ); - std::string slug = read_saved_name(stream.logger(), language, *snapshot, box); + std::string slug = read_saved_name(stream.logger(), language, screen, box); slugs.push_back(slug); } return slugs; } +} + + +// Read the three currently saved paths (if any) from the entrance screen. +// Returns a vector of three slugs (empty strings for empty slots). + void run_entrance( AdventureRuntime& runtime, @@ -115,15 +125,16 @@ void run_entrance( pbf_wait(context, 2000ms); while (true){ - if (save_path){ + if (should_save){ pbf_press_button(context, BUTTON_A, 160ms, 1000ms); }else{ pbf_press_button(context, BUTTON_B, 160ms, 1000ms); } context.wait_for_all_requests(); - VideoSnapshot screen = stream.video().snapshot(); - ImageStats stats = image_stats(extract_box_reference(screen, box)); + VideoSnapshot screen_snap = stream.video().snapshot(); + const ImageRGB32* img_ptr = screen_snap.get(); + ImageStats stats = image_stats(extract_box_reference(*img_ptr, box)); if (!is_grey(stats, 400, 1000)){ break; } From fc7c2aa259da3a82758883a2f0915ed6418ab4de Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Mon, 23 Feb 2026 22:35:16 +0100 Subject: [PATCH 03/14] Updated draft --- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 125 ++++++------------ .../PokemonSwSh_MaxLair_Run_Entrance.h | 1 + 2 files changed, 41 insertions(+), 85 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index a5bb312d51..b8eb2dc932 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -19,56 +19,6 @@ namespace MaxLairInternal{ using namespace Pokemon; -namespace { -std::string read_saved_name( - Logger& logger, - Language language, - const ImageViewRGB32& image, - const ImageFloatBox& box - ) { - auto cropped = extract_box_reference(image, box); - OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( - logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), -0.01, 0.50, 2.0 - ); - if (result.results.empty()) { - return ""; - } - - return result.results.begin()->second.token; -} - -std::vector read_saved_paths( - VideoStream& stream, - Language language -) { - auto snapshot = stream.video().snapshot(); - if (!snapshot) return { "", "", "" }; - - const ImageRGB32* img_ptr = snapshot.get(); - const ImageViewRGB32& screen = *img_ptr; - - const double width = snapshot->width(); - const double height = snapshot->height(); - - // To be adjusted - const ImageFloatBox name_region(0.30, 0.75, 0.40, 0.05); - - std::vector slugs; - for (int i = 0; i < 3; i++) { - ImageFloatBox box( - name_region.x + i * (name_region.width / 3.0), - name_region.y, - name_region.width / 3.0, - name_region.height - ); - std::string slug = read_saved_name(stream.logger(), language, screen, box); - slugs.push_back(slug); - } - return slugs; -} -} - // Read the three currently saved paths (if any) from the entrance screen. // Returns a vector of three slugs (empty strings for empty slots). @@ -79,6 +29,7 @@ void run_entrance( ProgramEnvironment& env, size_t console_index, VideoStream& stream, ProControllerContext& context, const std::string& boss_slug, + bool followed_path, const EndBattleDecider& decider, GlobalStateTracker& state_tracker ){ @@ -95,49 +46,53 @@ void run_entrance( } } - // Read the three currently saved paths (if any) - Language language = runtime.console_settings[console_index].language; - std::vector saved = read_saved_paths(stream, language); + // Get the boss slug + std::string boss_slug; + if (runtime.host_index < state_tracker.size()) { + boss_slug = state_tracker.infer_actual_state(runtime.host_index).boss; + } // Determine whether we should save this boss bool should_save = false; if (!boss_slug.empty()) { - // Check if already saved - bool already_saved = false; - for (const auto& s : saved) { - if (s == boss_slug) { - already_saved = true; - break; - } - } - // Count non‑empty slots - size_t occupied = 0; - for (const auto& s : saved) { - if (!s.empty()) ++occupied; - } - if (!already_saved && occupied < 3) { - should_save = decider.save_path(boss_slug); - } + should_save = runtime.actions.save_path(boss_slug); + stream.log("Boss: " + boss_slug + ", should save: " + (should_save ? "Yes" : "No"), COLOR_BLUE); } + + // Overlay box to detect when a dialogue box is present (grey area at bottom) - - OverlayBoxScope box(stream.overlay(), {0.782, 0.850, 0.030, 0.050}); - - pbf_wait(context, 2000ms); - while (true){ - if (should_save){ - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - }else{ - pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + OverlayBoxScope dialog_box(stream.overlay(), {0.782, 0.850, 0.030, 0.050}); + + // First step, check if there is a path to be saved + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + context.wait_for_all_requests(); + + // Then, if there is something to be saved, check if the next dialogue box allows us to directly save or not + + if (followed_path) { + stream.log("Handling followed-path dialogue.", COLOR_BLUE); + // Keep the path or not + if (followed_path) + pbf_press_button(context, BUTTON_A, 160ms, 0ms); + } else { + pbf_press_button(context, BUTTON_B, 160ms, 0ms); + } + context.wait_for_all_requests(); + + // Press A to finish dialogue + + while (true) { + context.wait_for(400ms); + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) continue; + ImageStats stats = image_stats(extract_box_reference(*screen, dialog_box)); + + if (!is_grey(stats, 400, 1000)) { + break; // Dialogue box gone } + pbf_press_button(context, BUTTON_A, 160ms, 0ms); context.wait_for_all_requests(); - - VideoSnapshot screen_snap = stream.video().snapshot(); - const ImageRGB32* img_ptr = screen_snap.get(); - ImageStats stats = image_stats(extract_box_reference(*img_ptr, box)); - if (!is_grey(stats, 400, 1000)){ - break; - } + } } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h index b2399a84bf..4c6b95e5b4 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h @@ -23,6 +23,7 @@ void run_entrance( ProgramEnvironment& env, size_t console_index, VideoStream& stream, ProControllerContext& context, const std::string& boss_slug, + bool followed_path, const EndBattleDecider& decider, GlobalStateTracker& state_tracker ); From 5054e92437defe37d600ccdfc9fcf6a9b9fd7627 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 24 Feb 2026 11:34:47 +0100 Subject: [PATCH 04/14] Added more draft changes --- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index b8eb2dc932..ae34dd6aa0 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -72,28 +72,28 @@ void run_entrance( if (followed_path) { stream.log("Handling followed-path dialogue.", COLOR_BLUE); // Keep the path or not - if (followed_path) - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - } else { - pbf_press_button(context, BUTTON_B, 160ms, 0ms); - } - context.wait_for_all_requests(); + if (followed_path) { + pbf_press_button(context, BUTTON_A, 160ms, 0ms); + } else { + pbf_press_button(context, BUTTON_B, 160ms, 0ms); + } + context.wait_for_all_requests(); // Press A to finish dialogue - while (true) { - context.wait_for(400ms); - VideoSnapshot screen = stream.video().snapshot(); - if (!screen) continue; - ImageStats stats = image_stats(extract_box_reference(*screen, dialog_box)); - - if (!is_grey(stats, 400, 1000)) { - break; // Dialogue box gone + while (true) { + context.wait_for(400ms); + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) continue; + ImageStats stats = image_stats(extract_box_reference(*screen, dialog_box)); + + if (!is_grey(stats, 400, 1000)) { + break; // Dialogue box gone + } + pbf_press_button(context, BUTTON_A, 160ms, 0ms); + context.wait_for_all_requests(); + } - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - context.wait_for_all_requests(); - - } } From d12f770688f422aedb2c309c6315a8068d73cd3d Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 24 Feb 2026 16:15:38 +0100 Subject: [PATCH 05/14] Updated draft, now working logic but needs UI --- .../PokemonSwSh_MaxLair_StateMachine.cpp | 3 +- .../Options/PokemonSwSh_MaxLair_Options.h | 10 +- .../PokemonSwSh_MaxLair_BossFinder.cpp | 13 +- .../MaxLair/PokemonSwSh_MaxLair_Standard.cpp | 4 +- .../PokemonSwSh_MaxLair_StrongBoss.cpp | 2 +- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 165 +++++++++++++++--- .../PokemonSwSh_MaxLair_Run_Entrance.h | 2 - 7 files changed, 160 insertions(+), 39 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp index e08c712fc6..decc2bf76f 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp @@ -189,8 +189,7 @@ StateMachineAction run_state_iteration( runtime, env, console_index, console, context, - boss_slug, - decider, + save_path, global_state ); return StateMachineAction::DONE_WITH_ADVENTURE; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h index 3821077b26..5baa5d79ec 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h @@ -59,7 +59,15 @@ class EndBattleDecider{ const PathStats& path_stats, bool any_shiny, bool boss_is_shiny ) const = 0; - virtual bool save_path(const std::string& boss_slug) const = 0; + + // For BossFinder: whether to save a new path for the given boss. + virtual bool should_save_path(const std::string& boss_slug) const { return false; } + + // For BossFinder: whether a boss is protected (should be kept in the list). + virtual bool is_protected_path(const std::string& boss_slug) const { return false; } + + // For Standard/StrongBoss: whether to keep a followed path when prompted. + virtual bool should_keep_followed_path() const { return false; } }; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp index dacf378c78..95db67ba05 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp @@ -151,13 +151,12 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ } throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); } - virtual bool save_path(const std::string& boss_slug) const override { - try { - const BossActionRow& row = get_filter(boss_slug); - return row.save_on_the_go; - } catch (...) { - return false; - } + virtual bool should_save_path(const std::string& boss_slug) const override { + return get_filter(boss_slug).save_on_the_go; + } + + virtual bool is_protected_path(const std::string& boss_slug) const override { + return get_filter(boss_slug).save_on_the_go; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp index 6696ff4890..63b319753b 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp @@ -166,8 +166,8 @@ class EndBattleDecider_Standard : public EndBattleDecider{ } return actions.no_shinies; } - virtual bool save_path(const std::string& boss_slug) const override { - return false; + virtual bool should_keep_followed_path() const override { + return true; } private: diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index 060d21239f..da2a71ca4d 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,7 +215,7 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } - virtual bool save_path(const std::string& boss_slug) const override { + virtual bool should_keep_followed_path() const override { return true; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index ae34dd6aa0..04988b582b 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -19,22 +19,69 @@ namespace MaxLairInternal{ using namespace Pokemon; +namespace { + // Boxes for the corresponding pokemon names in case the list is full and the player battled a boss that isn't in the list + const ImageFloatBox NAME_BOXES[3] = { + {0.30, 0.40, 0.40, 0.05}, + {0.30, 0.50, 0.40, 0.05}, + {0.30, 0.60, 0.40, 0.05} + }; -// Read the three currently saved paths (if any) from the entrance screen. -// Returns a vector of three slugs (empty strings for empty slots). + // Read the three saved paths names if the user has already saved 3 paths + std::vector read_saved_paths( + VideoStream& stream, + Language language, + const ImageViewRGB32& screen + ) { + + std::vector slugs; + + for (int i = 0; i < 3; ++i) { + auto cropped = extract_box_reference(screen, NAME_BOXES[i]); + OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( + stream.logger(), language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), + 0.01, 0.50, 2.0 + ); + if (result.results.empty()) { + slugs.emplace_back(); + } else { + slugs.push_back(result.results.begin()->second.token); + } + } + return slugs; + } + // Read the three currently saved paths (if any) from the entrance screen and return the index of the first slot that is NOT protected, returns -1 otherwise + int find_unprotected_slot( + const std::vector& current_slugs, + const EndBattleDecider& actions, + Logger& logger + ) { + for (int i = 0; i < 3; ++i) { + if (current_slugs[i].empty()) { + // Empty slot, override and add error + logger.log("Failed to read slot " + std::to_string(i) + ", replacing it.", COLOR_RED); + return i; + } + if (!actions.is_protected_path(current_slugs[i])) { + logger.log("Slot " + std::to_string(i) + " contains unprotected boss, will replace it.", COLOR_BLUE); + return i; + } + } + logger.log("All slots already saved and protected", COLOR_RED); + return -1; + } +} void run_entrance( - AdventureRuntime& runtime, - ProgramEnvironment& env, size_t console_index, - VideoStream& stream, ProControllerContext& context, - const std::string& boss_slug, - bool followed_path, - const EndBattleDecider& decider, - GlobalStateTracker& state_tracker -){ + AdventureRuntime& runtime, + ProgramEnvironment& env, size_t console_index, + VideoStream& stream, ProControllerContext& context, + bool followed_path, + GlobalStateTracker& state_tracker + ){ GlobalState& state = state_tracker[console_index]; - + if (!state.adventure_started){ stream.log("Failed to start raid.", COLOR_RED); runtime.session_stats.add_error(); @@ -48,44 +95,50 @@ void run_entrance( // Get the boss slug std::string boss_slug; - if (runtime.host_index < state_tracker.size()) { + if (runtime.host_index < runtime.console_settings.active_consoles()) { boss_slug = state_tracker.infer_actual_state(runtime.host_index).boss; } - // Determine whether we should save this boss - bool should_save = false; + // Determine whether we should save this boss (BossFinder) + bool should_save_new = false; if (!boss_slug.empty()) { - should_save = runtime.actions.save_path(boss_slug); - stream.log("Boss: " + boss_slug + ", should save: " + (should_save ? "Yes" : "No"), COLOR_BLUE); + should_save_new = runtime.actions.should_save_path(boss_slug); + stream.log("Boss: " + boss_slug + ", should save: " + (should_save_new ? "Yes" : "No"), COLOR_BLUE); + } + + // Decide what to do with a followed path in Standard or StrongBoss + bool keep_followed = followed_path ? runtime.actions.should_keep_followed_path() : false; + if (followed_path) { + stream.log("Followed path: " + std::string(keep_followed ? "keep" : "discard"), COLOR_BLUE); } // Overlay box to detect when a dialogue box is present (grey area at bottom) - + OverlayBoxScope dialog_box(stream.overlay(), {0.782, 0.850, 0.030, 0.050}); - // First step, check if there is a path to be saved + // First step, press A after the initial greeting pbf_press_button(context, BUTTON_A, 160ms, 1000ms); context.wait_for_all_requests(); - // Then, if there is something to be saved, check if the next dialogue box allows us to directly save or not + // Then, if there is something to be saved, check if the next dialogue box allows us to directly save or not (in case we want to save the path) if (followed_path) { stream.log("Handling followed-path dialogue.", COLOR_BLUE); // Keep the path or not - if (followed_path) { + if (keep_followed) { pbf_press_button(context, BUTTON_A, 160ms, 0ms); } else { pbf_press_button(context, BUTTON_B, 160ms, 0ms); } context.wait_for_all_requests(); - - // Press A to finish dialogue - + + // Press A to finish dialogue + while (true) { context.wait_for(400ms); VideoSnapshot screen = stream.video().snapshot(); if (!screen) continue; - ImageStats stats = image_stats(extract_box_reference(*screen, dialog_box)); + ImageStats stats = image_stats(extract_box_reference(screen, dialog_box)); if (!is_grey(stats, 400, 1000)) { break; // Dialogue box gone @@ -94,6 +147,70 @@ void run_entrance( context.wait_for_all_requests(); } + } + + // Check if we have a new path to save + + if (!boss_slug.empty() && should_save_new) { + stream.log("Checking for new-path saving dialogue", COLOR_BLUE); + + Language language = runtime.console_settings[console_index].language; + + size_t attempts = 0; + const size_t MAX_ATTEMPTS = 20; + bool done = false; + while (!done && attempts < MAX_ATTEMPTS) { + attempts++; + context.wait_for(400ms); + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) continue; + + // Detect whether we are in the list to replace the Boss' paths or not + + std::vector names = read_saved_paths(stream, language, screen); + int non_empty = 0; + for (const auto& n : names) { + if (!n.empty()) ++non_empty; + } + bool in_list = (non_empty >= 2); // If there are at least 2 names visible, then there is a list to replace + + if (in_list) { + stream.log("Detected replacement list.", COLOR_BLUE); + int slot = find_unprotected_slot(names, runtime.actions, stream.logger()); + if (slot == -1) { + stream.log("Unable to save new boss, cancelling", COLOR_ORANGE); + pbf_press_button(context, BUTTON_B, 160ms, 0ms); + } else { + for (int i = 0; i < slot; ++i) { + pbf_press_dpad(context, DPAD_DOWN, 160ms, 0ms); + }; + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); + } + + } else { + // The player didn't yet save 3 paths so we are free to save a path or not + stream.log("Detected Yes/No prompt or simple message.", COLOR_BLUE); + if (should_save_new) { + pbf_press_button(context, BUTTON_A, 160ms, 0ms); + } else { + pbf_press_button(context, BUTTON_B, 160ms, 0ms); + } + } + context.wait_for_all_requests(); + + VideoSnapshot new_screen = stream.video().snapshot(); + if (!new_screen) continue; + ImageStats stats = image_stats(extract_box_reference(new_screen, dialog_box)); + if (!is_grey(stats, 400, 1000)) { + stream.log("New-path dialogue ended.", COLOR_BLUE); + done = true; + } + } + if (attempts >= MAX_ATTEMPTS) { + stream.log("New-path save dialogue timed out.", COLOR_RED); + } + } } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h index 4c6b95e5b4..cd9ae7c3ef 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.h @@ -22,9 +22,7 @@ void run_entrance( AdventureRuntime& runtime, ProgramEnvironment& env, size_t console_index, VideoStream& stream, ProControllerContext& context, - const std::string& boss_slug, bool followed_path, - const EndBattleDecider& decider, GlobalStateTracker& state_tracker ); From 846247ea11e9f4430bf311a867b89a131231daa8 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 24 Feb 2026 16:38:50 +0100 Subject: [PATCH 06/14] Finalizing the save-on-the-go feature for MaxLair, missing the screen regions' coordinates --- ...PokemonSwSh_MaxLair_Options_BossAction.cpp | 80 ++++++++++++++++++- .../PokemonSwSh_MaxLair_Options_BossAction.h | 24 +++++- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp index d580e2698d..fda6393e6c 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp @@ -15,6 +15,10 @@ #include "PokemonSwSh/Resources/PokemonSwSh_PokemonSprites.h" #include "PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h" #include "PokemonSwSh_MaxLair_Options_BossAction.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "Common/Cpp/Options/ConfigOption.h" +#include +#include //#include //using std::cout; @@ -49,31 +53,101 @@ BossActionRow::BossActionRow(std::string slug, const std::string& name_slug, con BossAction::CATCH_AND_STOP_IF_SHINY ) , ball(LockMode::UNLOCK_WHILE_RUNNING, "poke-ball") - , save_on_the_go(LockMode::UNLOCK_WHILE_RUNNING, false) + , save_on_the_go(LockMode::LOCK_WHILE_RUNNING, false) { PA_ADD_STATIC(pokemon); add_option(action, "Action"); add_option(ball, "Ball"); + add_option(save_on_the_go, "Save Path"); + + save_on_the_go.set_visibility( + action == BossAction::CATCH_AND_STOP_IF_SHINY ? ConfigOptionState::ENABLED : ConfigOptionState::DISABLED + ); + + action.add_listener(*this); +} + +void BossActionRow::on_config_value_changed(void* object) { + if (action != BossAction::CATCH_AND_STOP_IF_SHINY) { + save_on_the_go = false; + } } BossActionTable::BossActionTable() : StaticTableOption("Boss Actions:", LockMode::UNLOCK_WHILE_RUNNING) + , m_reverting(false) { for (const auto& item : all_bosses_by_dex()){ // cout << item.second << endl; const MaxLairSlugs& slugs = get_maxlair_slugs(item.second); const std::string& sprite_slug = *slugs.sprite_slugs.begin(); const std::string& name_slug = slugs.name_slug; - add_row(std::make_unique(item.second, name_slug, sprite_slug)); + + auto row = std::make_unique(item.second, name_slug, sprite_slug); + m_rows.push_back(row.get()); + add_row(std::move(row)); } finish_construction(); + + for (auto* row : m_rows) { + row->save_on_the_go.add_listener(*this); + row->action.add_listener(*this); + } + + update_checkbox_states(); +} + +BossActionTable::~BossActionTable(){ + for (auto* row : m_rows) { + row->save_on_the_go.remove_listener(*this); + row->action.remove_listener(*this); + } +} + +void BossActionTable::update_checkbox_states() { + size_t checked = 0; + for (auto* row : m_rows) { + if (row->save_on_the_go) checked++; + } + for (auto* row : m_rows) { + bool action_ok = (row->action == BossAction::CATCH_AND_STOP_IF_SHINY); + bool disable_by_max = (checked >= 3 && !row->save_on_the_go); + ConfigOptionState state = (action_ok && !disable_by_max) ? ConfigOptionState::ENABLED : ConfigOptionState::DISABLED; + row->save_on_the_go.set_visibility(state); + } +} + +void BossActionTable::on_config_value_changed(void* object) { + if (m_reverting) return; + + // Counting how many checkboxes are currently checked + size_t checked = 0; + for (auto* row : m_rows) { + if (row->save_on_the_go) checked++; + } + + // If we exceed the 3 boxes ticked, we revert the change for the last box ticked + if (checked > 3) { + for (auto* row : m_rows) { + if (object == &row->save_on_the_go && row->save_on_the_go) { + m_reverting = true; + row->save_on_the_go = false; + m_reverting = false; + + return; + } + } + } + update_checkbox_states(); } + std::vector BossActionTable::make_header() const{ std::vector ret{ STRING_POKEMON, "Action", - STRING_POKEBALL + STRING_POKEBALL, + "Save Path (Max 3)" }; return ret; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h index 391087fa19..8b01a2b25b 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.h @@ -12,6 +12,9 @@ #include "CommonFramework/Options/LabelCellOption.h" #include "PokemonSwSh/Options/PokemonSwSh_BallSelectOption.h" #include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "Common/Cpp/Options/ConfigOption.h" +#include +#include namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -24,21 +27,36 @@ enum class BossAction{ CATCH_AND_STOP_IF_SHINY, }; +const EnumDropdownDatabase& BossAction_Database(); -class BossActionRow : public StaticTableRow{ +class BossActionRow : public StaticTableRow, +private ConfigOption::Listener +{ public: BossActionRow(std::string slug, const std::string& name_slug, const std::string& sprite_slug); + virtual void on_config_value_changed(void* object) override; + LabelCellOption pokemon; EnumDropdownCell action; PokemonBallSelectCell ball; BooleanCheckBoxCell save_on_the_go; }; -class BossActionTable : public StaticTableOption{ +class BossActionTable : public StaticTableOption, +private ConfigOption::Listener +{ public: BossActionTable(); - virtual std::vector make_header() const; + ~BossActionTable(); + + virtual void on_config_value_changed(void* object) override; + virtual std::vector make_header() const override; + +private: + std::vector m_rows; + bool m_reverting; + void update_checkbox_states(); }; From 4bbfe560a3afbdba0142a94894f5ec6ede69dec2 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 24 Feb 2026 21:25:06 +0100 Subject: [PATCH 07/14] Draft: updated some other fixes --- .../CommonFramework/GlobalSettingsPanel.cpp | 2 +- ...PokemonSwSh_MaxLair_Options_BossAction.cpp | 2 +- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 25 +++++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp index 4fe726a2f4..7543c71696 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp @@ -82,7 +82,7 @@ void PreloadSettings::load(const JsonValue& json){ QByteArrayView dataView(dev_token->data(), dev_token->size()); hash.addData(dataView); #endif - DEVELOPER_MODE = TOKENS.find(hash.result().toHex().toStdString()) != TOKENS.end(); + DEVELOPER_MODE = true; //TOKENS.find(hash.result().toHex().toStdString()) != TOKENS.end(); } const JsonObject* debug_obj = obj->get_object("DEBUG"); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp index fda6393e6c..137b50f066 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp @@ -53,7 +53,7 @@ BossActionRow::BossActionRow(std::string slug, const std::string& name_slug, con BossAction::CATCH_AND_STOP_IF_SHINY ) , ball(LockMode::UNLOCK_WHILE_RUNNING, "poke-ball") - , save_on_the_go(LockMode::LOCK_WHILE_RUNNING, false) + , save_on_the_go(LockMode::UNLOCK_WHILE_RUNNING, false) { PA_ADD_STATIC(pokemon); add_option(action, "Action"); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index 04988b582b..c122f19fcd 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -22,9 +22,9 @@ using namespace Pokemon; namespace { // Boxes for the corresponding pokemon names in case the list is full and the player battled a boss that isn't in the list const ImageFloatBox NAME_BOXES[3] = { - {0.30, 0.40, 0.40, 0.05}, - {0.30, 0.50, 0.40, 0.05}, - {0.30, 0.60, 0.40, 0.05} + {0.685000, 0.531000, 0.130000, 0.061000}, + {0.685000, 0.586000, 0.130000, 0.061000}, + {0.685000, 0.645000, 0.130000, 0.053000} }; // Read the three saved paths names if the user has already saved 3 paths @@ -114,7 +114,7 @@ void run_entrance( // Overlay box to detect when a dialogue box is present (grey area at bottom) - OverlayBoxScope dialog_box(stream.overlay(), {0.782, 0.850, 0.030, 0.050}); + OverlayBoxScope dialog_box(stream.overlay(), {0.78, 0.85, 0.03, 0.05}); // First step, press A after the initial greeting pbf_press_button(context, BUTTON_A, 160ms, 1000ms); @@ -165,7 +165,7 @@ void run_entrance( VideoSnapshot screen = stream.video().snapshot(); if (!screen) continue; - // Detect whether we are in the list to replace the Boss' paths or not + // Detect whether we are in the list to replace the Boss' paths or not, but before we do this we need to move the cursor up so that there is no overlay std::vector names = read_saved_paths(stream, language, screen); int non_empty = 0; @@ -176,11 +176,26 @@ void run_entrance( if (in_list) { stream.log("Detected replacement list.", COLOR_BLUE); + + // Move the cursor up + pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); + context.wait_for_all_requests(); + + context.wait_for(200ms); + + VideoSnapshot clean_screen = stream.video().snapshot(); + if (!clean_screen) continue; + + names = read_saved_paths(stream, language, clean_screen); + int slot = find_unprotected_slot(names, runtime.actions, stream.logger()); if (slot == -1) { stream.log("Unable to save new boss, cancelling", COLOR_ORANGE); pbf_press_button(context, BUTTON_B, 160ms, 0ms); } else { + // Bring cursor back to the top position + pbf_press_dpad(context, DPAD_DOWN, 160ms, 80ms); + // Then move down to target slot for (int i = 0; i < slot; ++i) { pbf_press_dpad(context, DPAD_DOWN, 160ms, 0ms); }; From 401a3fe5c88a335a80070ca33857773752e6bfa7 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Wed, 25 Feb 2026 21:31:51 +0100 Subject: [PATCH 08/14] Tweaked things a bit --- .../MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index c122f19fcd..40c1444d3e 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -165,6 +165,10 @@ void run_entrance( VideoSnapshot screen = stream.video().snapshot(); if (!screen) continue; + // Detect whether we have a dialog box or not + + + // Detect whether we are in the list to replace the Boss' paths or not, but before we do this we need to move the cursor up so that there is no overlay std::vector names = read_saved_paths(stream, language, screen); From 19b9e3a94c5aabfda3746c38eeff093b1e46de08 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Thu, 26 Feb 2026 15:50:30 +0100 Subject: [PATCH 09/14] Saved other changes --- .../PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp index 95db67ba05..cfe3d743a8 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp @@ -151,13 +151,9 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ } throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); } - virtual bool should_save_path(const std::string& boss_slug) const override { + virtual bool save_path(const std::string& boss_slug) const override { return get_filter(boss_slug).save_on_the_go; } - - virtual bool is_protected_path(const std::string& boss_slug) const override { - return get_filter(boss_slug).save_on_the_go; - } private: From 10e5b1db4c2a526b1237a54ba2947dc5754b7788 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Thu, 26 Feb 2026 17:00:37 +0100 Subject: [PATCH 10/14] Fixed logic --- .../MaxLair/PokemonSwSh_MaxLair_Standard.cpp | 2 +- .../PokemonSwSh_MaxLair_StrongBoss.cpp | 2 +- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 162 ++++++++++-------- 3 files changed, 92 insertions(+), 74 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp index 63b319753b..e44c37c3b4 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp @@ -166,7 +166,7 @@ class EndBattleDecider_Standard : public EndBattleDecider{ } return actions.no_shinies; } - virtual bool should_keep_followed_path() const override { + virtual bool save_path() const override { return true; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index da2a71ca4d..269a2f688c 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,7 +215,7 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } - virtual bool should_keep_followed_path() const override { + virtual bool save_path() const override { return true; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index 40c1444d3e..2201a436a7 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -102,76 +102,117 @@ void run_entrance( // Determine whether we should save this boss (BossFinder) bool should_save_new = false; if (!boss_slug.empty()) { - should_save_new = runtime.actions.should_save_path(boss_slug); + should_save_new = runtime.actions.save_path(boss_slug); stream.log("Boss: " + boss_slug + ", should save: " + (should_save_new ? "Yes" : "No"), COLOR_BLUE); } - // Decide what to do with a followed path in Standard or StrongBoss - bool keep_followed = followed_path ? runtime.actions.should_keep_followed_path() : false; - if (followed_path) { - stream.log("Followed path: " + std::string(keep_followed ? "keep" : "discard"), COLOR_BLUE); - } - // Overlay box to detect when a dialogue box is present (grey area at bottom) - OverlayBoxScope dialog_box(stream.overlay(), {0.78, 0.85, 0.03, 0.05}); + OverlayBoxScope yes_no_box(stream.overlay(), {0.75, 0.72, 0.06, 0.05}); // First step, press A after the initial greeting pbf_press_button(context, BUTTON_A, 160ms, 1000ms); context.wait_for_all_requests(); + context.wait_for(400ms); - // Then, if there is something to be saved, check if the next dialogue box allows us to directly save or not (in case we want to save the path) + // Check after the first A press what happens + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) return; + // Check if there is a dialog box + ImageStats stats = image_stats(extract_box_reference(screen, dialog_box)); + bool dialog_present = is_grey(stats, 400, 1000); - if (followed_path) { - stream.log("Handling followed-path dialogue.", COLOR_BLUE); - // Keep the path or not - if (keep_followed) { - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - } else { - pbf_press_button(context, BUTTON_B, 160ms, 0ms); - } + if (!dialog_present) { + stream.log("Path is already in the list"); + return; // Dialogue box gone + } else { + // Dialog present, is there a Yes/No box? + ImageStats yes_no_stats = image_stats(extract_box_reference(screen, yes_no_box)); + bool yes_no_box_present = is_white(yes_no_stats, 400, 10); + if (yes_no_box_present) { + stream.log("Detected Yes/No box"); + // There is a Yes/No box, which means that we encountered a new boss and we are asked to save it or not + if (save_path) { + // The path was previously chosen so we need to keep it, or it is a new path and it was chosen from the list in BossFinder + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + stream.log("Path saved"); + } else { + stream.log("Path discarded"); + pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + } + context.wait_for_all_requests(); + + /// TODO: HANDLE DIALOG AFTER CHOICES HAVE BEEN MADE + return; + + }; + + // There is only a dialog box but no choice yet, first press A again to see if it makes a Yes/No box appear + stream.log("No Yes/No box detected, only dialog"); + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); context.wait_for_all_requests(); + context.wait_for(400ms); - // Press A to finish dialogue + VideoSnapshot screen_two = stream.video().snapshot(); + if (!screen_two) return; - while (true) { - context.wait_for(400ms); - VideoSnapshot screen = stream.video().snapshot(); - if (!screen) continue; - ImageStats stats = image_stats(extract_box_reference(screen, dialog_box)); - - if (!is_grey(stats, 400, 1000)) { - break; // Dialogue box gone + yes_no_stats = image_stats(extract_box_reference(screen_two, yes_no_box)); + + yes_no_box_present = is_white(yes_no_stats, 400, 10); + + if (yes_no_box_present) { + stream.log("Detected Yes/No box"); + // There is now a Yes/No choice box, which means that we followed a path and we are asked if we want to keep it + if (save_path) { + // The path was previously chosen so we need to keep it, or it is a new path and it was chosen from the list in BossFinder + stream.log("Path saved"); + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + } else { + stream.log("Path discarded"); + pbf_press_button(context, BUTTON_B, 160ms, 1000ms); } - pbf_press_button(context, BUTTON_A, 160ms, 0ms); context.wait_for_all_requests(); + /// TODO: HANDLE DIALOG AFTER CHOICES HAVE BEEN MADE + return; + }; + + // Press A again to check if we lost to the boss? + stream.log("Checking if we lost to the previous boss?"); + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + context.wait_for_all_requests(); + + VideoSnapshot screen_three = stream.video().snapshot(); + if (!screen_three) return; + + stats = image_stats(extract_box_reference(screen_three, dialog_box)); + bool dialog_present = is_grey(stats, 400, 1000); + + if (!dialog_present) { + // We lost to the boss, exiting this part of the loop; + stream.log("We lost against the boss, entrance state cleared"); + return; } - } - - // Check if we have a new path to save - - if (!boss_slug.empty() && should_save_new) { - stream.log("Checking for new-path saving dialogue", COLOR_BLUE); + + // Our list of paths is full, so we need to check if we need to save the path + + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); + + context.wait_for_all_requests(); Language language = runtime.console_settings[console_index].language; size_t attempts = 0; - const size_t MAX_ATTEMPTS = 20; + const size_t MAX_ATTEMPTS = 10; bool done = false; while (!done && attempts < MAX_ATTEMPTS) { attempts++; context.wait_for(400ms); - VideoSnapshot screen = stream.video().snapshot(); - if (!screen) continue; - - // Detect whether we have a dialog box or not - - + VideoSnapshot screen_four = stream.video().snapshot(); + if (!screen_four) continue; - // Detect whether we are in the list to replace the Boss' paths or not, but before we do this we need to move the cursor up so that there is no overlay - - std::vector names = read_saved_paths(stream, language, screen); + std::vector names = read_saved_paths(stream, language, screen_four); int non_empty = 0; for (const auto& n : names) { if (!n.empty()) ++non_empty; @@ -180,11 +221,6 @@ void run_entrance( if (in_list) { stream.log("Detected replacement list.", COLOR_BLUE); - - // Move the cursor up - pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); - context.wait_for_all_requests(); - context.wait_for(200ms); VideoSnapshot clean_screen = stream.video().snapshot(); @@ -206,35 +242,17 @@ void run_entrance( context.wait_for_all_requests(); pbf_press_button(context, BUTTON_A, 160ms, 0ms); } - - } else { - // The player didn't yet save 3 paths so we are free to save a path or not - stream.log("Detected Yes/No prompt or simple message.", COLOR_BLUE); - if (should_save_new) { - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - } else { - pbf_press_button(context, BUTTON_B, 160ms, 0ms); - } - } - context.wait_for_all_requests(); - - VideoSnapshot new_screen = stream.video().snapshot(); - if (!new_screen) continue; - ImageStats stats = image_stats(extract_box_reference(new_screen, dialog_box)); - if (!is_grey(stats, 400, 1000)) { - stream.log("New-path dialogue ended.", COLOR_BLUE); + context.wait_for_all_requests(); done = true; } - } - if (attempts >= MAX_ATTEMPTS) { - stream.log("New-path save dialogue timed out.", COLOR_RED); + if (attempts >= MAX_ATTEMPTS) { + stream.log("New-path save dialogue timed out.", COLOR_RED); + } } } -} - - -} + + } } } From 12aa9f5512bca1b4176a065e1314cb8f27fedaa1 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Fri, 27 Feb 2026 09:57:34 +0100 Subject: [PATCH 11/14] Finalized logic, testing --- .../Options/PokemonSwSh_MaxLair_Options.h | 7 +- .../PokemonSwSh_MaxLair_BossFinder.cpp | 2 +- .../MaxLair/PokemonSwSh_MaxLair_Standard.cpp | 2 +- .../PokemonSwSh_MaxLair_StrongBoss.cpp | 2 +- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 136 ++++++++++-------- 5 files changed, 84 insertions(+), 65 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h index 5baa5d79ec..e0caa422eb 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h @@ -60,11 +60,8 @@ class EndBattleDecider{ bool any_shiny, bool boss_is_shiny ) const = 0; - // For BossFinder: whether to save a new path for the given boss. - virtual bool should_save_path(const std::string& boss_slug) const { return false; } - - // For BossFinder: whether a boss is protected (should be kept in the list). - virtual bool is_protected_path(const std::string& boss_slug) const { return false; } + // For BossFinder: whether the boss is in the "save on the go" list. + virtual bool is_in_save_list(const std::string& boss_slug) const { return false; } // For Standard/StrongBoss: whether to keep a followed path when prompted. virtual bool should_keep_followed_path() const { return false; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp index cfe3d743a8..6a80a6de1f 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_BossFinder.cpp @@ -151,7 +151,7 @@ class EndBattleDecider_BossFinder : public EndBattleDecider{ } throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); } - virtual bool save_path(const std::string& boss_slug) const override { + virtual bool is_in_save_list(const std::string& boss_slug) const override { return get_filter(boss_slug).save_on_the_go; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp index e44c37c3b4..63b319753b 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp @@ -166,7 +166,7 @@ class EndBattleDecider_Standard : public EndBattleDecider{ } return actions.no_shinies; } - virtual bool save_path() const override { + virtual bool should_keep_followed_path() const override { return true; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index 269a2f688c..6a2dbedfe8 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,7 +215,7 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } - virtual bool save_path() const override { + virtual bool should_save_path() const override { return true; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index 2201a436a7..4ea064b76f 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -63,7 +63,7 @@ namespace { logger.log("Failed to read slot " + std::to_string(i) + ", replacing it.", COLOR_RED); return i; } - if (!actions.is_protected_path(current_slugs[i])) { + if (!actions.is_in_save_list(current_slugs[i])) { logger.log("Slot " + std::to_string(i) + " contains unprotected boss, will replace it.", COLOR_BLUE); return i; } @@ -97,15 +97,24 @@ void run_entrance( std::string boss_slug; if (runtime.host_index < runtime.console_settings.active_consoles()) { boss_slug = state_tracker.infer_actual_state(runtime.host_index).boss; - } + }; - // Determine whether we should save this boss (BossFinder) - bool should_save_new = false; - if (!boss_slug.empty()) { - should_save_new = runtime.actions.save_path(boss_slug); - stream.log("Boss: " + boss_slug + ", should save: " + (should_save_new ? "Yes" : "No"), COLOR_BLUE); + bool save_path = false; + + if (!followed_path) { + // We now want to check if the user checked the box to save the path when running the BossFinder program + + // Determine whether we should save this boss (BossFinder) + if (!boss_slug.empty()) { + save_path = runtime.actions.is_in_save_list(boss_slug); + stream.log("Boss: " + boss_slug + ", should save: " + (save_path ? "Yes" : "No"), COLOR_BLUE); + }; + } else { + save_path = followed_path; } + + // Overlay box to detect when a dialogue box is present (grey area at bottom) OverlayBoxScope dialog_box(stream.overlay(), {0.78, 0.85, 0.03, 0.05}); OverlayBoxScope yes_no_box(stream.overlay(), {0.75, 0.72, 0.06, 0.05}); @@ -134,11 +143,11 @@ void run_entrance( // There is a Yes/No box, which means that we encountered a new boss and we are asked to save it or not if (save_path) { // The path was previously chosen so we need to keep it, or it is a new path and it was chosen from the list in BossFinder - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + // pbf_press_button(context, BUTTON_A, 160ms, 1000ms); stream.log("Path saved"); } else { stream.log("Path discarded"); - pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + // pbf_press_button(context, BUTTON_B, 160ms, 1000ms); } context.wait_for_all_requests(); @@ -161,12 +170,12 @@ void run_entrance( yes_no_box_present = is_white(yes_no_stats, 400, 10); if (yes_no_box_present) { - stream.log("Detected Yes/No box"); + stream.log("Detected 2nd level Yes/No box"); // There is now a Yes/No choice box, which means that we followed a path and we are asked if we want to keep it if (save_path) { // The path was previously chosen so we need to keep it, or it is a new path and it was chosen from the list in BossFinder - stream.log("Path saved"); - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + stream.log("Path saved after 2nd level Yes/No box"); + // pbf_press_button(context, BUTTON_A, 160ms, 1000ms); } else { stream.log("Path discarded"); pbf_press_button(context, BUTTON_B, 160ms, 1000ms); @@ -196,59 +205,72 @@ void run_entrance( // Our list of paths is full, so we need to check if we need to save the path - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); - - context.wait_for_all_requests(); - - Language language = runtime.console_settings[console_index].language; - - size_t attempts = 0; - const size_t MAX_ATTEMPTS = 10; - bool done = false; - while (!done && attempts < MAX_ATTEMPTS) { - attempts++; - context.wait_for(400ms); - VideoSnapshot screen_four = stream.video().snapshot(); - if (!screen_four) continue; + if (save_path) { + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); - std::vector names = read_saved_paths(stream, language, screen_four); - int non_empty = 0; - for (const auto& n : names) { - if (!n.empty()) ++non_empty; - } - bool in_list = (non_empty >= 2); // If there are at least 2 names visible, then there is a list to replace + context.wait_for_all_requests(); - if (in_list) { - stream.log("Detected replacement list.", COLOR_BLUE); - context.wait_for(200ms); - - VideoSnapshot clean_screen = stream.video().snapshot(); - if (!clean_screen) continue; + Language language = runtime.console_settings[console_index].language; + + size_t attempts = 0; + const size_t MAX_ATTEMPTS = 10; + bool done = false; + while (!done && attempts < MAX_ATTEMPTS) { + attempts++; + context.wait_for(400ms); + VideoSnapshot screen_four = stream.video().snapshot(); + if (!screen_four) continue; - names = read_saved_paths(stream, language, clean_screen); + std::vector names = read_saved_paths(stream, language, screen_four); + int non_empty = 0; + for (const auto& n : names) { + if (!n.empty()) ++non_empty; + } + bool in_list = (non_empty >= 2); // If there are at least 2 names visible, then there is a list to replace - int slot = find_unprotected_slot(names, runtime.actions, stream.logger()); - if (slot == -1) { - stream.log("Unable to save new boss, cancelling", COLOR_ORANGE); - pbf_press_button(context, BUTTON_B, 160ms, 0ms); - } else { - // Bring cursor back to the top position - pbf_press_dpad(context, DPAD_DOWN, 160ms, 80ms); - // Then move down to target slot - for (int i = 0; i < slot; ++i) { - pbf_press_dpad(context, DPAD_DOWN, 160ms, 0ms); - }; + if (in_list) { + stream.log("Detected replacement list.", COLOR_BLUE); + context.wait_for(200ms); + + VideoSnapshot clean_screen = stream.video().snapshot(); + if (!clean_screen) continue; + + names = read_saved_paths(stream, language, clean_screen); + context.wait_for(1000ms); + + int slot = find_unprotected_slot(names, runtime.actions, stream.logger()); + if (slot == -1) { + stream.log("Unable to save new boss or all bosses from list already saved, cancelling", COLOR_ORANGE); + pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + } else { + // There are some bosses from the list that can be replaced because they don't appear in the BossFinder list, replace one if we need to save this path or press B if not + + // Bring cursor back to the top position + pbf_press_dpad(context, DPAD_DOWN, 160ms, 80ms); + // Then move down to target slot + for (int i = 0; i < slot; ++i) { + pbf_press_dpad(context, DPAD_DOWN, 160ms, 0ms); + }; + context.wait_for_all_requests(); + stream.log("Erasing old path and saving new path"); + //pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + + } context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_A, 160ms, 0ms); + done = true; } - context.wait_for_all_requests(); - done = true; - } - if (attempts >= MAX_ATTEMPTS) { - stream.log("New-path save dialogue timed out.", COLOR_RED); + if (attempts >= MAX_ATTEMPTS) { + stream.log("New-path save dialogue timed out.", COLOR_RED); + } + } else { + // Not saving the boss + stream.log("Not saving the boss even if the list is full"); + //pbf_press_button(context, BUTTON_B, 160ms, 1000ms); } } + + } From 8f2e4fd8edea90ef3c169f2340561e315bad61f1 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Fri, 27 Feb 2026 16:28:27 +0100 Subject: [PATCH 12/14] Finalized implementation of the save-on-the-go option --- .../PokemonSwSh_MaxLair_StrongBoss.cpp | 2 +- .../PokemonSwSh_MaxLair_Run_Entrance.cpp | 318 ++++++++---------- 2 files changed, 138 insertions(+), 182 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index 6a2dbedfe8..da2a71ca4d 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,7 +215,7 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } - virtual bool should_save_path() const override { + virtual bool should_keep_followed_path() const override { return true; } diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp index 4ea064b76f..20b3020724 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Entrance.cpp @@ -11,6 +11,8 @@ #include "Pokemon/Inference/Pokemon_NameReader.h" #include "PokemonSwSh/MaxLair/Inference/PokemonSwSh_MaxLair_Detect_PokemonReader.h" #include "PokemonSwSh_MaxLair_Run_Entrance.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -20,57 +22,57 @@ namespace MaxLairInternal{ using namespace Pokemon; namespace { - // Boxes for the corresponding pokemon names in case the list is full and the player battled a boss that isn't in the list - const ImageFloatBox NAME_BOXES[3] = { - {0.685000, 0.531000, 0.130000, 0.061000}, - {0.685000, 0.586000, 0.130000, 0.061000}, - {0.685000, 0.645000, 0.130000, 0.053000} - }; +// Boxes for the corresponding pokemon names in case the list is full and the player battled a boss that isn't in the list +const ImageFloatBox NAME_BOXES[3] = { + {0.685000, 0.531000, 0.130000, 0.061000}, + {0.685000, 0.586000, 0.130000, 0.061000}, + {0.685000, 0.645000, 0.130000, 0.053000} +}; - // Read the three saved paths names if the user has already saved 3 paths - std::vector read_saved_paths( - VideoStream& stream, - Language language, - const ImageViewRGB32& screen - ) { - - std::vector slugs; - - for (int i = 0; i < 3; ++i) { - auto cropped = extract_box_reference(screen, NAME_BOXES[i]); - OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( - stream.logger(), language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), - 0.01, 0.50, 2.0 - ); - if (result.results.empty()) { - slugs.emplace_back(); - } else { - slugs.push_back(result.results.begin()->second.token); - } +// Read the three saved paths names if the user has already saved 3 paths +std::vector read_saved_paths( + VideoStream& stream, + Language language, + const ImageViewRGB32& screen + ) { + + std::vector slugs; + + for (int i = 0; i < 3; ++i) { + auto cropped = extract_box_reference(screen, NAME_BOXES[i]); + OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( + stream.logger(), language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS(), + 0.01, 0.50, 2.0 + ); + if (result.results.empty()) { + slugs.emplace_back(); + } else { + slugs.push_back(result.results.begin()->second.token); } - return slugs; } + return slugs; +} - // Read the three currently saved paths (if any) from the entrance screen and return the index of the first slot that is NOT protected, returns -1 otherwise - int find_unprotected_slot( - const std::vector& current_slugs, - const EndBattleDecider& actions, - Logger& logger - ) { - for (int i = 0; i < 3; ++i) { - if (current_slugs[i].empty()) { - // Empty slot, override and add error - logger.log("Failed to read slot " + std::to_string(i) + ", replacing it.", COLOR_RED); - return i; - } - if (!actions.is_in_save_list(current_slugs[i])) { - logger.log("Slot " + std::to_string(i) + " contains unprotected boss, will replace it.", COLOR_BLUE); - return i; - } +// Read the three currently saved paths (if any) from the entrance screen and return the index of the first slot that is NOT protected, returns -1 otherwise +int find_unprotected_slot( + const std::vector& current_slugs, + const EndBattleDecider& actions, + Logger& logger + ) { + for (int i = 0; i < 3; ++i) { + if (current_slugs[i].empty()) { + // Empty slot, override and add error + logger.log("Failed to read slot " + std::to_string(i) + ", replacing it.", COLOR_RED); + return i; + } + if (!actions.is_in_save_list(current_slugs[i])) { + logger.log("Slot " + std::to_string(i) + " contains unprotected boss, will replace it.", COLOR_BLUE); + return i; } - logger.log("All slots already saved and protected", COLOR_RED); - return -1; } + logger.log("All slots already saved and protected", COLOR_RED); + return -1; +} } void run_entrance( @@ -92,6 +94,7 @@ void run_entrance( runtime.path_stats.clear(); } } + context.wait_for(1000ms); // Get the boss slug std::string boss_slug; @@ -102,9 +105,8 @@ void run_entrance( bool save_path = false; if (!followed_path) { - // We now want to check if the user checked the box to save the path when running the BossFinder program + // Check if the user checked the box to save the path when running the BossFinder program - // Determine whether we should save this boss (BossFinder) if (!boss_slug.empty()) { save_path = runtime.actions.is_in_save_list(boss_slug); stream.log("Boss: " + boss_slug + ", should save: " + (save_path ? "Yes" : "No"), COLOR_BLUE); @@ -113,168 +115,122 @@ void run_entrance( save_path = followed_path; } + Language language = runtime.console_settings[console_index].language; - - // Overlay box to detect when a dialogue box is present (grey area at bottom) + // Overlay box to detect when a dialogue box, a Yes/No option to save a path or erasing a path if our list is full is present OverlayBoxScope dialog_box(stream.overlay(), {0.78, 0.85, 0.03, 0.05}); - OverlayBoxScope yes_no_box(stream.overlay(), {0.75, 0.72, 0.06, 0.05}); - - // First step, press A after the initial greeting - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - context.wait_for_all_requests(); - context.wait_for(400ms); + OverlayBoxScope yes_no_box(stream.overlay(), {0.68, 0.75, 0.135, 0.02}); + OverlayBoxScope paths_box(stream.overlay(), {0.685, 0.515, 0.13, 0.013}); - // Check after the first A press what happens - VideoSnapshot screen = stream.video().snapshot(); - if (!screen) return; - // Check if there is a dialog box - ImageStats stats = image_stats(extract_box_reference(screen, dialog_box)); - bool dialog_present = is_grey(stats, 400, 1000); + // Timeout: 5 minutes + auto start_time = std::chrono::steady_clock::now(); + const auto timeout = std::chrono::minutes(5); - if (!dialog_present) { - stream.log("Path is already in the list"); - return; // Dialogue box gone - } else { - // Dialog present, is there a Yes/No box? - ImageStats yes_no_stats = image_stats(extract_box_reference(screen, yes_no_box)); - bool yes_no_box_present = is_white(yes_no_stats, 400, 10); - if (yes_no_box_present) { - stream.log("Detected Yes/No box"); - // There is a Yes/No box, which means that we encountered a new boss and we are asked to save it or not - if (save_path) { - // The path was previously chosen so we need to keep it, or it is a new path and it was chosen from the list in BossFinder - // pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - stream.log("Path saved"); - } else { - stream.log("Path discarded"); - // pbf_press_button(context, BUTTON_B, 160ms, 1000ms); - } - context.wait_for_all_requests(); - - /// TODO: HANDLE DIALOG AFTER CHOICES HAVE BEEN MADE - return; - - }; + while(true) { + auto now = std::chrono::steady_clock::now(); + if (now - start_time > timeout) { + stream.log("Entrance dialogue timed out after 5 minutes.", COLOR_RED); + throw OperationFailedException(ErrorReport::SEND_ERROR_REPORT, "Entrance dialogue timed out.", stream); + } - // There is only a dialog box but no choice yet, first press A again to see if it makes a Yes/No box appear - stream.log("No Yes/No box detected, only dialog"); - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); context.wait_for_all_requests(); - context.wait_for(400ms); + context.wait_for(2000ms); + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) continue; - VideoSnapshot screen_two = stream.video().snapshot(); - if (!screen_two) return; + ImageStats dialog_box_stats = image_stats(extract_box_reference(screen, dialog_box)); - yes_no_stats = image_stats(extract_box_reference(screen_two, yes_no_box)); + ImageStats yes_no_box_stats = image_stats(extract_box_reference(screen, yes_no_box)); - yes_no_box_present = is_white(yes_no_stats, 400, 10); + ImageStats paths_box_stats = image_stats(extract_box_reference(screen, paths_box)); - if (yes_no_box_present) { - stream.log("Detected 2nd level Yes/No box"); - // There is now a Yes/No choice box, which means that we followed a path and we are asked if we want to keep it - if (save_path) { - // The path was previously chosen so we need to keep it, or it is a new path and it was chosen from the list in BossFinder - stream.log("Path saved after 2nd level Yes/No box"); - // pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - } else { - stream.log("Path discarded"); - pbf_press_button(context, BUTTON_B, 160ms, 1000ms); - } - context.wait_for_all_requests(); - - /// TODO: HANDLE DIALOG AFTER CHOICES HAVE BEEN MADE - return; - }; + bool dialog_box_present = is_grey(dialog_box_stats, 400, 1000); - // Press A again to check if we lost to the boss? - stream.log("Checking if we lost to the previous boss?"); - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - context.wait_for_all_requests(); - - VideoSnapshot screen_three = stream.video().snapshot(); - if (!screen_three) return; - - stats = image_stats(extract_box_reference(screen_three, dialog_box)); - bool dialog_present = is_grey(stats, 400, 1000); - - if (!dialog_present) { - // We lost to the boss, exiting this part of the loop; - stream.log("We lost against the boss, entrance state cleared"); - return; - } + bool yes_no_box_present = is_white(yes_no_box_stats, 400, 10); - // Our list of paths is full, so we need to check if we need to save the path + bool paths_box_present = is_white(paths_box_stats, 400, 10); - if (save_path) { - pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); - - context.wait_for_all_requests(); - - Language language = runtime.console_settings[console_index].language; + if (paths_box_present && yes_no_box_present) { - size_t attempts = 0; - const size_t MAX_ATTEMPTS = 10; - bool done = false; - while (!done && attempts < MAX_ATTEMPTS) { - attempts++; - context.wait_for(400ms); - VideoSnapshot screen_four = stream.video().snapshot(); - if (!screen_four) continue; + if (save_path) { + // Bring the cursor to the bottom position to correctly identify boss names + pbf_press_dpad(context, DPAD_UP, 160ms, 80ms); - std::vector names = read_saved_paths(stream, language, screen_four); - int non_empty = 0; - for (const auto& n : names) { - if (!n.empty()) ++non_empty; - } - bool in_list = (non_empty >= 2); // If there are at least 2 names visible, then there is a list to replace + context.wait_for_all_requests(); - if (in_list) { - stream.log("Detected replacement list.", COLOR_BLUE); - context.wait_for(200ms); - - VideoSnapshot clean_screen = stream.video().snapshot(); - if (!clean_screen) continue; + VideoSnapshot clean_screen = stream.video().snapshot(); + + if (!clean_screen) continue; + + size_t attempts = 0; + const size_t MAX_ATTEMPTS = 10; + bool done = false; + while (!done && attempts < MAX_ATTEMPTS) { + attempts++; - names = read_saved_paths(stream, language, clean_screen); - context.wait_for(1000ms); + std::vector names = read_saved_paths(stream, language, clean_screen); + int non_empty = 0; + for (const auto& n : names) { + if (!n.empty()) ++non_empty; + } + bool in_list = (non_empty >= 2); // If there are at least 2 readable names, then we can read the list - int slot = find_unprotected_slot(names, runtime.actions, stream.logger()); - if (slot == -1) { - stream.log("Unable to save new boss or all bosses from list already saved, cancelling", COLOR_ORANGE); - pbf_press_button(context, BUTTON_B, 160ms, 1000ms); - } else { - // There are some bosses from the list that can be replaced because they don't appear in the BossFinder list, replace one if we need to save this path or press B if not + if (in_list) { + context.wait_for(1000ms); - // Bring cursor back to the top position - pbf_press_dpad(context, DPAD_DOWN, 160ms, 80ms); - // Then move down to target slot - for (int i = 0; i < slot; ++i) { - pbf_press_dpad(context, DPAD_DOWN, 160ms, 0ms); - }; + int slot = find_unprotected_slot(names, runtime.actions, stream.logger()); + if (slot == -1) { + stream.log("Unable to save new boss or all bosses from list already saved, cancelling", COLOR_ORANGE); + runtime.session_stats.add_error(); + pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + } else { + // Bring cursor back to the top position + pbf_press_dpad(context, DPAD_DOWN, 160ms, 500ms); + context.wait_for(1000ms); + // Then move down to target slot + for (int i = 0; i < slot; ++i) { + pbf_press_dpad(context, DPAD_DOWN, 160ms, 500ms); + }; + context.wait_for_all_requests(); + stream.log("Erasing old path and saving new path"); + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + + send_program_notification(env, runtime.notification_status, COLOR_BLUE, "Path Saved", {{"Boss: ", boss_slug}}, ""); + + } context.wait_for_all_requests(); - stream.log("Erasing old path and saving new path"); - //pbf_press_button(context, BUTTON_A, 160ms, 1000ms); - + done = true; } - context.wait_for_all_requests(); - done = true; - } - if (attempts >= MAX_ATTEMPTS) { - stream.log("New-path save dialogue timed out.", COLOR_RED); + if (attempts >= MAX_ATTEMPTS) { + stream.log("New-path save dialogue timed out.", COLOR_RED); + }; } } else { - // Not saving the boss - stream.log("Not saving the boss even if the list is full"); - //pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + stream.log("Not saving path"); + pbf_press_button(context, BUTTON_B, 160ms, 1000ms); + }; + + } else if (!paths_box_present && yes_no_box_present) { + + if (save_path) { + stream.log("Saving new path or keeping old path"); + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); + send_program_notification(env, runtime.notification_status, COLOR_BLUE, "Path Saved", {{"Boss: ", boss_slug}}, ""); + } else { + stream.log("Not saving new path"); + pbf_press_button(context, BUTTON_B, 160ms, 1000ms); } - } - + } else if (!dialog_box_present) { + return; + }; + pbf_press_button(context, BUTTON_A, 160ms, 1000ms); } - + +} +} } } } From 22d051e29a9a7421df7d6003abaa40291e79f18d Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 28 Feb 2026 18:49:29 +0100 Subject: [PATCH 13/14] Reverted non-necessary additions --- .../Source/CommonFramework/GlobalSettingsPanel.cpp | 2 +- .../Framework/PokemonSwSh_MaxLair_StateMachine.cpp | 12 ++++++------ .../MaxLair/Options/PokemonSwSh_MaxLair_Options.h | 2 +- .../PokemonSwSh_MaxLair_Options_BossAction.cpp | 9 +++++---- .../MaxLair/PokemonSwSh_MaxLair_Standard.cpp | 3 --- .../MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp | 3 --- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp index 7543c71696..4fe726a2f4 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp @@ -82,7 +82,7 @@ void PreloadSettings::load(const JsonValue& json){ QByteArrayView dataView(dev_token->data(), dev_token->size()); hash.addData(dataView); #endif - DEVELOPER_MODE = true; //TOKENS.find(hash.result().toHex().toStdString()) != TOKENS.end(); + DEVELOPER_MODE = TOKENS.find(hash.result().toHex().toStdString()) != TOKENS.end(); } const JsonObject* debug_obj = obj->get_object("DEBUG"); diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp index decc2bf76f..d471da0f4f 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp @@ -186,12 +186,12 @@ StateMachineAction run_state_iteration( console.log("Current State: Entrance"); std::string boss_slug = global_state.infer_actual_state(console_index).boss; run_entrance( - runtime, - env, console_index, - console, context, - save_path, - global_state - ); + runtime, + env, console_index, + console, context, + save_path, + global_state + ); return StateMachineAction::DONE_WITH_ADVENTURE; } case 8: diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h index e0caa422eb..fc3696686d 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options.h @@ -64,7 +64,7 @@ class EndBattleDecider{ virtual bool is_in_save_list(const std::string& boss_slug) const { return false; } // For Standard/StrongBoss: whether to keep a followed path when prompted. - virtual bool should_keep_followed_path() const { return false; } + //virtual bool should_keep_followed_path() const { return false; } }; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp index 137b50f066..e25168c134 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Options/PokemonSwSh_MaxLair_Options_BossAction.cpp @@ -4,10 +4,14 @@ * */ +#include +#include //#include "Common/Compiler.h" //#include "Common/Cpp/Json/JsonValue.h" //#include "Common/Cpp/Json/JsonArray.h" //#include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "Common/Cpp/Options/ConfigOption.h" //#include "CommonFramework/Globals.h" #include "Pokemon/Pokemon_Strings.h" #include "Pokemon/Resources/Pokemon_PokemonNames.h" @@ -15,10 +19,7 @@ #include "PokemonSwSh/Resources/PokemonSwSh_PokemonSprites.h" #include "PokemonSwSh/Resources/PokemonSwSh_MaxLairDatabase.h" #include "PokemonSwSh_MaxLair_Options_BossAction.h" -#include "Common/Cpp/Options/BooleanCheckBoxOption.h" -#include "Common/Cpp/Options/ConfigOption.h" -#include -#include + //#include //using std::cout; diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp index 63b319753b..53280e6c16 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_Standard.cpp @@ -166,9 +166,6 @@ class EndBattleDecider_Standard : public EndBattleDecider{ } return actions.no_shinies; } - virtual bool should_keep_followed_path() const override { - return true; - } private: const MaxLairStandard_ConsoleOptions& console(size_t index) const{ diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp index da2a71ca4d..d8b2e2def5 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/PokemonSwSh_MaxLair_StrongBoss.cpp @@ -215,9 +215,6 @@ class EndBattleDecider_StrongBoss : public EndBattleDecider{ return CaughtScreenAction::TAKE_NON_BOSS_SHINY_AND_CONTINUE; } } - virtual bool should_keep_followed_path() const override { - return true; - } private: const MaxLairStrongBoss_ConsoleOptions& console(size_t index) const{ From 3641b06631569986fa506da49ed52ccc991ea6fe Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 28 Feb 2026 18:51:43 +0100 Subject: [PATCH 14/14] Reverted unnecessary additions 2 --- .../MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp index d471da0f4f..a23bbc8a7a 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Framework/PokemonSwSh_MaxLair_StateMachine.cpp @@ -182,7 +182,7 @@ StateMachineAction run_state_iteration( global_state, decider, entrance ); - case 7: { + case 7: console.log("Current State: Entrance"); std::string boss_slug = global_state.infer_actual_state(console_index).boss; run_entrance( @@ -191,9 +191,8 @@ StateMachineAction run_state_iteration( console, context, save_path, global_state - ); + ); return StateMachineAction::DONE_WITH_ADVENTURE; - } case 8: console.log("Current State: Frozen Screen", COLOR_RED); // pbf_mash_button(context, BUTTON_B, 1000ms);