From bbbf3ed1ccad30132a3401bb47c80296763220eb Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 19 May 2026 14:28:59 +0200 Subject: [PATCH 01/39] Add BDSP AutoStory program Co-Authored-By: Claude Sonnet 4.6 --- .../PokemonBDSP_BattleOpponentNameReader.cpp | 75 + .../PokemonBDSP_BattleOpponentNameReader.h | 44 + .../Source/PokemonBDSP/PokemonBDSP_Panels.cpp | 5 + .../AutoStory/DIRECTORY_CLEANUP_SUMMARY.md | 114 ++ .../Data/PokemonBDSP_AutoStory_BDSPData.cpp | 233 +++ .../Data/PokemonBDSP_AutoStory_BDSPData.h | 101 ++ ...kemonBDSP_AutoStory_MenuCursorDetector.cpp | 270 ++++ ...PokemonBDSP_AutoStory_MenuCursorDetector.h | 143 ++ ...okemonBDSP_AutoStory_OverworldDetector.cpp | 58 + .../PokemonBDSP_AutoStory_OverworldDetector.h | 65 + .../AutoStory/PokemonBDSP_AutoStory.cpp | 608 +++++++ .../AutoStory/PokemonBDSP_AutoStory.h | 72 + .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 758 +++++++++ .../AutoStory/PokemonBDSP_AutoStoryTools.h | 249 +++ .../PokemonBDSP_AutoStory_Segment_00.cpp | 910 +++++++++++ .../PokemonBDSP_AutoStory_Segment_00.h | 149 ++ ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 1204 ++++++++++++++ .../PokemonBDSP_AutoStory_Segment_00_tools.h | 133 ++ .../PokemonBDSP_AutoStory_Segment_01.cpp | 153 ++ .../PokemonBDSP_AutoStory_Segment_01.h | 77 + .../PokemonBDSP_AutoStory_Segment_02.cpp | 344 ++++ .../PokemonBDSP_AutoStory_Segment_02.h | 130 ++ .../PokemonBDSP_AutoStory_Segment_03.cpp | 463 ++++++ .../PokemonBDSP_AutoStory_Segment_03.h | 165 ++ .../PokemonBDSP_AutoStory_Segment_04.cpp | 1014 ++++++++++++ .../PokemonBDSP_AutoStory_Segment_04.h | 57 + .../PokemonBDSP_AutoStory_Segment_05.cpp | 1429 +++++++++++++++++ .../PokemonBDSP_AutoStory_Segment_05.h | 57 + .../PokemonBDSP_AutoStory_Segment_06.cpp | 104 ++ .../PokemonBDSP_AutoStory_Segment_06.h | 57 + .../PokemonBDSP_AutoStory_Segment_07.cpp | 68 + .../PokemonBDSP_AutoStory_Segment_07.h | 57 + .../PokemonBDSP_AutoStory_Segment_08.cpp | 139 ++ .../PokemonBDSP_AutoStory_Segment_08.h | 66 + .../PokemonBDSP_AutoStory_Segment_09.cpp | 68 + .../PokemonBDSP_AutoStory_Segment_09.h | 57 + .../StorySegments/butters segment data.md | 817 ++++++++++ .../Utils/PokemonBDSP_AutoStory_Battle.cpp | 353 ++++ .../Utils/PokemonBDSP_AutoStory_Battle.h | 89 + .../Utils/PokemonBDSP_AutoStory_Dialog.cpp | 44 + .../Utils/PokemonBDSP_AutoStory_Dialog.h | 68 + .../PokemonBDSP_AutoStory_Navigation.cpp | 57 + .../Utils/PokemonBDSP_AutoStory_Navigation.h | 82 + .../AutoStory/butters segment data.md | 817 ++++++++++ SerialPrograms/cmake/SourceFiles.cmake | 42 +- 45 files changed, 12063 insertions(+), 2 deletions(-) create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.cpp new file mode 100644 index 0000000000..5934464203 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.cpp @@ -0,0 +1,75 @@ +/* BDSP Battle Opponent Name Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "Pokemon/Inference/Pokemon_NameReader.h" +#include "PokemonBDSP_BattleOpponentNameReader.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using namespace Pokemon; + + +// ⚠ These regions are estimated from BDSP's typical battle layout. +// Calibrate against a real capture if OCR reads are consistently wrong. +// +// In BDSP singles, the one opponent's info box is in the top-right quadrant. +// In BDSP doubles, two boxes sit side-by-side in the top half of the screen. +static const ImageFloatBox BOX_SINGLES_OPP (0.505, 0.010, 0.420, 0.085); +static const ImageFloatBox BOX_DOUBLES_LEFT (0.505, 0.010, 0.200, 0.085); +static const ImageFloatBox BOX_DOUBLES_RIGHT (0.710, 0.010, 0.260, 0.085); + + +static std::string ocr_name_box( + Logger& logger, + Language language, + const ImageViewRGB32& frame, + const ImageFloatBox& box +){ + ImageViewRGB32 cropped = extract_box_reference(frame, box); + OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( + logger, + language, + cropped, + OCR::BLACK_OR_WHITE_TEXT_FILTERS() + ); + if (result.results.empty()){ + return ""; + } + // Use the best match (lowest log10p) if it meets the confidence threshold + double log10p = result.results.begin()->first; + if (log10p > PokemonNameReader::MAX_LOG10P){ + return ""; + } + return result.results.begin()->second.token; +} + + +std::vector read_battle_opponent_names( + Logger& logger, + Language language, + const ImageViewRGB32& frame, + BDSPBattleType battle_type +){ + std::vector names; + if (battle_type == BDSPBattleType::SINGLES){ + names.push_back(ocr_name_box(logger, language, frame, BOX_SINGLES_OPP)); + } else { + names.push_back(ocr_name_box(logger, language, frame, BOX_DOUBLES_LEFT)); + names.push_back(ocr_name_box(logger, language, frame, BOX_DOUBLES_RIGHT)); + } + return names; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h new file mode 100644 index 0000000000..b1592c8705 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h @@ -0,0 +1,44 @@ +/* BDSP Battle Opponent Name Reader + * + * From: https://github.com/PokemonAutomation/ + * + * OCR-based detection of opponent pokemon names from the BDSP battle screen. + * Used by the outside-help battle handler to track which pokemon are currently + * on the field and update move selection accordingly. + * + * ⚠ Screen region coordinates are estimates and may require calibration + * against a real BDSP capture before they work reliably in production. + */ + +#ifndef PokemonAutomation_PokemonBDSP_BattleOpponentNameReader_H +#define PokemonAutomation_PokemonBDSP_BattleOpponentNameReader_H + +#include +#include +#include "CommonFramework/Language.h" +#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h" + +namespace PokemonAutomation{ + class Logger; + class ImageViewRGB32; +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// Read the opponent pokemon name boxes at the top of the BDSP battle screen. +// +// Returns one slug per opponent slot (up to 1 for singles, 2 for doubles). +// An empty string in a slot means OCR confidence was too low to identify the pokemon. +// The caller should keep the previous state for any slot that returns empty string. +std::vector read_battle_opponent_names( + Logger& logger, + Language language, + const ImageViewRGB32& frame, + BDSPBattleType battle_type +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp index e2aa07801f..1054f4dd03 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp @@ -7,6 +7,8 @@ #include "CommonFramework/StaticGlobals.h" #include "CommonFramework/GlobalAutoPaths.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h" + #include "PokemonBDSP_Panels.h" #include "PokemonBDSP_Settings.h" @@ -91,6 +93,9 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_SingleSwitchProgram()); ret.emplace_back(make_SingleSwitchProgram()); + ret.emplace_back("---- Auto Story ----"); + ret.emplace_back(make_single_switch_program()); + ret.emplace_back("---- Glitches (v1.1.3) ----"); ret.emplace_back(make_SingleSwitchProgram()); ret.emplace_back(make_SingleSwitchProgram()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md new file mode 100644 index 0000000000..f077c236d8 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md @@ -0,0 +1,114 @@ +# Story Directory Cleanup and Organization Summary + +## Overview +This document summarizes the cleanup and organization of the PokemonBDSP AutoStory directory (`SerialPrograms/Source/PokemonBDSP/Programs/Story`). + +## Cleanup Actions Completed + +### 1. Deleted Temporary Fix Documentation (9 files) +The following temporary documentation files created during development have been deleted as they are no longer needed: + +- ✅ `COMPILATION_FIXES.md` - Notes on compiler error fixes +- ✅ `INCLUDE_PATH_FIX.md` - Include path configuration notes +- ✅ `LINKER_ERROR_FIX.md` - Linker error resolution notes +- ✅ `PROCONTROLLER_MISSING_INCLUDE_FIX.md` - ProController header fixes +- ✅ `DIALOGUE_DETECTION_FIX.md` - Dialogue detection fixes +- ✅ `DIALOGUE_DETECTION_IMPLEMENTATION.md` - Implementation details +- ✅ `COLOR_FIX_IMPLEMENTATION.md` - Color detection implementation +- ✅ `FLEXIBLE_DIALOGUE_DETECTION.md` - Flexible detection notes +- ✅ `DEBUGGING_GUIDE.md` - Development debugging guide + +**Reason:** These files documented temporary issues fixed during development. The actual code is now fixed and in production. These notes should be archived in git history, not maintained in the working directory. + +## Current Directory Structure + +### Root Level Files (Primary Directory) +``` +C:\Users\mackk\Arduino-Source\SerialPrograms\Source\PokemonBDSP\Programs\Story\ +├── PokemonBDSP_AutoStory.h - Main program header +├── PokemonBDSP_AutoStory.cpp - Main program implementation +├── BDSP_RouteBuilder.h - Route composition interface +├── BDSP_RouteBuilder.cpp - Route composition logic +└── BDSP_RouteConfig.h - Route configuration enum/types +``` + +### Routes Subdirectory +``` +Routes/ +├── BaseRoute.h/cpp - Initial story progression (title → starter selection) +├── Starter_Chimchar.h/cpp - Chimchar selection and confirmation +├── Starter_Piplup.h/cpp - Piplup selection and confirmation +├── Starter_Turtwig.h/cpp - Turtwig selection and confirmation +├── OutsideHelp_Modifiers.h/cpp - Story path using NPC assistance +├── NoOutsideHelp_Modifiers.h/cpp - Solo story path +├── Legendary_Segment.h/cpp - Box legendary Pokemon catching +├── DateTimeSetup.h/cpp - ⚠️ Unused placeholder (see below) +├── ARCHITECTURE.md - System design and file organization ✓ +└── STARTER_SELECTION_GUIDE.md - Starter selection mechanics ✓ +``` + +## Findings: Unused Files + +### DateTimeSetup.h/cpp (Candidates for Removal) +These files are **currently unused** and can be safely removed: + +- **Status:** Declared but not included in any route builder or main program +- **Purpose:** Was intended to handle date/time setup, but this functionality is now handled by the framework's `DateSpam` and `DateManip` modules +- **Evidence:** + - Not included in `BDSP_RouteBuilder.cpp` + - Not referenced in `PokemonBDSP_AutoStory.cpp` + - Comment in route builder explicitly states: "Date/time setting happens in the main program before route execution. This is handled in PokemonBDSP_AutoStory::program()" via framework modules + +**Recommendation:** Delete `Routes/DateTimeSetup.h` and `Routes/DateTimeSetup.cpp` in the next cleanup phase if confirmed to be unused in the broader codebase. + +## File Organization Assessment + +### ✅ Strengths +1. **Clear separation of concerns** - Each route type has dedicated files +2. **Logical naming** - File names clearly indicate purpose (Starter_*, Modifiers_*, Segment) +3. **Good documentation** - ARCHITECTURE.md and STARTER_SELECTION_GUIDE.md provide context +4. **Modular design** - Route builder composes segments from multiple sources cleanly + +### Consolidation Opportunities + +The current file structure is well-organized and follows a logical module pattern. No consolidation is recommended because: + +1. **Starter files** (Chimchar, Piplup, Turtwig) are similar in structure but represent distinct game choices - separate files aid maintainability +2. **Route modifier files** (OutsideHelp vs. NoOutsideHelp) represent strategic divergence points - worth keeping separate +3. **Legendary catching** is a distinct feature - appropriate as separate module +4. **BaseRoute** handles a critical common path - appropriate as standalone + +## Recommendations + +### Immediate +- ✅ **COMPLETED:** Remove temporary fix documentation files + +### Short-term (Next Phase) +1. **Remove DateTimeSetup files** - Confirm no other project uses them, then delete `Routes/DateTimeSetup.h/cpp` +2. **Archive development notes** - The deleted documentation should be in git commit history for future reference + +### Long-term +- Monitor for other unused placeholder segments +- Consider creating a test suite to verify all route compositions work end-to-end +- Update ARCHITECTURE.md when adding new features + +## File Statistics + +| Category | Count | Notes | +|----------|-------|-------| +| Active Code Files | 15 | Header + Implementation pairs (+ config header) | +| Documentation Files | 2 | ARCHITECTURE.md, STARTER_SELECTION_GUIDE.md | +| Unused Code Files | 2 | DateTimeSetup.h/cpp (flagged for removal) | +| Deleted Files | 9 | Temporary fix documentation | + +## Verification + +- ✅ No compilation errors in Story directory files +- ✅ All active code files are properly included in route builder +- ✅ Documentation files are current and relevant +- ✅ Project structure matches documented architecture + +--- + +**Last Updated:** April 12, 2026 +**By:** Code Cleanup Agent diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp new file mode 100644 index 0000000000..23385d64da --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp @@ -0,0 +1,233 @@ +/* BDSP AutoStory - Game Data + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Json/JsonValue.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "CommonFramework/Globals.h" +#include "PokemonBDSP_AutoStory_BDSPData.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +static const std::string RESOURCE_DIR = "PokemonBDSP/AutoStory/"; + + +// --------------------------------------------------------------------------- +// Shared parsing helpers +// --------------------------------------------------------------------------- + +static PokemonType parse_type(const std::string& slug, const std::string& path){ + static const std::map TABLE{ + {"normal", PokemonType::NORMAL}, + {"fire", PokemonType::FIRE}, + {"fighting", PokemonType::FIGHTING}, + {"water", PokemonType::WATER}, + {"flying", PokemonType::FLYING}, + {"grass", PokemonType::GRASS}, + {"poison", PokemonType::POISON}, + {"electric", PokemonType::ELECTRIC}, + {"ground", PokemonType::GROUND}, + {"psychic", PokemonType::PSYCHIC}, + {"rock", PokemonType::ROCK}, + {"ice", PokemonType::ICE}, + {"bug", PokemonType::BUG}, + {"dragon", PokemonType::DRAGON}, + {"ghost", PokemonType::GHOST}, + {"dark", PokemonType::DARK}, + {"steel", PokemonType::STEEL}, + {"fairy", PokemonType::FAIRY}, + }; + auto iter = TABLE.find(slug); + if (iter == TABLE.end()){ + throw FileException(nullptr, PA_CURRENT_FUNCTION, "Unknown type: " + slug, path); + } + return iter->second; +} + +static MoveCategory parse_category(const std::string& slug, const std::string& path){ + if (slug == "physical") return MoveCategory::PHYSICAL; + if (slug == "special") return MoveCategory::SPECIAL; + if (slug == "status") return MoveCategory::STATUS; + throw FileException(nullptr, PA_CURRENT_FUNCTION, "Unknown move category: " + slug, path); +} + + +// --------------------------------------------------------------------------- +// Move database +// --------------------------------------------------------------------------- + +struct BDSPMoveDatabase{ + std::map data; + + static const BDSPMoveDatabase& instance(){ + static BDSPMoveDatabase db; + return db; + } + + BDSPMoveDatabase(){ + std::string path = RESOURCE_PATH() + RESOURCE_DIR + "bdsp_move_data.json"; + JsonValue json = load_json_file(path); + JsonObject& root = json.to_object_throw(path); + + for (auto& item : root){ + const std::string& slug = item.first; + JsonObject& obj = item.second.to_object_throw(path); + + BDSPMoveData& move = data[slug]; + move.category = parse_category(obj.get_string_throw("category", path), path); + move.type = parse_type(obj.get_string_throw("type", path), path); + move.base_power = (uint8_t)obj.get_integer_throw("base_power", path); + move.pp = (uint8_t)obj.get_integer_throw("pp", path); + move.spread = obj.get_boolean_throw("spread", path); + } + } +}; + +const BDSPMoveData& bdsp_move(const std::string& slug){ + const auto& db = BDSPMoveDatabase::instance().data; + auto iter = db.find(slug); + if (iter == db.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown BDSP move slug: " + slug); + } + return iter->second; +} + + +// --------------------------------------------------------------------------- +// Pokemon database +// --------------------------------------------------------------------------- + +struct BDSPPokemonDatabase{ + std::map data; + + static const BDSPPokemonDatabase& instance(){ + static BDSPPokemonDatabase db; + return db; + } + + BDSPPokemonDatabase(){ + std::string path = RESOURCE_PATH() + RESOURCE_DIR + "bdsp_pokemon_data.json"; + JsonValue json = load_json_file(path); + JsonObject& root = json.to_object_throw(path); + + for (auto& item : root){ + const std::string& slug = item.first; + JsonObject& obj = item.second.to_object_throw(path); + + BDSPPokemonData& mon = data[slug]; + mon.type[0] = PokemonType::NONE; + mon.type[1] = PokemonType::NONE; + { + JsonArray& type_arr = obj.get_array_throw("type", path); + if (type_arr.size() >= 1) + mon.type[0] = parse_type(type_arr[0].to_string_throw(path), path); + if (type_arr.size() >= 2) + mon.type[1] = parse_type(type_arr[1].to_string_throw(path), path); + } + { + JsonObject& stats = obj.get_object_throw("base_stats", path); + mon.hp = (uint8_t)stats.get_integer_throw("hp", path); + mon.attack = (uint8_t)stats.get_integer_throw("attack", path); + mon.defense = (uint8_t)stats.get_integer_throw("defense", path); + mon.spatk = (uint8_t)stats.get_integer_throw("spatk", path); + mon.spdef = (uint8_t)stats.get_integer_throw("spdef", path); + mon.speed = (uint8_t)stats.get_integer_throw("speed", path); + } + } + } +}; + +const BDSPPokemonData& bdsp_pokemon(const std::string& slug){ + const auto& db = BDSPPokemonDatabase::instance().data; + auto iter = db.find(slug); + if (iter == db.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown BDSP pokemon slug: " + slug); + } + return iter->second; +} + + +// --------------------------------------------------------------------------- +// Trainer database +// --------------------------------------------------------------------------- + +struct BDSPTrainerDatabase{ + std::map data; + + static const BDSPTrainerDatabase& instance(){ + static BDSPTrainerDatabase db; + return db; + } + + BDSPTrainerDatabase(){ + std::string path = RESOURCE_PATH() + RESOURCE_DIR + "trainers.json"; + JsonValue json = load_json_file(path); + JsonObject& root = json.to_object_throw(path); + + for (auto& item : root){ + const std::string& battle_id = item.first; + JsonObject& obj = item.second.to_object_throw(path); + + BDSPTrainerData& battle = data[battle_id]; + { + const std::string& type_str = obj.get_string_throw("battle_type", path); + if (type_str == "2v2") + battle.battle_type = BDSPBattleType::DOUBLES; + else + battle.battle_type = BDSPBattleType::SINGLES; + } + { + JsonArray& trainers_arr = obj.get_array_throw("trainers", path); + for (auto& t_val : trainers_arr){ + JsonObject& t_obj = t_val.to_object_throw(path); + BDSPTrainer trainer; + JsonArray& pkmn_arr = t_obj.get_array_throw("pokemon", path); + for (auto& p_val : pkmn_arr){ + JsonObject& p_obj = p_val.to_object_throw(path); + BDSPTrainerPokemon tp; + tp.species = p_obj.get_string_throw("species", path); + tp.level = (uint8_t)p_obj.get_integer_throw("level", path); + + auto parse_stat_set = [&](BDSPStatSet& set, const char* key){ + JsonObject* s = p_obj.get_object(key); + if (!s) return; + set.hp = (uint8_t)s->get_integer_default("hp", 0); + set.attack = (uint8_t)s->get_integer_default("attack", 0); + set.defense = (uint8_t)s->get_integer_default("defense", 0); + set.spatk = (uint8_t)s->get_integer_default("spatk", 0); + set.spdef = (uint8_t)s->get_integer_default("spdef", 0); + set.speed = (uint8_t)s->get_integer_default("speed", 0); + }; + parse_stat_set(tp.ivs, "ivs"); + parse_stat_set(tp.evs, "evs"); + + trainer.pokemon.push_back(std::move(tp)); + } + battle.trainers.push_back(std::move(trainer)); + } + } + } + } +}; + +const BDSPTrainerData& bdsp_trainer(const std::string& battle_id){ + const auto& db = BDSPTrainerDatabase::instance().data; + auto iter = db.find(battle_id); + if (iter == db.end()){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Unknown BDSP battle ID: " + battle_id); + } + return iter->second; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h new file mode 100644 index 0000000000..9a3de6b20d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h @@ -0,0 +1,101 @@ +/* BDSP AutoStory - Game Data + * + * From: https://github.com/PokemonAutomation/ + * + * Provides access to move metadata, pokemon base stats, and the trainer + * database used by the outside-help battle handler. + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_BDSPData_H +#define PokemonAutomation_PokemonBDSP_AutoStory_BDSPData_H + +#include +#include +#include "Pokemon/Pokemon_Types.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using namespace Pokemon; + + +// --------------------------------------------------------------------------- +// Move data +// --------------------------------------------------------------------------- + +struct BDSPMoveData{ + MoveCategory category; + PokemonType type; + uint8_t base_power; + uint8_t pp; + bool spread; +}; + +// Returns move data for the given slug (e.g. "earthquake"). +// Throws InternalProgramError if the slug is not found. +const BDSPMoveData& bdsp_move(const std::string& slug); + + +// --------------------------------------------------------------------------- +// Pokemon base stats +// --------------------------------------------------------------------------- + +struct BDSPPokemonData{ + PokemonType type[2]; // type[1] == NONE for mono-type pokemon + uint8_t hp; + uint8_t attack; + uint8_t defense; + uint8_t spatk; + uint8_t spdef; + uint8_t speed; +}; + +// Returns base stats for the given species slug (e.g. "beautifly"). +// Throws InternalProgramError if the slug is not found. +const BDSPPokemonData& bdsp_pokemon(const std::string& slug); + + +// --------------------------------------------------------------------------- +// Trainer database +// --------------------------------------------------------------------------- + +enum class BDSPBattleType{ + SINGLES, + DOUBLES, +}; + +struct BDSPStatSet{ + uint8_t hp = 0; + uint8_t attack = 0; + uint8_t defense = 0; + uint8_t spatk = 0; + uint8_t spdef = 0; + uint8_t speed = 0; +}; + +struct BDSPTrainerPokemon{ + std::string species; + uint8_t level; + BDSPStatSet ivs; + BDSPStatSet evs; +}; + +struct BDSPTrainer{ + std::vector pokemon; +}; + +struct BDSPTrainerData{ + BDSPBattleType battle_type; + std::vector trainers; +}; + +// Returns trainer data for the given battle ID (e.g. "Veilstone_GalacticGrunts_2v2"). +// Throws InternalProgramError if the ID is not found. +const BDSPTrainerData& bdsp_trainer(const std::string& battle_id); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp new file mode 100644 index 0000000000..3b288bb241 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp @@ -0,0 +1,270 @@ +/* BDSP AutoStory - Menu Cursor Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Images/ImageFilter.h" +#include "PokemonBDSP_AutoStory_MenuCursorDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +std::string menu_cursor_to_string(MenuCursorPosition pos){ + switch (pos){ + case MenuCursorPosition::POKEDEX: return "POKEDEX"; + case MenuCursorPosition::POKEMON: return "POKEMON"; + case MenuCursorPosition::BAG: return "BAG"; + case MenuCursorPosition::CARD: return "CARD"; + case MenuCursorPosition::OPTIONS: return "OPTIONS"; + case MenuCursorPosition::MAP: return "MAP"; + case MenuCursorPosition::CAPSULES: return "CAPSULES"; + case MenuCursorPosition::MYSTERYGIFT: return "MYSTERYGIFT"; + default: return "UNKNOWN"; + } +} + + +MenuCursorDetector::MenuCursorDetector(Color color) + : m_color(color) + // ------------------------------------------------------------------------- + // Top row — left to right + // ------------------------------------------------------------------------- + // TODO: replace each ImageFloatBox(0,0,0,0) with the real normalized + // (x, y, width, height) for a small region that gains dark pixels when + // that icon is hovered. Use the test program overlays to tune coordinates. + // ------------------------------------------------------------------------- + , m_pokedex_box{ + ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), + } + , m_pokemon_box{ + ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), + } + , m_bag_box{ + ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), + } + , m_card_box{ + ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), + } + // ------------------------------------------------------------------------- + // Bottom row — left to right + // 5-icon layout: OPTIONS is the only bottom icon (far-left position). + // 8-icon layout: MAP, CAPSULES, OPTIONS, MYSTERYGIFT from left to right. + // OPTIONS sits at a different x-position in each layout — use a separate + // MenuCursorDetector instance if you need to detect OPTIONS in both. + // ------------------------------------------------------------------------- + , m_options_box{ + ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), + } + , m_map_box{ + ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), + } + , m_capsules_box{ + ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), + } + , m_mysterygift_box{ + ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), + } + // ------------------------------------------------------------------------- + // Approach 2 search boxes — one per icon, same area as the dark-pixel box. + // The template PNG is slid across this area at its native size. + // ------------------------------------------------------------------------- + , m_icon_match_box{ + ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), // 0 POKEDEX + ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), // 1 POKEMON + ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), // 2 BAG + ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), // 3 CARD + ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), // 4 OPTIONS (8-icon layout) + ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), // 5 MAP + ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), // 6 CAPSULES + ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), // 7 MYSTERYGIFT + } +{} + + +void MenuCursorDetector::make_overlays(VideoOverlaySet& items) const{ + for (const ImageFloatBox* icon_boxes : { + m_pokedex_box, m_pokemon_box, m_bag_box, m_card_box, + m_options_box, m_map_box, m_capsules_box, m_mysterygift_box, + }){ + for (int i = 0; i < BOXES_PER_ICON; i++){ + if (icon_boxes[i].width > 0.0 && icon_boxes[i].height > 0.0) + items.add(m_color, icon_boxes[i]); + } + } + for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ + if (m_icon_match_box[i].width > 0.0 && m_icon_match_box[i].height > 0.0) + items.add(m_color, m_icon_match_box[i]); + } +} + + +namespace{ + +// Count how many of `n` boxes contain >= MIN_DARK_PIXEL_COUNT pixels with +// R, G, B all <= BLACK_CHANNEL_MAX. Boxes with zero width are skipped. +int count_dark_boxes( + const ImageViewRGB32& screen, + const ImageFloatBox* boxes, int n +){ + const uint32_t ch = uint32_t(MenuCursorDetector::BLACK_CHANNEL_MAX); + const uint32_t maxs = 0xff000000u | (ch << 16) | (ch << 8) | ch; + + int count = 0; + for (int i = 0; i < n; i++){ + if (boxes[i].width == 0.0) continue; + size_t dark_pixels = 0; + filter_rgb32_range( + dark_pixels, + extract_box_reference(screen, boxes[i]), + 0xff000000u, maxs, + Color(0xffffffff), false + ); + if (dark_pixels >= MenuCursorDetector::MIN_DARK_PIXEL_COUNT) count++; + } + return count; +} + +// Pixel-wise RMSD between two same-size images over RGB channels only. +double compute_rmsd(const ImageViewRGB32& a, const ImageViewRGB32& b){ + size_t n = a.width() * a.height(); + if (n == 0) return 0.0; + double sum_sq = 0.0; + for (size_t y = 0; y < a.height(); y++){ + for (size_t x = 0; x < a.width(); x++){ + uint32_t pa = a.pixel(x, y); + uint32_t pb = b.pixel(x, y); + int dr = int((pa >> 16) & 0xff) - int((pb >> 16) & 0xff); + int dg = int((pa >> 8) & 0xff) - int((pb >> 8) & 0xff); + int db = int( pa & 0xff) - int( pb & 0xff); + sum_sq += double(dr*dr + dg*dg + db*db); + } + } + return std::sqrt(sum_sq / (3.0 * n)); +} + +// Slide `tmpl` (at its native pixel size) across the region of `screen` +// defined by `box`. Returns the minimum RMSD found over all window positions. +double template_match_rmsd( + const ImageViewRGB32& screen, + const ImageFloatBox& box, + const ImageViewRGB32& tmpl +){ + ImageViewRGB32 region = extract_box_reference(screen, box); + size_t rw = tmpl.width(), rh = tmpl.height(); + size_t lw = region.width(), lh = region.height(); + if (rw == 0 || rh == 0 || rw > lw || rh > lh) + return 0.0; + + double min_rmsd = std::numeric_limits::max(); + for (size_t dy = 0; dy <= lh - rh; dy++){ + for (size_t dx = 0; dx <= lw - rw; dx++){ + double r = compute_rmsd(region.sub_image(dx, dy, rw, rh), tmpl); + if (r < min_rmsd) min_rmsd = r; + } + } + return min_rmsd; +} + +} // anonymous namespace + + +MenuCursorPosition MenuCursorDetector::detect_dark_pixels( + const ImageViewRGB32& screen +) const { + struct IconEntry{ + MenuCursorPosition position; + const ImageFloatBox* boxes; + }; + const IconEntry icons[] = { + { MenuCursorPosition::POKEDEX, m_pokedex_box }, + { MenuCursorPosition::POKEMON, m_pokemon_box }, + { MenuCursorPosition::BAG, m_bag_box }, + { MenuCursorPosition::CARD, m_card_box }, + { MenuCursorPosition::OPTIONS, m_options_box }, + { MenuCursorPosition::MAP, m_map_box }, + { MenuCursorPosition::CAPSULES, m_capsules_box }, + { MenuCursorPosition::MYSTERYGIFT, m_mysterygift_box }, + }; + + MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; + int best_count = 0; + + for (const auto& entry : icons){ + int count = count_dark_boxes(screen, entry.boxes, BOXES_PER_ICON); + if (count > best_count){ + best_count = count; + best_pos = entry.position; + } + } + + return (best_count >= REQUIRED_VOTES) ? best_pos : MenuCursorPosition::UNKNOWN; +} + + +MenuCursorPosition MenuCursorDetector::detect_image_match( + const ImageViewRGB32& screen +) const { + MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; + double best_rmsd = MATCH_RMSD_THRESHOLD; // must exceed this + + for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ + if (!m_icon_references[i].has_value()) continue; + if (m_icon_match_box[i].width == 0.0) continue; + double r = template_match_rmsd(screen, m_icon_match_box[i], *m_icon_references[i]); + if (r > best_rmsd){ + best_rmsd = r; + best_pos = static_cast(i); + } + } + return best_pos; +} + + +MenuCursorPosition MenuCursorDetector::detect( + const ImageViewRGB32& screen +) const { + MenuCursorPosition p1 = detect_dark_pixels(screen); + MenuCursorPosition p2 = detect_image_match(screen); + + if (p1 == p2) return p1; // both agree (or both UNKNOWN) + if (p1 == MenuCursorPosition::UNKNOWN) return p2; // only approach 2 fired + if (p2 == MenuCursorPosition::UNKNOWN) return p1; // only approach 1 fired + return p1; // conflict: prefer approach 1 +} + + +void MenuCursorDetector::set_icon_reference( + MenuCursorPosition icon, + ImageRGB32 reference +){ + m_icon_references[static_cast(icon)].emplace(std::move(reference)); +} + + +double MenuCursorDetector::icon_rmsd( + const ImageViewRGB32& screen, MenuCursorPosition icon +) const { + int idx = static_cast(icon); + if (!m_icon_references[idx].has_value() || m_icon_match_box[idx].width == 0.0) + return -1.0; + return template_match_rmsd(screen, m_icon_match_box[idx], *m_icon_references[idx]); +} + + +MenuCursorPosition detect_menu_cursor(VideoStream& stream, ProControllerContext& /*context*/){ + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) return MenuCursorPosition::UNKNOWN; + MenuCursorDetector detector; + return detector.detect(screen); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h new file mode 100644 index 0000000000..c5ac2626c1 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h @@ -0,0 +1,143 @@ +/* BDSP AutoStory - Menu Cursor Detector + * + * From: https://github.com/PokemonAutomation/ + * + * Detects which icon the cursor is hovering over in the X-menu. + * + * Two independent detection approaches are provided for testing: + * + * Approach 1 — dark pixel count (detect_dark_pixels): + * Each icon has BOXES_PER_ICON small boxes placed over a region that gains + * dark pixels when hovered. A box fires if >= MIN_DARK_PIXEL_COUNT pixels + * in it have R, G, B all <= BLACK_CHANNEL_MAX. An icon is selected when its + * fire count reaches REQUIRED_VOTES. + * Tune BLACK_CHANNEL_MAX upward if real dark pixels are being missed. + * Tune it downward (or raise MIN_DARK_PIXEL_COUNT) to reduce false positives. + * + * Approach 2 — template matching (detect_image_match): + * Each icon has one larger search box and a hovered-state reference PNG loaded + * at runtime via set_icon_reference(). The reference PNG is slid (at its native + * pixel size, without resizing) across the search box and the minimum RMSD over + * all window positions is computed. The icon with the lowest min-RMSD that is + * also <= MATCH_RMSD_THRESHOLD is considered selected. + * Tune MATCH_RMSD_THRESHOLD downward to require a closer match. + * + * detect() is the production method and currently delegates to approach 1. + * It will become a combined vote once both approaches are validated. + * + * Menu layouts: + * 5-icon (early game): POKEDEX POKEMON BAG CARD / OPTIONS + * 8-icon (late game): POKEDEX POKEMON BAG CARD / MAP CAPSULES OPTIONS MYSTERYGIFT + * + * Note: OPTIONS sits at a different screen position in each layout. + * Use separate MenuCursorDetector instances with different box coordinates + * if you need to detect OPTIONS reliably in both layouts. + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_MenuCursorDetector_H +#define PokemonAutomation_PokemonBDSP_AutoStory_MenuCursorDetector_H + +#include +#include +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +enum class MenuCursorPosition{ + UNKNOWN = -1, + POKEDEX = 0, + POKEMON = 1, + BAG = 2, + CARD = 3, + OPTIONS = 4, + MAP = 5, + CAPSULES = 6, + MYSTERYGIFT = 7, +}; + +static constexpr int MENU_CURSOR_NUM_ICONS = 8; + +std::string menu_cursor_to_string(MenuCursorPosition pos); + + +class MenuCursorDetector{ +public: + // ── Approach 1: dark pixel detection ───────────────────────────────────── + static constexpr int BOXES_PER_ICON = 3; + static constexpr int REQUIRED_VOTES = 1; // out of BOXES_PER_ICON + static constexpr uint8_t BLACK_CHANNEL_MAX = 50; // R,G,B <= this → dark pixel + static constexpr size_t MIN_DARK_PIXEL_COUNT = 1; // dark pixels per box to fire + + // ── Approach 2: template match ─────────────────────────────────────────── + // Reference PNGs are the UN-hovered state of each icon. When hovered, the + // icon gains dark pixels and diverges from the reference, so RMSD increases. + // detect_image_match() selects the icon with the HIGHEST RMSD >= threshold. + // Tune upward to reduce false positives; downward if the correct icon is missed. + static constexpr double MATCH_RMSD_THRESHOLD = 53.0; + + MenuCursorDetector(Color color = COLOR_RED); + + void make_overlays(VideoOverlaySet& items) const; + + // Approach 1: votes across dark-pixel boxes, returns icon with highest + // vote count >= REQUIRED_VOTES, or UNKNOWN. + MenuCursorPosition detect_dark_pixels(const ImageViewRGB32& screen) const; + + // Approach 2: returns the icon with the highest RMSD against its reference, + // provided that RMSD >= MATCH_RMSD_THRESHOLD (high RMSD = hovered). + // Returns UNKNOWN if no references are loaded or nothing exceeds threshold. + MenuCursorPosition detect_image_match(const ImageViewRGB32& screen) const; + + // Production method — currently delegates to detect_dark_pixels(). + MenuCursorPosition detect(const ImageViewRGB32& screen) const; + + // Load a hovered-state reference image for one icon (enables approach 2 for + // that icon). The match box for that icon must already be set in the + // constructor (m_icon_match_box); this method just attaches the reference. + void set_icon_reference(MenuCursorPosition icon, ImageRGB32 reference); + + // Returns the raw RMSD of `icon` against its reference on the given frame. + // Returns -1.0 if no reference is loaded for that icon. + double icon_rmsd(const ImageViewRGB32& screen, MenuCursorPosition icon) const; + +private: + Color m_color; + + // ── Approach 1 boxes ───────────────────────────────────────────────────── + // 3 boxes per icon targeting regions that gain dark pixels when hovered. + // All initialized to (0,0,0,0) — replace with real normalized coordinates. + ImageFloatBox m_pokedex_box[BOXES_PER_ICON]; + ImageFloatBox m_pokemon_box[BOXES_PER_ICON]; + ImageFloatBox m_bag_box[BOXES_PER_ICON]; + ImageFloatBox m_card_box[BOXES_PER_ICON]; + ImageFloatBox m_options_box[BOXES_PER_ICON]; + ImageFloatBox m_map_box[BOXES_PER_ICON]; + ImageFloatBox m_capsules_box[BOXES_PER_ICON]; + ImageFloatBox m_mysterygift_box[BOXES_PER_ICON]; + + // ── Approach 2 search boxes + references ──────────────────────────────── + // One search box per icon (the area to slide the template across). + // References are populated via set_icon_reference() at runtime. + ImageFloatBox m_icon_match_box[MENU_CURSOR_NUM_ICONS]; + std::array, MENU_CURSOR_NUM_ICONS> m_icon_references; +}; + + +// Captures the current frame from `stream` and runs detect() on it. +MenuCursorPosition detect_menu_cursor(VideoStream& stream, ProControllerContext& context); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp new file mode 100644 index 0000000000..8cb97c5179 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp @@ -0,0 +1,58 @@ +/* BDSP AutoStory - Overworld Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "PokemonBDSP_AutoStory_OverworldDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// OverworldDetector +// --------------------------------------------------------------------------- + +OverworldDetector::OverworldDetector(Color color) + : m_color(color) + , m_battle_menu(BattleType::STANDARD, color) +{} + +void OverworldDetector::make_overlays(VideoOverlaySet& items) const{ + m_dialog.make_overlays(items); + m_battle_menu.make_overlays(items); + m_menu.make_overlays(items); +} + +bool OverworldDetector::detect(const ImageViewRGB32& screen){ + // We are in the overworld when none of the overlay screens are present. + if (m_dialog.detect(screen)) return false; + if (m_battle_menu.detect(screen)) return false; + if (m_menu.detect(screen)) return false; + return true; +} + + +// --------------------------------------------------------------------------- +// OverworldWatcher +// --------------------------------------------------------------------------- + +OverworldWatcher::OverworldWatcher(Color color) + : OverworldDetector(color) + , VisualInferenceCallback("OverworldWatcher") +{} + +void OverworldWatcher::make_overlays(VideoOverlaySet& items) const{ + OverworldDetector::make_overlays(items); +} + +bool OverworldWatcher::process_frame(const ImageViewRGB32& frame, WallClock /*timestamp*/){ + return detect(frame); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h new file mode 100644 index 0000000000..aa0bddff24 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h @@ -0,0 +1,65 @@ +/* BDSP AutoStory - Overworld Detector + * + * From: https://github.com/PokemonAutomation/ + * + * Detects that the player is in the free-roaming overworld state: + * no dialog box, no battle menu, and no in-game menu open. + * + * BDSP does not ship a standalone OverworldDetector in PokemonBDSP/Inference/, + * so this composite detector is defined here for AutoStory use. + * + * Reuses: + * - ShortDialogDetector (PokemonBDSP_DialogDetector.h) — absence required + * - BattleMenuDetector (PokemonBDSP_BattleMenuDetector.h) — absence required + * - MenuDetector (PokemonBDSP_MenuDetector.h) — absence required + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_OverworldDetector_H +#define PokemonAutomation_PokemonBDSP_AutoStory_OverworldDetector_H + +#include "Common/Cpp/Color.h" +#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" +#include "CommonTools/VisualDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// Static (single-frame) version — use when you already have a frame. +class OverworldDetector : public StaticScreenDetector{ +public: + OverworldDetector(Color color = COLOR_CYAN); + + virtual void make_overlays(VideoOverlaySet& items) const override; + + // Returns true when none of: short dialog, battle menu, or in-game menu + // are present on the given frame. + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + ShortDialogDetector m_dialog; + BattleMenuDetector m_battle_menu; + MenuDetector m_menu; +}; + + +// Watcher (callback) version — use with run_until<> or wait_until(). +class OverworldWatcher : public OverworldDetector, public VisualInferenceCallback{ +public: + OverworldWatcher(Color color = COLOR_CYAN); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp new file mode 100644 index 0000000000..d295c4839d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -0,0 +1,608 @@ +#include +#include + +#include "PokemonBDSP_AutoStory.h" + +#include "CommonFramework/Globals.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + +#include "StorySegments/PokemonBDSP_AutoStory_Segment_00.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_01.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_02.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_03.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_04.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_05.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_06.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_07.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_08.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_09.h" + +#include "CommonTools/Options/StringSelectOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using namespace std::chrono_literals; + + +// --------------------------------------------------------------------------- +// Segment list +// --------------------------------------------------------------------------- + +static std::vector> make_segment_list(){ + std::vector> segment_list; + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + return segment_list; +} + +static const std::vector>& ALL_SEGMENTS(){ + static std::vector> list = make_segment_list(); + return list; +} +static std::vector> make_checkpoint_list(){ + std::vector> checkpoint_list; + + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + return checkpoint_list; +} + +static const std::vector>& ALL_CHECKPOINTS(){ + static std::vector> list = make_checkpoint_list(); + return list; +} + +// --------------------------------------------------------------------------- +// Segment database +// --------------------------------------------------------------------------- + +StringSelectDatabase make_segment_database(){ + StringSelectDatabase db; + int index = 0; + for (const auto& segment : ALL_SEGMENTS()){ + db.add_entry(StringSelectEntry(std::to_string(index), segment->name())); + index++; + } + return db; +} +// --------------------------------------------------------------------------- +// Checkpoint database (SV-style) +// --------------------------------------------------------------------------- +const StringSelectDatabase& SEGMENT_DATABASE(){ + static StringSelectDatabase db = make_segment_database(); + return db; +} +StringSelectDatabase make_checkpoint_database(){ + StringSelectDatabase db; + int index = 0; + for (const auto& checkpoint : ALL_CHECKPOINTS()){ + db.add_entry(StringSelectEntry(std::to_string(index), checkpoint->name())); + index++; + } + return db; +} + +const StringSelectDatabase& CHECKPOINT_DATABASE(){ + static StringSelectDatabase db = make_checkpoint_database(); + return db; +} + +// --------------------------------------------------------------------------- +// Descriptor +// --------------------------------------------------------------------------- + +BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonBDSP:AutoStory", + "Pokemon BDSP", + "Auto Story", + "Programs/PokemonBDSP/AutoStory.html", + "Automatically completes BDSP story segments.", + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + + +std::unique_ptr +BDSPAutoStory_Descriptor::make_instance() const{ + return std::make_unique(); +} + +std::unique_ptr +BDSPAutoStory_Descriptor::make_stats() const{ + return std::make_unique(); +} + + +// --------------------------------------------------------------------------- +// Program Instance +// --------------------------------------------------------------------------- + +BDSPAutoStory::~BDSPAutoStory(){ + OUTSIDE_HELP_POKEMON.remove_listener(*this); + HELP_MODE.remove_listener(*this); + +} + + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- + +BDSPAutoStory::BDSPAutoStory() + : SingleSwitchProgramInstance() + , ENABLE_ADVANCED_MODE( + "Advanced mode:
" + "Use checkpoints instead of segments.", + LockMode::UNLOCK_WHILE_RUNNING, + false + ) + , START_CHECKPOINT_TUTORIAL( + "Start Checkpoint:", + CHECKPOINT_DATABASE(), + LockMode::LOCK_WHILE_RUNNING, + "0" + ) + , END_CHECKPOINT_TUTORIAL( + "End Checkpoint:", + CHECKPOINT_DATABASE(), + LockMode::UNLOCK_WHILE_RUNNING, + "2" + ) + + , START_DESCRIPTION_SEGMENT("Start:
", "") + , END_DESCRIPTION_SEGMENT("End:
", "") + + , START_DESCRIPTION_CHECKPOINT("Start:
", "") + , END_DESCRIPTION_CHECKPOINT("End:
", "") + + , START_CHECKPOINT_MAINSTORY( + "Start Checkpoint (Main):", + CHECKPOINT_DATABASE(), + LockMode::LOCK_WHILE_RUNNING, + "0" + ) + + , END_CHECKPOINT_MAINSTORY( + "End Checkpoint (Main):", + CHECKPOINT_DATABASE(), + LockMode::UNLOCK_WHILE_RUNNING, + "2" + ) + + , START_SEGMENT( + "Start Segment:", + SEGMENT_DATABASE(), + LockMode::LOCK_WHILE_RUNNING, + "0" + ) + + , END_SEGMENT( + "End Segment:", + SEGMENT_DATABASE(), + LockMode::UNLOCK_WHILE_RUNNING, + std::to_string(ALL_SEGMENTS().size() - 1) + ) + + , STARTERCHOICE( + "Starter Pokemon:", + { + {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, + {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, + {StarterChoice::PIPLUP, "piplup", "Piplup"}, + }, + LockMode::LOCK_WHILE_RUNNING, + StarterChoice::CHIMCHAR + ) + + , STARTER_SHINY( + "Shiny Starter:", + LockMode::LOCK_WHILE_RUNNING, + false + ) + + , CATCH_LEGENDARY( + "Catch Box Legendary:", + LockMode::LOCK_WHILE_RUNNING, + false + ) + + , HELP_MODE( + "Help Mode:", + { + {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, + {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, + }, + LockMode::LOCK_WHILE_RUNNING, + HelpMode::NO_OUTSIDE_HELP + ) + + , OUTSIDE_HELP_POKEMON( + "Pokemon:", + { + {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, + }, + LockMode::LOCK_WHILE_RUNNING, + OutsideHelpPokemon::TORTERRA + ) + + , OUTSIDE_HELP_ATTACK_1( + "Attack 1:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::EARTHQUAKE + ) + + , OUTSIDE_HELP_ATTACK_2( + "Attack 2:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::ROCK_SLIDE + ) + + , OUTSIDE_HELP_ATTACK_3( + "Attack 3:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::WOOD_HAMMER + ) + + , OUTSIDE_HELP_ATTACK_4( + "Attack 4:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::CRUNCH + ) + + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)) + , NOTIFICATIONS({ + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_RECOVERABLE, + &NOTIFICATION_ERROR_FATAL, + }) + +{ + // ----------------------------------------------------------------------- + // Options + // ----------------------------------------------------------------------- + + PA_ADD_OPTION(ENABLE_ADVANCED_MODE); + + // Segment UI + PA_ADD_OPTION(START_SEGMENT); + PA_ADD_OPTION(START_DESCRIPTION_SEGMENT); + + PA_ADD_OPTION(END_SEGMENT); + PA_ADD_OPTION(END_DESCRIPTION_SEGMENT); + + // Checkpoint UI + PA_ADD_OPTION(START_CHECKPOINT_TUTORIAL); + PA_ADD_OPTION(START_DESCRIPTION_CHECKPOINT); + + PA_ADD_OPTION(END_CHECKPOINT_TUTORIAL); + PA_ADD_OPTION(END_DESCRIPTION_CHECKPOINT); + + + PA_ADD_OPTION(STARTERCHOICE); + PA_ADD_OPTION(STARTER_SHINY); + PA_ADD_OPTION(CATCH_LEGENDARY); + + PA_ADD_OPTION(HELP_MODE); + PA_ADD_OPTION(OUTSIDE_HELP_POKEMON); + + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_1); + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_2); + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_3); + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_4); + + PA_ADD_OPTION(NOTIFICATIONS); + + HELP_MODE.add_listener(*this); + OUTSIDE_HELP_POKEMON.add_listener(*this); + START_CHECKPOINT_TUTORIAL.add_listener(*this); + END_CHECKPOINT_TUTORIAL.add_listener(*this); + ENABLE_ADVANCED_MODE.add_listener(*this); + START_SEGMENT.add_listener(*this); + END_SEGMENT.add_listener(*this); + update_ui_state(); +} +void BDSPAutoStory::update_ui_state(){ + bool advanced = ENABLE_ADVANCED_MODE; + START_DESCRIPTION_SEGMENT.set_visibility( + advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED + ); + END_DESCRIPTION_SEGMENT.set_visibility( + advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED + ); + + START_DESCRIPTION_CHECKPOINT.set_visibility( + advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + END_DESCRIPTION_CHECKPOINT.set_visibility( + advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + START_DESCRIPTION_SEGMENT.set_visibility( + advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED + ); + END_DESCRIPTION_SEGMENT.set_visibility( + advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED + ); + + START_DESCRIPTION_CHECKPOINT.set_visibility( + advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + END_DESCRIPTION_CHECKPOINT.set_visibility( + advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + + bool show_help = ((HelpMode)HELP_MODE == HelpMode::OUTSIDE_HELP); + + OUTSIDE_HELP_POKEMON.set_visibility( + show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + + OUTSIDE_HELP_ATTACK_1.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + OUTSIDE_HELP_ATTACK_2.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + OUTSIDE_HELP_ATTACK_3.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + OUTSIDE_HELP_ATTACK_4.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); +} +void BDSPAutoStory::on_config_value_changed(void*){ + bool advanced = ENABLE_ADVANCED_MODE; + + // Segments + START_SEGMENT.set_visibility( + advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED + ); + END_SEGMENT.set_visibility( + advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED + ); + + // Checkpoints (tutorial for now) + START_CHECKPOINT_TUTORIAL.set_visibility( + advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + END_CHECKPOINT_TUTORIAL.set_visibility( + advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + if (ENABLE_ADVANCED_MODE){ + const auto& checkpoints = ALL_CHECKPOINTS(); + + size_t start = START_CHECKPOINT_TUTORIAL.index(); + size_t end = END_CHECKPOINT_TUTORIAL.index(); + + if (start < checkpoints.size()){ + START_DESCRIPTION_CHECKPOINT.set_text( + "Start: " + checkpoints[start]->start_text() + ); + } + if (end < checkpoints.size()){ + END_DESCRIPTION_CHECKPOINT.set_text( + "End: " + checkpoints[end]->end_text() + ); + } + + }else{ + const auto& segments = ALL_SEGMENTS(); + + size_t start = START_SEGMENT.index(); + size_t end = END_SEGMENT.index(); + + if (start < segments.size()){ + START_DESCRIPTION_SEGMENT.set_text( + "Start: " + segments[start]->start_text() + ); + } + if (end < segments.size()){ + END_DESCRIPTION_SEGMENT.set_text( + "End: " + segments[end]->end_text() + ); + } + } + + update_ui_state(); +} +// --------------------------------------------------------------------------- +// Program logic +// --------------------------------------------------------------------------- + +void BDSPAutoStory::program( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + AutoStoryStats& stats = env.current_stats(); + + AutoStoryOptions options{ + Language::English, + STARTERCHOICE, + NOTIFICATION_STATUS_UPDATE, + (HelpMode)HELP_MODE, + OutsideHelpConfig{ + (OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, + { + (TorterraMove)OUTSIDE_HELP_ATTACK_1, + (TorterraMove)OUTSIDE_HELP_ATTACK_2, + (TorterraMove)OUTSIDE_HELP_ATTACK_3, + (TorterraMove)OUTSIDE_HELP_ATTACK_4, + } + } + }; + + const auto& segments = ALL_SEGMENTS(); + + + + if (ENABLE_ADVANCED_MODE){ + size_t start = START_CHECKPOINT_TUTORIAL.index(); + size_t end = END_CHECKPOINT_TUTORIAL.index(); + + env.log("Starting AutoStory from checkpoint " + std::to_string(start) + + " to " + std::to_string(end)); + + const auto& checkpoints = ALL_CHECKPOINTS(); + + for (size_t i = start; i <= end && i < checkpoints.size(); i++){ + env.log("Running Checkpoint: " + std::to_string(i)); + checkpoints[i]->run_checkpoint(env, context, options, stats); + } + + }else{ + size_t start = START_SEGMENT.index(); + size_t end = END_SEGMENT.index(); + + for (size_t i = start; i <= end && i < segments.size(); i++){ + env.log("Running Segment: " + std::to_string(i)); + segments[i]->run_segment(env, context, options, stats); + } + } + + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); +} + + + +bool activate_repel( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Activating repel"); + DpadState dpad; + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_wait(context, 1000ms); + pbf_press_button(context, BUTTON_PLUS, 80ms, 300ms); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + pbf_press_button(context, BUTTON_B, 80ms, 300ms); + pbf_wait(context, 1000ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + context.wait_for_all_requests(); + + pbf_wait(context, 1000ms); + { + auto repel_tab_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/AutoStory/Bag_RepelTab.png" + ); + + const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; + const double repel_tab_rmsd = 50.0; + + // BDSP bag has 8 compartment tabs; check before each press so zero-press + // detection works when the bag already starts on the repel tab. + bool found = false; + for (int press = 0; press <= 8; press++){ + context.wait_for_all_requests(); + + ImageMatchWatcher watcher(repel_tab_ref, repel_tab_box, repel_tab_rmsd); + int ret = wait_until(stream, context, 400ms, {{watcher}}); + if (ret == 0){ + stream.log("[AutoStory] activate_repel: repel tab found after " + + std::to_string(press) + " press(es).", COLOR_GREEN); + found = true; + break; + } + + if (press < 8){ + stream.log("[AutoStory] activate_repel: tab not found, pressing right (" + + std::to_string(press + 1) + "/8)."); + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); + } + } + + if (!found){ + stream.log("[AutoStory] activate_repel: failed to find repel compartment tab.", COLOR_RED); + return false; + } + } + context.wait_for_all_requests(); + pbf_wait(context, 400ms); + pbf_mash_button(context, BUTTON_A, 500ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + return true; +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h new file mode 100644 index 0000000000..b4d6b0a9fc --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "Common/Cpp/Options/StaticTextOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "CommonTools/Options/StringSelectOption.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +class BDSPAutoStory_Descriptor : public SingleSwitchProgramDescriptor{ +public: + BDSPAutoStory_Descriptor(); + + virtual std::unique_ptr make_instance() const override; + virtual std::unique_ptr make_stats() const override; +}; + + +class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::Listener{ +public: + ~BDSPAutoStory(); + BDSPAutoStory(); + + virtual void program( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context + ) override; + + +private: + virtual void on_config_value_changed(void* object) override; + void update_ui_state(); +private: + BooleanCheckBoxOption ENABLE_ADVANCED_MODE; + + StringSelectOption START_CHECKPOINT_TUTORIAL; + StringSelectOption END_CHECKPOINT_TUTORIAL; + StaticTextOption START_DESCRIPTION_SEGMENT; + StaticTextOption END_DESCRIPTION_SEGMENT; + + StaticTextOption START_DESCRIPTION_CHECKPOINT; + StaticTextOption END_DESCRIPTION_CHECKPOINT; + + StringSelectOption START_CHECKPOINT_MAINSTORY; + StringSelectOption END_CHECKPOINT_MAINSTORY; + StringSelectOption START_SEGMENT; + StringSelectOption END_SEGMENT; + + EnumDropdownOption STARTERCHOICE; + BooleanCheckBoxOption STARTER_SHINY; + BooleanCheckBoxOption CATCH_LEGENDARY; + EnumDropdownOption HELP_MODE; + EnumDropdownOption OUTSIDE_HELP_POKEMON; + EnumDropdownOption OUTSIDE_HELP_ATTACK_1; + EnumDropdownOption OUTSIDE_HELP_ATTACK_2; + EnumDropdownOption OUTSIDE_HELP_ATTACK_3; + EnumDropdownOption OUTSIDE_HELP_ATTACK_4; + + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp new file mode 100644 index 0000000000..b2435ae662 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -0,0 +1,758 @@ +/* BDSP AutoStory Tools + * + * From: https://github.com/PokemonAutomation/ + * + */ +#include +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" +#include "Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "PokemonBDSP_AutoStoryTools.h" + +#include +#include + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +const char* torterra_move_slug(TorterraMove move){ + switch (move){ + case TorterraMove::EARTHQUAKE: return "earthquake"; + case TorterraMove::WOOD_HAMMER: return "wood-hammer"; + case TorterraMove::RAZOR_LEAF: return "razor-leaf"; + case TorterraMove::CRUNCH: return "crunch"; + case TorterraMove::GIGA_DRAIN: return "giga-drain"; + case TorterraMove::IRON_TAIL: return "iron-tail"; + case TorterraMove::ROCK_TOMB: return "rock-tomb"; + case TorterraMove::FACADE: return "facade"; + case TorterraMove::STONE_EDGE: return "stone-edge"; + case TorterraMove::ROCK_SLIDE: return "rock-slide"; + case TorterraMove::BULLDOZE: return "bulldoze"; + case TorterraMove::STRENGTH: return "strength"; + case TorterraMove::ROCK_SMASH: return "rock-smash"; + case TorterraMove::ROCK_CLIMB: return "rock-climb"; + } + return "unknown"; +} + +void fake_save_game( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + pbf_press_button(context, BUTTON_R, 80ms, 1000ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); +} + + +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +){ + static constexpr size_t MAX_RETRIES = 10; + + bool success = false; + + for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++){ + try{ + env.console.log( + "Checkpoint save attempt: " + std::to_string(attempt + 1) + ); + + // --------------------------- + // Force-close anything weird first. + // --------------------------- + pbf_mash_button(context, BUTTON_B, 1000ms); + + // --------------------------- + // Aggressively attempt to open menu. + // No dependence on MenuWatcher. + // --------------------------- + pbf_press_button(context, BUTTON_X, 160ms, 1500ms); + + // --------------------------- + // Trigger save + // --------------------------- + pbf_press_button(context, BUTTON_R, 160ms, 2500ms); + + // --------------------------- + // Confirm save + // --------------------------- + pbf_press_button(context, BUTTON_ZL, 160ms, 5000ms); + + // --------------------------- + // Return to overworld cleanly + // --------------------------- + pbf_mash_button(context, BUTTON_B, 1500ms); + + // --------------------------- + // Verify overworld + // --------------------------- + OverworldWatcher overworld; + + int ret = wait_until( + env.console, + context, + std::chrono::seconds(5), + {overworld} + ); + + if (ret < 0){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to detect overworld after save.", + env.console + ); + } + + env.console.log("Checkpoint save verified."); + + success = true; + break; + + }catch (OperationFailedException& e){ + env.console.log( + "Checkpoint save failed: " + e.message(), + COLOR_RED + ); + + context.wait_for(1000ms); + } + } + + // --------------------------- + // Blind fallback + // --------------------------- + if (!success){ + env.console.log( + "All save retries failed. Using blind fallback.", + COLOR_ORANGE + ); + + pbf_mash_button(context, BUTTON_B, 1000ms); + pbf_press_button(context, BUTTON_X, 160ms, 1500ms); + pbf_press_button(context, BUTTON_R, 160ms, 2500ms); + pbf_press_button(context, BUTTON_ZL, 160ms, 5000ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + + context.wait_for_all_requests(); + } + + stats.m_checkpoint++; + env.update_stats(); + + send_program_status_notification( + env, + notif_status_update, + success + ? "Saved at checkpoint (verified)." + : "Saved at checkpoint (blind fallback)." + ); +} + + +void checkpoint_reattempt_loop( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + std::function&& action +){ + static constexpr size_t MAX_ATTEMPTS = 20; + + for (size_t i = 0;; i++){ + try{ + if (i == 0){ + checkpoint_save(env, context, notif_status_update, stats); + } + + action(i); + return; // success + + }catch (OperationFailedException& e){ + env.console.log("checkpoint_reattempt_loop: attempt " + std::to_string(i) + " failed: " + e.message(), COLOR_RED); + stats.m_reset++; + env.update_stats(); + + if (i + 1 >= MAX_ATTEMPTS){ + throw; + } + + env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); + bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); + if (!ok){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to reset game from HOME during checkpoint retry.", + env.console + ); + } + } + } +} + + + +void repeat(std::initializer_list> actions, size_t times){ + for (size_t i = 0; i < times; i++){ + for (const auto& action : actions){ + action(); + } + } +} + + +void wait_for_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label, + Milliseconds timeout +){ + auto log_detected = [&]{ + if (!label.empty()){ + stream.log(label + " dialogue detected."); + } + }; + + ShortDialogWatcher watcher_cyan(COLOR_CYAN); + int ret = run_until( + stream, context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_cyan}} + ); + if (ret == 0){ log_detected(); return; } + + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until( + stream, context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_red}} + ); + if (ret == 0){ log_detected(); return; } + + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", + stream + ); +} + + +void mash_until_dialogue_ends( + VideoStream& stream, + ProControllerContext& context, + Button button, + Milliseconds timeout +){ + OverworldWatcher overworld; + int ret = run_until( + stream, context, + [button, timeout](ProControllerContext& context){ + pbf_mash_button(context, button, timeout); + }, + {{overworld}} + ); + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "mash_until_dialogue_ends: overworld not reached within timeout.", + stream + ); + } +} + + +void repeat_dpad( + ProControllerContext& context, + DpadState& state, + DpadPosition dir, + Milliseconds press, + Milliseconds hold, + size_t times, + bool include_last_dir +){ + if (include_last_dir && state.last_dir != DPAD_NONE && state.last_dir != dir){ + times += 1; + } + state.last_dir = dir; + for (size_t i = 0; i < times; i++){ + pbf_press_dpad(context, dir, press, hold); + } +} + + +bool heal_pokemon( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 7+ + context.wait_for_all_requests(); + + pbf_mash_button(context, BUTTON_A, 5000ms); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + wait_for_dialogue(stream, context, label + " - Pokemon healing"); + mash_until_dialogue_ends(stream, context, BUTTON_B); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + // Exiting the Pokemon Center + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("heal_pokemon(" + label + "): black screen not detected", COLOR_RED); + return false; + } + + return true; +} + +bool fly_to( + VideoStream& stream, + ProControllerContext& context, + const std::string& place +){ + static const std::unordered_set EAST_CITIES = { + "celestic_town", + "hearthome_city", + "pastoria_city", + "pokemon_league_lower", + "pokemon_league_upper", + "solaceon_town", + "sunyshore_city", + "veilstone_city" + }; + + static const std::unordered_set NORTH_CITY = { + "snowpoint_city", + }; + + // Each city branch below should update this to the city icon's position on the fly map. + // Detection: when the cursor brackets overlap the icon, its normally uniform + // color becomes non-uniform (stddev rises). Box stays as a rough fallback + // until each city is calibrated. + ImageFloatBox city_icon_box{0.700000, 0.550000, 0.100000, 0.120000}; + + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + pbf_press_button(context, BUTTON_PLUS, 80ms, 1000ms); + if (EAST_CITIES.count(place)){ + pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); + } else if (NORTH_CITY.count(place)) { + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + } else { + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + } + context.wait_for_all_requests(); + + ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; + + struct FlyAButtonDetector : VisualInferenceCallback { + ImageFloatBox m_box; + FlyAButtonDetector(const ImageFloatBox& box) + : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} + void make_overlays(VideoOverlaySet&) const override {} + bool process_frame(const ImageViewRGB32& frame, WallClock) override { + ImageStats s = image_stats(extract_box_reference(frame, m_box)); + // Blue stripe is uniform when no button shown; A button disrupts it + return s.stddev.sum() > 30.0; + } + }; + FlyAButtonDetector watcher_1(button_a_box); + + if (place == "canalave_city") { + + } else if (place == "celestic_town") { + + } else if (place == "eterna_city") { + + } else if (place == "floaroma_town") { + + } else if (place == "hearthome_city") { + + city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); + + } else if (place == "jubilife_city") { + + } else if (place == "oreburgh_city") { + + } else if (place == "pastoria_city") { + + } else if (place == "pokemon_league_lower") { + + } else if (place == "pokemon_league_upper") { + + } else if (place == "route_221") { + + } else if (place == "sandgem_town") { + + } else if (place == "solaceon_town") { + + city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); + + } else if (place == "sunyshore_city") { + + } else if (place == "twinleaf_town") { + + } else if (place == "veilstone_city") { + + } + + context.wait_for_all_requests(); + + struct CityIconDetector : VisualInferenceCallback { + ImageFloatBox m_box; + CityIconDetector(const ImageFloatBox& box) + : VisualInferenceCallback("CityIconDetector"), m_box(box) {} + void make_overlays(VideoOverlaySet&) const override {} + bool process_frame(const ImageViewRGB32& frame, WallClock) override { + ImageStats s = image_stats(extract_box_reference(frame, m_box)); + // City icon is normally a uniform color; cursor brackets disrupt it + return s.stddev.sum() > 30.0; + } + }; + CityIconDetector watcher_2(city_icon_box); + + int ret_1 = wait_until(stream, context, 3000ms, {{watcher_1}}); + int ret_2 = wait_until(stream, context, 4000ms, {{watcher_2}}); + if (ret_1 < 0) { + stream.log("A button not found, retrying...", COLOR_RED); + }; + if (ret_2 < 0) { + stream.log(place + " not found, retrying...", COLOR_RED); + }; + if (ret_1 == 0 and ret_2 == 0){ + stream.log("Found " + place + ", flying towards it.", COLOR_GREEN); + BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{flying_black_screen}} + ); + if (ret < 0){ + stream.log("Flying to " + place + ": black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("Flying to " + place + ": black screen detected!", COLOR_GREEN); + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- +// Arrow - Based Potion Usage Helper + +// This version assumes : +// +// you are already inside the Bag +// Potion has already been selected +// the game is currently waiting for you to choose a Pokemon +// +// Flow : +// 1. Move LEFT once to Pokemon list +// 2. Press A on first Pokemon +// 3. Use SelectionArrow to detect arrow position +// 4. Move DOWN until arrow reaches slot 3 +// 5. Mash A for 3 seconds +// 6. Mash B for 10 seconds +// +// --------------------------------------------------------------------------- + +void use_potion_first_pokemon( + VideoStream & stream, + ProControllerContext & context +) { + stream.log( + "[AutoStory] Starting arrow-based potion helper...", + COLOR_BLUE + ); + pbf_press_button( + context, + BUTTON_X, + 160ms, + 1500ms + ); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + // ------------------------------------------------------------ + // Move from item pane to pokemon pane. + // ------------------------------------------------------------ + + pbf_press_dpad( + context, + DPAD_LEFT, + 100ms, + 500ms + ); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + // ------------------------------------------------------------ + // Select pokemon menu pokemon. + // ------------------------------------------------------------ + + pbf_press_button( + context, + BUTTON_A, + 160ms, + 1500ms + ); + + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + // ------------------------------------------------------------ + // Select first pokemon. + // ------------------------------------------------------------ + + pbf_press_button( + context, + BUTTON_A, + 160ms, + 1500ms + ); + + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + + // ------------------------------------------------------------ + // Move from "Check summary" to "Restore". + // ------------------------------------------------------------ + + pbf_press_dpad( + context, + DPAD_DOWN, + 80ms, + 250ms + ); + + pbf_press_dpad( + context, + DPAD_DOWN, + 80ms, + 500ms + ); + + // ------------------------------------------------------------ + // Mash A. + // ------------------------------------------------------------ + + stream.log( + "[AutoStory] Mashing A...", + COLOR_BLUE + ); + + pbf_mash_button( + context, + BUTTON_A, + 3000ms + ); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + // ------------------------------------------------------------ + // Mash B to fully exit. + // ------------------------------------------------------------ + + stream.log( + "[AutoStory] Exiting menus...", + COLOR_BLUE + ); + + pbf_mash_button( + context, + BUTTON_B, + 3000ms + ); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + // ------------------------------------------------------------ + // open menu and reset cursor to bag. + // ------------------------------------------------------------ + stream.log( + "[AutoStory] Exiting menus...", + COLOR_BLUE + ); + + pbf_press_button( + context, + BUTTON_X, + 160ms, + 1500ms + ); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + pbf_press_dpad( + context, + DPAD_RIGHT, + 100ms, + 500ms + ); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + pbf_press_button( + context, + BUTTON_X, + 160ms, + 1500ms + ); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + stream.log( + "[AutoStory] Potion helper complete.", + COLOR_GREEN + ); +} + + +namespace{ + +struct IconGrid { int row, col; }; + +IconGrid icon_to_grid(MenuCursorPosition pos, int num_icons){ + switch (pos){ + case MenuCursorPosition::POKEDEX: return {0, 0}; + case MenuCursorPosition::POKEMON: return {0, 1}; + case MenuCursorPosition::BAG: return {0, 2}; + case MenuCursorPosition::CARD: return {0, 3}; + case MenuCursorPosition::OPTIONS: return {1, num_icons <= 5 ? 0 : 2}; + case MenuCursorPosition::MAP: return {1, 0}; + case MenuCursorPosition::CAPSULES: return {1, 1}; + case MenuCursorPosition::MYSTERYGIFT: return {1, 3}; + default: return {-1, -1}; + } +} + +} // anonymous namespace + + +void open_menu( + VideoStream& stream, + ProControllerContext& context, + MenuCursorPosition target, + int num_icons +){ + using namespace std::chrono_literals; + + overworld_to_menu(stream, context); + context.wait_for(1000ms); + + MenuCursorDetector detector; + { + static const char* ICON_NAMES[MENU_CURSOR_NUM_ICONS] = { + "POKEDEX", "POKEMON", "BAG", "CARD", + "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", + }; + std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/AutoStory"; + for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ + std::string path = ref_dir + "/" + ICON_NAMES[i] + ".png"; + ImageRGB32 img(path); + if (img){ + detector.set_icon_reference( + static_cast(i), + std::move(img) + ); + } + } + } + MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; + for (int i = 0; i < 10 && cur == MenuCursorPosition::UNKNOWN; i++){ + context.wait_for(50ms); + VideoSnapshot snap = stream.video().snapshot(); + if (snap) cur = detector.detect(snap); + } + if (cur == MenuCursorPosition::UNKNOWN){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Menu cursor not detected after opening menu.", + stream + ); + } + + IconGrid cur_g = icon_to_grid(cur, num_icons); + IconGrid tgt_g = icon_to_grid(target, num_icons); + if (tgt_g.row == -1){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Unknown target menu icon.", + stream + ); + } + + if (cur_g.row != tgt_g.row){ + DpadPosition vert = (cur_g.row < tgt_g.row) ? DPAD_DOWN : DPAD_UP; + pbf_press_dpad(context, vert, 80ms, 300ms); + context.wait_for_all_requests(); + } + + int col_diff = tgt_g.col - cur_g.col; + if (col_diff != 0){ + DpadPosition horiz = (col_diff > 0) ? DPAD_RIGHT : DPAD_LEFT; + for (int i = 0; i < std::abs(col_diff); i++){ + pbf_press_dpad(context, horiz, 80ms, 300ms); + context.wait_for_all_requests(); + } + } + + pbf_press_button(context, BUTTON_A, 80ms, 200ms); +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h new file mode 100644 index 0000000000..cf4c6159b3 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -0,0 +1,249 @@ +/* BDSP AutoStory Tools + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStoryTools_H +#define PokemonAutomation_PokemonBDSP_AutoStoryTools_H + +#include +#include +#include +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// Stats +// --------------------------------------------------------------------------- + +struct AutoStoryStats : public StatsTracker{ + AutoStoryStats() + : m_checkpoint(m_stats["Checkpoint"]) + , m_segment(m_stats["Segment"]) + , m_reset(m_stats["Reset"]) + { + m_display_order.emplace_back("Checkpoint"); + m_display_order.emplace_back("Segment"); + m_display_order.emplace_back("Reset"); + } + std::atomic& m_checkpoint; + std::atomic& m_segment; + std::atomic& m_reset; +}; + + +// --------------------------------------------------------------------------- +// Options passed to every segment/checkpoint +// --------------------------------------------------------------------------- + +enum class StarterChoice{ + TURTWIG, + CHIMCHAR, + PIPLUP, +}; + +enum class HelpMode{ + NO_OUTSIDE_HELP, + OUTSIDE_HELP, +}; + +enum class OutsideHelpPokemon{ + TORTERRA, +}; + +enum class TorterraMove{ + EARTHQUAKE, + WOOD_HAMMER, + RAZOR_LEAF, + CRUNCH, + GIGA_DRAIN, + IRON_TAIL, + ROCK_TOMB, + FACADE, + STONE_EDGE, + ROCK_SLIDE, + BULLDOZE, + STRENGTH, + ROCK_SMASH, + ROCK_CLIMB, +}; + +// Returns the JSON slug for a TorterraMove (e.g. "earthquake"). +const char* torterra_move_slug(TorterraMove move); + +struct OutsideHelpConfig{ + OutsideHelpPokemon pokemon; + TorterraMove attacks[4]; +}; + +struct AutoStoryOptions{ + Language language; + StarterChoice starter_choice; + EventNotificationOption& notif_status_update; + HelpMode help_mode; + OutsideHelpConfig outside_help; +}; + + +// --------------------------------------------------------------------------- +// Base classes +// --------------------------------------------------------------------------- + +class AutoStory_Segment{ +public: + virtual ~AutoStory_Segment() = default; + virtual std::string name() const = 0; + virtual std::string start_text() const = 0; + virtual std::string end_text() const = 0; + virtual void run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const = 0; +}; + +class AutoStory_Checkpoint{ +public: + virtual ~AutoStory_Checkpoint() = default; + virtual std::string name() const = 0; + virtual std::string start_text() const = 0; + virtual std::string end_text() const = 0; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const = 0; +}; + + +// --------------------------------------------------------------------------- +// Shared utility functions (implementations in .cpp) +// --------------------------------------------------------------------------- + +// Fake save game to be replaced with save_game when program runs smoothly +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +); + +// Save the game from the overworld, increment the checkpoint counter, +// update stats display, and send a status notification. +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +); + +// Save before the first attempt, then run `action(attempt_number)`. +// On any thrown exception, increment the reset counter and retry. +// Throws a fatal error after too many consecutive failures. +void checkpoint_reattempt_loop( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + std::function&& action +); + +// Execute every action in `actions` in order, repeated `times` times. +void repeat(std::initializer_list> actions, size_t times); + +// Tracks the last D-pad direction pressed. Pass one per navigation function +// so each function gets a clean slate on each attempt. +struct DpadState { + DpadPosition last_dir = DPAD_NONE; +}; + +// Press `dir` on the D-pad `times` times (plus one extra if the direction +// changed from the previous call, since the first press turns the character). +void repeat_dpad( + ProControllerContext& context, + DpadState& state, + DpadPosition dir, + Milliseconds press, + Milliseconds hold, + size_t times, + bool include_last_dir = true +); + +// Wait (without pressing any buttons) until a short dialogue box appears. +// Logs `label + " dialogue detected."` when found (omit label to skip logging). +// Throws OperationFailedException if no dialogue appears within `timeout`. +void wait_for_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label = "", + Milliseconds timeout = std::chrono::seconds(60) +); + +// Mash `button` (default B) until the game returns to the free-roaming overworld +// (no dialogue, menu, or battle UI visible). +// Throws OperationFailedException if the overworld is not reached within `timeout`. +void mash_until_dialogue_ends( + VideoStream& stream, + ProControllerContext& context, + Button button = BUTTON_B, + Milliseconds timeout = std::chrono::seconds(60) +); + +// Open the bag, navigate to the repel compartment tab, and use a repel. +// Returns false if the repel compartment tab could not be found. +bool activate_repel( + VideoStream& stream, + ProControllerContext& context +); + +// Walk to the nurse, heal the party, and exit the Pokemon Center. +// `label` names the center for log messages (e.g. "Veilstone"). +// Returns false if the exit black screen was not detected. +bool heal_pokemon( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +); + +// Open the map, move the cursor to either bottom left, bottom right or +// top left, then move the cursor a number of steps, then checks for +// correctness, and mashes A until a black screen is detected +bool fly_to( + VideoStream& stream, + ProControllerContext& context, + const std::string& place +); + +// Opens the X-menu from the overworld, navigates the cursor to `target`, and presses A. +// num_icons: 5 (early-game layout) or 8 (late-game layout). +void open_menu( + VideoStream& stream, + ProControllerContext& context, + MenuCursorPosition target, + int num_icons +); + +void use_potion_first_pokemon( + VideoStream& stream, + ProControllerContext& context +); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp new file mode 100644 index 0000000000..bad790aaac --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -0,0 +1,910 @@ +/* BDSP AutoStory - Segment 00 + * From player's bedroom to pokedex acquisition. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "PokemonBDSP_AutoStory_Segment_00.h" +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" + +#include "CommonFramework/Exceptions/OperationFailedException.h" + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + +#include "Pokemon/Pokemon_Strings.h" +#include "Pokemon/Pokemon_Notification.h" + +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/ShinyHunting/PokemonBDSP_StarterReset.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" + +#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" + +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + // --------------------------------------------------------------------------- + // Starter selection + // --------------------------------------------------------------------------- + + static void select_turtwig(VideoStream& stream, ProControllerContext& context) { + stream.log("[DEBUG] Turtwig Selection: Starting starter selection"); + + pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); + context.wait_for_all_requests(); + + ShortDialogWatcher watcher(COLOR_CYAN); + int ret = run_until( + stream, context, + [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, + { watcher } + ); + if (ret != 0) { + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until( + stream, context, + [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, + { watcher_red } + ); + } + stream.log(ret == 0 ? "[DEBUG] Turtwig Selection: Confirmation dialogue detected" + : "[WARNING] Turtwig Selection: Dialogue detection failed, proceeding anyway"); + + pbf_wait(context, 400ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); + context.wait_for_all_requests(); + stream.log("[DEBUG] Turtwig Selection: Selection complete"); + } + + static void select_chimchar(VideoStream& stream, ProControllerContext& context) { + stream.log("[DEBUG] Chimchar Selection: Starting starter selection"); + + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); + pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); + context.wait_for_all_requests(); + + ShortDialogWatcher watcher(COLOR_CYAN); + int ret = run_until( + stream, context, + [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, + { watcher } + ); + if (ret != 0) { + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until( + stream, context, + [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, + { watcher_red } + ); + } + stream.log(ret == 0 ? "[DEBUG] Chimchar Selection: Confirmation dialogue detected" + : "[WARNING] Chimchar Selection: Dialogue detection failed, proceeding anyway"); + + pbf_wait(context, 400ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); + context.wait_for_all_requests(); + stream.log("[DEBUG] Chimchar Selection: Selection complete"); + } + + static void select_piplup(VideoStream& stream, ProControllerContext& context) { + stream.log("[DEBUG] Piplup Selection: Starting starter selection"); + + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); + pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); + context.wait_for_all_requests(); + + ShortDialogWatcher watcher(COLOR_CYAN); + int ret = run_until( + stream, context, + [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, + { watcher } + ); + if (ret != 0) { + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until( + stream, context, + [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, + { watcher_red } + ); + } + stream.log(ret == 0 ? "[DEBUG] Piplup Selection: Confirmation dialogue detected" + : "[WARNING] Piplup Selection: Dialogue detection failed, proceeding anyway"); + + pbf_wait(context, 400ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); + context.wait_for_all_requests(); + stream.log("[DEBUG] Piplup Selection: Selection complete"); + } + + // Navigation functions for each part of the tutorial. These functions are meant to be used + // in order and rely on the state of the game being what it is at the end of the previous function. + // They may also be modified or removed without warning. + + // --------------------------------------------------------------------------- + // Segment 00-000 begins + // Navigates from player's room to Route 201 through Mom/Barry dialogues. + // --------------------------------------------------------------------------- + + static void tutorial_part_1_navigation(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + DateTime t{ 2026, 1, 1, 0, 0, 0 }; + set_time_fast(env, context, t); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 527ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 456ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 915ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 642ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 557ms, 0ms); + pbf_wait(context, 978ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1112ms, 0ms); + pbf_wait(context, 4967ms); + + wait_for_dialogue(stream, context, "Phase 1 (Mom)"); + + pbf_mash_button(context, BUTTON_A, 5000ms); + pbf_wait(context, 300ms); + + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1662ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -1.000000 }, 49ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1215ms, 0ms); + pbf_wait(context, 1419ms); + + wait_for_dialogue(stream, context, "Phase 2 (Mom Warning)"); + + pbf_mash_button(context, BUTTON_A, 2500ms); + pbf_wait(context, 300ms); + + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 680ms, 0ms); + + pbf_wait(context, 4000ms); + + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1748ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 18ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2763ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 58ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1364ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); + pbf_mash_button(context, BUTTON_A, 6000ms); + pbf_wait(context, 800ms); + wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); + pbf_mash_button(context, BUTTON_A, 6000ms); + pbf_wait(context, 800ms); + + pbf_wait(context, 1306ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2722ms, 0ms); + pbf_wait(context, 1445ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 161ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 370ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 667ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 130ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 705ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 196ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 684ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 205ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 590ms, 0ms); + pbf_wait(context, 1422ms); + + wait_for_dialogue(stream, context, "Phase 8 (Barry 2nd dialog)"); + pbf_mash_button(context, BUTTON_A, 10000ms); + + pbf_wait(context, 856ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 920ms, 0ms); + pbf_wait(context, 631ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 972ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 72ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1127ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 82ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1335ms, 0ms); + pbf_wait(context, 1124ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2513ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 51ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5861ms, 0ms); + pbf_wait(context, 134ms); + + wait_for_dialogue(stream, context, "Phase 9 (Barry Route 201)"); + + pbf_mash_button(context, BUTTON_B, 6000ms); + pbf_wait(context, 800ms); + + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 700ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 279ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 10ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 145ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 7703ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 140ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1700ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 39ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 544ms, 1000ms); + + pbf_wait(context, 1000ms); + + save_game(stream, context); + context.wait_for_all_requests(); + } + // --------------------------------------------------------------------------- + // Segment 00-001 begins + // Navigates into lake Verity to recieve starter + // --------------------------------------------------------------------------- + static void starter_nav(VideoStream& stream, ProControllerContext& context) { + // re-sync control + pbf_press_button(context, BUTTON_B, 20ms, 200ms); + pbf_wait(context, 500ms); + + // continue movement + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 300ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 300ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 10 (Barry before the starter selection)"); + + pbf_mash_button(context, BUTTON_A, 1000ms); + + pbf_wait(context, 500ms); + std::shared_ptr briefcase = + std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/StarterBriefcase.png" + ); + + // Mash B until we see the briefcase. + ImageMatchWatcher detector(briefcase, { 0.5, 0.1, 0.5, 0.7 }, 100, true); + + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_B, 120000ms); + }, + { {detector} } + ); + + if (ret == 0) { + stream.log("Detected briefcase!"); + } + else { + stream.log("Timed out waiting for briefcase."); + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to detect briefcase in Segment 00.", + stream + ); + } + + // Wait for briefcase to fully open. + stream.log("Mashing B for briefcase to fully open."); + pbf_mash_button(context, BUTTON_B, 2000ms); + } + // --------------------------------------------------------------------------- + // Segment 00-002 begins + // Navigates from Exit of lake Verity to receipt of the Pokedex + // --------------------------------------------------------------------------- + + static void tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + + context.wait_for_all_requests(); + pbf_wait(context, 563ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1700ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 510ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 50ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 310ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5500ms, 0ms); + pbf_wait(context, 3963ms); + wait_for_dialogue(stream, context, "Phase 10 (Professor dialog to mom)"); + pbf_mash_button(context, BUTTON_B, 40000ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 820ms, 0ms); + pbf_wait(context, 1483ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 900ms, 0ms); + pbf_wait(context, 555ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 50ms, 0ms); + pbf_wait(context, 23ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5020ms, 0ms); + pbf_wait(context, 169ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 745ms, 0ms); + DateTime t{ 2026, 1, 1, 0, 0, 50 }; + set_time_fast(env, context, t); + walk_right_until_on_path(stream, context); + walk_up_through_grass_2(stream, context); + walk_right_through_grass_2(stream, context); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 730ms, 0ms); + pbf_wait(context, 941ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1950ms, 0ms); + wait_for_dialogue(stream, context, "Phase 11 (dawn conversation)"); + pbf_mash_button(context, BUTTON_B, 50000ms); + wait_for_dialogue(stream, context, "Phase 12 (Nickname reject)"); + pbf_mash_button(context, BUTTON_B, 40000ms); + wait_for_dialogue(stream, context, "Phase 13 (Pokedex accept)"); + pbf_mash_button(context, BUTTON_A, 40000ms); + + + // pokedex received + } + + + // --------------------------------------------------------------------------- + // End of Segment 00 - 002 + // Standing in front of the professor after receiving the pokedex. + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Begin Segment 00 - 003 + // Will heal at Sandgem pokeCenter to reliably catch a second pokemon for Home + // transfer. + // Collects poketech and stands in front of the PC to end the tutorial section + // and send the starter to pokemon HOME. + // --------------------------------------------------------------------------- + + static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 2010ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 14 (dawn tour)"); + + pbf_mash_button(context, BUTTON_B, 25000ms); + + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 5125ms, 0ms); + pbf_wait(context, 336ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1700ms, 0ms); + pbf_wait(context, 213ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2325ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 950ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2360ms, 0ms); + pbf_wait(context, 253ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4610ms, 0ms); + pbf_wait(context, 954ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 220ms, 0ms); + pbf_wait(context, 587ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1375ms, 0ms); + pbf_wait(context, 464ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 775ms, 0ms); + pbf_wait(context, 1655ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 640ms, 0ms); + pbf_wait(context, 3128ms); + pbf_press_dpad(context, DPAD_UP, 180ms, 0ms); + pbf_wait(context, 472ms); + pbf_press_dpad(context, DPAD_UP, 130ms, 0ms); + pbf_wait(context, 1621ms); + pbf_press_dpad(context, DPAD_RIGHT, 130ms, 0ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 15 (Mom conversation to accept parcel)"); + + pbf_mash_button(context, BUTTON_B, 40000ms); + + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + pbf_press_dpad(context, DPAD_LEFT, 150ms, 0ms); + pbf_wait(context, 1143ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 875ms, 0ms); + pbf_wait(context, 1169ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 946ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 9092ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 740ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 2998ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 240ms, 0ms); + pbf_wait(context, 893ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5300ms, 0ms); + pbf_wait(context, 282ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 260ms, 0ms); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + DateTime t{ 2026, 1, 1, 0, 0, 50 }; + set_time_fast(env, context, t); + walk_right_until_on_path(stream, context); + walk_up_through_grass_2(stream, context); + walk_right_through_grass_2(stream, context); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 560ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1855ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 430ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 850ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1120ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 16 (catch tutorial)"); + + pbf_mash_button(context, BUTTON_B, 70000ms); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + catch_1_pokemon(stream, context); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + //-------------------------------------------------------- + // To get potions + // ------------------------------------------------------- + walk_right_through_grass_2(stream, context); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1421ms, 0ms); + pbf_wait(context, 52ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1013ms, 0ms); + pbf_wait(context, 142ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1112ms, 0ms); + pbf_wait(context, 45ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 3206ms, 0ms); + pbf_wait(context, 912ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 856ms, 0ms); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + walk_left_through_grass_3(stream, context); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 17 (accept potions)"); + + pbf_mash_button(context, BUTTON_B, 10000ms); + + walk_right_through_grass_2(stream, context); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + // --------------------------------------------------------- + // Navigate back to catch tutorial grass + // --------------------------------------------------------- + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 560ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1855ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 430ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 850ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1120ms, 0ms); + + // Continue on to jubilife + walk_left_through_grass_3(stream, context); + walk_up_through_grass_3(stream, context); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1000ms, 0ms); + wait_for_dialogue(stream, context, "Phase 18 (tristan encounter dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + fight_tristan(stream, context); + + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1366ms, 0ms); + pbf_wait(context, 467ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1018ms, 0ms); + pbf_wait(context, 311ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 353ms, 0ms); + pbf_wait(context, 660ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 580ms, 0ms); + pbf_wait(context, 1672ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1344ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 19 (natalie encounter dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + + fight_Natalie(stream, context); + context.wait_for_all_requests(); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1301ms, 0ms); + walk_through_grass(stream, context, 0, 1, 15, "up through grass"); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 392ms, 0ms); + pbf_wait(context, 1261ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 815ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 20 (logan encounter dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + fight_Natalie(stream, context); + context.wait_for_all_requests(); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + walk_through_grass(stream, context, -1, 0, 8, "left through grass"); + + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2469ms, 0ms); + wait_for_dialogue(stream, context, "Phase 21 (dawn Jubilife dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + } + // --------------------------------------------------------------------------- + // End of Segment 00 - 003 + // Standing in Jubilife after speaking to dawn. + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Begin Segment 00 - 004 + // Standing at the entrance to jubilife after speaking to dawn, will navigate to the poketech challenge. + // Collects poketech and stands in front of the PC to end the tutorial section + // and send the starter to pokemon HOME. + // --------------------------------------------------------------------------- + + static void tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2580ms, 0ms); + pbf_wait(context, 504ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 830ms, 0ms); + pbf_wait(context, 617ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 800ms, 0ms); + pbf_wait(context, 509ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); + pbf_wait(context, 598ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 200ms, 0ms); + pbf_wait(context, 581ms); + pbf_press_button(context, BUTTON_A, 150ms, 0ms); + pbf_wait(context, 795ms); + + wait_for_dialogue(stream, context, "Phase 22 (barry town map dialog)"); + + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1521ms, 0ms); + pbf_wait(context, 602ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1100ms, 0ms); + pbf_wait(context, 864ms); + pbf_press_dpad(context, DPAD_RIGHT, 138ms, 0ms); + pbf_wait(context, 2398ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1018ms, 0ms); + pbf_wait(context, 3811ms); + + wait_for_dialogue(stream, context, "Phase 23 (poketech challenge dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 192ms, 0ms); + pbf_wait(context, 820ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 980ms, 0ms); + pbf_wait(context, 543ms); + pbf_press_dpad(context, DPAD_UP, 203ms, 0ms); + pbf_wait(context, 593ms); + pbf_press_dpad(context, DPAD_UP, 115ms, 0ms); + pbf_wait(context, 566ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1567ms, 0ms); + pbf_wait(context, 538ms); + pbf_press_dpad(context, DPAD_LEFT, 157ms, 0ms); + pbf_wait(context, 735ms); + pbf_press_dpad(context, DPAD_LEFT, 164ms, 0ms); + pbf_wait(context, 564ms); + pbf_press_dpad(context, DPAD_LEFT, 250ms, 0ms); + pbf_wait(context, 513ms); + pbf_press_dpad(context, DPAD_LEFT, 250ms, 0ms); + pbf_wait(context, 513ms); + pbf_press_dpad(context, DPAD_DOWN, 163ms, 0ms); + pbf_wait(context, 609ms); + pbf_press_dpad(context, DPAD_DOWN, 193ms, 0ms); + pbf_wait(context, 584ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 151ms, 0ms); + context.wait_for_all_requests(); + + //phase 24 Clown 1 dialog + clown_dialog_helper(stream, context, DPAD_RIGHT); + context.wait_for_all_requests(); + + pbf_press_dpad(context, DPAD_UP, 187ms, 0ms); + pbf_wait(context, 106ms); + pbf_press_dpad(context, DPAD_UP, 32ms, 0ms); + pbf_wait(context, 671ms); + pbf_press_dpad(context, DPAD_UP, 262ms, 0ms); + pbf_wait(context, 684ms); + pbf_press_dpad(context, DPAD_UP, 372ms, 0ms); + pbf_wait(context, 392ms); + pbf_press_dpad(context, DPAD_UP, 419ms, 0ms); + pbf_wait(context, 658ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2013ms, 0ms); + pbf_wait(context, 369ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1448ms, 0ms); + pbf_wait(context, 2435ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1077ms, 0ms); + pbf_wait(context, 1084ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1280ms, 0ms); + pbf_wait(context, 1895ms); + pbf_press_dpad(context, DPAD_LEFT, 88ms, 0ms); + + context.wait_for_all_requests(); + + //phase 25 Clown 2 dialog + + clown_dialog_helper(stream, context, DPAD_UP); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 511ms, 0ms); + pbf_wait(context, 1318ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 140ms, 0ms); + pbf_wait(context, 1502ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2093ms, 0ms); + pbf_wait(context, 2327ms); + pbf_press_dpad(context, DPAD_LEFT, 151ms, 0ms); + pbf_wait(context, 549ms); + pbf_press_dpad(context, DPAD_LEFT, 118ms, 0ms); + pbf_wait(context, 999ms); + pbf_press_dpad(context, DPAD_UP, 206ms, 0ms); + pbf_wait(context, 474ms); + pbf_press_dpad(context, DPAD_UP, 249ms, 0ms); + pbf_wait(context, 333ms); + pbf_press_dpad(context, DPAD_UP, 212ms, 0ms); + pbf_wait(context, 427ms); + pbf_press_dpad(context, DPAD_UP, 210ms, 0ms); + context.wait_for_all_requests(); + //phase 26 Clown 3 dialog + + clown_dialog_helper(stream, context, DPAD_LEFT); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1098ms, 0ms); + pbf_wait(context, 2779ms); + pbf_press_dpad(context, DPAD_LEFT, 177ms, 0ms); + pbf_wait(context, 488ms); + pbf_press_dpad(context, DPAD_LEFT, 200ms, 0ms); + pbf_wait(context, 649ms); + pbf_press_dpad(context, DPAD_LEFT, 160ms, 0ms); + pbf_wait(context, 4285ms); + pbf_press_dpad(context, DPAD_DOWN, 307ms, 0ms); + pbf_wait(context, 639ms); + pbf_press_dpad(context, DPAD_DOWN, 463ms, 0ms); + pbf_wait(context, 2521ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3600ms, 0ms); + pbf_wait(context, 610ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1907ms, 0ms); + pbf_wait(context, 529ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 275ms, 0ms); + pbf_wait(context, 3600ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 899ms, 0ms); + pbf_wait(context, 1308ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 192ms, 0ms); + pbf_wait(context, 3238ms); + pbf_press_dpad(context, DPAD_UP, 108ms, 0ms); + pbf_wait(context, 433ms); + pbf_press_dpad(context, DPAD_UP, 260ms, 0ms); + pbf_wait(context, 365ms); + pbf_press_dpad(context, DPAD_UP, 187ms, 0ms); + pbf_wait(context, 542ms); + pbf_press_dpad(context, DPAD_UP, 243ms, 0ms); + pbf_wait(context, 370ms); + pbf_press_dpad(context, DPAD_UP, 217ms, 0ms); + pbf_wait(context, 495ms); + pbf_press_dpad(context, DPAD_UP, 219ms, 0ms); + pbf_wait(context, 572ms); + pbf_press_dpad(context, DPAD_UP, 231ms, 0ms); + pbf_wait(context, 1757ms); + pbf_press_dpad(context, DPAD_RIGHT, 115ms, 0ms); + pbf_press_button(context, BUTTON_A, 150ms, 0ms); + pbf_wait(context, 795ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Phase 27 (poketech challenge completed dialog)"); + + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); + + // poketech acquired + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1194ms, 0ms); + pbf_wait(context, 1984ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 832ms, 0ms); + pbf_wait(context, 1522ms); + pbf_press_dpad(context, DPAD_RIGHT, 144ms, 0ms); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1344ms, 0ms); + pbf_wait(context, 3428ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1099ms, 0ms); + pbf_wait(context, 1222ms); + pbf_press_dpad(context, DPAD_RIGHT, 228ms, 0ms); + pbf_wait(context, 625ms); + pbf_press_dpad(context, DPAD_RIGHT, 238ms, 0ms); + pbf_wait(context, 537ms); + pbf_press_dpad(context, DPAD_RIGHT, 210ms, 0ms); + pbf_wait(context, 566ms); + pbf_press_dpad(context, DPAD_RIGHT, 179ms, 0ms); + pbf_wait(context, 692ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 642ms, 0ms); + } + // --------------------------------------------------------------------------- + // Segment 00-04 ends + // Standing in front of the Jubilife City Pokemon Center PC. + // Player will need to deposit pokemon in the pc and transfer to Pokemon Home + // and level to 100 before starting the next segment. + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Segment / Checkpoint classes + // --------------------------------------------------------------------------- + + std::string AutoStory_Segment_00::name() const { return "00: Intro to Poketech"; } + std::string AutoStory_Segment_00::start_text() const { return "Start: Game launched, Standing in the player's room after tv stops playing"; } + std::string AutoStory_Segment_00::end_text() const { return "End: Starter Pokemon chosen, received, and Pokedex acquired, standing in front of the PC."; } + + void AutoStory_Segment_00::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_000().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_001().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_002().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_003().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_004().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); + } + + static void checkpoint_000( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // Segment 00-00: Intro and starter selection + tutorial_part_1_navigation(env, env.console, context); + } + ); + } + static void checkpoint_001( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // Segment 00-01: Starter acquisition + starter_nav(env.console, context); + // Segment 00-00 starter selection + switch (options.starter_choice) { + case StarterChoice::TURTWIG: select_turtwig(env.console, context); break; + case StarterChoice::CHIMCHAR: select_chimchar(env.console, context); break; + case StarterChoice::PIPLUP: select_piplup(env.console, context); break; + } + //Fight Starly + fight_starly(env.console, context); + } + ); + } + static void checkpoint_002( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // Segment 00-002: Pokedex acquisition + tutorial_part_2_navigation(env, env.console, context); + } + ); + } + static void checkpoint_003( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // Segment 00-003: Poketech acquisition + tutorial_part_3_navigation(env, env.console, context); + } + ); + } + static void checkpoint_004( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // Segment 00-004: Poketech acquisition + tutorial_part_4_navigation(env, env.console, context); + } + ); + } + + // ======================== + // Checkpoint 000 Wrapper + // ======================== + std::string AutoStory_Checkpoint_000::name() const { return "000 - " + AutoStory_Segment_00().name(); } + std::string AutoStory_Checkpoint_000::start_text() const { return "Game launched, Standing in the player's room after tv stops playing"; } + std::string AutoStory_Checkpoint_000::end_text() const { return "Standing outside Lake Verity before picking starter."; } + + void AutoStory_Checkpoint_000::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_000(env, context, options, stats); + } + + // ======================== + // Checkpoint 001 Wrapper + // ======================== + + std::string AutoStory_Checkpoint_001::name() const { return "001 - " + AutoStory_Segment_00().name(); } + std::string AutoStory_Checkpoint_001::start_text() const { return AutoStory_Checkpoint_000().end_text(); } + std::string AutoStory_Checkpoint_001::end_text() const { return "Standing outside Lake Verity after picking starter."; } + + void AutoStory_Checkpoint_001::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_001(env, context, options, stats); + } + + + // ======================== + // Checkpoint 002 Wrapper + // ======================== + + std::string AutoStory_Checkpoint_002::name() const { return "002 - " + AutoStory_Segment_00().name(); } + std::string AutoStory_Checkpoint_002::start_text() const { return AutoStory_Checkpoint_001().end_text(); } + std::string AutoStory_Checkpoint_002::end_text() const { return "Standing in front of The professor after recieving the Pokedex"; } + + void AutoStory_Checkpoint_002::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_002(env, context, options, stats); + } + // ======================== + // Checkpoint 003 Wrapper + // ======================== + + std::string AutoStory_Checkpoint_003::name() const { return "003 - " + AutoStory_Segment_00().name(); } + std::string AutoStory_Checkpoint_003::start_text() const { return AutoStory_Checkpoint_002().end_text(); } + std::string AutoStory_Checkpoint_003::end_text() const { return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; } + + void AutoStory_Checkpoint_003::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_003(env, context, options, stats); + } + // ======================== + // Checkpoint 004 Wrapper + // ======================== + + std::string AutoStory_Checkpoint_004::name() const { return "004 - " + AutoStory_Segment_00().name(); } + std::string AutoStory_Checkpoint_004::start_text() const { return AutoStory_Checkpoint_003().end_text(); } + std::string AutoStory_Checkpoint_004::end_text() const { return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; } + + void AutoStory_Checkpoint_004::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_004(env, context, options, stats); + } + + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h new file mode 100644 index 0000000000..01e0ad3bb6 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h @@ -0,0 +1,149 @@ +/* BDSP AutoStory - Segment 00 + * From beginning cinematic until after choosing the starter Pokemon. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment00_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment00_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_00 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_000 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_001 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_002 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_003 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_004 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: game launched, in front of the TV. +// end: Standing outside lake varety before starter +//Segment 00-00 begins +void checkpoint_000( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +); + +// start: Standing outside lake varety before starter +// end: Standing outside lake varety after starter battle +//Segment 00-001 begins +void checkpoint_001( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +); + +// start: Standing outside lake varety after starter battle +// end: recieved Pokedex, standing in front of the professor +//Segment 00-002 begins +void checkpoint_002( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +); + +// start: recieved Pokedex, standing in front of the professor +// end: Standing in the entrance to Jubilife City after speaking to dawn +//Segment 00-003 begins +void checkpoint_003( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +); + +// start: Standing in the entrance to Jubilife City after speaking to dawn +// end: Standing in the Pokemon Center ready to deposit pokemon after recieving poketech. +//Segment 00-004 begins +void checkpoint_004( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +); +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp new file mode 100644 index 0000000000..4c48707df5 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -0,0 +1,1204 @@ +/* BDSP AutoStory - Segment 00 Tools + * + * Helper implementations for Segment 00. + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Tools/ErrorDumper.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" + +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" + +#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" +#include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_HomeToDateTime.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" + +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" + +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" + +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" +#include "PokemonBDSP/Programs/PokemonBDSP_BasicCatcher.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + + // --------------------------------------------------------------------------- + // BattleDialogWatcher + // --------------------------------------------------------------------------- + + BattleDialogWatcher::BattleDialogWatcher(Color color) + : DetectorToFinder( + "BattleDialogWatcher", + std::chrono::milliseconds(250), + color + ) + { + } + + // --------------------------------------------------------------------------- + // Poketech Clown dialog helper + // --------------------------------------------------------------------------- + + int detect_clown_choice_arrow( + VideoOverlay& overlay, + VideoSnapshot snapshot + ) { + SelectionArrowFinder top_arrow( + overlay, + { 0.62, 0.37, 0.08, 0.10 }, + COLOR_RED + ); + + SelectionArrowFinder bottom_arrow( + overlay, + { 0.62, 0.48, 0.08, 0.10 }, + COLOR_RED + ); + + if (top_arrow.detect(snapshot)) { + return 0; + } + + if (bottom_arrow.detect(snapshot)) { + return 1; + } + + return -1; + } + + void clown_dialog_helper( + VideoStream& stream, + ProControllerContext& context, + DpadPosition direction + ) { + stream.log( + "[AutoStory] Starting clown dialog helper...", + COLOR_BLUE + ); + + const auto overall_deadline = + std::chrono::steady_clock::now() + 30s; + + // ------------------------------------------------- + // Phase 1: Move + interact until dialog is detected. + // ------------------------------------------------- + ShortDialogWatcher dialog(COLOR_YELLOW); + bool dialog_started = false; + + while (!dialog_started) { + + if (std::chrono::steady_clock::now() > overall_deadline) { + stream.log( + "[AutoStory] Clown helper timed out finding dialog.", + COLOR_RED + ); + return; + } + + context.wait_for_all_requests(); + pbf_press_dpad(context, direction, 80ms, 200ms); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + + if (wait_until(stream, context, 1500ms, { {dialog} }) == 0) { + dialog_started = true; + stream.log("[AutoStory] Dialog detected.", COLOR_GREEN); + } + } + + // ------------------------------------------------- + // Phase 2: A mash for 2 seconds. + // ------------------------------------------------- + stream.log("[AutoStory] Mashing A to advance dialog...", COLOR_BLUE); + pbf_mash_button(context, BUTTON_A, 4000ms); + context.wait_for_all_requests(); + + // ------------------------------------------------- + // Phase 3: Clear remaining dialog. + // ------------------------------------------------- + pbf_mash_button(context, BUTTON_B, 4000ms); + + context.wait_for_all_requests(); + + stream.log( + "[AutoStory] Clown dialog helper complete.", + COLOR_GREEN + ); + } + // --------------------------------------------------------------------------- + // Time helper + // --------------------------------------------------------------------------- + + void set_time_fast( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + const DateTime& target + ) { + env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); + + // 1) Home + NintendoSwitch::go_home(env.console, context); + + // 2) Navigate to Date/Time settings quickly. + NintendoSwitch::home_to_date_time(env.console, context, true); + + // 3) Enter date editor. + pbf_press_button(context, BUTTON_A, 80ms, 240ms); + + // 4) Wait for date screen and set exact time. + NintendoSwitch::DateChangeWatcher date_reader(env.console); + int ret = wait_until( + env.console, context, 10s, + { date_reader } + ); + if (ret < 0) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to enter Date Change screen.", + env.console + ); + } + + { + VideoOverlaySet overlays(env.console.overlay()); + date_reader.make_overlays(overlays); + date_reader.set_date(env.program_info(), env.console, context, target); + } + + // 5) Commit date. + pbf_press_button(context, BUTTON_A, 160ms, 240ms); + + // 6) Back to game. + pbf_press_button( + context, BUTTON_HOME, 160ms, + ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0 + ); + NintendoSwitch::resume_game_from_home(env.console, context, false); + + context.wait_for_all_requests(); + context.wait_for(500ms); + } + + + // --------------------------------------------------------------------------- + // Natalie battle + // --------------------------------------------------------------------------- + void fight_Natalie( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log( + "[AutoStory] Starting Natalie battle...", + COLOR_BLUE + ); + + // Allow battle transition to finish. + pbf_wait(context, 5000ms); + + bool battle_menu_seen = false; + + // State machine loop. + while (true) { + + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(BattleType::TRAINER); + EndBattleWatcher end_battle; + + // Detect move-learning arrows. + SelectionArrowFinder learn_move( + stream.overlay(), + {0.50, 0.62, 0.40, 0.18}, + COLOR_YELLOW + ); + + int ret = run_until( + stream, + context, + [](ProControllerContext& context) { + // Continuously clear: + // EXP gain + // level up + // faint text + // send-out text + // battle narration + pbf_mash_button( + context, + BUTTON_B, + 120000ms + ); + }, + { + {battle_menu}, + battle_menu_seen + ? PeriodicInferenceCallback{end_battle} + : PeriodicInferenceCallback{}, + {learn_move}, + } + ); + + switch (ret) { + + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: + { + stream.log( + "[AutoStory] Battle menu detected.", + COLOR_BLUE + ); + + battle_menu_seen = true; + + // Open Fight menu. + pbf_press_button( + context, + BUTTON_A, + 80ms, + 500ms + ); + + // Hard reset move cursor to top. + pbf_press_dpad( + context, + DPAD_UP, + 80ms, + 150ms + ); + + pbf_press_dpad( + context, + DPAD_UP, + 80ms, + 150ms + ); + + pbf_press_dpad( + context, + DPAD_UP, + 80ms, + 150ms + ); + + // Move to move slot 3. + pbf_press_dpad( + context, + DPAD_DOWN, + 80ms, + 150ms + ); + + pbf_press_dpad( + context, + DPAD_DOWN, + 80ms, + 250ms + ); + + // Use move slot 3. + pbf_press_button( + context, + BUTTON_A, + 80ms, + 3000ms + ); + + break; + } + + // ------------------------------------------------- + // Battle finished. + // ------------------------------------------------- + case 1: + { + stream.log( + "[AutoStory] Natalie battle complete.", + COLOR_GREEN + ); + + pbf_mash_button( + context, + BUTTON_B, + 5000ms + ); + + return; + } + + // ------------------------------------------------- + // Move learning detected. + // ------------------------------------------------- + case 2: + { + stream.log( + "[AutoStory] Learn move detected.", + COLOR_ORANGE + ); + + // Always refuse move learning. + pbf_move_right_joystick( + context, + { 0, -1 }, + 160ms, + 840ms + ); + + pbf_press_button( + context, + BUTTON_A, + 160ms, + 840ms + ); + + break; + } + + // ------------------------------------------------- + // Timeout. + // ------------------------------------------------- + default: + { + stream.log( + "[AutoStory] Natalie battle timed out.", + COLOR_RED + ); + + pbf_mash_button( + context, + BUTTON_B, + 5000ms + ); + + return; + } + + } + } + } + + // --------------------------------------------------------------------------- + // Starly battle + // --------------------------------------------------------------------------- + + void fight_starly(VideoStream& stream, ProControllerContext& context) { + stream.log("Starting Starly battle..."); + + // Give time for battle transition + pbf_wait(context, 5000ms); + + // Mash A to get through intro dialogue + pbf_mash_button(context, BUTTON_A, 5000ms); + + // Use first move repeatedly (Tackle/Scratch/Pound) + for (int i = 0; i < 6; i++) { + pbf_press_button(context, BUTTON_A, 100ms, 2000ms); // Select move + pbf_wait(context, 3000ms); // Wait for animation + damage + } + + // Mash through faint + post-battle dialogue + pbf_mash_button(context, BUTTON_A, 8000ms); + + stream.log("Starly battle complete."); + + wait_for_dialogue(stream, context, "Phase 9 (post battle text)"); + + pbf_mash_button(context, BUTTON_B, 20000ms); + + wait_for_dialogue(stream, context, "Phase 9 (post battle text)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + + context.wait_for_all_requests(); + + + } + + // --------------------------------------------------------------------------- + // Tristan battle + // --------------------------------------------------------------------------- + void fight_tristan( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log( + "[AutoStory] Starting Tristan battle...", + COLOR_BLUE + ); + + // Wait for battle transition to fully finish. + pbf_wait(context, 5000ms); + + bool battle_menu_seen = false; + + while (true) { + + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(BattleType::TRAINER); + EndBattleWatcher end_battle; + + int ret = run_until( + stream, + context, + [](ProControllerContext& context) { + // Mash through battle text continuously. + pbf_mash_button( + context, + BUTTON_B, + 10000ms + ); + }, + { + {battle_menu}, + battle_menu_seen + ? PeriodicInferenceCallback{end_battle} + : PeriodicInferenceCallback{}, + } + ); + + switch (ret) { + + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: + battle_menu_seen = true; + + stream.log( + "[AutoStory] Battle menu detected.", + COLOR_BLUE + ); + + // Select Fight. + pbf_press_button( + context, + BUTTON_A, + 120ms, + 500ms + ); + + // Select move slot 1 (Scratch). + pbf_press_button( + context, + BUTTON_A, + 120ms, + 3000ms + ); + + break; + + // ------------------------------------------------- + // End battle detected. + // ------------------------------------------------- + case 1: + stream.log( + "[AutoStory] Tristan battle complete.", + COLOR_GREEN + ); + + pbf_mash_button( + context, + BUTTON_B, + 5000ms + ); + + return; + + default: + stream.log( + "[AutoStory] Tristan battle timeout.", + COLOR_RED + ); + + return; + } + } + } + + // --------------------------------------------------------------------------- + // Catch 1 Pokemon + // --------------------------------------------------------------------------- + + void catch_1_pokemon( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + bool encounter_found = false; + + // Wiggle left/right until battle starts. + while (!encounter_found) { + + // Turn left. + pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); + context.wait_for_all_requests(); + + int ret = wait_until( + stream, + context, + 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (ret >= 0) { + encounter_found = true; + break; + } + + // Turn right. + pbf_move_left_joystick(context, {1, 0}, 120ms, 200ms); + context.wait_for_all_requests(); + + ret = wait_until( + stream, + context, + 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (ret >= 0) { + encounter_found = true; + break; + } + } + + stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); + + // Wait until the actual battle menu appears. + BattleMenuWatcher confirm_menu(BattleType::STANDARD); + + int menu_ret = wait_until( + stream, + context, + 15s, + { + {confirm_menu}, + } + ); + + if (menu_ret < 0) { + stream.log( + "[AutoStory] Failed to detect battle menu.", + COLOR_RED + ); + return; + } + + stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + + // Catch the pokemon. + CatchResults result = basic_catcher( + stream, + context, + Language::English, + "poke-ball", + 10 + ); + + switch (result.result) { + case CatchResult::POKEMON_CAUGHT: + stream.log( + "[AutoStory] Pokemon successfully caught.", + COLOR_GREEN + ); + break; + + default: + stream.log( + "[AutoStory] Catch attempt failed.", + COLOR_ORANGE + ); + break; + } + + context.wait_for_all_requests(); + + // Force clear any remaining text boxes / summaries. + pbf_mash_button(context, BUTTON_B, 15000ms); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + } + + // --------------------------------------------------------------------------- + // Movement Helpers + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Generic Movement Helper - Walk through grass in specified direction for + // specified steps, attempting to run from any battles that are encountered. + // --------------------------------------------------------------------------- + void walk_through_grass( + VideoStream& stream, + ProControllerContext& context, + int8_t x, + int8_t y, + size_t steps, + const std::string& direction_name + ) { + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < steps; step++) { + + context.wait_for_all_requests(); + + // Move. + pbf_move_left_joystick( + context, + {(double)x, (double)y}, + 200ms, + 0ms + ); + + context.wait_for_all_requests(); + + // Check for battle. + int battle_ret = wait_until( + stream, + context, + 2500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (battle_ret >= 0) { + + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + + // Advance text. + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + // Move cursor to Run. + pbf_move_left_joystick(context, {0, 1}, 80ms, 80ms); + + // Select Run. + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + if (wait_until(stream, context, 1500ms, {{battle_end}}) == 0) { + escaped = true; + break; + } + } + + if (!escaped) { + stream.log( + "[AutoStory] Failed to run from battle.", + COLOR_RED + ); + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle.", + stream + ); + } + + // Let overworld stabilize. + context.wait_for_all_requests(); + context.wait_for(1000ms); + } + } + + stream.log( + "[AutoStory] Completed grass movement: " + direction_name, + COLOR_GREEN + ); + } + + // --------------------------------------------------------------------------- + // Helper function for 1st grass patch + // --------------------------------------------------------------------------- + void walk_right_until_on_path(VideoStream& stream, ProControllerContext& context) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 20; step++) { + + context.wait_for_all_requests(); + + // Move right (fixed step) + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + COLOR_ORANGE + ); + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + // Spam escape inputs + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + escaped = true; + break; + } + } + + // Light text advance + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + // Reposition cursor on "Run" + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + if (!escaped) { + stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Critical: let overworld stabilize before next step + context.wait_for_all_requests(); + context.wait_for(1000ms); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed right movement (18 steps).", COLOR_GREEN); + } + + // --------------------------------------------------------------------------- + // Helper function to walk up through the second grass patch + // --------------------------------------------------------------------------- + void walk_up_through_grass_2(VideoStream& stream, ProControllerContext& context) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 18; step++) { + + context.wait_for_all_requests(); + + // Move right (fixed step) + pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + COLOR_ORANGE + ); + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + // Spam escape inputs + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + escaped = true; + break; + } + } + + // Light text advance + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + // Reposition cursor on "Run" + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + if (!escaped) { + stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Critical: let overworld stabilize before next step + context.wait_for_all_requests(); + context.wait_for(1000ms); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); + } + // --------------------------------------------------------------------------- + // Helper function to walk right through the second grass patch + // --------------------------------------------------------------------------- + void walk_right_through_grass_2(VideoStream& stream, ProControllerContext& context) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 10; step++) { + + context.wait_for_all_requests(); + + // Move right (fixed step) + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + COLOR_ORANGE + ); + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + // Spam escape inputs + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + escaped = true; + break; + } + } + + // Light text advance + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + // Reposition cursor on "Run" + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + if (!escaped) { + stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Critical: let overworld stabilize before next step + context.wait_for_all_requests(); + context.wait_for(1000ms); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed right movement (10 steps).", COLOR_GREEN); + } + // --------------------------------------------------------------------------- + // Helper function to walk left through the third grass patch + // --------------------------------------------------------------------------- + void walk_left_through_grass_3(VideoStream& stream, ProControllerContext& context) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 18; step++) { + + context.wait_for_all_requests(); + + // Move left (fixed step) + pbf_move_left_joystick(context, { -1, 0 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + COLOR_ORANGE + ); + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + // Spam escape inputs + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + escaped = true; + break; + } + } + + // Light text advance + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + // Reposition cursor on "Run" + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + if (!escaped) { + stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Critical: let overworld stabilize before next step + context.wait_for_all_requests(); + context.wait_for(1000ms); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); + } + // --------------------------------------------------------------------------- + // Helper function to walk up through the third grass patch + // --------------------------------------------------------------------------- + void walk_up_through_grass_3(VideoStream& stream, ProControllerContext& context) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 18; step++) { + + context.wait_for_all_requests(); + + // Move up (fixed step) + pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + COLOR_ORANGE + ); + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + // Spam escape inputs + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + escaped = true; + break; + } + } + + // Light text advance + pbf_press_button(context, BUTTON_B, 40ms, 120ms); + context.wait_for_all_requests(); + + // Reposition cursor on "Run" + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 180ms); + context.wait_for_all_requests(); + + if (!escaped) { + stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Critical: let overworld stabilize before next step + context.wait_for_all_requests(); + context.wait_for(1000ms); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); + } + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h new file mode 100644 index 0000000000..494a4f81c3 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -0,0 +1,133 @@ +#pragma once + +#include "PokemonBDSP_AutoStory_Segment_00.h" +#include "CommonTools/Async/InferenceRoutines.h" + +#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" + +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + + // --------------------------------------------------------------------------- + // Utility Watchers + // --------------------------------------------------------------------------- + + class BattleDialogWatcher : public DetectorToFinder { + public: + BattleDialogWatcher(Color color = COLOR_RED); + }; + + // --------------------------------------------------------------------------- + // Time / setup helpers + // --------------------------------------------------------------------------- + + void set_time_fast( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + const DateTime& target + ); + + // --------------------------------------------------------------------------- + // Battles + // --------------------------------------------------------------------------- + + void fight_starly( + VideoStream& stream, + ProControllerContext& context + ); + + void fight_tristan( + VideoStream& stream, + ProControllerContext& context + ); + + void fight_Natalie( + VideoStream& stream, + ProControllerContext& context + ); + + // --------------------------------------------------------------------------- + // Catching / healing + // --------------------------------------------------------------------------- + + void catch_1_pokemon( + VideoStream& stream, + ProControllerContext& context + ); + + void use_potion_first_pokemon( + VideoStream& stream, + ProControllerContext& context + ); + + // --------------------------------------------------------------------------- + // Grass movement helpers + // --------------------------------------------------------------------------- + + void walk_right_until_on_path( + VideoStream& stream, + ProControllerContext& context + ); + + void walk_up_through_grass_2( + VideoStream& stream, + ProControllerContext& context + ); + + void walk_right_through_grass_2( + VideoStream& stream, + ProControllerContext& context + ); + + void walk_left_through_grass_3( + VideoStream& stream, + ProControllerContext& context + ); + + void walk_up_through_grass_3( + VideoStream& stream, + ProControllerContext& context + ); + void walk_through_grass( + VideoStream& stream, + ProControllerContext& context, + int8_t x, + int8_t y, + size_t steps, + const std::string& direction_name + ); + // --------------------------------------------------------------------------- + // Helper function to navigate the poketech challenge clown dialogue. + // Example usage + // Move up and talk to clown. + // clown_dialog_helper( + // stream, + // context, + // DPAD_UP + // ); + + // Move left and talk to clown. + // clown_dialog_helper( + // stream, + // context, + // DPAD_LEFT + // ); + + // Move right and talk to clown. + // clown_dialog_helper( + // stream, + // context, + // DPAD_RIGHT + // ); + // ---------------------------------------------------------------------------- + void clown_dialog_helper( + VideoStream& stream, + ProControllerContext& context, + DpadPosition direction + ); + } + } +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp new file mode 100644 index 0000000000..f2b34eac05 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -0,0 +1,153 @@ +/* BDSP AutoStory - Segment 01 + * From after choosing the starter Pokemon until after acquiring the first badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_01.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + + } + + void oreburgh_gate_to_first_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + + } + // --------------------------------------------------------------------------- + // Segment 1 Part ? Gym navigation + // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. + // --------------------------------------------------------------------------- + + static void gym_Puzzle_01(VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + }; + const Move sequence[] = { + {DPAD_UP, 3}, + {DPAD_LEFT, 4}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 8}, + {DPAD_UP, 11}, + {DPAD_LEFT, 5}, + {DPAD_UP, 4}, + }; + + for (const auto& move : sequence) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate the Gym battle. + + //Battle automation code. + + //Button presses after battle with small cutscene of badge being put in case between dialogue boxes. + + for (int i = 0; i < 21; i++) { + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + //Exits Buiilding + pbf_wait(context, 1000ms); + } + + std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } + std::string AutoStory_Segment_01::start_text() const { return "Start: Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } + + void AutoStory_Segment_01::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_005().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_006().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); + } + + + std::string AutoStory_checkpoint_005::name() const { return "005 - " + AutoStory_Segment_01().name(); } + std::string AutoStory_checkpoint_005::start_text() const { return "Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_checkpoint_005::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } + + void AutoStory_checkpoint_005::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_005(env, context, options, stats); + } + + + void checkpoint_005( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + jubilife_to_oreburgh_gate(env, env.console, context); + + } + ); + } + + std::string AutoStory_checkpoint_006::name() const { return "006 - " + AutoStory_Segment_01().name(); } + std::string AutoStory_checkpoint_006::start_text() const { return AutoStory_checkpoint_005().end_text(); } + std::string AutoStory_checkpoint_006::end_text() const { return "Standing At the first gym leader after defeating him."; } + + void AutoStory_checkpoint_006::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_006(env, context, options, stats); + } + + + void checkpoint_006( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + oreburgh_gate_to_first_gym(env, env.console, context); + //#. Oreburgh Gym navigation and battle + gym_Puzzle_01(env.console, context); + + } + ); + } + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h new file mode 100644 index 0000000000..b3612ff0e0 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -0,0 +1,77 @@ +/* BDSP AutoStory - Segment 01 + * From after choosing the starter Pokemon until after acquiring the first badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_01 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_005 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: starter Pokemon chosen and received +// end: first Gym Badge acquired (Oreburgh City) +void checkpoint_005( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); +class AutoStory_checkpoint_006 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: starter Pokemon chosen and received +// end: first Gym Badge acquired (Oreburgh City) +void checkpoint_006( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp new file mode 100644 index 0000000000..4e08547b99 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -0,0 +1,344 @@ +/* BDSP AutoStory - Segment 02 + * From after acquiring the first badge until after acquiring the second badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_02.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + void roark_to_mars(VideoStream& stream, ProControllerContext& context) { + + } + void mars_to_eterna_forest(VideoStream& stream, ProControllerContext& context) { + + } + void eterna_forest(VideoStream& stream, ProControllerContext& context) { + + } + void eterna_forest_exit_to_gym(VideoStream& stream, ProControllerContext& context) { + + } +static void gym_Puzzle_02(VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + }; + + const Move sequence1[] = { + {DPAD_UP, 5}, + {DPAD_LEFT, 2}, + {DPAD_UP, 9}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 7}, + {DPAD_LEFT, 3}, + {DPAD_UP, 2}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 4}, + }; + + const Move sequence2[] = { + {DPAD_UP, 6}, + {DPAD_LEFT, 6}, + {DPAD_DOWN, 9}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 5}, + }; + + const Move sequence3[] = { + {DPAD_LEFT, 5}, + {DPAD_UP, 6}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, + {DPAD_LEFT, 2}, + {DPAD_UP, 8}, + {DPAD_RIGHT, 7}, + {DPAD_DOWN, 2}, + {DPAD_RIGHT, 5}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_RIGHT, 6}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, + }; + + const Move sequence4[] = { + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 6}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 11}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 16}, + {DPAD_LEFT, 6}, + }; + + const Move sequence5[] = { + {DPAD_RIGHT, 6}, + {DPAD_DOWN, 17}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 2}, + {DPAD_LEFT, 8}, + {DPAD_UP, 11}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 7}, + {DPAD_LEFT, 2}, + {DPAD_UP, 7}, + }; + + const Move sequence6[] = { + {DPAD_DOWN, 10}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 13}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 2}, + }; + + for(int i=0; i < 7; i++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } + + pbf_mash_button(context, BUTTON_A, 2500ms); //Delay may need increasing depending on length of dialogue. + pbf_press_dpad(context, DPAD_UP, 80ms, 2000ms); + + for (const auto& move : sequence1){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 01 (Lass Caroline). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. + + for (const auto& move : sequence2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 02 (Aroma Lady Jenna). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. + + for (const auto& move : sequence3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 03 (Aroma Lady Angela). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 04 (Beauty Lindsay). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Gym battle. + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence6){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + pbf_wait(context, 2000ms); + + for(int i=0; i < 9; i++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + //Exits Buiilding + pbf_wait(context, 1000ms); +} + +std::string AutoStory_Segment_02::name() const{ return "02: First Badge to Second Badge"; } +std::string AutoStory_Segment_02::start_text() const{ return "Start: First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_Segment_02::end_text() const{ return "End: Second Gym Badge acquired (Eterna City)."; } + +void AutoStory_Segment_02::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_007().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_008().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_009().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} +std::string AutoStory_checkpoint_007::name() const { return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_007::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_007::end_text() const { return "Standing in front of the scientist in Valley windworks after beating Mars."; } + +void AutoStory_checkpoint_007::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + checkpoint_007(env, context, options, stats); +} + + +void checkpoint_007( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 02 gameplay logic + roark_to_mars(env.console, context); + + } + ); +} +std::string AutoStory_checkpoint_008::name() const { return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_008::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_008::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } + +void AutoStory_checkpoint_008::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + checkpoint_008(env, context, options, stats); +} + + +void checkpoint_008( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 02 gameplay logic + mars_to_eterna_forest(env.console, context); + + } + ); +} + +std::string AutoStory_checkpoint_009::name() const{ return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_009::start_text() const{ return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_009::end_text() const{ return "Second Gym Badge acquired (Eterna City)."; } + +void AutoStory_checkpoint_009::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_009(env, context, options, stats); +} + + +void checkpoint_009( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 02 gameplay logic + eterna_forest(env.console, context); + + } + ); +} +std::string AutoStory_checkpoint_010::name() const { return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_010::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_010::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } + +void AutoStory_checkpoint_010::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + checkpoint_010(env, context, options, stats); +} + + +void checkpoint_010( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 02 gameplay logic + eterna_forest_exit_to_gym(env.console, context); + //#. Eterna Gym navigation and battle + gym_Puzzle_02(env.console, context); + } + ); +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h new file mode 100644 index 0000000000..4210a110cc --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h @@ -0,0 +1,130 @@ +/* BDSP AutoStory - Segment 02 + * From after acquiring the first badge until after acquiring the second badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_02 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_007 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_007( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_008( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); +class AutoStory_checkpoint_008 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_008( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); +class AutoStory_checkpoint_009 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_009( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_checkpoint_010 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_010( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp new file mode 100644 index 0000000000..e3286196c5 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -0,0 +1,463 @@ +/* BDSP AutoStory - Segment 03 + * From after acquiring the second badge until after acquiring the third badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_03.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context) { + + } + void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context) { + + } + void coronet1(VideoStream& stream, ProControllerContext& context) { + + } + void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context) { + + } + void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context) { + + } + static void lost_tower(VideoStream& stream, ProControllerContext& context) { + + } + void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context) { + + } + +static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + std::chrono::milliseconds delay; + }; + + const Move sequence1[] = { + {DPAD_RIGHT, 8, 0ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_UP, 2, 0ms}, + }; + + const Move sequence2[] = { + {DPAD_UP, 5, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_DOWN, 10, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_DOWN, 13, 0ms}, + {DPAD_LEFT, 3, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 6, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 7, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 3, 0ms}, + }; + + const Move sequence3[] = { + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 3000ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_LEFT, 8, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 3000ms}, + {DPAD_UP, 11, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_DOWN, 13, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 3, 0ms}, + }; + + const Move sequence4[] = { + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 3, 0ms}, + }; + + const Move sequence5[] = { + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 8, 0ms}, + }; + + const Move sequence6[] = { + {DPAD_DOWN, 8, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 3000ms}, + {DPAD_DOWN, 6, 0ms}, + }; + + for (const auto& move : sequence1){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 01 (Black Belt Rafael). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 02 (Black Belt Jeffrey). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 03 (Black Belt Darren). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 04 (Black Belt Colby). + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate the Gym battle. + + //Battle automation code. + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence6){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Exits Buiilding + pbf_wait(context, 1000ms); +} + +std::string AutoStory_Segment_03::name() const{ return "03: Second Badge to Third Badge"; } +std::string AutoStory_Segment_03::start_text() const{ return "Start: Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Segment_03::end_text() const{ return "End: Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Segment_03::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_011().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_012().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_013().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_014().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_015().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_011::name() const{ return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_011::start_text() const{ return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_011::end_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_011::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + Checkpoint_011(env, context, options, stats); +} + + +void Checkpoint_011( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 03 gameplay logic + eterna_gym_to_jupiter(env.console, context); + + } + ); +} +std::string AutoStory_Checkpoint_012::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_012::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_012::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_012::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_012(env, context, options, stats); +} + + +void Checkpoint_012( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 03 gameplay logic + jupiter_to_coronet(env.console, context); + + } + ); +} +std::string AutoStory_Checkpoint_013::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_013::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_013::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_013::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_013(env, context, options, stats); +} + + +void Checkpoint_013( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 03 gameplay logic + coronet1(env.console, context); + + } + ); +} +std::string AutoStory_Checkpoint_014::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_014::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_014::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_014::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_014(env, context, options, stats); +} + + +void Checkpoint_014( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 03 gameplay logic + coronet_to_heartholme(env.console, context); + + } + ); +} +std::string AutoStory_Checkpoint_015::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_015::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_015::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_015::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_015(env, context, options, stats); +} + + +void Checkpoint_015( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 03 gameplay logic + heartholme_to_lost_tower(env.console, context); + lost_tower(env.console, context); + + } + ); +} +std::string AutoStory_Checkpoint_016::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_016::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_016::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_016::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_016(env, context, options, stats); +} + + +void Checkpoint_016( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 03 gameplay logic + lost_tower_to_veilstone_gym(env.console, context); + //#. Veilstone Gym navigation and battle + gym_Puzzle_03(env.console, context); + } + ); +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h new file mode 100644 index 0000000000..af86147546 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h @@ -0,0 +1,165 @@ +/* BDSP AutoStory - Segment 03 + * From after acquiring the second badge until after acquiring the third badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment03_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment03_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_03 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_011 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_011( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); +class AutoStory_Checkpoint_012 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_012( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_013 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_013( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_014 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_014( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_015 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_015( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_016 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_016( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp new file mode 100644 index 0000000000..74430c52a0 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -0,0 +1,1014 @@ +/* BDSP AutoStory - Segment 04 + * From after acquiring the third badge until after acquiring the fourth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP_AutoStory_Segment_04.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +std::string AutoStory_Segment_04::name() const{ return "04: Third Badge to Fourth Badge"; } +std::string AutoStory_Segment_04::start_text() const{ return "Start: Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Segment_04::end_text() const{ return "End: Fourth Gym Badge acquired (Pastoria City)."; } + + +void AutoStory_Segment_04::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_017().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_017::name() const{ return "004 - " + AutoStory_Segment_04().name(); } +std::string AutoStory_Checkpoint_017::start_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_017::end_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } + +void AutoStory_Checkpoint_017::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + Checkpoint_017(env, context, options, stats); +} + +static void move_back_after_unexpected_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + if (trainerid == "route_213_tuber_chelsea") { + + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + } else if (trainerid == "route_213_tuber_jared") { + // Just need to wait, nothing else + } else if (trainerid == "route_213_beauty_cindy") { + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + } +} + + +static bool handle_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + context.wait_for_all_requests(); + pbf_wait(context, 15000ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 500ms); + + if (trainerid == "veilstone_galactic_grunts_1"){ + // Select Razor Leaf and mash that + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_veilstone_galactic_grunts_2: black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_veilstone_galactic_grunts_2: transition confirmed."); + return true; + } else if (trainerid == "route_214_psychic_abigail"){ + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_psychic_abigail: black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_psychic_abigail: transition confirmed."); + return true; + } else if (trainerid == "route_214_collector_brady"){ + // Select Bulldoze + pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_collector_brady: black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + return true; + } else if (trainerid == "route_214_psychic_mitchell"){ + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_psychic_mitchell: black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + return true; + } else if (trainerid == "route_214_collector_douglas"){ + // Select Bulldoze + pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_collector_douglas: black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + return true; + } else if ( + trainerid == "route_213_tuber_chelsea" or + trainerid == "route_213_tuber_jared" or + trainerid == "route_213_beauty_cindy" + ) { + // Select Razor leaf + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("route_213_tuber_chelsea / jared: black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + move_back_after_unexpected_battle(stream, context, trainerid); + return true; + } + return false; +} + +static bool leave_gym( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("leave_gym: walking out of gym"); + + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 2 + pbf_move_left_joystick(context, {+1, 0}, 300ms, 100ms); // 2 + + pbf_press_dpad(context, DPAD_RIGHT, 280ms, 200ms); + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 2 + pbf_press_dpad(context, DPAD_LEFT, 280ms, 200ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4 + pbf_press_dpad(context, DPAD_RIGHT, 280ms, 200ms); + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 2 + pbf_press_dpad(context, DPAD_LEFT, 280ms, 200ms); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 0ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("leave_gym: black screen not detected!", COLOR_RED); + return false; + } + stream.log("leave_gym: transition confirmed."); + return true; +} + +static bool go_to_veilstone_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + stream.log("Walking towards the pokemon center"); + pbf_wait(context, 4000ms); + wait_for_dialogue(stream, context, "Dawn"); + mash_until_dialogue_ends(stream, context, BUTTON_B); + context.wait_for_all_requests(); + pbf_wait(context, 4000ms); + + pbf_move_left_joystick(context, {0, -1}, 3000ms, 100ms); // 21 + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 14 + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 25 + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 11 + pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 12 + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 8+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); + context.wait_for_all_requests(); + + /* Entering the Pokemon Center */ + BlackScreenOverWatcher black_screen1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen1}} + ); + if (ret < 0){ + stream.log("Didn't enter pokemon center!", COLOR_RED); + return false; + } + + stream.log("Entered pokemon center successfully!"); + + stream.log("go_to_veilstone_pokemon_center: transition confirmed."); + return true; + +} + +static bool heal_at_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + return heal_pokemon(stream, context, "Veilstone"); +} + +static bool go_to_galactic_warehouse( + VideoStream& stream, + ProControllerContext& context +){ + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 7 + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 9+ + pbf_move_left_joystick(context, {-1, 0}, 2800ms, 100ms); // 12+ + pbf_move_left_joystick(context, {0, +1}, 4000ms, 100ms); // 15+ + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 5+ + pbf_press_dpad(context, DPAD_DOWN, 400ms, 200ms); + + context.wait_for_all_requests(); + BlackScreenOverWatcher battle_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 12000ms); + }, + {{battle_black_screen}} + ); + if (ret < 0){ + stream.log("go_to_galactic_warehouse: battle black screen not detected (2)", COLOR_RED); + return false; + } + stream.log("go_to_galactic_warehouse: battle transition confirmed."); + return true; +} + +static bool enter_galactic_warehouse( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + pbf_wait(context, 3000ms); + context.wait_for_all_requests(); + + pbf_mash_button(context, BUTTON_B, 16000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 1200ms, 100ms); // 6+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen_1}} + ); + if (ret < 0){ + stream.log("Entering Galactic warehouse black screen not detected!", COLOR_RED); + return false; + } + stream.log("enter_galactic_warehouse: transition confirmed."); + return true; +} + +static bool exit_galactic_warehouse( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 7+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + context.wait_for_all_requests(); + // Get fly + + ShortDialogWatcher getting_fly_TM(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{getting_fly_TM}} + ); + if (ret < 0) { + stream.log("enter_galactic_warehouse_getting_fly_TM: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("enter_galactic_warehouse_getting_fly_TM: getting TM Fly not detected!"); + pbf_mash_button(context, BUTTON_A, 9000ms); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 5+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5, true); + + // Black screen detector here when exiting the warehouse + BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_2}} + ); + if (ret < 0){ + stream.log("enter_galactic_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("exit_galactic_warehouse: transition confirmed."); + return true; +} + +static bool go_to_route_214( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + stream.log("Moving towards Route 214"); + pbf_wait(context, 3000ms); + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 5+ + pbf_move_left_joystick(context, {-1, 0}, 1300ms, 100ms); // 8 + pbf_move_left_joystick(context, {0, -1}, 3000ms, 100ms); // 13+ + pbf_move_left_joystick(context, {+1, 0}, 3000ms, 100ms); // 15+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4, true); + + pbf_move_left_joystick(context, {0, -1}, 2600ms, 100ms); // 14 + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 7+ + + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3, true); + + pbf_move_left_joystick(context, {+1, 0}, 2200ms, 100ms); // 15 + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}} + ); + if (ret < 0){ + stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); + return false; + } + stream.log("Entered house"); + pbf_wait(context, 1500ms); + + context.wait_for_all_requests(); + // Add a black screen detector here when entering the connecting house + BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_2}} + ); + if (ret < 0){ + stream.log("go_to_route_214_exit_house: black screen not detected!", COLOR_RED); + return false; + } + stream.log("Exited house"); + pbf_wait(context, 1500ms); + + pbf_move_left_joystick(context, {0, -1}, 6000ms, 100ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_route_214: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_route_214: transition confirmed."); + return true; +} + +static bool walk_through_route_214( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + stream.log("walk_through_route_214: Continuing on the first part of route 214"); + pbf_wait(context, 3000ms); + DpadState dpad; + + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {+1, 0}, 3000ms, 100ms); // 13+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3, true); + + activate_repel(stream, context); + + { + MarkDetector battle_dialogue_1(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); // 13+ + }, + {{battle_dialogue_1}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("walk_through_route_214_collector_brady: black screen not detected!", COLOR_RED); + return false; + }; + handle_battle(stream, context, "route_214_collector_brady"); + } + } + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ + + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 6, true); + + { + MarkDetector battle_dialogue_2(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_wait(context, 30000ms); + }, + {{battle_dialogue_2}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("walk_through_route_214_psychic_mitchell: black screen not detected!", COLOR_RED); + return false; + }; + handle_battle(stream, context, "route_214_psychic_mitchell"); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 6+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ + + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 3+ + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, true); + + pbf_move_left_joystick(context, {0, -1}, 6200ms, 100ms); // 31+ + + stream.log("walk_through_route_214: transition confirmed."); + return true; +} + +static bool pass_by_valor_lakefront( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + stream.log("Finishing going through route 214"); + pbf_wait(context, 1000ms); + DpadState dpad; + + { + ShortDialogWatcher repel_ran_out(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + DpadState dpad; + dpad.last_dir = DPAD_DOWN; + for (int c = 0; c < 20; c++){ + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, true); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); + } + }, + {{repel_ran_out}} + ); + if (ret == 0){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }; + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 20+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6, true); + + pbf_move_left_joystick(context, {0, -1}, 8000ms, 100ms); // 41+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4+ + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4, true); + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("walk_through_route_214: Didn't enter building!", COLOR_RED); + return false; + } + + return true; +} + +static bool go_through_building_route_213( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Going through building between routes 214 and 213"); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + ShortDialogWatcher repel_ran_out(COLOR_CYAN); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{repel_ran_out}} + ); + if (ret == 0){ + stream.log("go_through_building_route_214: Not reapplying repel.", COLOR_RED); + pbf_mash_button(context, BUTTON_B, 2000ms); + } + + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_building_route_214: Didn't exit building!", COLOR_RED); + return false; + } + stream.log("go_through_building_route_214: transition confirmed."); + + return true; +} + +static bool go_through_route_213( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + stream.log("Going through route 213"); + pbf_wait(context, 2000ms); + DpadState dpad; + + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4, true); + + MarkDetector tuber_chelsea_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + }, + {{tuber_chelsea_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_route_213_tuber_chelsea: Starting battle."); + handle_battle(stream, context, "route_213_tuber_chelsea"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + context.wait_for_all_requests(); + + MarkDetector tuber_jared_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3 + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + }, + {{tuber_jared_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213_tuber_jared: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_route_213_tuber_jared: Starting battle."); + handle_battle(stream, context, "route_213_tuber_jared"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {-1, 0}, 3600ms, 100ms); // 20+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 3 + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 13+ + + MarkDetector beauty_cindy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5000ms, 100ms); // 15+ + }, + {{beauty_cindy_fight}} + ); + if (ret == 0){ + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213_beauty_cindy: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + context.wait_for_all_requests(); + stream.log("go_through_route_213_beauty_cindy: Starting battle."); + handle_battle(stream, context, "route_213_beauty_cindy"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 8 + + activate_repel(stream, context); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213: Didn't enter building!", COLOR_RED); + return false; + } + stream.log("go_to_route_213: transition confirmed."); + + return true; +} + +static bool go_through_house_east_pastoria( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Going through the building at the entrance of Pastoria city"); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_house_east_pastoria: Didn't exit building!", COLOR_RED); + return false; + } + stream.log("go_through_house_east_pastoria: transition confirmed."); + + return true; +} + +static bool go_through_pastoria( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Going through Pastoria city and to the Pokemon center"); + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 4200ms, 100ms); // 23+ + pbf_move_left_joystick(context, {0, -1}, 500ms, 100ms); // 3 + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 + + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 + }, + {{repel_dialog}} + ); + if (ret == 0){ + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_through_pastoria: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8, true); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_pastoria: Didn't enter Pokemon center!", COLOR_RED); + return false; + } + stream.log("go_through_pastoria: transition confirmed."); + + return true; +} + +static bool go_to_pastoria_gym( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Going to Pastoria gym"); + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 8 + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3, true); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_pastoria_gym: Didn't enter building!", COLOR_RED); + return false; + } + stream.log("go_to_pastoria_gym: transition confirmed."); + return true; +} + +void Checkpoint_017( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + } + if (!go_to_veilstone_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_veilstone_pokemon_center: transition not detected.", env.console); + } + if (!heal_at_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); + } + if (!go_to_galactic_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_warehouse: transition not detected.", env.console); + } + if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); + } + if (!enter_galactic_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_galactic_warehouse: transition not detected.", env.console); + } + if (!exit_galactic_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_galactic_warehouse: transition not detected.", env.console); + } + if (!go_to_route_214(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_route_214: transition not detected.", env.console); + } + if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); + } + if (!walk_through_route_214(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); + } + if (!pass_by_valor_lakefront(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pass_by_valor_lakefront: transition not detected.", env.console); + } + if (!go_through_building_route_213(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); + } + if (!go_through_route_213(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); + } + if (!go_through_house_east_pastoria(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); + } + if (!go_through_pastoria(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); + } + if (!heal_at_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); + } + if (!go_to_pastoria_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); + } + } + ); +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h new file mode 100644 index 0000000000..3f13081a84 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Segment 04 + * From after acquiring the third badge until after acquiring the fourth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment04_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment04_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_04 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_017 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: third badge acquired +// end: fourth Gym Badge acquired (Pastoria City) +void Checkpoint_017( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp new file mode 100644 index 0000000000..adebfc97e0 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -0,0 +1,1429 @@ +/* BDSP AutoStory - Segment 05 + * From after acquiring the fourth badge until after acquiring the fifth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP_AutoStory_Segment_05.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +std::string AutoStory_Segment_05::name() const{ return "05: Fourth Badge to Fifth Badge"; } +std::string AutoStory_Segment_05::start_text() const{ return "Start: Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_Segment_05::end_text() const{ return "End: Fifth Gym Badge acquired (Veilstone City)."; } + +void AutoStory_Segment_05::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_018().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_checkpoint_018::name() const{ return "005 - " + AutoStory_Segment_05().name(); } +std::string AutoStory_checkpoint_018::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_checkpoint_018::end_text() const{ return "Fifth Gym Badge acquired (Veilstone City)."; } + +void AutoStory_checkpoint_018::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_018(env, context, options, stats); +} + +static void move_back_after_unexpected_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + stream.log("Starting battle with " + trainerid, COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + if (trainerid == "route_213_tuber_chelsea") { + DpadState dpad; + + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6); + } else if (trainerid == "route_213_tuber_jared") { + // Just need to wait, nothing else + } else if (trainerid == "route_213_beauty_cindy") { + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + } else if (trainerid == "route_210_ace_ernest") { + pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ + } +} + +static bool handle_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + context.wait_for_all_requests(); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); + if (ret_menu < 0){ + stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 500ms); + + if ( + trainerid == "pastoria_clint" or + trainerid == "route_213_tuber_chelsea" or + trainerid == "route_213_tuber_jared" or + trainerid == "route_213_beauty_cindy" or + trainerid == "route_210_veteran_brian" + ) { + // Select Razor leaf + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + + } else if ( + trainerid == "route_214_galactic_grunt" or + trainerid == "route_210_rancher_marco" or + trainerid == "route_210_breeder_amber" or + trainerid == "route_210_blackbelt_adam" + ){ + // Select Bulldoze + pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + + } else if ( + trainerid == "route_210_ace_alyssa" or + trainerid == "route_210_birdkeeper_brianna" or + trainerid == "celestic_town_galactic_grunt" + ){ + // Select Crunch + } + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); + return false; + } + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_" + trainerid + ": transition confirmed."); + return true; +} + +static bool leave_gym( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("leave_gym: walking out of gym"); + + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 6000ms, 100ms); // 34+ + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3, true); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("leave_gym: Didn't enter building!", COLOR_RED); + return false; + } + stream.log("leave_gym: transition confirmed."); + return true; +} + +static bool go_to_pastoria_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_pastoria_pokemon_center: going to pokemon center"); + + pbf_move_left_joystick(context, {+1, 0}, 900ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7, true); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret1 = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}} + ); + if (ret1 < 0){ + stream.log("go_to_pastoria_pokemon_centert: Didn't detect transition!", COLOR_RED); + return false; + } + stream.log("go_to_pastoria_pokemon_center: transition confirmed."); + return true; +} + +static bool exit_pastoria_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + return heal_pokemon(stream, context, "Pastoria"); +} + +static bool pastoria_city_drama( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("pastoria_city_drama: starting the drama"); + + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2, true); + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 10 + pbf_move_left_joystick(context, {0, +1}, 500ms, 100ms); // 3 + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 7 + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 2+ + + context.wait_for_all_requests(); + + { + ShortDialogWatcher talk_to_grunt_1(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{talk_to_grunt_1}} + ); + if (ret < 0) { + stream.log("pastoria_city_drama_talk_to_grunt_1: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("pastoria_city_drama_talk_to_grunt_1: talking to grunt!"); + pbf_mash_button(context, BUTTON_B, 8000ms); + }; + } + + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {+1, 0}, 4800ms, 100ms); // 24+ + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); + + { + ShortDialogWatcher talk_to_grunt_2(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{talk_to_grunt_2}} + ); + if (ret < 0) { + stream.log("pastoria_city_drama_talk_to_grunt_2: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("pastoria_city_drama_talk_to_grunt_2: talking to grunt!"); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret1 = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 200000ms); + }, + {{black_screen}} + ); + if (ret1 < 0){ + stream.log("pastoria_city_drama_talk_to_clint: Didn't detect start of battle with Clint!", COLOR_RED); + return false; + } + stream.log("pastoria_city_drama_talk_to_clint: battling Clint."); + handle_battle(stream, context, "pastoria_clint"); + pbf_mash_button(context, BUTTON_B, 3000ms); + }; + } + + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("pastoria_city_drama: transition not detected!", COLOR_RED); + return false; + } + stream.log("pastoria_city_drama: transition detected."); + } + + return true; +} + +static bool go_through_house_east_pastoria( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Going through the building at the entrance of Pastoria city"); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_house_east_pastoria: Didn't exit building!", COLOR_RED); + return false; + } + stream.log("go_through_house_east_pastoria: transition confirmed."); + + return true; +} + +static bool go_through_route_213_chasing( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_route_213_chasing: continuing the drama"); + + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 12 + activate_repel(stream, context); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + pbf_move_left_joystick(context, {+1, 0}, 3600ms, 100ms); // 18+ + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5, true); + + MarkDetector beauty_cindy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 15+ + }, + {{beauty_cindy_fight}} + ); + if (ret == 0){ + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213_beauty_cindy: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + context.wait_for_all_requests(); + stream.log("go_through_route_213_beauty_cindy: Starting battle."); + handle_battle(stream, context, "route_213_beauty_cindy"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 9 + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2, true); + + context.wait_for_all_requests(); + + ShortDialogWatcher talk_to_grunt(COLOR_BLUE); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}} + ); + if (ret < 0) { + stream.log("go_through_route_213_chasing_talk_to_grunt: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("go_through_route_213_chasing_talk_to_grunt: talking to grunt!"); + pbf_mash_button(context, BUTTON_B, 6000ms); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + context.wait_for_all_requests(); + + ShortDialogWatcher repel_dialog(COLOR_RED); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); + }, + {{repel_dialog}} + ); + if (ret == 0){ + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_through_route_213: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + + MarkDetector tuber_jared_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 17 + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 3 + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + }, + {{tuber_jared_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213_tuber_jared: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_route_213_tuber_jared: Starting battle."); + handle_battle(stream, context, "route_213_tuber_jared"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + + context.wait_for_all_requests(); + + MarkDetector tuber_chelsea_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + DpadState dpad; + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6, true); + }, + {{tuber_chelsea_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_route_213_tuber_chelsea: Starting battle."); + handle_battle(stream, context, "route_213_tuber_chelsea"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + + BlackScreenOverWatcher route_213_east_building(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{route_213_east_building}} + ); + if (ret < 0){ + stream.log("go_through_route_213_east_building: Didn't detect entering the building!", COLOR_RED); + return false; + }; + + return true; +} + +static bool go_through_building_route_213( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("Going through building between routes 214 and 213"); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 30000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_building_route_214: Didn't exit building!", COLOR_RED); + return false; + } + stream.log("go_through_building_route_213: transition confirmed."); + + return true; +} + +static bool go_through_route_214( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_route_214: continuing the drama"); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1, true); + pbf_move_left_joystick(context, {0, +1}, 4000ms, 100ms); // 21+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, true); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, true); + + context.wait_for_all_requests(); + + ShortDialogWatcher talk_to_grunt(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}} + ); + if (ret < 0) { + stream.log("go_through_route_214_talk_to_grunt: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("go_through_route_214_talk_to_grunt: talking to grunt!"); + pbf_mash_button(context, BUTTON_B, 6000ms); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 2200ms, 100ms); // 11+ + pbf_move_left_joystick(context, {+1, 0}, 1200ms, 100ms); // 6+ + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); + + ShortDialogWatcher talk_to_grunt_2(COLOR_BLUE); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt_2}} + ); + if (ret < 0) { + stream.log("go_through_route_214_talk_to_grunt_2: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("go_through_route_214_talk_to_grunt_2: talking to grunt!"); + context.wait_for_all_requests(); + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_214_galactic_grunt: Didn't detect battle starting!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_route_214_galactic_grunt: Starting battle."); + handle_battle(stream, context, "route_214_galactic_grunt"); + context.wait_for_all_requests(); + pbf_wait(context, 5000ms); + } + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1, true); + + ShortDialogWatcher talk_to_cynthia(COLOR_BLUE); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + {{talk_to_cynthia}} + ); + if (ret < 0) { + stream.log("go_through_route_214_talk_to_cynthia: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("go_through_route_214_talk_to_cynthia: talking to Cynthia!"); + pbf_mash_button(context, BUTTON_A, 12000ms); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 4000ms); + + fly_to(stream, context, "solaceon_town"); + + return true; +} + +static bool go_inside_solaceon_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("go_inside_solaceon_pokemon_center: going inside"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 200000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_inside_solaceon_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_inside_solaceon_pokemon_center: black screen detected!", COLOR_GREEN); + return true; + } + return true; +} + +static bool heal_at_solaceon_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("heal_at_solaceon_pokemon_center: healing pokemon"); + context.wait_for_all_requests(); + heal_pokemon(stream, context, "Solaceon"); + return true; +} + +static bool go_to_the_psyduck_route_210( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_the_psyduck_route_210: going towards the Psyducks!"); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {0, +1}, 5500ms, 100ms); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + activate_repel(stream, context); + + context.wait_for_all_requests(); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, +1}, 200ms, 100ms); // 1+ + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); + }, + {{battle_dialogue}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + stream.log("go_to_the_psyduck_route_210_rancher_marco: marker detected!", COLOR_GREEN); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_rancher_marco: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_rancher_marco"); + } + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 8+ tall grass + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ tall grass + + + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + ShortDialogWatcher battle_dialogue_2(COLOR_BLUE); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + DpadState dpad; + pbf_move_left_joystick(context, {0, +1}, 6600ms, 100ms); // 22+ tall grass + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{battle_dialogue}, {battle_dialogue_2}} + ); + if (ret >= 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + stream.log("go_to_the_psyduck_route_210_breeder_amber: marker detected!", COLOR_GREEN); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_breeder_amber: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_breeder_amber"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 4+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + ShortDialogWatcher talk_to_psyducks(COLOR_BLUE); + int ret_psyducks = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_psyducks}} + ); + if (ret_psyducks < 0) { + stream.log("go_to_the_psyduck_route_210_talk_to_psyducks: dialog box not detected!", COLOR_RED); + } + if (ret_psyducks == 0){ + stream.log("go_to_the_psyduck_route_210_talk_to_psyducks: talking to Psyducks!"); + pbf_mash_button(context, BUTTON_A, 28000ms); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9); + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + context.wait_for_all_requests(); + + activate_repel(stream, context); + + pbf_move_left_joystick(context, {0, +1}, 8000ms, 100ms); // 29+ tall grass + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_the_psyduck_route_210: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_to_the_psyduck_route_210: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 5+ + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + + context.wait_for_all_requests(); + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, +1}, 10000ms, 100ms); + }, + {{battle_dialogue}} + ); + if (ret < 0) { + stream.log("go_to_the_psyduck_route_210_ace_alyssa: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_ace_alyssa: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_ace_alyssa"); + } + } + } + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4); + + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 5+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ + }, + {{battle_dialogue}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_ace_ernest: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_ace_ernest"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_210_ace_ernest"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); + }, + {{battle_dialogue}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_blackbelt_adam: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_blackbelt_adam"); + } + } + } + + context.wait_for_all_requests(); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_the_psyduck_route_210: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_to_the_psyduck_route_210: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5); + + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); + }, + {{battle_dialogue}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_birdkeeper_brianna: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_birdkeeper_brianna"); + } + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {-1, 0}, 200ms, 100ms); // 1+ + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + + { + MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); + }, + {{battle_dialogue}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_veteran_brian: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_210_veteran_brian"); + } + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_psyduck_route_210_enter_celestic_town_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + return true; + } + + } + return true; +} + +static bool heal_at_celestic_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("heal_at_celestic_pokemon_center: healing pokemon"); + context.wait_for_all_requests(); + heal_pokemon(stream, context, "Celestic"); + return true; +} + +static bool celestic_town_drama( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("celestic_town_drama: continuing the drama."); + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + + { + ShortDialogWatcher talk_to_cynthias_grandma(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_cynthias_grandma}} + ); + if (ret < 0) { + stream.log("celestic_town_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("celestic_town_talk_to_cynthias_grandma: talking to Cynthia's grandma!"); + pbf_mash_button(context, BUTTON_B, 4000ms); + }; + } + + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, +1}, 3600ms, 100ms); // 18+ + pbf_move_left_joystick(context, {-1, 0}, 200ms, 100ms); // 1 + + { + ShortDialogWatcher talk_to_the_grunt(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {-1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_the_grunt}} + ); + if (ret < 0) { + stream.log("celestic_town_talk_to_the_grunt: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("celestic_town_talk_to_the_grunt: talking to the grunt!"); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret1 = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret1 < 0){ + stream.log("celestic_town_talk_to_the_grunt: Didn't detect start of battle with Clint!", COLOR_RED); + return false; + } + stream.log("celestic_town_talk_to_the_grunt: starting the battle."); + handle_battle(stream, context, "celestic_town_galactic_grunt"); + pbf_mash_button(context, BUTTON_B, 17000ms); + }; + } + + context.wait_for_all_requests(); + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("celestic_town_enter_cave: black screen not detected!", COLOR_RED); + return false; + } + } + + return true; +} + + +static bool discover_the_paintings( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("discover_the_paintings: going to the paintings."); + + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + + { + ShortDialogWatcher talk_to_cynthias_grandma(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_cynthias_grandma}} + ); + if (ret < 0) { + stream.log("discover_the_paintings_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); + } + if (ret == 0){ + stream.log("discover_the_paintings_talk_to_cynthias_grandma: talking to the grandma!"); + pbf_mash_button(context, BUTTON_B, 20000ms); + }; + } + context.wait_for_all_requests(); + pbf_wait(context, 5000ms); + + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 7+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("discover_the_paintings: black screen for exiting the building not detected!", COLOR_RED); + return false; + } + } + + return true; +} + +static bool talk_to_cyrus_and_fly_hearthome( + VideoStream& stream, + ProControllerContext& context +){ + pbf_press_button(context, BUTTON_L, 80ms, 300ms); + pbf_press_button(context, BUTTON_L, 80ms, 300ms); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("talk_to_cyrus_and_fly_hearthome: continuing the drama."); + + wait_for_dialogue(stream, context, "Cyrus", 10000ms); + pbf_mash_button(context, BUTTON_B, 10000ms); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + fly_to(stream, context, "hearthome_city"); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("talk_to_cyrus_and_fly_hearthome: black screen for entering pokemon center not detected!", COLOR_RED); + return false; + } + } + return true; +} + + +static bool heal_at_hearthome_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("heal_at_hearthome_pokemon_center: healing pokemon"); + context.wait_for_all_requests(); + heal_pokemon(stream, context, "Hearthome"); + return true; +} + +static bool get_into_hearthome_gym( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("get_into_hearthome_gym: getting into the gym."); + + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {+1, 0}, 7000ms, 100ms); // 38+ + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("get_into_hearthome_gym: black screen not detected!", COLOR_RED); + return false; + } + } + + return true; +} + +void checkpoint_018( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + /*if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + }*/ + if (!go_to_pastoria_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); + } + if (!exit_pastoria_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_pastoria_pokemon_center: transition not detected.", env.console); + } + if (!pastoria_city_drama(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); + } + if (!go_through_house_east_pastoria(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); + } + if (!go_through_route_213_chasing(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213_chasing: transition not detected.", env.console); + } + if (!go_through_building_route_213(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); + } + if (!go_through_route_214(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); + } + if (!go_inside_solaceon_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", env.console); + } + if (!heal_at_solaceon_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", env.console); + } + if (!go_to_the_psyduck_route_210(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_psyduck_route_210: transition not detected.", env.console); + } + if (!heal_at_celestic_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", env.console); + } + if (!celestic_town_drama(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); + } + if (!discover_the_paintings(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "discover_the_paintings: transition not detected.", env.console); + } + if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); + } + if (!heal_at_hearthome_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", env.console); + } + if (!get_into_hearthome_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); + } + } + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h new file mode 100644 index 0000000000..7c39818d4d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Segment 05 + * From after acquiring the fourth badge until after acquiring the fifth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment05_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment05_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_05 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_018 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: fourth badge acquired +// end: fifth Gym Badge acquired (Veilstone City) +void checkpoint_018( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp new file mode 100644 index 0000000000..e0707cc8d6 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -0,0 +1,104 @@ +/* BDSP AutoStory - Segment 06 + * From after acquiring the fifth badge until after acquiring the sixth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP_AutoStory_Segment_06.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +std::string AutoStory_Segment_06::name() const{ return "06: Fifth Badge to Sixth Badge"; } +std::string AutoStory_Segment_06::start_text() const{ return "Start: Fifth Gym Badge acquired (Veilstone City)."; } +std::string AutoStory_Segment_06::end_text() const{ return "End: Sixth Gym Badge acquired (Canalave City)."; } + +void AutoStory_Segment_06::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_019().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_019::name() const{ return "006 - " + AutoStory_Segment_06().name(); } +std::string AutoStory_Checkpoint_019::start_text() const{ return "Fifth Gym Badge acquired (Veilstone City)."; } +std::string AutoStory_Checkpoint_019::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } + +void AutoStory_Checkpoint_019::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_019(env, context, options, stats); +} + +static bool test_opening_menu( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("test_opening_menu: testing"); + + open_menu(stream, context, MenuCursorPosition::BAG, 8); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + open_menu(stream, context, MenuCursorPosition::POKEDEX, 8); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + open_menu(stream, context, MenuCursorPosition::CARD, 8); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + + return true; +} + + +void checkpoint_019( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 06 gameplay logic + if (!test_opening_menu(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "test_opening_menu: transition not detected.", env.console); + } + } + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h new file mode 100644 index 0000000000..9a4f47a707 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Segment 06 + * From after acquiring the fifth badge until after acquiring the sixth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment06_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment06_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_06 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_019 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: fifth badge acquired +// end: sixth Gym Badge acquired (Canalave City) +void checkpoint_019( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp new file mode 100644 index 0000000000..ab4f0e3b5f --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -0,0 +1,68 @@ +/* BDSP AutoStory - Segment 07 + * From after acquiring the sixth badge until after acquiring the seventh badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_07.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +std::string AutoStory_Segment_07::name() const{ return "07: Sixth Badge to Seventh Badge"; } +std::string AutoStory_Segment_07::start_text() const{ return "Start: Sixth Gym Badge acquired (Canalave City)."; } +std::string AutoStory_Segment_07::end_text() const{ return "End: Seventh Gym Badge acquired (Snowpoint City)."; } + +void AutoStory_Segment_07::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_020().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_020::name() const{ return "007 - " + AutoStory_Segment_07().name(); } +std::string AutoStory_Checkpoint_020::start_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } +std::string AutoStory_Checkpoint_020::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } + +void AutoStory_Checkpoint_020::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_020(env, context, options, stats); +} + + +void checkpoint_020( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 07 gameplay logic + } + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h new file mode 100644 index 0000000000..cb447ac81d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Segment 07 + * From after acquiring the sixth badge until after acquiring the seventh badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment07_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment07_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_07 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_020 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: sixth badge acquired +// end: seventh Gym Badge acquired (Snowpoint City) +void checkpoint_020( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp new file mode 100644 index 0000000000..c20923bb21 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -0,0 +1,139 @@ +/* BDSP AutoStory - Segment 08 + * From after acquiring the seventh badge until after acquiring the eighth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_08.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// Legendary encounter (from Routes/Legendary_Segment.cpp) +// --------------------------------------------------------------------------- + +static void approach_legendary(VideoStream& /*stream*/, ProControllerContext& context){ + pbf_wait(context, 2000ms); + pbf_press_button(context, BUTTON_A, 100ms, 300ms); + pbf_wait(context, 2000ms); +} + +static void enter_legendary_battle(VideoStream& /*stream*/, ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 3000ms); +} + +static void weaken_legendary(VideoStream& /*stream*/, ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 1500ms); + pbf_press_dpad(context, DPAD_UP, 100ms, 100ms); + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 2000ms); +} + +static void throw_pokeball_at_legendary(VideoStream& /*stream*/, ProControllerContext& context){ + for (int i = 0; i < 3; i++){ + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 1000ms); + pbf_press_dpad(context, DPAD_DOWN, 100ms, 100ms); + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 2500ms); + } +} + +static void confirm_legendary_catch(VideoStream& /*stream*/, ProControllerContext& context){ + pbf_wait(context, 1500ms); + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 2000ms); +} + +static void exit_legendary_encounter(VideoStream& /*stream*/, ProControllerContext& context){ + pbf_press_button(context, BUTTON_A, 100ms, 100ms); + pbf_wait(context, 2000ms); +} + + +// --------------------------------------------------------------------------- +// Segment / Checkpoint classes +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_08::name() const{ return "08: Seventh Badge to Eighth Badge"; } +std::string AutoStory_Segment_08::start_text() const{ return "Start: Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Segment_08::end_text() const{ return "End: Eighth Gym Badge acquired (Sunyshore City)."; } + +void AutoStory_Segment_08::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); + checkpoint_021_legendary(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_021::name() const{ return "008 - " + AutoStory_Segment_08().name(); } +std::string AutoStory_Checkpoint_021::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Checkpoint_021::end_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } + +void AutoStory_Checkpoint_021::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_021(env, context, options, stats); +} + + +void checkpoint_021( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 08 gameplay logic (seventh badge area) + } + ); +} + +void checkpoint_021_legendary( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + approach_legendary(env.console, context); + enter_legendary_battle(env.console, context); + weaken_legendary(env.console, context); + throw_pokeball_at_legendary(env.console, context); + confirm_legendary_catch(env.console, context); + exit_legendary_encounter(env.console, context); + } + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h new file mode 100644 index 0000000000..c15b39b43a --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -0,0 +1,66 @@ +/* BDSP AutoStory - Segment 08 + * From after acquiring the seventh badge until after acquiring the eighth badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment08_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment08_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_08 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: seventh badge acquired +// end: legendary encounter (Spear Pillar / Mt. Coronet) +void checkpoint_021( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +// start: legendary encounter +// end: eighth Gym Badge acquired (Sunyshore City) +void checkpoint_021_legendary( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp new file mode 100644 index 0000000000..7c3277625b --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -0,0 +1,68 @@ +/* BDSP AutoStory - Segment 09 + * From after acquiring the eighth badge until defeating the Elite Four and Champion. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_09.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +std::string AutoStory_Segment_09::name() const{ return "09: Eighth Badge to Champion"; } +std::string AutoStory_Segment_09::start_text() const{ return "Start: Eighth Gym Badge acquired (Sunyshore City)."; } +std::string AutoStory_Segment_09::end_text() const{ return "End: Elite Four and Champion defeated, credits roll."; } + +void AutoStory_Segment_09::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_022().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_022::name() const{ return "009 - " + AutoStory_Segment_09().name(); } +std::string AutoStory_Checkpoint_022::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } +std::string AutoStory_Checkpoint_022::end_text() const{ return "Elite Four and Champion defeated, credits roll."; } + +void AutoStory_Checkpoint_022::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_022(env, context, options, stats); +} + + +void checkpoint_022( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 09 gameplay logic + } + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h new file mode 100644 index 0000000000..b01813e1a4 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Segment 09 + * From after acquiring the eighth badge until defeating the Elite Four and Champion. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment09_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment09_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class AutoStory_Segment_09 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_Checkpoint_022 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: eighth badge acquired +// end: Elite Four and Champion defeated, credits roll +void checkpoint_022( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md new file mode 100644 index 0000000000..d032dc15e7 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md @@ -0,0 +1,817 @@ +these are the counts and direction inputs from my segement beginning in the player's room to the entrance of the 3rd gym (fighting gym) + + +U2 +R6 +U1 +R2 +Amash +D5 +L4 +Amash +D1 +L8 +U10 +L4 +Amash +U2 +U3 +L5 +U4 +R1 +Amash +U1 +L1 +D8 +R5 +D2 +R5 +U19 +Amash +U3 +L30 +U9 +Starter select program +A mash +D8 +R20 +Amash +D3 +L4 +u33 +r34 +u8 +r18 +amash +b +a +amash +pokedex received +d9 +amash +l36 +d6 +l38 +d34 +r3 +u4 +r1 +amash +b +amash +l1 +d3 +amash +l3 +u31 +r25 +u2 +r8 +u7 +r35 +u9 +r3 +u10 +l5 +amash +catch a pokemon +l13 +u9 +r1 +youngster Tristan +r10 +d2 +r6 +lass Natalie +r6 +u11 +l3 +youngster logan +l11 +u12 +amash +u19 +r5 +u7 +heal pokecenter +a +bmash +r4 +u3 +deposit starter +home transfer +withdraw starter +d3 +l4 +d6 +l13 +u9 +l1 +a +bmash +d8 +r6 +u2 +amash +poketech challenge begins +r3 +u9 +r7 +d2 +amash +u2 +l10 +u6 +l1 +u10 +r1 +amash +d5 +l22 +u2 +amash +d2 +r30 +d6 +r1 +d8 +r1 +amash +poketech acquired +b +u11 +r23 +barry 1 +bmash +r19 +u8 +r11 +d4 +r17 +u7 +r6 +rock smash acquired +bmash +r9 +d4 +r2 +u3 +r5 +u4 +r2 +d2 +r10 +oreburg tour +d2 +r20 +u37 +catch machop +d42 +r3 +d61 +r2 +d2 +l1 +d6 +l11 +d2 +l2 +d4 +r3 +d4 +l1 (pick up escape rope) +r2 +d1 +r11 +u1 +a +bmash +escape rope +u33 +l20 +u8 +l3 +u4 +r7 +u10 +l4 +u3 +save +roark +d20 +r4 +u10 +l1 +u5 +purchases +d4 +l7 +u3 +machop trade +d3 +l12 +d2 +l4 +barry text bmash +l3 +d1 +l4 +u2 +l2 +d4 +l5 +d2 +l2 +u3 +l8 +u1 +l4 +u3 +l17 +d9 +l4 +u3 +l11 +d4 +l8 +d1 +l32 +u13 +l1 +u2 +double battle +bmash +u19 +l1 +u15 +l2 +u8 +r2 +amash +r7 +d2 +l8 +u5 +r9 +u10 +box all but 1 pokemon +l11 +u24 +r32 +u1 +a +bmash +r1 +u1 +a +bmash +r8 +bmash +l37 +u15 +l11 +u9 +bmash +battle +amash +battle +bmash +d25 +r60 +d2 +r19 +u6 +r1 +u1 +battle +amash +u1 +amash +u1 +amash +l10 +u7 +battle +u1 +r6 +d1 +r8 +u1 +r3 +mars1 +bmash +l6 +d1 +l8 +u1 +l2 +d7 +r9 +d6 +l1 +d3 +l24 +u9 +l4 +u11 +l6 +u24 +l2 +u9 +r2 +u7 +l2 +u21 +Cheryl +Bmash +R3 +U1 +R4 +U11 +R4 +U5 +Double battle +U3 +R4 +U6 +R7 +D7 +battle +R1 +D1 +Battle +D12 +R12 +U17 +R22 +U16 +L7 +U9 +R5 +Cheryl +R7 +D5 +R10 +D4 +R38 +U6 +R1 +Undergournd man +A +Bmash +Y +D1 +A +A +Bmash +D3 +L3 +D8 +R1 +D21 +R5 +U1 +Gardenia gym +U7 +A +Bmash +U6 +L1 +U9 +R1 +U6 +L2 +U1 +l4 +d3 +battle +u5 +l5 +d7 +r1 +d5 +l1 +d3 +l1 +d5 +r4 +a +battle +bmash +l4 +u5 +r1 +u4 +r1 +u4 +l1 +u7 +r6 +d1 +r4 +d5 +r1 +d2 +l1 +d4 +r5 +u2 +r1 +l1 +d2 +l5 +d5 +r4 +d1 +r6 +u1 +r1 +u16 +l5 +battle +r5 +d16 +l4 +d1 +l7 +u10 +r1 +u6 +l1 +u6 +a +gardenia +bmash +d6 +r1 +d6 +l1 +d13 +r1 +d9 +l8 +u32 +l4 +u7 +r3 +Cynthia +Bmash +R2 +U2 +Amash +U7 +R4 +U2 +Battle +U1 +L1 +D2 +R7 +U1 +L1 +D2 +R2 +Battle +R5 +U1 +L1 +D4 +R3 +D1 +A +Jupiter1 +Bmash +U3 +L2 +U1 +R1 +D2 +L7 +U1 +R1d2 +L7 +U1 +R1 +D9 +L4 +D1 +Amash +D5 +L4 +D7 +R5 +D9 +R4 +U6 +L4 +U1 +Bike obtained +A +Amash +Bmash +Register bike ++ +L6 +D54 +Battle +Wait +R1 +Battle +Battle +Battle +L1 +L3 +D1 ++ +D28 +Item finder +Bmash +D2 +R3 +A +D6 +R4 +D8 +R13 +D2 +battle +U1 +R7 +U3 +R4 +Battle +D1 +R7 +U4 +L4 +U1 +Battle +L1 +U2 +R7 +U2 +R1 +Repel +D15 +R10 +Boss +Bmash +U6 +L1 +A +U1 +R1 +U3 +R4 +D1 +R5 +U2 +R3 +D7 +R17 +Battle +R11 +U2 +r1 +a +battle +d1 +r3 +u8 +r5 +d13 +r37 +buneary +bmash +r24 +u12 +l16 +u16 +l4 +u7 +a +a +a +a +a +bmash +u6 +r14 +u5 +a +Fatima +a +Bmash +U3 +Keira +Bmash +D10 +R19 +d10 +l21 +u5 +buy repels +bmash +d9 +r8 +d2 +barry 2 +d9 +r35 +u3 +r11 +u1 +r21 +u6 +r6 +u3 +r7 +u13 +l4 +u2 +l1 +u9 +r10 +u12 +l2 +u5 +l1 +u1 +r1 +d8 +l2 +d2 +l8 +u7 +r2 +u2 +r2 +d5 +r1 +d2 +l1 +d4 +l4 +u2 +l1 +u3 +l5 +u3 +r2 +u2 +r1 +d5 +l7 +u2 +l1 +u2 +r1 +d3 +l4 +d2 +a +a +a +a +a +bmash +escape rope +d3 +l4 +u8 +l3 +u45 +l7 +u11 +r10 +u18 +r9 +u4 +r19 +u11 +l4 +d4 +r1 +battle +r10 +amash +r6 +d8 +r6 +u1 +amash +bmash +r3 +u16 +l13 +u4 +r6 +d4 +a +battle +r1 +d7 +r16 +u7 +r10 +d2 +r28 +u6 +r1 +amash +d6 +l6 +d3 +r1 +amash +r5 +u1 +amash +l7 +u2 +l2 +d2 +l6 +d6 +r1 +a +battle +d1 +r1 +battle +r48 +d10 +r11 +d10 +r7 +u6 +r5 +u7 +a +a +a +a +bmash +d6 +l5 +u6 +l8 +u2 +l3 +u3 +r4 +u7 +r1 +l1 +a +a +d2 +a +a +a +r1 +d3 +l9 +d4 +l2 +d1 +buy TM29 psychic +buy TM21 dazzling gleam +sell tms +r3 +u1 +r1 +u4 +l9 +buy x items +d2 +l1 +buy vitamins +d2 +l1 +get counter app +r3 +d4 +l3 +d2 +r7 +d9 +r2 +d4 +r12 +d2 +r4 +get metronome +a +bmash +d10 +l30 +d2 +l12 +u16 +dawn +bmash +u4 + + + diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp new file mode 100644 index 0000000000..a95af419b7 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -0,0 +1,353 @@ +/* BDSP AutoStory - Battle Utilities + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/OCR/OCR_RawOCR.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "Pokemon/Pokemon_StatsCalculation.h" +#include "PokemonSwSh/Resources/PokemonSwSh_TypeMatchup.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h" +#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "PokemonBDSP_AutoStory_Battle.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using namespace Pokemon; +using namespace PokemonSwSh; +using namespace std::chrono_literals; + + +// --------------------------------------------------------------------------- +// run_battle_press_A +// --------------------------------------------------------------------------- + +void run_battle_press_A( + const ProgramInfo& /*info*/, + VideoStream& stream, + ProControllerContext& context +){ + EndBattleWatcher end_battle; + run_until( + stream, context, + [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, + {{end_battle}} + ); + + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, + {{overworld}} + ); +} + + +// --------------------------------------------------------------------------- +// handle_unexpected_battle +// --------------------------------------------------------------------------- + +void handle_unexpected_battle( + const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + std::function&& action, + std::function&& recovery_action, + size_t max_attempts +){ + for (size_t attempt = 0; attempt < max_attempts; attempt++){ + StartBattleDetector start_battle(stream.overlay()); + int result = run_until( + stream, context, + [&](ProControllerContext& ctx){ action(info, stream, ctx); }, + {{start_battle}} + ); + if (result < 0){ + return; + } + run_battle_press_A(info, stream, context); + recovery_action(info, stream, context); + } + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "handle_unexpected_battle: exceeded max attempts", + stream + ); +} + + +// --------------------------------------------------------------------------- +// Internal helpers for handle_battle_with_outside_help +// --------------------------------------------------------------------------- + +static constexpr int TORTERRA_ATK = 238; +static constexpr int TORTERRA_SPATK = 170; + +// ⚠️ Estimated PP regions (fight menu, 2×2 move grid); require calibration. +static const ImageFloatBox PP_BOX[4] = { + {0.36, 0.590, 0.10, 0.060}, + {0.75, 0.590, 0.10, 0.060}, + {0.36, 0.730, 0.10, 0.060}, + {0.75, 0.730, 0.10, 0.060}, +}; + +static int ocr_pp_slot(Logger& logger, const ImageViewRGB32& frame, const ImageFloatBox& box){ + ImageViewRGB32 region = extract_box_reference(frame, box); + std::string text = OCR::ocr_read(Language::English, region, OCR::PageSegMode::SINGLE_LINE); + text.erase(0, text.find_first_not_of(" \t\r\n")); + if (!text.empty()) text.erase(text.find_last_not_of(" \t\r\n") + 1); + + size_t slash = text.find('/'); + std::string num_str = (slash != std::string::npos) ? text.substr(0, slash) : text; + size_t digit_pos = num_str.find_first_of("0123456789"); + if (digit_pos == std::string::npos){ + logger.log("PP OCR: no digit in \"" + text + "\"", COLOR_YELLOW); + return -1; + } + try { + return std::stoi(num_str.substr(digit_pos)); + } catch (...) { + logger.log("PP OCR: parse failed \"" + num_str + "\"", COLOR_YELLOW); + return -1; + } +} + +static std::array read_all_pp(Logger& logger, VideoStream& stream){ + VideoSnapshot frame = stream.video().snapshot(); + std::array result; + for (int i = 0; i < 4; i++) result[i] = ocr_pp_slot(logger, frame, PP_BOX[i]); + return result; +} + +static std::pair damage_range(int level, int base_power, int atk, int def, double type_mult){ + if (base_power == 0) return {0, 0}; + int base = (((2 * level) / 5 + 2) * base_power * atk / def) / 50 + 2; + return {(int)(base * 85.0 / 100.0 * type_mult), (int)(base * type_mult)}; +} + +struct MoveScore { + int lower_sum = 0; + int upper_sum = 0; + bool ohko_all = false; +}; + +static MoveScore score_move( + const BDSPMoveData& mdata, + const std::vector& on_field, + const BDSPTrainerData& trainer_data +){ + if (mdata.category == MoveCategory::STATUS) return {}; + + int atk_stat = (mdata.category == MoveCategory::PHYSICAL) ? TORTERRA_ATK : TORTERRA_SPATK; + + MoveScore score; + score.ohko_all = !on_field.empty(); + + for (const std::string& slug : on_field){ + if (slug.empty()){ score.ohko_all = false; continue; } + + const BDSPTrainerPokemon* tp = nullptr; + for (const BDSPTrainer& trainer : trainer_data.trainers){ + for (const BDSPTrainerPokemon& mon : trainer.pokemon){ + if (mon.species == slug){ tp = &mon; break; } + } + if (tp) break; + } + if (!tp){ score.ohko_all = false; continue; } + + const BDSPPokemonData& pdata = bdsp_pokemon(slug); + uint16_t hp = calc_stats_hp(pdata.hp, tp->level, tp->ivs.hp, tp->evs.hp); + uint16_t def = (mdata.category == MoveCategory::PHYSICAL) + ? calc_stats_nonhp(pdata.defense, tp->level, tp->ivs.defense, tp->evs.defense, NatureAdjustment::NEUTRAL) + : calc_stats_nonhp(pdata.spdef, tp->level, tp->ivs.spdef, tp->evs.spdef, NatureAdjustment::NEUTRAL); + + double mult = type_multiplier(mdata.type, pdata.type[0], pdata.type[1]); + if (mdata.spread && trainer_data.battle_type == BDSPBattleType::DOUBLES) mult *= 0.75; + + auto [lo, hi] = damage_range(100, (int)mdata.base_power, atk_stat, (int)def, mult); + score.lower_sum += lo; + score.upper_sum += hi; + if (lo < (int)hp) score.ohko_all = false; + } + return score; +} + +static void navigate_to_slot(ProControllerContext& context, int slot){ + if (slot == 1 || slot == 3) pbf_press_dpad(context, DPAD_RIGHT, 10ms, 40ms); + if (slot == 2 || slot == 3) pbf_press_dpad(context, DPAD_DOWN, 10ms, 40ms); +} + + +// --------------------------------------------------------------------------- +// handle_battle_with_outside_help +// --------------------------------------------------------------------------- + +void handle_battle_with_outside_help( + const ProgramInfo& /*info*/, + VideoStream& stream, + ProControllerContext& context, + const std::string& trainer_id, + const AutoStoryOptions& options +){ + Logger& logger = stream.logger(); + const BDSPTrainerData& trainer_data = bdsp_trainer(trainer_id); + const OutsideHelpConfig& help = options.outside_help; + + std::array move_slugs; + for (int i = 0; i < 4; i++) move_slugs[i] = torterra_move_slug(help.attacks[i]); + + std::array pp_remaining; + for (int i = 0; i < 4; i++) pp_remaining[i] = (int)bdsp_move(move_slugs[i]).pp; + + std::vector on_field; + for (const BDSPTrainer& trainer : trainer_data.trainers){ + if (!trainer.pokemon.empty()) on_field.push_back(trainer.pokemon[0].species); + } + + bool battle_active = true; + while (battle_active){ + + // Wait for Torterra's battle menu + { + BattleMenuWatcher menu(BattleType::TRAINER); + EndBattleWatcher end; + int r = wait_until(stream, context, 120s, {{menu}, {end}}); + if (r == 1 || r < 0){ battle_active = false; break; } + } + + // Open Fight menu + pbf_press_button(context, BUTTON_A, 10ms, 60ms); + context.wait_for_all_requests(); + context.wait_for(300ms); + + // OCR current PP + { + auto ocr_pp = read_all_pp(logger, stream); + for (int i = 0; i < 4; i++){ + if (ocr_pp[i] >= 0) pp_remaining[i] = ocr_pp[i]; + else logger.log("PP OCR failed slot " + std::to_string(i) + "; using tracked", COLOR_YELLOW); + } + } + + // Check for all-zero PP (Struggle) + bool all_zero = true; + for (int pp : pp_remaining) if (pp > 0){ all_zero = false; break; } + + if (all_zero){ + logger.log("All PP at 0 — using Struggle", COLOR_YELLOW); + pbf_press_button(context, BUTTON_B, 10ms, 60ms); + context.wait_for_all_requests(); + } else { + // Score all usable moves + int best_slot = -1, best_hi = -1; + bool best_ohko = false; + + for (int i = 0; i < 4; i++){ + if (pp_remaining[i] <= 0) continue; + MoveScore s = score_move(bdsp_move(move_slugs[i]), on_field, trainer_data); + if (bdsp_move(move_slugs[i]).category == MoveCategory::STATUS) continue; + + bool prefer = (best_slot < 0) + || (s.ohko_all && !best_ohko) + || (s.ohko_all && best_ohko && s.upper_sum < best_hi) + || (!s.ohko_all && !best_ohko && s.upper_sum > best_hi); + + if (prefer){ best_slot = i; best_hi = s.upper_sum; best_ohko = s.ohko_all; } + } + if (best_slot < 0){ + for (int i = 0; i < 4; i++) if (pp_remaining[i] > 0){ best_slot = i; break; } + } + + logger.log( + "Move slot " + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ")" + + (best_ohko ? " [OHKO]" : " [best effort]"), + COLOR_WHITE + ); + + navigate_to_slot(context, best_slot); + pbf_mash_button(context, BUTTON_A, 500ms); + context.wait_for_all_requests(); + pp_remaining[best_slot]--; + + // Single-target in doubles: confirm default target + if (!bdsp_move(move_slugs[best_slot]).spread + && trainer_data.battle_type == BDSPBattleType::DOUBLES) + { + pbf_press_button(context, BUTTON_A, 10ms, 60ms); + context.wait_for_all_requests(); + } + + // Mash through target confirmation and partner's move selection + pbf_mash_button(context, BUTTON_A, 3000ms); + context.wait_for_all_requests(); + } + + // Wait for next event + { + BattleMenuWatcher menu2(BattleType::TRAINER); + ExperienceGainWatcher exp; + EndBattleWatcher end; + + int event = wait_until(stream, context, 60s, {{menu2}, {exp}, {end}}); + + if (event == 2 || event < 0){ + battle_active = false; + } else if (event == 1){ + // XP gain — mash through, then update on_field via OCR + BattleMenuWatcher menu3(BattleType::TRAINER); + EndBattleWatcher end2; + int after = run_until( + stream, context, + [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, + {{menu3}, {end2}} + ); + if (after == 1 || after < 0){ + battle_active = false; + } else { + // Update on_field via OCR + VideoSnapshot frame = stream.video().snapshot(); + std::vector names = read_battle_opponent_names( + logger, options.language, frame, trainer_data.battle_type + ); + for (size_t i = 0; i < names.size() && i < on_field.size(); i++){ + if (!names[i].empty() && names[i] != on_field[i]){ + logger.log("Slot " + std::to_string(i) + ": " + on_field[i] + " → " + names[i], COLOR_WHITE); + on_field[i] = names[i]; + } + } + } + } + // event == 0: next turn, loop continues + } + } + + // Mash through XP/dialog until overworld + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, + {{overworld}} + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h new file mode 100644 index 0000000000..fc97a4b63b --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h @@ -0,0 +1,89 @@ +/* BDSP AutoStory - Battle Utilities + * + * From: https://github.com/PokemonAutomation/ + * + * Helpers for handling trainer battles and unexpected wild encounters. + * These wrap existing BDSP battle detectors: + * - BattleMenuDetector / BattleMenuWatcher (PokemonBDSP_BattleMenuDetector.h) + * - EndBattleWatcher (PokemonBDSP_EndBattleDetector.h) + * - StartBattleDetector (PokemonBDSP_StartBattleDetector.h) + * - ExperienceGainWatcher (PokemonBDSP_ExperienceGainDetector.h) + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Battle_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Battle_H + +#include +#include +#include "CommonFramework/Tools/VideoStream.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ + struct ProgramInfo; +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// Scripted (expected) battles +// --------------------------------------------------------------------------- + +// Mash A through a battle (trainer or scripted wild) until EndBattleWatcher fires. +// This does not attempt to catch — it simply presses A to select the first move +// repeatedly until the battle ends. +// After the black-screen transition and experience gain, returns to the caller. +void run_battle_press_A( + const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context +); + + +// --------------------------------------------------------------------------- +// Unexpected battle handling +// --------------------------------------------------------------------------- + +// Run `action`. If a StartBattleDetector fires during `action`, the action +// is aborted, the battle is resolved with run_battle_press_A(), and then +// `recovery_action` is called to restore the player's position before +// retrying `action`. +// The whole sequence retries up to `max_attempts` times. +// Throws OperationFailedException if max_attempts is exceeded. +void handle_unexpected_battle( + const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + std::function&& action, + std::function&& recovery_action, + size_t max_attempts = 5 +); + + +// --------------------------------------------------------------------------- +// Outside-help battle handler +// --------------------------------------------------------------------------- + +// Handle a scripted trainer battle where a human helper (Torterra lv100) joins. +// Each turn it: +// 1. OCRs current PP for all 4 configured moves. +// 2. Calculates damage against all on-field opponents. +// 3. Navigates to and selects the optimal move (guaranteed OHKO if possible; +// highest total damage otherwise). +// 4. Mashes A to cover target selection and partner's move. +// 5. After XP gain, updates on-field opponents via OCR. +// Returns when the battle ends and the player is back in the overworld. +void handle_battle_with_outside_help( + const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + const std::string& trainer_id, + const AutoStoryOptions& options +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp new file mode 100644 index 0000000000..99d341353f --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp @@ -0,0 +1,44 @@ +/* BDSP AutoStory - Dialog Utilities + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP_AutoStory_Dialog.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +void clear_dialog( + VideoStream& stream, + ProControllerContext& context, + ClearDialogMode mode, + uint16_t timeout_sec +){ + // TODO: implement + // - use ShortDialogWatcher + (optionally) ShortDialogPromptDetector + // + OverworldDetector (from Detect/) with run_until<> + // - loop mashing A until stop condition fires + (void)stream; (void)context; (void)mode; (void)timeout_sec; +} + +void wait_for_dialog( + VideoStream& stream, + ProControllerContext& context, + uint16_t timeout_sec +){ + // TODO: implement + // - use wait_until() with ShortDialogWatcher + (void)stream; (void)context; (void)timeout_sec; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h new file mode 100644 index 0000000000..750a40e3a4 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h @@ -0,0 +1,68 @@ +/* BDSP AutoStory - Dialog Utilities + * + * From: https://github.com/PokemonAutomation/ + * + * Helpers for clearing NPC dialog and waiting for dialog to appear. + * These wrap the existing BDSP dialog detectors: + * - ShortDialogDetector / ShortDialogWatcher (PokemonBDSP_DialogDetector.h) + * - ShortDialogPromptDetector (PokemonBDSP_DialogDetector.h) + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Dialog_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Dialog_H + +#include +#include "CommonFramework/Tools/VideoStream.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" + +namespace PokemonAutomation{ + struct ProgramInfo; +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// Stop conditions for clear_dialog() +// --------------------------------------------------------------------------- + +enum class ClearDialogMode{ + // Stop once the dialog has closed and the overworld is detected. + STOP_OVERWORLD, + + // Stop when a YES/NO prompt (ShortDialogPromptDetector) is detected. + STOP_PROMPT, + + // Run for exactly `timeout_sec` seconds regardless of screen state. + STOP_TIMEOUT, +}; + + +// --------------------------------------------------------------------------- +// Dialog clearing +// --------------------------------------------------------------------------- + +// Mash A through NPC dialog until `mode` stop condition is met. +// Uses ShortDialogWatcher as the primary "still in dialog" signal. +// Throws OperationFailedException if no dialog is ever detected, or if +// the stop condition is not met within `timeout_sec` seconds. +void clear_dialog( + VideoStream& stream, + ProControllerContext& context, + ClearDialogMode mode, + uint16_t timeout_sec = 60 +); + +// Block until a short dialog box appears, then return. +// Throws OperationFailedException if no dialog appears within `timeout_sec`. +void wait_for_dialog( + VideoStream& stream, + ProControllerContext& context, + uint16_t timeout_sec = 30 +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp new file mode 100644 index 0000000000..6ecf21f39e --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp @@ -0,0 +1,57 @@ +/* BDSP AutoStory - Navigation Utilities + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" +#include "PokemonBDSP_AutoStory_Navigation.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +void walk_steps(ProControllerContext& context, uint16_t direction, uint16_t steps){ + // TODO: implement — press D-pad `direction` for `steps` tiles + (void)context; (void)direction; (void)steps; +} + +void walk_until_dialog( + VideoStream& stream, + ProControllerContext& context, + uint16_t direction, + uint16_t timeout_sec +){ + // TODO: implement — walk in direction; stop when ShortDialogWatcher fires + (void)stream; (void)context; (void)direction; (void)timeout_sec; +} + +void walk_until_battle( + VideoStream& stream, + ProControllerContext& context, + uint16_t direction, + uint16_t timeout_sec +){ + // TODO: implement — walk in direction; stop when StartBattleDetector fires + (void)stream; (void)context; (void)direction; (void)timeout_sec; +} + +void recalibrate_after_battle( + const ProgramInfo& /*info*/, + VideoStream& /*stream*/, + ProControllerContext& context, + std::function&& recovery_action +){ + // TODO: implement — wait for overworld, then run recovery_action + recovery_action(context); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h new file mode 100644 index 0000000000..186f1c01d4 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h @@ -0,0 +1,82 @@ +/* BDSP AutoStory - Navigation Utilities + * + * From: https://github.com/PokemonAutomation/ + * + * Helpers for moving the player character in the BDSP overworld. + * BDSP uses a tile-based grid, so most movement is fixed D-pad sequences. + * Detection-based stop conditions and post-battle recalibration are also here. + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Navigation_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Navigation_H + +#include +#include "CommonFramework/Tools/VideoStream.h" +#include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" + +namespace PokemonAutomation{ + struct ProgramInfo; +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// Fixed-sequence walking (no detection — pure tile counting) +// --------------------------------------------------------------------------- + +// Walk `steps` tiles in the given D-pad direction. +// direction must be one of DPAD_UP / DPAD_DOWN / DPAD_LEFT / DPAD_RIGHT. +void walk_steps(ProControllerContext& context, uint16_t direction, uint16_t steps); + + +// --------------------------------------------------------------------------- +// Detection-guided walking (walk until a screen state is detected) +// --------------------------------------------------------------------------- + +// Walk in `direction` until a short dialog box is detected, or `timeout_sec` elapses. +// Throws OperationFailedException on timeout. +void walk_until_dialog( + VideoStream& stream, + ProControllerContext& context, + uint16_t direction, + uint16_t timeout_sec = 30 +); + +// Walk in `direction` until a battle starts (StartBattleDetector fires), +// or `timeout_sec` elapses. +// Throws OperationFailedException on timeout. +void walk_until_battle( + VideoStream& stream, + ProControllerContext& context, + uint16_t direction, + uint16_t timeout_sec = 30 +); + + +// --------------------------------------------------------------------------- +// Post-battle recalibration +// +// BDSP movement is deterministic on a grid, but a random encounter shifts +// the player's position unpredictably (post-battle exit tile varies). +// These functions restore the player to a known reference tile by navigating +// to a fixed landmark (e.g. a building entrance). +// --------------------------------------------------------------------------- + +// After an unexpected battle, navigate to the nearest known landmark and +// re-establish a known tile position. The exact recovery sequence depends +// on the calling segment and is passed as a lambda. +// `recovery_action` should move the player from wherever they ended up back +// to a defined starting tile for the current checkpoint. +void recalibrate_after_battle( + const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + std::function&& recovery_action +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md new file mode 100644 index 0000000000..d032dc15e7 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md @@ -0,0 +1,817 @@ +these are the counts and direction inputs from my segement beginning in the player's room to the entrance of the 3rd gym (fighting gym) + + +U2 +R6 +U1 +R2 +Amash +D5 +L4 +Amash +D1 +L8 +U10 +L4 +Amash +U2 +U3 +L5 +U4 +R1 +Amash +U1 +L1 +D8 +R5 +D2 +R5 +U19 +Amash +U3 +L30 +U9 +Starter select program +A mash +D8 +R20 +Amash +D3 +L4 +u33 +r34 +u8 +r18 +amash +b +a +amash +pokedex received +d9 +amash +l36 +d6 +l38 +d34 +r3 +u4 +r1 +amash +b +amash +l1 +d3 +amash +l3 +u31 +r25 +u2 +r8 +u7 +r35 +u9 +r3 +u10 +l5 +amash +catch a pokemon +l13 +u9 +r1 +youngster Tristan +r10 +d2 +r6 +lass Natalie +r6 +u11 +l3 +youngster logan +l11 +u12 +amash +u19 +r5 +u7 +heal pokecenter +a +bmash +r4 +u3 +deposit starter +home transfer +withdraw starter +d3 +l4 +d6 +l13 +u9 +l1 +a +bmash +d8 +r6 +u2 +amash +poketech challenge begins +r3 +u9 +r7 +d2 +amash +u2 +l10 +u6 +l1 +u10 +r1 +amash +d5 +l22 +u2 +amash +d2 +r30 +d6 +r1 +d8 +r1 +amash +poketech acquired +b +u11 +r23 +barry 1 +bmash +r19 +u8 +r11 +d4 +r17 +u7 +r6 +rock smash acquired +bmash +r9 +d4 +r2 +u3 +r5 +u4 +r2 +d2 +r10 +oreburg tour +d2 +r20 +u37 +catch machop +d42 +r3 +d61 +r2 +d2 +l1 +d6 +l11 +d2 +l2 +d4 +r3 +d4 +l1 (pick up escape rope) +r2 +d1 +r11 +u1 +a +bmash +escape rope +u33 +l20 +u8 +l3 +u4 +r7 +u10 +l4 +u3 +save +roark +d20 +r4 +u10 +l1 +u5 +purchases +d4 +l7 +u3 +machop trade +d3 +l12 +d2 +l4 +barry text bmash +l3 +d1 +l4 +u2 +l2 +d4 +l5 +d2 +l2 +u3 +l8 +u1 +l4 +u3 +l17 +d9 +l4 +u3 +l11 +d4 +l8 +d1 +l32 +u13 +l1 +u2 +double battle +bmash +u19 +l1 +u15 +l2 +u8 +r2 +amash +r7 +d2 +l8 +u5 +r9 +u10 +box all but 1 pokemon +l11 +u24 +r32 +u1 +a +bmash +r1 +u1 +a +bmash +r8 +bmash +l37 +u15 +l11 +u9 +bmash +battle +amash +battle +bmash +d25 +r60 +d2 +r19 +u6 +r1 +u1 +battle +amash +u1 +amash +u1 +amash +l10 +u7 +battle +u1 +r6 +d1 +r8 +u1 +r3 +mars1 +bmash +l6 +d1 +l8 +u1 +l2 +d7 +r9 +d6 +l1 +d3 +l24 +u9 +l4 +u11 +l6 +u24 +l2 +u9 +r2 +u7 +l2 +u21 +Cheryl +Bmash +R3 +U1 +R4 +U11 +R4 +U5 +Double battle +U3 +R4 +U6 +R7 +D7 +battle +R1 +D1 +Battle +D12 +R12 +U17 +R22 +U16 +L7 +U9 +R5 +Cheryl +R7 +D5 +R10 +D4 +R38 +U6 +R1 +Undergournd man +A +Bmash +Y +D1 +A +A +Bmash +D3 +L3 +D8 +R1 +D21 +R5 +U1 +Gardenia gym +U7 +A +Bmash +U6 +L1 +U9 +R1 +U6 +L2 +U1 +l4 +d3 +battle +u5 +l5 +d7 +r1 +d5 +l1 +d3 +l1 +d5 +r4 +a +battle +bmash +l4 +u5 +r1 +u4 +r1 +u4 +l1 +u7 +r6 +d1 +r4 +d5 +r1 +d2 +l1 +d4 +r5 +u2 +r1 +l1 +d2 +l5 +d5 +r4 +d1 +r6 +u1 +r1 +u16 +l5 +battle +r5 +d16 +l4 +d1 +l7 +u10 +r1 +u6 +l1 +u6 +a +gardenia +bmash +d6 +r1 +d6 +l1 +d13 +r1 +d9 +l8 +u32 +l4 +u7 +r3 +Cynthia +Bmash +R2 +U2 +Amash +U7 +R4 +U2 +Battle +U1 +L1 +D2 +R7 +U1 +L1 +D2 +R2 +Battle +R5 +U1 +L1 +D4 +R3 +D1 +A +Jupiter1 +Bmash +U3 +L2 +U1 +R1 +D2 +L7 +U1 +R1d2 +L7 +U1 +R1 +D9 +L4 +D1 +Amash +D5 +L4 +D7 +R5 +D9 +R4 +U6 +L4 +U1 +Bike obtained +A +Amash +Bmash +Register bike ++ +L6 +D54 +Battle +Wait +R1 +Battle +Battle +Battle +L1 +L3 +D1 ++ +D28 +Item finder +Bmash +D2 +R3 +A +D6 +R4 +D8 +R13 +D2 +battle +U1 +R7 +U3 +R4 +Battle +D1 +R7 +U4 +L4 +U1 +Battle +L1 +U2 +R7 +U2 +R1 +Repel +D15 +R10 +Boss +Bmash +U6 +L1 +A +U1 +R1 +U3 +R4 +D1 +R5 +U2 +R3 +D7 +R17 +Battle +R11 +U2 +r1 +a +battle +d1 +r3 +u8 +r5 +d13 +r37 +buneary +bmash +r24 +u12 +l16 +u16 +l4 +u7 +a +a +a +a +a +bmash +u6 +r14 +u5 +a +Fatima +a +Bmash +U3 +Keira +Bmash +D10 +R19 +d10 +l21 +u5 +buy repels +bmash +d9 +r8 +d2 +barry 2 +d9 +r35 +u3 +r11 +u1 +r21 +u6 +r6 +u3 +r7 +u13 +l4 +u2 +l1 +u9 +r10 +u12 +l2 +u5 +l1 +u1 +r1 +d8 +l2 +d2 +l8 +u7 +r2 +u2 +r2 +d5 +r1 +d2 +l1 +d4 +l4 +u2 +l1 +u3 +l5 +u3 +r2 +u2 +r1 +d5 +l7 +u2 +l1 +u2 +r1 +d3 +l4 +d2 +a +a +a +a +a +bmash +escape rope +d3 +l4 +u8 +l3 +u45 +l7 +u11 +r10 +u18 +r9 +u4 +r19 +u11 +l4 +d4 +r1 +battle +r10 +amash +r6 +d8 +r6 +u1 +amash +bmash +r3 +u16 +l13 +u4 +r6 +d4 +a +battle +r1 +d7 +r16 +u7 +r10 +d2 +r28 +u6 +r1 +amash +d6 +l6 +d3 +r1 +amash +r5 +u1 +amash +l7 +u2 +l2 +d2 +l6 +d6 +r1 +a +battle +d1 +r1 +battle +r48 +d10 +r11 +d10 +r7 +u6 +r5 +u7 +a +a +a +a +bmash +d6 +l5 +u6 +l8 +u2 +l3 +u3 +r4 +u7 +r1 +l1 +a +a +d2 +a +a +a +r1 +d3 +l9 +d4 +l2 +d1 +buy TM29 psychic +buy TM21 dazzling gleam +sell tms +r3 +u1 +r1 +u4 +l9 +buy x items +d2 +l1 +buy vitamins +d2 +l1 +get counter app +r3 +d4 +l3 +d2 +r7 +d9 +r2 +d4 +r12 +d2 +r4 +get metronome +a +bmash +d10 +l30 +d2 +l12 +u16 +dawn +bmash +u4 + + + diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 9e4e0895bb..ac86abd8b6 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1497,6 +1497,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h Source/PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.cpp Source/PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h + Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.cpp + Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h Source/PokemonBDSP/Inference/BoxSystem/PokemonBDSP_BoxDetector.cpp Source/PokemonBDSP/Inference/BoxSystem/PokemonBDSP_BoxDetector.h Source/PokemonBDSP/Inference/BoxSystem/PokemonBDSP_BoxGenderDetector.cpp @@ -1563,8 +1565,44 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/PokemonBDSP_Panels.h Source/PokemonBDSP/PokemonBDSP_Settings.cpp Source/PokemonBDSP/PokemonBDSP_Settings.h - Source/PokemonBDSP/PokemonBDSP_Tests.cpp - Source/PokemonBDSP/PokemonBDSP_Tests.h + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp + Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h + Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp + Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp + Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h + Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp + Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h + Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp + Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomous.cpp Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomous.h Source/PokemonBDSP/Programs/Eggs/PokemonBDSP_EggAutonomousState.cpp From 44694c5bd6e615e12cdc64c61dad49ae57a14f95 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Mon, 25 May 2026 18:03:55 -0500 Subject: [PATCH 02/39] Updated checkpoints for autostory.cpp and added nav to segment 01 I had to change some of the checkpoint names again to match what would be expected in the autostory.cpp Added the navigation segments to segment 01 that I'm responsible for and created placeholder navigation functions for oreburgh gate and oreburgh mine. --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 53 +++ .../PokemonBDSP_AutoStory_Segment_01.cpp | 375 ++++++++++++------ .../PokemonBDSP_AutoStory_Segment_01.h | 4 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 8 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 12 +- .../PokemonBDSP_AutoStory_Segment_05.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_08.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_09.cpp | 2 +- 9 files changed, 328 insertions(+), 132 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index d295c4839d..32e3d41e47 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -60,6 +60,59 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + /*checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique());*/ return checkpoint_list; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index f2b34eac05..16eafad4cb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -5,149 +5,292 @@ * */ +#include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" #include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP_AutoStory_Segment_01.h" +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" using namespace std::chrono_literals; -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + static void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + stream.log("Starting Barry 1 battle..."); - } + // State machine: loop until EndBattleWatcher fires + for (size_t c = 0; c < 20; c++) { + context.wait_for_all_requests(); - void oreburgh_gate_to_first_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + BattleMenuWatcher battle_menu(BattleType::STANDARD); + EndBattleWatcher end_battle; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_ZL, 120000ms); + }, + { + battle_menu, + end_battle, + } + ); + switch (ret) { + case 0: + stream.log("Battle menu detected - using first move."); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + break; + case 1: + stream.log("Barry 1 battle complete."); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + return; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Barry_1_fight: Timed out waiting for battle menu or end.", + stream + ); + } + } - } - // --------------------------------------------------------------------------- - // Segment 1 Part ? Gym navigation - // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. - // --------------------------------------------------------------------------- - - static void gym_Puzzle_01(VideoStream& stream, ProControllerContext& context){ - - //Upon entering the Gym. - - struct Move { - DpadPosition dir; - int count; - }; - const Move sequence[] = { - {DPAD_UP, 3}, - {DPAD_LEFT, 4}, - {DPAD_UP, 5}, - {DPAD_RIGHT, 8}, - {DPAD_UP, 11}, - {DPAD_LEFT, 5}, - {DPAD_UP, 4}, - }; - - for (const auto& move : sequence) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(context, move.dir, 80ms, 300ms); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Barry_1_fight: Battle did not end after 20 turns.", + stream + ); } - } - //Button presses to initiate the Gym battle. + // ---------------------------------------------------------------- + // Segment 1 Part 1: Jubilife City to Oreburgh Gate + // Navigates the player from Jubilife City to the Oreburgh Gate, including the + // first Barry fight. + // --------------------------------------------------------------------------- + static void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + // Wait for game to fully accept input after save/overworld return + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); - //Battle automation code. + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1465ms, 0ms); + pbf_wait(context, 127ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); + pbf_wait(context, 698ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 814ms, 0ms); + pbf_wait(context, 2340ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); + pbf_wait(context, 348ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2694ms, 0ms); + pbf_wait(context, 482ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3810ms, 0ms); - //Button presses after battle with small cutscene of badge being put in case between dialogue boxes. + wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); + pbf_wait(context, 1647ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 415ms, 0ms); + pbf_wait(context, 390ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 321ms, 0ms); + walk_through_grass(stream, context, 1, 0, 16, "right through grass"); + walk_through_grass(stream, context, 0, -1, 3, "down through grass"); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1952ms, 0ms); + walk_through_grass(stream, context, 1, 0, 16, "right through grass"); + walk_through_grass(stream, context, 0, 1, 16, "up through grass"); + pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + } + static void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { - for (int i = 0; i < 21; i++) { - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } + } + // --------------------------------------------------------------------------- + // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine + // Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. + // Navigates to the mine. + // ---------------------------------------------------------------------------- + static void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); + wait_for_dialogue(stream, context, "Phase 2 (oreburgh tour dialog)"); + pbf_mash_button(context, BUTTON_B, 12000ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); + pbf_wait(context, 23ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); - //Exits Buiilding - pbf_wait(context, 1000ms); - } + } + static void oreburgh_mine_nav(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { - std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } - std::string AutoStory_Segment_01::start_text() const { return "Start: Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } - - void AutoStory_Segment_01::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_checkpoint_005().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_006().run_checkpoint(env, context, options, stats); - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); - } + } + // --------------------------------------------------------------------------- + // Segment 1 Part 3: Oreburgh Mine to Gym + // Assumes that escape rope was picked up and used to exit the mine. + // Navigates to the gym. + // ---------------------------------------------------------------------------- + static void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4443ms, 0ms); + pbf_wait(context, 1165ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 540ms, 0ms); + pbf_wait(context, 281ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 858ms, 0ms); + pbf_wait(context, 764ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2972ms, 0ms); + pbf_wait(context, 677ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 160ms, 0ms); + pbf_press_button(context, BUTTON_UP, 180ms, 0ms); + } + // --------------------------------------------------------------------------- + // Segment 1 Part 4: Gym navigation + // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. + // --------------------------------------------------------------------------- + static void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { - std::string AutoStory_checkpoint_005::name() const { return "005 - " + AutoStory_Segment_01().name(); } - std::string AutoStory_checkpoint_005::start_text() const { return "Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_checkpoint_005::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } + //Upon entering the Gym. - void AutoStory_checkpoint_005::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_005(env, context, options, stats); - } + struct Move { + DpadPosition dir; + int count; + }; + const Move sequence[] = { + {DPAD_UP, 3}, + {DPAD_LEFT, 4}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 8}, + {DPAD_UP, 11}, + {DPAD_LEFT, 5}, + {DPAD_UP, 4}, + }; + + for (const auto& move : sequence) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate the Gym battle. + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + //Barry 1 battle logic can be used here since the moves don't matter this early. + Barry_1_fight(env, stream, context); + //Button presses after battle with small cutscene of badge being put in case between dialogue boxes. - void checkpoint_005( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/) { - jubilife_to_oreburgh_gate(env, env.console, context); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + for (int i = 0; i < 21; i++) { + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + //Exits Buiilding + pbf_wait(context, 1000ms); } - ); - } - std::string AutoStory_checkpoint_006::name() const { return "006 - " + AutoStory_Segment_01().name(); } - std::string AutoStory_checkpoint_006::start_text() const { return AutoStory_checkpoint_005().end_text(); } - std::string AutoStory_checkpoint_006::end_text() const { return "Standing At the first gym leader after defeating him."; } - - void AutoStory_checkpoint_006::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_006(env, context, options, stats); - } + std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } + std::string AutoStory_Segment_01::start_text() const { return "Start: Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } + void AutoStory_Segment_01::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); - void checkpoint_006( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/) { - oreburgh_gate_to_first_gym(env, env.console, context); - //#. Oreburgh Gym navigation and battle - gym_Puzzle_01(env.console, context); - + AutoStory_checkpoint_005().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_006().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); } - ); - } - - -} -} -} + + std::string AutoStory_checkpoint_005::name() const { return "005 - " + AutoStory_Segment_01().name(); } + std::string AutoStory_checkpoint_005::start_text() const { return "Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_checkpoint_005::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } + // ======================== + // Checkpoint 005 Wrapper + // ======================== + void AutoStory_checkpoint_005::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_005(env, context, options, stats); + } + + + void checkpoint_005( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + jubilife_to_oreburgh_gate(env, env.console, context); + oreburgh_gate_nav(env, env.console, context); + } + ); + } + + // ======================== + // Checkpoint 006 Wrapper + // ======================== + + std::string AutoStory_checkpoint_006::name() const { return "006 - " + AutoStory_Segment_01().name(); } + std::string AutoStory_checkpoint_006::start_text() const { return AutoStory_checkpoint_005().end_text(); } + std::string AutoStory_checkpoint_006::end_text() const { return "Standing At the first gym leader after defeating him."; } + + void AutoStory_checkpoint_006::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_006(env, context, options, stats); + } + + + void checkpoint_006( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + oreburgh_gate_to_mine(env, env.console, context); + oreburgh_mine_nav(env, env.console, context); + oreburgh_mine_to_gym(env, env.console, context); + //#. Oreburgh Gym navigation and battle + gym_Puzzle_01(env, env.console, context); + + } + ); + } + + + } + } +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h index b3612ff0e0..52fd409af8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -41,7 +41,7 @@ class AutoStory_checkpoint_005 : public AutoStory_Checkpoint{ ) const override; }; -// start: starter Pokemon chosen and received +// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) void checkpoint_005( SingleSwitchProgramEnvironment& env, @@ -62,7 +62,7 @@ class AutoStory_checkpoint_006 : public AutoStory_Checkpoint { ) const override; }; -// start: starter Pokemon chosen and received +// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) void checkpoint_006( SingleSwitchProgramEnvironment& env, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 4e08547b99..19c658bc80 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -224,7 +224,7 @@ void AutoStory_Segment_02::run_segment( context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_checkpoint_007::name() const { return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_007::name() const { return "007 - " + AutoStory_Segment_02().name(); } std::string AutoStory_checkpoint_007::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } std::string AutoStory_checkpoint_007::end_text() const { return "Standing in front of the scientist in Valley windworks after beating Mars."; } @@ -252,7 +252,7 @@ void checkpoint_007( } ); } -std::string AutoStory_checkpoint_008::name() const { return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_008::name() const { return "008 - " + AutoStory_Segment_02().name(); } std::string AutoStory_checkpoint_008::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } std::string AutoStory_checkpoint_008::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } @@ -281,7 +281,7 @@ void checkpoint_008( ); } -std::string AutoStory_checkpoint_009::name() const{ return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_009::name() const{ return "009 - " + AutoStory_Segment_02().name(); } std::string AutoStory_checkpoint_009::start_text() const{ return "First Gym Badge acquired (Oreburgh City)."; } std::string AutoStory_checkpoint_009::end_text() const{ return "Second Gym Badge acquired (Eterna City)."; } @@ -309,7 +309,7 @@ void checkpoint_009( } ); } -std::string AutoStory_checkpoint_010::name() const { return "002 - " + AutoStory_Segment_02().name(); } +std::string AutoStory_checkpoint_010::name() const { return "010 - " + AutoStory_Segment_02().name(); } std::string AutoStory_checkpoint_010::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } std::string AutoStory_checkpoint_010::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index e3286196c5..e135e918e7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -287,7 +287,7 @@ void AutoStory_Segment_03::run_segment( } -std::string AutoStory_Checkpoint_011::name() const{ return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_011::name() const{ return "011 - " + AutoStory_Segment_03().name(); } std::string AutoStory_Checkpoint_011::start_text() const{ return "Second Gym Badge acquired (Eterna City)."; } std::string AutoStory_Checkpoint_011::end_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } @@ -315,7 +315,7 @@ void Checkpoint_011( } ); } -std::string AutoStory_Checkpoint_012::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_012::name() const { return "012 - " + AutoStory_Segment_03().name(); } std::string AutoStory_Checkpoint_012::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } std::string AutoStory_Checkpoint_012::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } @@ -343,7 +343,7 @@ void Checkpoint_012( } ); } -std::string AutoStory_Checkpoint_013::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_013::name() const { return "013 - " + AutoStory_Segment_03().name(); } std::string AutoStory_Checkpoint_013::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } std::string AutoStory_Checkpoint_013::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } @@ -371,7 +371,7 @@ void Checkpoint_013( } ); } -std::string AutoStory_Checkpoint_014::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_014::name() const { return "014 - " + AutoStory_Segment_03().name(); } std::string AutoStory_Checkpoint_014::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } std::string AutoStory_Checkpoint_014::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } @@ -399,7 +399,7 @@ void Checkpoint_014( } ); } -std::string AutoStory_Checkpoint_015::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_015::name() const { return "015 - " + AutoStory_Segment_03().name(); } std::string AutoStory_Checkpoint_015::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } std::string AutoStory_Checkpoint_015::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } @@ -428,7 +428,7 @@ void Checkpoint_015( } ); } -std::string AutoStory_Checkpoint_016::name() const { return "003 - " + AutoStory_Segment_03().name(); } +std::string AutoStory_Checkpoint_016::name() const { return "016 - " + AutoStory_Segment_03().name(); } std::string AutoStory_Checkpoint_016::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } std::string AutoStory_Checkpoint_016::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index adebfc97e0..efc6bb4761 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -46,7 +46,7 @@ void AutoStory_Segment_05::run_segment( } -std::string AutoStory_checkpoint_018::name() const{ return "005 - " + AutoStory_Segment_05().name(); } +std::string AutoStory_checkpoint_018::name() const{ return "018 - " + AutoStory_Segment_05().name(); } std::string AutoStory_checkpoint_018::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } std::string AutoStory_checkpoint_018::end_text() const{ return "Fifth Gym Badge acquired (Veilstone City)."; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index e0707cc8d6..c67268aa97 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -46,7 +46,7 @@ void AutoStory_Segment_06::run_segment( } -std::string AutoStory_Checkpoint_019::name() const{ return "006 - " + AutoStory_Segment_06().name(); } +std::string AutoStory_Checkpoint_019::name() const{ return "019 - " + AutoStory_Segment_06().name(); } std::string AutoStory_Checkpoint_019::start_text() const{ return "Fifth Gym Badge acquired (Veilstone City)."; } std::string AutoStory_Checkpoint_019::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index c20923bb21..770162b9d8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -88,7 +88,7 @@ void AutoStory_Segment_08::run_segment( } -std::string AutoStory_Checkpoint_021::name() const{ return "008 - " + AutoStory_Segment_08().name(); } +std::string AutoStory_Checkpoint_021::name() const{ return "021 - " + AutoStory_Segment_08().name(); } std::string AutoStory_Checkpoint_021::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } std::string AutoStory_Checkpoint_021::end_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 7c3277625b..709af0c957 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -35,7 +35,7 @@ void AutoStory_Segment_09::run_segment( } -std::string AutoStory_Checkpoint_022::name() const{ return "009 - " + AutoStory_Segment_09().name(); } +std::string AutoStory_Checkpoint_022::name() const{ return "022 - " + AutoStory_Segment_09().name(); } std::string AutoStory_Checkpoint_022::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } std::string AutoStory_Checkpoint_022::end_text() const{ return "Elite Four and Champion defeated, credits roll."; } From 72168915803861f96670deed727a3fbf72c1071c Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 19 May 2026 21:06:55 +0200 Subject: [PATCH 03/39] Initial route for segment 06 up to Canalave City --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 63 ---- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 234 ++++++++---- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 35 +- .../PokemonBDSP_AutoStory_Segment_05.cpp | 7 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 334 +++++++++++++++++- 5 files changed, 520 insertions(+), 153 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 32e3d41e47..97b26cde05 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -593,69 +593,6 @@ void BDSPAutoStory::program( -bool activate_repel( - VideoStream& stream, - ProControllerContext& context -){ - stream.log("Activating repel"); - DpadState dpad; - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_wait(context, 1000ms); - pbf_press_button(context, BUTTON_PLUS, 80ms, 300ms); - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - pbf_press_button(context, BUTTON_B, 80ms, 300ms); - pbf_wait(context, 1000ms); - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - context.wait_for_all_requests(); - - pbf_wait(context, 1000ms); - { - auto repel_tab_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/AutoStory/Bag_RepelTab.png" - ); - - const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; - const double repel_tab_rmsd = 50.0; - - // BDSP bag has 8 compartment tabs; check before each press so zero-press - // detection works when the bag already starts on the repel tab. - bool found = false; - for (int press = 0; press <= 8; press++){ - context.wait_for_all_requests(); - - ImageMatchWatcher watcher(repel_tab_ref, repel_tab_box, repel_tab_rmsd); - int ret = wait_until(stream, context, 400ms, {{watcher}}); - if (ret == 0){ - stream.log("[AutoStory] activate_repel: repel tab found after " - + std::to_string(press) + " press(es).", COLOR_GREEN); - found = true; - break; - } - - if (press < 8){ - stream.log("[AutoStory] activate_repel: tab not found, pressing right (" - + std::to_string(press + 1) + "/8)."); - pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); - } - } - - if (!found){ - stream.log("[AutoStory] activate_repel: failed to find repel compartment tab.", COLOR_RED); - return false; - } - } - context.wait_for_all_requests(); - pbf_wait(context, 400ms); - pbf_mash_button(context, BUTTON_A, 500ms); - pbf_mash_button(context, BUTTON_B, 2000ms); - return true; -} - - } } } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index b2435ae662..470350f0f9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -16,6 +16,7 @@ #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "CommonTools/Images/SolidColorTest.h" #include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" @@ -55,24 +56,24 @@ const char* torterra_move_slug(TorterraMove move){ return "unknown"; } -void fake_save_game( +bool fake_save_game( SingleSwitchProgramEnvironment& env, ProControllerContext& context ){ + pbf_mash_button(context, BUTTON_L, 1000ms); + context.wait_for_all_requests(); pbf_press_button(context, BUTTON_X, 80ms, 1000ms); - pbf_press_button(context, BUTTON_X, 80ms, 1000ms); pbf_press_button(context, BUTTON_R, 80ms, 1000ms); pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); -} + return true; +} -void checkpoint_save( +bool save_game( SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats + ProControllerContext& context ){ static constexpr size_t MAX_RETRIES = 10; @@ -163,6 +164,18 @@ void checkpoint_save( context.wait_for_all_requests(); } + return success; +} + + +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats +){ + bool success = fake_save_game(env, context); + stats.m_checkpoint++; env.update_stats(); @@ -346,26 +359,33 @@ bool heal_pokemon( return true; } +std::string fly_point_name(FlyPoint place) { + switch (place) { + case FlyPoint::CanalavCity: return "canalave_city"; + case FlyPoint::CellesticTown: return "celestic_town"; + case FlyPoint::EternaCity: return "eterna_city"; + case FlyPoint::FloaromaTown: return "floaroma_town"; + case FlyPoint::HearthomeCity: return "hearthome_city"; + case FlyPoint::JubilifeCity: return "jubilife_city"; + case FlyPoint::OreburghCity: return "oreburgh_city"; + case FlyPoint::PastoriaCity: return "pastoria_city"; + case FlyPoint::PokemonLeagueLower: return "pokemon_league_lower"; + case FlyPoint::PokemonLeagueUpper: return "pokemon_league_upper"; + case FlyPoint::Route221: return "route_221"; + case FlyPoint::SandgemTown: return "sandgem_town"; + case FlyPoint::SolaceonTown: return "solaceon_town"; + case FlyPoint::SnowpointCity: return "snowpoint_city"; + case FlyPoint::SunyshoreCity: return "sunyshore_city"; + case FlyPoint::TwinleafTown: return "twinleaf_town"; + case FlyPoint::VeilstoneCity: return "veilstone_city"; + } +} + bool fly_to( VideoStream& stream, ProControllerContext& context, - const std::string& place + FlyPoint place ){ - static const std::unordered_set EAST_CITIES = { - "celestic_town", - "hearthome_city", - "pastoria_city", - "pokemon_league_lower", - "pokemon_league_upper", - "solaceon_town", - "sunyshore_city", - "veilstone_city" - }; - - static const std::unordered_set NORTH_CITY = { - "snowpoint_city", - }; - // Each city branch below should update this to the city icon's position on the fly map. // Detection: when the cursor brackets overlap the icon, its normally uniform // color becomes non-uniform (stddev rises). Box stays as a rough fallback @@ -377,12 +397,24 @@ bool fly_to( pbf_wait(context, 1000ms); pbf_press_button(context, BUTTON_X, 80ms, 1000ms); pbf_press_button(context, BUTTON_PLUS, 80ms, 1000ms); - if (EAST_CITIES.count(place)){ - pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); - } else if (NORTH_CITY.count(place)) { - pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); - } else { - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + + switch (place) { + case FlyPoint::CellesticTown: + case FlyPoint::HearthomeCity: + case FlyPoint::PastoriaCity: + case FlyPoint::PokemonLeagueLower: + case FlyPoint::PokemonLeagueUpper: + case FlyPoint::SolaceonTown: + case FlyPoint::SunyshoreCity: + case FlyPoint::VeilstoneCity: + pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); + break; + case FlyPoint::SnowpointCity: + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + break; + default: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; } context.wait_for_all_requests(); @@ -401,46 +433,50 @@ bool fly_to( }; FlyAButtonDetector watcher_1(button_a_box); - if (place == "canalave_city") { - - } else if (place == "celestic_town") { - - } else if (place == "eterna_city") { - - } else if (place == "floaroma_town") { - - } else if (place == "hearthome_city") { - - city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); - - } else if (place == "jubilife_city") { - - } else if (place == "oreburgh_city") { - - } else if (place == "pastoria_city") { - - } else if (place == "pokemon_league_lower") { - - } else if (place == "pokemon_league_upper") { - - } else if (place == "route_221") { - - } else if (place == "sandgem_town") { - - } else if (place == "solaceon_town") { - - city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); - - } else if (place == "sunyshore_city") { - - } else if (place == "twinleaf_town") { - - } else if (place == "veilstone_city") { - + switch (place) { + case FlyPoint::CanalavCity: + break; + case FlyPoint::CellesticTown: + break; + case FlyPoint::EternaCity: + break; + case FlyPoint::FloaromaTown: + break; + case FlyPoint::HearthomeCity: + city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); + break; + case FlyPoint::JubilifeCity: + city_icon_box = {0.453000, 0.702000, 0.016000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); + break; + case FlyPoint::OreburghCity: + break; + case FlyPoint::PastoriaCity: + break; + case FlyPoint::PokemonLeagueLower: + break; + case FlyPoint::PokemonLeagueUpper: + break; + case FlyPoint::Route221: + break; + case FlyPoint::SandgemTown: + break; + case FlyPoint::SolaceonTown: + city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); + break; + case FlyPoint::SnowpointCity: + break; + case FlyPoint::SunyshoreCity: + break; + case FlyPoint::TwinleafTown: + break; + case FlyPoint::VeilstoneCity: + break; } context.wait_for_all_requests(); @@ -458,16 +494,17 @@ bool fly_to( }; CityIconDetector watcher_2(city_icon_box); + const std::string name = fly_point_name(place); int ret_1 = wait_until(stream, context, 3000ms, {{watcher_1}}); int ret_2 = wait_until(stream, context, 4000ms, {{watcher_2}}); if (ret_1 < 0) { stream.log("A button not found, retrying...", COLOR_RED); }; if (ret_2 < 0) { - stream.log(place + " not found, retrying...", COLOR_RED); + stream.log(name + " not found, retrying...", COLOR_RED); }; if (ret_1 == 0 and ret_2 == 0){ - stream.log("Found " + place + ", flying towards it.", COLOR_GREEN); + stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); int ret = run_until( stream, context, @@ -477,10 +514,10 @@ bool fly_to( {{flying_black_screen}} ); if (ret < 0){ - stream.log("Flying to " + place + ": black screen not detected!", COLOR_RED); + stream.log("Flying to " + name + ": black screen not detected!", COLOR_RED); return false; } else if (ret == 0) { - stream.log("Flying to " + place + ": black screen detected!", COLOR_GREEN); + stream.log("Flying to " + name + ": black screen detected!", COLOR_GREEN); return true; } } @@ -753,6 +790,57 @@ void open_menu( } +bool activate_repel( + VideoStream& stream, + ProControllerContext& context, + int num_icons +){ + stream.log("Activating repel"); + open_menu(stream, context, MenuCursorPosition::BAG, num_icons); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + auto repel_tab_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/AutoStory/Bag_RepelTab.png" + ); + const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; + const double repel_tab_rmsd = 50.0; + + // BDSP bag has 8 compartment tabs; check before each press so zero-press + // detection works when the bag already starts on the repel tab. + bool found = false; + for (int press = 0; press <= 8; press++){ + context.wait_for_all_requests(); + + ImageMatchWatcher watcher(repel_tab_ref, repel_tab_box, repel_tab_rmsd); + int ret = wait_until(stream, context, 400ms, {{watcher}}); + if (ret == 0){ + stream.log("[AutoStory] activate_repel: repel tab found after " + + std::to_string(press) + " press(es).", COLOR_GREEN); + found = true; + break; + } + + if (press < 8){ + stream.log("[AutoStory] activate_repel: tab not found, pressing right (" + + std::to_string(press + 1) + "/8)."); + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); + } + } + + if (!found){ + stream.log("[AutoStory] activate_repel: failed to find repel compartment tab.", COLOR_RED); + return false; + } + + context.wait_for_all_requests(); + pbf_wait(context, 400ms); + pbf_mash_button(context, BUTTON_A, 500ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + return true; +} + + } } } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index cf4c6159b3..0a7e230a59 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -137,6 +137,13 @@ class AutoStory_Checkpoint{ // Shared utility functions (implementations in .cpp) // --------------------------------------------------------------------------- +// Save the game from the overworld with retries. Returns true if the save was +// verified via overworld detection, false if the blind fallback was used. +bool save_game( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +); + // Fake save game to be replaced with save_game when program runs smoothly void checkpoint_save( SingleSwitchProgramEnvironment& env, @@ -206,9 +213,11 @@ void mash_until_dialogue_ends( // Open the bag, navigate to the repel compartment tab, and use a repel. // Returns false if the repel compartment tab could not be found. +// num_icons: 5 (early-game layout) or 8 (late-game layout). bool activate_repel( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + int num_icons = 8 ); // Walk to the nurse, heal the party, and exit the Pokemon Center. @@ -220,13 +229,35 @@ bool heal_pokemon( const std::string& label ); +enum class FlyPoint { + CanalavCity, + CellesticTown, + EternaCity, + FloaromaTown, + HearthomeCity, + JubilifeCity, + OreburghCity, + PastoriaCity, + PokemonLeagueLower, + PokemonLeagueUpper, + Route221, + SandgemTown, + SolaceonTown, + SnowpointCity, + SunyshoreCity, + TwinleafTown, + VeilstoneCity, +}; + +std::string fly_point_name(FlyPoint place); + // Open the map, move the cursor to either bottom left, bottom right or // top left, then move the cursor a number of steps, then checks for // correctness, and mashes A until a black screen is detected bool fly_to( VideoStream& stream, ProControllerContext& context, - const std::string& place + FlyPoint place ); // Opens the X-menu from the overworld, navigates the cursor to `target`, and presses A. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index efc6bb4761..709d7b5b90 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -512,8 +512,7 @@ static bool go_through_route_213_chasing( {{black_screen}} ); if (ret < 0){ - stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!", COLOR_RED); - return false; + stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!"); } else if (ret == 0) { stream.log("go_through_route_213_tuber_chelsea: Starting battle."); handle_battle(stream, context, "route_213_tuber_chelsea"); @@ -665,7 +664,7 @@ static bool go_through_route_214( context.wait_for_all_requests(); pbf_wait(context, 4000ms); - fly_to(stream, context, "solaceon_town"); + fly_to(stream, context, FlyPoint::SolaceonTown); return true; } @@ -1295,7 +1294,7 @@ static bool talk_to_cyrus_and_fly_hearthome( context.wait_for_all_requests(); pbf_wait(context, 3000ms); - fly_to(stream, context, "hearthome_city"); + fly_to(stream, context, FlyPoint::HearthomeCity); context.wait_for_all_requests(); pbf_wait(context, 3000ms); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index c67268aa97..e960be172f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -59,25 +59,320 @@ void AutoStory_Checkpoint_019::run_checkpoint( checkpoint_019(env, context, options, stats); } -static bool test_opening_menu( +static void move_back_after_unexpected_battle( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + const std::string& trainerid ){ + stream.log("Starting battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); + + if (trainerid == "route_218_sailor_skyler") { + pbf_move_left_joystick(context, {0, +1}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 200ms, 100ms); // 1+ + } +} + +static bool handle_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ context.wait_for_all_requests(); - stream.log("test_opening_menu: testing"); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); + if (ret_menu < 0){ + stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 500ms); - open_menu(stream, context, MenuCursorPosition::BAG, 8); - pbf_mash_button(context, BUTTON_B, 2000ms); + if ( + trainerid == "route_218_sailor_skyler" + ) { + // Select Razor leaf + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + } context.wait_for_all_requests(); - open_menu(stream, context, MenuCursorPosition::POKEDEX, 8); - pbf_mash_button(context, BUTTON_B, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); + return false; + } context.wait_for_all_requests(); - open_menu(stream, context, MenuCursorPosition::CARD, 8); pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_" + trainerid + ": transition confirmed."); + return true; +} + +static bool cynthia_and_fly_to_jubilife( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("cynthia_and_fly_to_jubilife: talking to Cynthia and then flying to Jubilife."); + + /* + { + ShortDialogWatcher talk_to_cynthia(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_cynthia}}); + if (ret < 0){ + stream.log("cynthia_and_fly_to_jubilife: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("cynthia_and_fly_to_jubilife: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 12000ms); + context.wait_for_all_requests(); + open_menu(stream, context, MenuCursorPosition::MAP, 8); + fly_to(stream, context, FlyPoint::JubilifeCity); + } + }*/ + // Temporary, will be removed when the gym exit is done + + context.wait_for_all_requests(); + open_menu(stream, context, MenuCursorPosition::MAP, 8); + fly_to(stream, context, FlyPoint::JubilifeCity); + + // End of temporary addition + + return true; +} + +static bool enter_jubilife_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("enter_jubilife_pokemon_center: entering..."); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("enter_jubilife_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + }; + return true; +} + +static bool heal_and_exit_jubilife_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_and_exit_jubilife_pokemon_center: healing and exiting..."); + heal_pokemon(stream, context, "Jubilife city"); + return true; +} + +static bool move_to_jubilife_west_exit( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("move_to_jubilife_west_exit: moving towards building..."); + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 18 + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); // 46 + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("move_to_jubilife_west_exit: transition not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("move_to_jubilife_west_exit: transition detected.", COLOR_GREEN); + } + } + + return true; +} + +static bool go_through_jubilife_west_exit( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_jubilife_west_exit: going through the building..."); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_jubilife_west_exit: transition not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_jubilife_west_exit: transition detected.", COLOR_GREEN); + } + } + + return true; +} + +static bool go_through_route_218( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_route_218: going through the road..."); + + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 7+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 9+ context.wait_for_all_requests(); + activate_repel(stream, context); + pbf_mash_button(context, BUTTON_A, 2000ms); + context.wait_for_all_requests(); + pbf_wait(context, 6000ms); + context.wait_for_all_requests(); + + { + MarkDetector sailor_skyler_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); // 28++ (getting out of the water) + pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ + }, + {{sailor_skyler_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_218_sailor_skyler: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_218_sailor_skyler"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_218_sailor_skyler"); + } + } + } + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_218: transition not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_route_218: transition detected.", COLOR_GREEN); + } + } + return true; +} + +static bool go_through_building_route_218( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_building_route_218: going through the building..."); + + { + ShortDialogWatcher talk_to_scientist(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{talk_to_scientist}} + ); + if (ret < 0){ + stream.log("go_through_building_route_218_scientist: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_building_route_218_scientist: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 12000ms); + context.wait_for_all_requests(); + } + } + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_building_route_218: transition not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_through_building_route_218: transition detected.", COLOR_GREEN); + } + } return true; } @@ -90,9 +385,26 @@ void checkpoint_019( ){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ - // TODO: implement Segment 06 gameplay logic - if (!test_opening_menu(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "test_opening_menu: transition not detected.", env.console); + if (!cynthia_and_fly_to_jubilife(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); + } + if (!enter_jubilife_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_jubilife_pokemon_center: transition not detected.", env.console); + } + if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); + } + if (!move_to_jubilife_west_exit(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "move_to_jubilife_west_exit: transition not detected.", env.console); + } + if (!go_through_jubilife_west_exit(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_jubilife_west_exit: transition not detected.", env.console); + } + if (!go_through_route_218(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); + } + if (!go_through_building_route_218(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); } } ); From 1716ad784044a40b7ecd5dc6c3663bf65cef73b5 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Wed, 20 May 2026 13:42:18 +0200 Subject: [PATCH 04/39] Finished segment 6, improved activate repel --- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 20 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 214 ++++++++++++++++-- 2 files changed, 215 insertions(+), 19 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 470350f0f9..97442b62d2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -395,8 +395,7 @@ bool fly_to( DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 1000ms); - pbf_press_button(context, BUTTON_X, 80ms, 1000ms); - pbf_press_button(context, BUTTON_PLUS, 80ms, 1000ms); + open_menu(stream, context, MenuCursorPosition::MAP, 8); switch (place) { case FlyPoint::CellesticTown: @@ -726,8 +725,10 @@ void open_menu( ){ using namespace std::chrono_literals; + context.wait_for_all_requests(); overworld_to_menu(stream, context); - context.wait_for(1000ms); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); MenuCursorDetector detector; { @@ -835,7 +836,18 @@ bool activate_repel( context.wait_for_all_requests(); pbf_wait(context, 400ms); - pbf_mash_button(context, BUTTON_A, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + context.wait_for_all_requests(); + + const ImageFloatBox repel_item_box{0.720000, 0.100000, 0.070000, 0.150000}; + SelectionArrowFinder arrow_finder(stream.overlay(), repel_item_box, COLOR_GREEN); + int arrow_ret = wait_until(stream, context, 2000ms, {{arrow_finder}}); + if (arrow_ret != 0){ + stream.log("[AutoStory] activate_repel: selection arrow not found after pressing A.", COLOR_RED); + return false; + } + stream.log("[AutoStory] activate_repel: selection arrow confirmed.", COLOR_GREEN); + pbf_press_button(context, BUTTON_A, 80ms, 105ms); pbf_mash_button(context, BUTTON_B, 2000ms); return true; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index e960be172f..2f37090614 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -64,13 +64,14 @@ static void move_back_after_unexpected_battle( ProControllerContext& context, const std::string& trainerid ){ - stream.log("Starting battle with " + trainerid, COLOR_GREEN); + stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); if (trainerid == "route_218_sailor_skyler") { - pbf_move_left_joystick(context, {0, +1}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {-1, 0}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ } } @@ -95,27 +96,46 @@ static bool handle_battle( ) { // Select Razor leaf pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + } else if ( + trainerid == "canalave_clint" + ) { + // Select Crunch } context.wait_for_all_requests(); - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); + return false; + } } + context.wait_for_all_requests(); pbf_mash_button(context, BUTTON_B, 2000ms); stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } +static bool test( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("test: Testing..."); + activate_repel(stream, context); + return true; +} + static bool cynthia_and_fly_to_jubilife( VideoStream& stream, ProControllerContext& context @@ -143,7 +163,6 @@ static bool cynthia_and_fly_to_jubilife( // Temporary, will be removed when the gym exit is done context.wait_for_all_requests(); - open_menu(stream, context, MenuCursorPosition::MAP, 8); fly_to(stream, context, FlyPoint::JubilifeCity); // End of temporary addition @@ -376,6 +395,158 @@ static bool go_through_building_route_218( return true; } +static bool go_to_clint_and_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_clint_and_pokemon_center: going to the Pokemon Center..."); + + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 7+ + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 5+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, +1}, 4200ms, 100ms); // 21+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_clint_and_pokemon_center: Not activating repel again.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_to_clint_and_pokemon_center: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 300ms); // 10+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 8); + + { + ShortDialogWatcher talk_to_clint(COLOR_BLUE); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{talk_to_clint}} + ); + if (ret < 0){ + stream.log("go_to_clint_and_pokemon_center_clint: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_to_clint_and_pokemon_center_clint: dialog box detected, starting battle.", COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_clint_and_pokemon_center_clint: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "canalave_clint"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + } + } + } + + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 3000ms); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 3000ms, 300ms); // 15+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_clint_and_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_clint_and_pokemon_center: entered pokemon center", COLOR_GREEN); + }; + return true; +} + +static bool heal_and_exit_canalave_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_and_exit_canalave_pokemon_center: healing and exiting..."); + heal_pokemon(stream, context, "Canalave city"); + return true; +} + +static bool go_to_canalave_gym( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_canalave_gym: going for the gym..."); + + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2, false); + + pbf_move_left_joystick(context, {-1, 0}, 5000ms, 100ms); // 26+ + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 9 + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_canalave_gym: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_canalave_gym: entered gym", COLOR_GREEN); + }; + return true; + +} + + void checkpoint_019( SingleSwitchProgramEnvironment& env, @@ -385,6 +556,9 @@ void checkpoint_019( ){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ + /*if (!test(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "test: transition not detected.", env.console); + }*/ if (!cynthia_and_fly_to_jubilife(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); } @@ -406,6 +580,16 @@ void checkpoint_019( if (!go_through_building_route_218(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); } + if (!go_to_clint_and_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_clint_and_pokemon_center: transition not detected.", env.console); + } + if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); + } + if (!go_to_canalave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + } + } ); } From d193ec4a3815fbd81136c923dd40fcb0afd85455 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 26 May 2026 20:49:24 +0200 Subject: [PATCH 05/39] Rebasing from Butters' changes --- .../VisualDetectors/BlackScreenDetector.cpp | 32 +- .../VisualDetectors/BlackScreenDetector.h | 21 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 37 +- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 8 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 378 +++++++++++++++++- 5 files changed, 458 insertions(+), 18 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp index d4d919960f..caaeff3c1f 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp @@ -101,24 +101,34 @@ bool BlackScreenOverWatcher::black_is_over(const ImageViewRGB32& frame){ WhiteScreenOverWatcher::WhiteScreenOverWatcher( Color color, const ImageFloatBox& box, double min_rgb_sum, - double max_stddev_sum + double max_stddev_sum, + std::chrono::milliseconds hold_duration, + std::chrono::milliseconds release_duration ) - : VisualInferenceCallback("BlackScreenOverWatcher") - , m_detector(color, box, min_rgb_sum, max_stddev_sum) + : VisualInferenceCallback("WhiteScreenOverWatcher") + , m_on(color, box, min_rgb_sum, max_stddev_sum, WhiteScreenWatcher::FinderType::PRESENT, hold_duration) + , m_off(color, box, min_rgb_sum, max_stddev_sum, WhiteScreenWatcher::FinderType::GONE, release_duration) {} void WhiteScreenOverWatcher::make_overlays(VideoOverlaySet& items) const{ - m_detector.make_overlays(items); + m_on.make_overlays(items); } - bool WhiteScreenOverWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ - return white_is_over(frame); -} -bool WhiteScreenOverWatcher::white_is_over(const ImageViewRGB32& frame){ - if (m_detector.detect(frame)){ - m_has_been_white = true; + if (m_white_is_over.load(std::memory_order_acquire)){ + return true; + } + if (!m_has_been_white){ + m_has_been_white = m_on.process_frame(frame, timestamp); return false; } - return m_has_been_white; + bool is_over = m_off.process_frame(frame, timestamp); + if (!is_over){ + return false; + } + m_white_is_over.store(true, std::memory_order_release); + return true; +} +bool WhiteScreenOverWatcher::white_is_over(const ImageViewRGB32& frame){ + return m_white_is_over.load(std::memory_order_acquire); } diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h index 12c99b2a01..229159e2bf 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h @@ -71,6 +71,19 @@ class BlackScreenWatcher : public DetectorToFinder{ : DetectorToFinder("BlackScreenWatcher", finder_type, duration, color, box, max_rgb_sum, max_stddev_sum) {} }; +class WhiteScreenWatcher : public DetectorToFinder{ +public: + WhiteScreenWatcher( + Color color = COLOR_RED, + const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, + double min_rgb_sum = 500, + double max_stddev_sum = 10, + FinderType finder_type = FinderType::PRESENT, + std::chrono::milliseconds duration = std::chrono::milliseconds(100) + ) + : DetectorToFinder("WhiteScreenWatcher", finder_type, duration, color, box, min_rgb_sum, max_stddev_sum) + {} +}; // Detect when a period of black screen is over class BlackScreenOverWatcher : public VisualInferenceCallback{ @@ -106,7 +119,9 @@ class WhiteScreenOverWatcher : public VisualInferenceCallback{ Color color = COLOR_RED, const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, double min_rgb_sum = 500, - double max_stddev_sum = 10 + double max_stddev_sum = 10, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(100), + std::chrono::milliseconds release_duration = std::chrono::milliseconds(100) ); bool white_is_over(const ImageViewRGB32& frame); @@ -116,8 +131,10 @@ class WhiteScreenOverWatcher : public VisualInferenceCallback{ virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; private: - WhiteScreenDetector m_detector; + WhiteScreenWatcher m_on; + WhiteScreenWatcher m_off; bool m_has_been_white = false; + std::atomic m_white_is_over = false; }; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 97442b62d2..8a63df85fa 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -362,7 +362,7 @@ bool heal_pokemon( std::string fly_point_name(FlyPoint place) { switch (place) { case FlyPoint::CanalavCity: return "canalave_city"; - case FlyPoint::CellesticTown: return "celestic_town"; + case FlyPoint::CelesticTown: return "celestic_town"; case FlyPoint::EternaCity: return "eterna_city"; case FlyPoint::FloaromaTown: return "floaroma_town"; case FlyPoint::HearthomeCity: return "hearthome_city"; @@ -398,7 +398,9 @@ bool fly_to( open_menu(stream, context, MenuCursorPosition::MAP, 8); switch (place) { - case FlyPoint::CellesticTown: + case FlyPoint::CelesticTown: + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + break; case FlyPoint::HearthomeCity: case FlyPoint::PastoriaCity: case FlyPoint::PokemonLeagueLower: @@ -435,7 +437,10 @@ bool fly_to( switch (place) { case FlyPoint::CanalavCity: break; - case FlyPoint::CellesticTown: + case FlyPoint::CelesticTown: + city_icon_box = {0.660000, 0.470000, 0.012000, 0.020000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 15, false); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 13, false); break; case FlyPoint::EternaCity: break; @@ -853,6 +858,32 @@ bool activate_repel( } +void use_strength( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("[AutoStory] use_strength: pressing A until selection arrow appears."); + const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; + SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 30000ms); + }, + {{arrow}} + ); + if (ret != 0){ + stream.log("[AutoStory] use_strength: selection arrow not found.", COLOR_RED); + return; + } + + stream.log("[AutoStory] use_strength: selection arrow found, confirming.", COLOR_GREEN); + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + pbf_mash_button(context, BUTTON_B, 4000ms); +} + + } } } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 0a7e230a59..c7bb30fd6c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -220,6 +220,12 @@ bool activate_repel( int num_icons = 8 ); +// Press A until the Strength selection arrow appears, confirm with A, then mash B for 3 s. +void use_strength( + VideoStream& stream, + ProControllerContext& context +); + // Walk to the nurse, heal the party, and exit the Pokemon Center. // `label` names the center for log messages (e.g. "Veilstone"). // Returns false if the exit black screen was not detected. @@ -231,7 +237,7 @@ bool heal_pokemon( enum class FlyPoint { CanalavCity, - CellesticTown, + CelesticTown, EternaCity, FloaromaTown, HearthomeCity, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index ab4f0e3b5f..5dcd08a208 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -5,6 +5,18 @@ * */ +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Battle.h" #include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP_AutoStory_Segment_07.h" @@ -48,6 +60,356 @@ void AutoStory_Checkpoint_020::run_checkpoint( checkpoint_020(env, context, options, stats); } +static bool leave_gym( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("leave_gym: leaving the gym..."); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("leave_gym: black screen not detected!", COLOR_RED); + return false; + } + stream.log("leave_gym: exited gym", COLOR_GREEN); + }; + return true; +} + +static bool go_to_the_library( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_the_library: talking to Clint and going to the Library..."); + + { + ShortDialogWatcher talk_to_clint(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_clint}}); + if (ret < 0){ + stream.log("go_to_the_library_talk_to_clint: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_to_the_library_talk_to_clint: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 7000ms); + context.wait_for_all_requests(); + } + } + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 8+ + pbf_move_left_joystick(context, {0, +1}, 3800ms, 100ms); // 19+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_the_library: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_the_library: entered the library", COLOR_GREEN); + }; + return true; +} + +static bool drama_at_the_library( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("drama_at_the_library: going to professor Rowan..."); + + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); + return false; + } + stream.log("drama_at_the_library: moved to the first floor", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 7 + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); + return false; + } + stream.log("drama_at_the_library: moved to the second floor", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + ShortDialogWatcher talk_to_clint(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_clint}}); + if (ret < 0){ + stream.log("drama_at_the_library_talk_to_clint: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("drama_at_the_library_talk_to_clint: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 45000ms); + context.wait_for_all_requests(); + } + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 700ms, 100ms); // 5 + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); + return false; + } + stream.log("drama_at_the_library: moved back to the first floor", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 7 + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); + return false; + } + stream.log("drama_at_the_library: moved back to the ground floor", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); + return false; + } + stream.log("drama_at_the_library: moved out of the library", COLOR_GREEN); + }; + return true; +} + +static bool talk_fly_and_enter_mount_coronet( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("talk_fly_and_enter_mount_coronet: talking, flying and becoming a speleologist..."); + + { + ShortDialogWatcher talk_to_rowan(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_rowan}}); + if (ret < 0){ + stream.log("talk_fly_and_enter_mount_coronet_talk_to_rowan: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("talk_fly_and_enter_mount_coronet_talk_to_rowan: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 21000ms); + context.wait_for_all_requests(); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + fly_to(stream, context, FlyPoint::CelesticTown); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + stream.log("talk_fly_and_enter_mount_coronet: going towards Mt. Coronet..."); + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 11); + pbf_move_left_joystick(context, {-1, 0}, 3200ms, 100ms); // 16+ + context.wait_for_all_requests(); + activate_repel(stream, context); + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("talk_fly_and_enter_mount_coronet: black screen not detected!", COLOR_RED); + return false; + } + stream.log("talk_fly_and_enter_mount_coronet: entered Mt. Coronet", COLOR_GREEN); + }; + return true; +} + +static bool go_through_mount_coronet( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_mount_coronet: going through Mt. Coronet..."); + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + use_strength(stream, context); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 4000ms, 100ms); // 10++ (strength) + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_mount_coronet: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_mount_coronet: finished first part of Mt. Coronet", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + stream.log("go_through_mount_coronet: continuing through Mt. Coronet..."); + + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_mount_coronet: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_mount_coronet: finished second part of Mt. Coronet", COLOR_GREEN); + }; + + pbf_move_left_joystick(context, {+1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + + return true; +} void checkpoint_020( SingleSwitchProgramEnvironment& env, @@ -57,7 +419,21 @@ void checkpoint_020( ){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ - // TODO: implement Segment 07 gameplay logic + /*if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + } + if (!go_to_the_library(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); + } + if (!drama_at_the_library(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); + } + if (!talk_fly_and_enter_mount_coronet(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); + }*/ + if (!go_through_mount_coronet(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + } } ); } From ddffb05eb03156065a4c4667f1fa29d72f2be4cd Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 30 May 2026 14:52:43 +0200 Subject: [PATCH 06/39] Updated segments to use the Checkpoint logic --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 69 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 52 +- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 16 +- .../PokemonBDSP_AutoStory_Segment_04.cpp | 413 +++-- .../PokemonBDSP_AutoStory_Segment_04.h | 234 ++- .../PokemonBDSP_AutoStory_Segment_05.cpp | 269 ++- .../PokemonBDSP_AutoStory_Segment_05.h | 220 ++- .../PokemonBDSP_AutoStory_Segment_06.cpp | 171 +- .../PokemonBDSP_AutoStory_Segment_06.h | 136 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 1583 ++++++++++++++++- .../PokemonBDSP_AutoStory_Segment_07.h | 248 ++- 11 files changed, 3167 insertions(+), 244 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 97b26cde05..01641ecab4 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -72,10 +72,71 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); + // Segment 04 (050-066) + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + // Segment 05 (067-082) + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + // Segment 06 (083-092) + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + // Segment 07 (093-110) + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); /*checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 8a63df85fa..05353cf0e7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -189,18 +189,31 @@ void checkpoint_save( } +void reload_backup_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + // TODO: implement backup save reload. + // This is called when a checkpoint with save=false fails. + // Load the game's automatic backup save rather than resetting from HOME. + (void)env; + (void)context; +} + + void checkpoint_reattempt_loop( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EventNotificationOption& notif_status_update, AutoStoryStats& stats, - std::function&& action + std::function&& action, + bool save ){ static constexpr size_t MAX_ATTEMPTS = 20; for (size_t i = 0;; i++){ try{ - if (i == 0){ + if (i == 0 && save){ checkpoint_save(env, context, notif_status_update, stats); } @@ -216,14 +229,19 @@ void checkpoint_reattempt_loop( throw; } - env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); - bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); - if (!ok){ - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to reset game from HOME during checkpoint retry.", - env.console - ); + if (save){ + env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); + bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); + if (!ok){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to reset game from HOME during checkpoint retry.", + env.console + ); + } + } else { + env.console.log("Reloading from backup save before retry...", COLOR_ORANGE); + reload_backup_save(env, context); } } } @@ -413,6 +431,9 @@ bool fly_to( case FlyPoint::SnowpointCity: pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); break; + case FlyPoint::TwinleafTown: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; default: pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); break; @@ -474,12 +495,21 @@ bool fly_to( repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); break; case FlyPoint::SnowpointCity: + city_icon_box = {0.602000, 0.070000, 0.013000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, false); break; case FlyPoint::SunyshoreCity: break; case FlyPoint::TwinleafTown: + city_icon_box = {0.415000, 0.870000, 0.013000, 0.022000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, false); break; case FlyPoint::VeilstoneCity: + city_icon_box = {0.829000, 0.537000, 0.017000, 0.054000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 10, false); break; } @@ -880,7 +910,7 @@ void use_strength( stream.log("[AutoStory] use_strength: selection arrow found, confirming.", COLOR_GREEN); pbf_press_button(context, BUTTON_A, 80ms, 200ms); - pbf_mash_button(context, BUTTON_B, 4000ms); + pbf_mash_button(context, BUTTON_B, 6000ms); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index c7bb30fd6c..9754a32211 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -159,15 +159,25 @@ void checkpoint_save( AutoStoryStats& stats ); -// Save before the first attempt, then run `action(attempt_number)`. -// On any thrown exception, increment the reset counter and retry. +// Reload the game from the most recent backup save (auto-save). +// Called on retry when a checkpoint has no hard save (save=false). +// TODO: implement this function. +void reload_backup_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +); + +// If save=true (default): save the game before the first attempt, then run +// `action(attempt_number)`. On failure, reset from HOME and retry. +// If save=false: skip the hard save. On failure, reload from backup save and retry. // Throws a fatal error after too many consecutive failures. void checkpoint_reattempt_loop( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EventNotificationOption& notif_status_update, AutoStoryStats& stats, - std::function&& action + std::function&& action, + bool save = true ); // Execute every action in `actions` in order, repeated `times` times. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 74430c52a0..764d010262 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -42,24 +42,146 @@ void AutoStory_Segment_04::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_017().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_050().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_051().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_052().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_053().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_054().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_055().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_056().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_057().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_058().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_059().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_060().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_061().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_062().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_063().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_064().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_065().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_066().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_017::name() const{ return "004 - " + AutoStory_Segment_04().name(); } -std::string AutoStory_Checkpoint_017::start_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } -std::string AutoStory_Checkpoint_017::end_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_Checkpoint_050::name() const{ return "004.01 - leave_gym"; } +std::string AutoStory_Checkpoint_050::start_text() const{ return "Inside Hearthome City gym after third badge."; } +std::string AutoStory_Checkpoint_050::end_text() const{ return "Exited Hearthome City gym."; } +void AutoStory_Checkpoint_050::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_050(env, context, options, stats); +} -void AutoStory_Checkpoint_017::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - Checkpoint_017(env, context, options, stats); +std::string AutoStory_Checkpoint_051::name() const{ return "004.02 - go_to_veilstone_pokemon_center"; } +std::string AutoStory_Checkpoint_051::start_text() const{ return "Outside Hearthome City gym."; } +std::string AutoStory_Checkpoint_051::end_text() const{ return "Entered Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_051::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_051(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_052::name() const{ return "004.03 - heal_at_pokemon_center (Veilstone)"; } +std::string AutoStory_Checkpoint_052::start_text() const{ return "Inside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_052::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_052::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_052(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_053::name() const{ return "004.04 - go_to_galactic_warehouse"; } +std::string AutoStory_Checkpoint_053::start_text() const{ return "Outside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_053::end_text() const{ return "Reached Galactic Warehouse, battle started."; } +void AutoStory_Checkpoint_053::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_053(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_054::name() const{ return "004.05 - handle_battle (veilstone_galactic_grunts_1)"; } +std::string AutoStory_Checkpoint_054::start_text() const{ return "In battle against Galactic Grunt at Veilstone."; } +std::string AutoStory_Checkpoint_054::end_text() const{ return "Defeated Galactic Grunt at Veilstone."; } +void AutoStory_Checkpoint_054::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_054(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_055::name() const{ return "004.06 - enter_galactic_warehouse"; } +std::string AutoStory_Checkpoint_055::start_text() const{ return "Outside Galactic Warehouse."; } +std::string AutoStory_Checkpoint_055::end_text() const{ return "Entered Galactic Warehouse."; } +void AutoStory_Checkpoint_055::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_055(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_056::name() const{ return "004.07 - exit_galactic_warehouse"; } +std::string AutoStory_Checkpoint_056::start_text() const{ return "Inside Galactic Warehouse."; } +std::string AutoStory_Checkpoint_056::end_text() const{ return "Exited Galactic Warehouse with TM Fly."; } +void AutoStory_Checkpoint_056::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_056(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_057::name() const{ return "004.08 - go_to_route_214"; } +std::string AutoStory_Checkpoint_057::start_text() const{ return "Outside Galactic Warehouse."; } +std::string AutoStory_Checkpoint_057::end_text() const{ return "Arrived at Route 214, battle started."; } +void AutoStory_Checkpoint_057::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_057(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_058::name() const{ return "004.09 - handle_battle (route_214_psychic_abigail)"; } +std::string AutoStory_Checkpoint_058::start_text() const{ return "In battle against Psychic Abigail on Route 214."; } +std::string AutoStory_Checkpoint_058::end_text() const{ return "Defeated Psychic Abigail on Route 214."; } +void AutoStory_Checkpoint_058::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_058(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_059::name() const{ return "004.10 - walk_through_route_214"; } +std::string AutoStory_Checkpoint_059::start_text() const{ return "On Route 214 after Psychic Abigail."; } +std::string AutoStory_Checkpoint_059::end_text() const{ return "Walked through Route 214."; } +void AutoStory_Checkpoint_059::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_059(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_060::name() const{ return "004.11 - pass_by_valor_lakefront"; } +std::string AutoStory_Checkpoint_060::start_text() const{ return "End of Route 214."; } +std::string AutoStory_Checkpoint_060::end_text() const{ return "Passed Valor Lakefront, entered building."; } +void AutoStory_Checkpoint_060::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_060(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_061::name() const{ return "004.12 - go_through_building_route_213"; } +std::string AutoStory_Checkpoint_061::start_text() const{ return "Inside building between Route 214 and Route 213."; } +std::string AutoStory_Checkpoint_061::end_text() const{ return "Exited building onto Route 213."; } +void AutoStory_Checkpoint_061::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_061(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_062::name() const{ return "004.13 - go_through_route_213"; } +std::string AutoStory_Checkpoint_062::start_text() const{ return "On Route 213."; } +std::string AutoStory_Checkpoint_062::end_text() const{ return "Walked through Route 213, entered east Pastoria building."; } +void AutoStory_Checkpoint_062::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_062(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_063::name() const{ return "004.14 - go_through_house_east_pastoria"; } +std::string AutoStory_Checkpoint_063::start_text() const{ return "Inside east Pastoria building."; } +std::string AutoStory_Checkpoint_063::end_text() const{ return "Exited east Pastoria building into Pastoria City."; } +void AutoStory_Checkpoint_063::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_063(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_064::name() const{ return "004.15 - go_through_pastoria"; } +std::string AutoStory_Checkpoint_064::start_text() const{ return "In Pastoria City."; } +std::string AutoStory_Checkpoint_064::end_text() const{ return "Entered Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_064::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_064(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_065::name() const{ return "004.16 - heal_at_pokemon_center (Pastoria)"; } +std::string AutoStory_Checkpoint_065::start_text() const{ return "Inside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_065::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_065::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_065(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_066::name() const{ return "004.17 - go_to_pastoria_gym"; } +std::string AutoStory_Checkpoint_066::start_text() const{ return "Outside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_066::end_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +void AutoStory_Checkpoint_066::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_066(env, context, options, stats); } static void move_back_after_unexpected_battle( @@ -89,51 +211,40 @@ static bool handle_battle( const std::string& trainerid ){ context.wait_for_all_requests(); - pbf_wait(context, 15000ms); + stream.log("Battling " + trainerid + "...", COLOR_GREEN); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); + if (ret_menu < 0){ + stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if (trainerid == "veilstone_galactic_grunts_1"){ - // Select Razor Leaf and mash that + if ( + trainerid == "veilstone_galactic_grunts_1" or + trainerid == "route_213_tuber_chelsea" or + trainerid == "route_213_tuber_jared" or + trainerid == "route_213_beauty_cindy" + ) { + // Select Razor Leaf pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("handle_battle_veilstone_galactic_grunts_2: black screen not detected!", COLOR_RED); - return false; - } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_veilstone_galactic_grunts_2: transition confirmed."); - return true; - } else if (trainerid == "route_214_psychic_abigail"){ - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("handle_battle_psychic_abigail: black screen not detected!", COLOR_RED); - return false; - } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_psychic_abigail: transition confirmed."); - return true; - } else if (trainerid == "route_214_collector_brady"){ + } else if ( + trainerid == "route_214_collector_brady" or + trainerid == "route_214_collector_douglas" + ){ // Select Bulldoze pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + } else if ( + trainerid == "route_214_psychic_abigail" or + trainerid == "route_214_psychic_mitchell" + ){ + // Select Crunch + } + context.wait_for_all_requests(); + { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); int ret = run_until( stream, context, @@ -143,73 +254,22 @@ static bool handle_battle( {{black_screen}} ); if (ret < 0){ - stream.log("handle_battle_collector_brady: black screen not detected!", COLOR_RED); - return false; - } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - return true; - } else if (trainerid == "route_214_psychic_mitchell"){ - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("handle_battle_psychic_mitchell: black screen not detected!", COLOR_RED); + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - return true; - } else if (trainerid == "route_214_collector_douglas"){ - // Select Bulldoze - pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + } - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("handle_battle_collector_douglas: black screen not detected!", COLOR_RED); - return false; - } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - return true; - } else if ( + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + if ( trainerid == "route_213_tuber_chelsea" or trainerid == "route_213_tuber_jared" or trainerid == "route_213_beauty_cindy" - ) { - // Select Razor leaf - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("route_213_tuber_chelsea / jared: black screen not detected!", COLOR_RED); - return false; - } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); + ){ move_back_after_unexpected_battle(stream, context, trainerid); - return true; } - return false; + stream.log("handle_battle_" + trainerid + ": transition confirmed."); + return true; } static bool leave_gym( @@ -945,66 +1005,189 @@ static bool go_to_pastoria_gym( return true; } -void Checkpoint_017( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ +void checkpoint_050(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); } + } + ); +} + +void checkpoint_051(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_veilstone_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_veilstone_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_052(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_053(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_galactic_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_warehouse: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_054(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_055(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!enter_galactic_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_galactic_warehouse: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_056(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!exit_galactic_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_galactic_warehouse: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_057(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_route_214: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_058(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_059(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_060(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!pass_by_valor_lakefront(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pass_by_valor_lakefront: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_061(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_062(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_route_213(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_063(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_064(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_pastoria(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_065(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_066(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_pastoria_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); } - } + }, + false ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h index 3f13081a84..78ed57c732 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -28,7 +28,7 @@ class AutoStory_Segment_04 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_017 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_050 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,15 +40,231 @@ class AutoStory_Checkpoint_017 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; +void checkpoint_050(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -// start: third badge acquired -// end: fourth Gym Badge acquired (Pastoria City) -void Checkpoint_017( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +class AutoStory_Checkpoint_051 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_051(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_052 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_052(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_053 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_053(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_054 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_054(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_055 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_055(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_056 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_056(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_057 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_057(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_058 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_058(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_059 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_059(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_060 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_060(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_061 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_061(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_062 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_062(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_063 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_063(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_064 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_064(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_065 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_065(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_066 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_066(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index 709d7b5b90..8e59db6199 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -39,24 +39,138 @@ void AutoStory_Segment_05::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_checkpoint_018().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_067().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_068().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_069().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_070().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_071().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_072().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_073().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_074().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_075().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_076().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_077().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_078().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_079().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_080().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_081().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_082().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_checkpoint_018::name() const{ return "018 - " + AutoStory_Segment_05().name(); } -std::string AutoStory_checkpoint_018::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } -std::string AutoStory_checkpoint_018::end_text() const{ return "Fifth Gym Badge acquired (Veilstone City)."; } +std::string AutoStory_Checkpoint_067::name() const{ return "005.01 - go_to_pastoria_pokemon_center"; } +std::string AutoStory_Checkpoint_067::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_Checkpoint_067::end_text() const{ return "Entered Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_067::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_067(env, context, options, stats); +} -void AutoStory_checkpoint_018::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_018(env, context, options, stats); +std::string AutoStory_Checkpoint_068::name() const{ return "005.02 - exit_pastoria_pokemon_center"; } +std::string AutoStory_Checkpoint_068::start_text() const{ return "Inside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_068::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_068::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_068(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_069::name() const{ return "005.03 - pastoria_city_drama"; } +std::string AutoStory_Checkpoint_069::start_text() const{ return "Outside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_069::end_text() const{ return "Pastoria City drama complete, exited east building."; } +void AutoStory_Checkpoint_069::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_069(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_070::name() const{ return "005.04 - go_through_house_east_pastoria"; } +std::string AutoStory_Checkpoint_070::start_text() const{ return "Inside east Pastoria building."; } +std::string AutoStory_Checkpoint_070::end_text() const{ return "Exited east Pastoria building onto Route 213."; } +void AutoStory_Checkpoint_070::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_070(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_071::name() const{ return "005.05 - go_through_route_213_chasing"; } +std::string AutoStory_Checkpoint_071::start_text() const{ return "On Route 213 chasing Galactic Grunts."; } +std::string AutoStory_Checkpoint_071::end_text() const{ return "Entered east Route 213 building."; } +void AutoStory_Checkpoint_071::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_071(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_072::name() const{ return "005.06 - go_through_building_route_213"; } +std::string AutoStory_Checkpoint_072::start_text() const{ return "Inside building on Route 213."; } +std::string AutoStory_Checkpoint_072::end_text() const{ return "Exited building onto Route 214."; } +void AutoStory_Checkpoint_072::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_072(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_073::name() const{ return "005.07 - go_through_route_214"; } +std::string AutoStory_Checkpoint_073::start_text() const{ return "On Route 214 chasing Galactic Grunts."; } +std::string AutoStory_Checkpoint_073::end_text() const{ return "Flew to Solaceon Town."; } +void AutoStory_Checkpoint_073::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_073(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_074::name() const{ return "005.08 - go_inside_solaceon_pokemon_center"; } +std::string AutoStory_Checkpoint_074::start_text() const{ return "In Solaceon Town."; } +std::string AutoStory_Checkpoint_074::end_text() const{ return "Entered Solaceon Town Pokemon Center."; } +void AutoStory_Checkpoint_074::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_074(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_075::name() const{ return "005.09 - heal_at_solaceon_pokemon_center"; } +std::string AutoStory_Checkpoint_075::start_text() const{ return "Inside Solaceon Town Pokemon Center."; } +std::string AutoStory_Checkpoint_075::end_text() const{ return "Healed and exited Solaceon Town Pokemon Center."; } +void AutoStory_Checkpoint_075::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_075(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_076::name() const{ return "005.10 - go_to_the_psyduck_route_210"; } +std::string AutoStory_Checkpoint_076::start_text() const{ return "Outside Solaceon Town Pokemon Center."; } +std::string AutoStory_Checkpoint_076::end_text() const{ return "Reached Celestic Town Pokemon Center via Route 210."; } +void AutoStory_Checkpoint_076::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_076(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_077::name() const{ return "005.11 - heal_at_celestic_pokemon_center"; } +std::string AutoStory_Checkpoint_077::start_text() const{ return "Inside Celestic Town Pokemon Center."; } +std::string AutoStory_Checkpoint_077::end_text() const{ return "Healed and exited Celestic Town Pokemon Center."; } +void AutoStory_Checkpoint_077::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_077(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_078::name() const{ return "005.12 - celestic_town_drama"; } +std::string AutoStory_Checkpoint_078::start_text() const{ return "In Celestic Town."; } +std::string AutoStory_Checkpoint_078::end_text() const{ return "Entered Celestic Town cave."; } +void AutoStory_Checkpoint_078::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_078(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_079::name() const{ return "005.13 - discover_the_paintings"; } +std::string AutoStory_Checkpoint_079::start_text() const{ return "Inside Celestic Town cave."; } +std::string AutoStory_Checkpoint_079::end_text() const{ return "Discovered paintings and exited cave."; } +void AutoStory_Checkpoint_079::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_079(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_080::name() const{ return "005.14 - talk_to_cyrus_and_fly_hearthome"; } +std::string AutoStory_Checkpoint_080::start_text() const{ return "Outside Celestic Town cave."; } +std::string AutoStory_Checkpoint_080::end_text() const{ return "Entered Hearthome City Pokemon Center."; } +void AutoStory_Checkpoint_080::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_080(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_081::name() const{ return "005.15 - heal_at_hearthome_pokemon_center"; } +std::string AutoStory_Checkpoint_081::start_text() const{ return "Inside Hearthome City Pokemon Center."; } +std::string AutoStory_Checkpoint_081::end_text() const{ return "Healed and exited Hearthome City Pokemon Center."; } +void AutoStory_Checkpoint_081::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_081(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_082::name() const{ return "005.16 - get_into_hearthome_gym"; } +std::string AutoStory_Checkpoint_082::start_text() const{ return "Outside Hearthome City gym."; } +std::string AutoStory_Checkpoint_082::end_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } +void AutoStory_Checkpoint_082::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_082(env, context, options, stats); } static void move_back_after_unexpected_battle( @@ -90,6 +204,7 @@ static bool handle_battle( const std::string& trainerid ){ context.wait_for_all_requests(); + stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); if (ret_menu < 0){ @@ -1359,66 +1474,178 @@ static bool get_into_hearthome_gym( return true; } -void checkpoint_018( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ +void checkpoint_067(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ - /*if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); - }*/ if (!go_to_pastoria_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); } + } + ); +} + +void checkpoint_068(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!exit_pastoria_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_pastoria_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_069(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!pastoria_city_drama(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_070(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_071(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_route_213_chasing(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213_chasing: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_072(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_073(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_074(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_inside_solaceon_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_075(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_at_solaceon_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_076(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_the_psyduck_route_210(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_psyduck_route_210: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_077(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_at_celestic_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_078(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!celestic_town_drama(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_079(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!discover_the_paintings(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "discover_the_paintings: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_080(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_081(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_at_hearthome_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_082(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); } - } + }, + false ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h index 7c39818d4d..91df97f59c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h @@ -28,7 +28,7 @@ class AutoStory_Segment_05 : public AutoStory_Segment{ ) const override; }; -class AutoStory_checkpoint_018 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_067 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,15 +40,217 @@ class AutoStory_checkpoint_018 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; +void checkpoint_067(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -// start: fourth badge acquired -// end: fifth Gym Badge acquired (Veilstone City) -void checkpoint_018( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +class AutoStory_Checkpoint_068 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_068(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_069 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_069(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_070 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_070(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_071 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_071(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_072 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_072(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_073 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_073(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_074 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_074(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_075 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_075(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_076 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_076(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_077 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_077(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_078 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_078(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_079 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_079(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_080 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_080(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_081 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_081(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_082 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_082(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 2f37090614..602b497211 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -39,24 +39,90 @@ void AutoStory_Segment_06::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_019().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_083().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_084().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_085().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_086().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_087().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_088().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_089().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_090().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_091().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_092().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_019::name() const{ return "019 - " + AutoStory_Segment_06().name(); } -std::string AutoStory_Checkpoint_019::start_text() const{ return "Fifth Gym Badge acquired (Veilstone City)."; } -std::string AutoStory_Checkpoint_019::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } +std::string AutoStory_Checkpoint_083::name() const{ return "006.01 - cynthia_and_fly_to_jubilife"; } +std::string AutoStory_Checkpoint_083::start_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_083::end_text() const{ return "Flew to Jubilife City."; } +void AutoStory_Checkpoint_083::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_083(env, context, options, stats); +} -void AutoStory_Checkpoint_019::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_019(env, context, options, stats); +std::string AutoStory_Checkpoint_084::name() const{ return "006.02 - enter_jubilife_pokemon_center"; } +std::string AutoStory_Checkpoint_084::start_text() const{ return "In Jubilife City."; } +std::string AutoStory_Checkpoint_084::end_text() const{ return "Entered Jubilife City Pokemon Center."; } +void AutoStory_Checkpoint_084::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_084(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_085::name() const{ return "006.03 - heal_and_exit_jubilife_pokemon_center"; } +std::string AutoStory_Checkpoint_085::start_text() const{ return "Inside Jubilife City Pokemon Center."; } +std::string AutoStory_Checkpoint_085::end_text() const{ return "Healed and exited Jubilife City Pokemon Center."; } +void AutoStory_Checkpoint_085::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_085(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_086::name() const{ return "006.04 - move_to_jubilife_west_exit"; } +std::string AutoStory_Checkpoint_086::start_text() const{ return "Outside Jubilife City Pokemon Center."; } +std::string AutoStory_Checkpoint_086::end_text() const{ return "Entered west exit building of Jubilife City."; } +void AutoStory_Checkpoint_086::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_086(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_087::name() const{ return "006.05 - go_through_jubilife_west_exit"; } +std::string AutoStory_Checkpoint_087::start_text() const{ return "Inside Jubilife City west exit building."; } +std::string AutoStory_Checkpoint_087::end_text() const{ return "Exited onto Route 218."; } +void AutoStory_Checkpoint_087::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_087(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_088::name() const{ return "006.06 - go_through_route_218"; } +std::string AutoStory_Checkpoint_088::start_text() const{ return "On Route 218."; } +std::string AutoStory_Checkpoint_088::end_text() const{ return "Entered building at end of Route 218."; } +void AutoStory_Checkpoint_088::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_088(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_089::name() const{ return "006.07 - go_through_building_route_218"; } +std::string AutoStory_Checkpoint_089::start_text() const{ return "Inside building at end of Route 218."; } +std::string AutoStory_Checkpoint_089::end_text() const{ return "Exited building into Canalave City."; } +void AutoStory_Checkpoint_089::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_089(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_090::name() const{ return "006.08 - go_to_clint_and_pokemon_center"; } +std::string AutoStory_Checkpoint_090::start_text() const{ return "In Canalave City."; } +std::string AutoStory_Checkpoint_090::end_text() const{ return "Battled Clint and entered Canalave City Pokemon Center."; } +void AutoStory_Checkpoint_090::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_090(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_091::name() const{ return "006.09 - heal_and_exit_canalave_pokemon_center"; } +std::string AutoStory_Checkpoint_091::start_text() const{ return "Inside Canalave City Pokemon Center."; } +std::string AutoStory_Checkpoint_091::end_text() const{ return "Healed and exited Canalave City Pokemon Center."; } +void AutoStory_Checkpoint_091::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_091(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_092::name() const{ return "006.10 - go_to_canalave_gym"; } +std::string AutoStory_Checkpoint_092::start_text() const{ return "Outside Canalave City Pokemon Center."; } +std::string AutoStory_Checkpoint_092::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } +void AutoStory_Checkpoint_092::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_092(env, context, options, stats); } static void move_back_after_unexpected_battle( @@ -81,6 +147,7 @@ static bool handle_battle( const std::string& trainerid ){ context.wait_for_all_requests(); + stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); if (ret_menu < 0){ @@ -548,12 +615,7 @@ static bool go_to_canalave_gym( -void checkpoint_019( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ +void checkpoint_083(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ /*if (!test(env.console, context)){ @@ -562,35 +624,106 @@ void checkpoint_019( if (!cynthia_and_fly_to_jubilife(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); } + } + ); +} + +void checkpoint_084(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!enter_jubilife_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_jubilife_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_085(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_086(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "move_to_jubilife_west_exit: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_087(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_jubilife_west_exit: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_088(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_route_218(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_089(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_090(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_clint_and_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_clint_and_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_091(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); } + }, + false + ); +} + +void checkpoint_092(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); } - - } + }, + false ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h index 9a4f47a707..55f84af77d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h @@ -28,7 +28,7 @@ class AutoStory_Segment_06 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_019 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_083 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,15 +40,133 @@ class AutoStory_Checkpoint_019 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; +void checkpoint_083(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -// start: fifth badge acquired -// end: sixth Gym Badge acquired (Canalave City) -void checkpoint_019( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +class AutoStory_Checkpoint_084 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_084(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_085 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_085(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_086 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_086(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_087 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_087(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_088 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_088(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_089 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_089(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_090 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_090(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_091 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_091(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_092 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_092(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 5dcd08a208..3f7fe6a9dd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -40,24 +40,237 @@ void AutoStory_Segment_07::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_020().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_093().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_094().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_095().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_096().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_097().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_098().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_099().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_100().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_101().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_102().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_103().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_104().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_105().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_106().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_107().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_108().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_109().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_110().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_020::name() const{ return "007 - " + AutoStory_Segment_07().name(); } -std::string AutoStory_Checkpoint_020::start_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } -std::string AutoStory_Checkpoint_020::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Checkpoint_093::name() const{ return "007.01 - leave_gym"; } +std::string AutoStory_Checkpoint_093::start_text() const{ return "Inside Canalave City gym after sixth badge."; } +std::string AutoStory_Checkpoint_093::end_text() const{ return "Exited Canalave City gym."; } +void AutoStory_Checkpoint_093::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_093(env, context, options, stats); +} -void AutoStory_Checkpoint_020::run_checkpoint( - SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_094::name() const{ return "007.02 - go_to_the_library"; } +std::string AutoStory_Checkpoint_094::start_text() const{ return "Outside Canalave City gym."; } +std::string AutoStory_Checkpoint_094::end_text() const{ return "Entered Canalave City library."; } +void AutoStory_Checkpoint_094::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_094(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_095::name() const{ return "007.03 - drama_at_the_library"; } +std::string AutoStory_Checkpoint_095::start_text() const{ return "Inside Canalave City library."; } +std::string AutoStory_Checkpoint_095::end_text() const{ return "Library drama complete, exited library."; } +void AutoStory_Checkpoint_095::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_095(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_096::name() const{ return "007.04 - talk_fly_and_enter_mount_coronet"; } +std::string AutoStory_Checkpoint_096::start_text() const{ return "Outside Canalave City library."; } +std::string AutoStory_Checkpoint_096::end_text() const{ return "Entered Mt. Coronet."; } +void AutoStory_Checkpoint_096::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_096(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_097::name() const{ return "007.05 - go_through_mount_coronet"; } +std::string AutoStory_Checkpoint_097::start_text() const{ return "Inside Mt. Coronet."; } +std::string AutoStory_Checkpoint_097::end_text() const{ return "Exited Mt. Coronet."; } +void AutoStory_Checkpoint_097::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_097(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_098::name() const{ return "007.06 - go_through_route_216"; } +std::string AutoStory_Checkpoint_098::start_text() const{ return "On Route 216."; } +std::string AutoStory_Checkpoint_098::end_text() const{ return "Entered small chalet on Route 216."; } +void AutoStory_Checkpoint_098::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_098(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_099::name() const{ return "007.07 - heal_small_house_route_216"; } +std::string AutoStory_Checkpoint_099::start_text() const{ return "Inside small chalet on Route 216."; } +std::string AutoStory_Checkpoint_099::end_text() const{ return "Rested in chalet and exited."; } +void AutoStory_Checkpoint_099::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_099(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_100::name() const{ return "007.08 - continue_through_route_216"; } +std::string AutoStory_Checkpoint_100::start_text() const{ return "Outside chalet on Route 216."; } +std::string AutoStory_Checkpoint_100::end_text() const{ return "Entered Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_100::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_100(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_101::name() const{ return "007.09 - heal_and_exit_veilstone_pokemon_center"; } +std::string AutoStory_Checkpoint_101::start_text() const{ return "Inside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_101::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_101::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_101(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_102::name() const{ return "007.10 - go_to_house_south_veilstone"; } +std::string AutoStory_Checkpoint_102::start_text() const{ return "Outside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_102::end_text() const{ return "Entered house south of Veilstone City."; } +void AutoStory_Checkpoint_102::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_102(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_103::name() const{ return "007.11 - go_through_house_south_veilstone"; } +std::string AutoStory_Checkpoint_103::start_text() const{ return "Inside house south of Veilstone City."; } +std::string AutoStory_Checkpoint_103::end_text() const{ return "Exited house onto Route 214."; } +void AutoStory_Checkpoint_103::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_103(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_104::name() const{ return "007.12 - walk_through_route_214"; } +std::string AutoStory_Checkpoint_104::start_text() const{ return "On Route 214 heading south."; } +std::string AutoStory_Checkpoint_104::end_text() const{ return "Entered Valor Lakefront."; } +void AutoStory_Checkpoint_104::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_104(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_105::name() const{ return "007.13 - go_through_lake_valor"; } +std::string AutoStory_Checkpoint_105::start_text() const{ return "At Valor Lakefront."; } +std::string AutoStory_Checkpoint_105::end_text() const{ return "Flew to Twinleaf Town and entered own house."; } +void AutoStory_Checkpoint_105::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_105(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_106::name() const{ return "007.14 - heal_at_home"; } +std::string AutoStory_Checkpoint_106::start_text() const{ return "Inside own house in Twinleaf Town."; } +std::string AutoStory_Checkpoint_106::end_text() const{ return "Healed at home and exited."; } +void AutoStory_Checkpoint_106::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_106(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_107::name() const{ return "007.15 - go_to_lake_verity"; } +std::string AutoStory_Checkpoint_107::start_text() const{ return "Outside own house in Twinleaf Town."; } +std::string AutoStory_Checkpoint_107::end_text() const{ return "Entered Verity Lakefront."; } +void AutoStory_Checkpoint_107::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_107(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_108::name() const{ return "007.16 - commander_mars_and_snowpoint"; } +std::string AutoStory_Checkpoint_108::start_text() const{ return "At Lake Verity."; } +std::string AutoStory_Checkpoint_108::end_text() const{ return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_108::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_108(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_109::name() const{ return "007.17 - heal_at_snowpoint_pokemon_center"; } +std::string AutoStory_Checkpoint_109::start_text() const{ return "Inside Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_109::end_text() const{ return "Healed and exited Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_109::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_109(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_110::name() const{ return "007.18 - go_to_snowpoint_gym"; } +std::string AutoStory_Checkpoint_110::start_text() const{ return "Outside Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_110::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +void AutoStory_Checkpoint_110::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_110(env, context, options, stats); +} + +static void move_back_after_unexpected_battle( + VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_020(env, context, options, stats); + const std::string& trainerid +){ + stream.log("Finishing battle with " + trainerid, COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + if (trainerid == "route_216_ace_maria") { + pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 8 + } else if (trainerid == "route_215_skier_edward") { + pbf_move_left_joystick(context, {-1, 0}, 8100ms, 100ms); // 27++ (snow) + } else if (trainerid == "route_217_skier_lexie") { + pbf_move_left_joystick(context, {0, +1}, 2100ms, 100ms); // 7++ (snow) + } else if (trainerid == "route_217_blackbelt_luke") { + pbf_move_left_joystick(context, {0, +1}, 5100ms, 100ms); // 17++ (snow) + } +} + +static bool handle_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + context.wait_for_all_requests(); + stream.log("Battling " + trainerid + "...", COLOR_GREEN); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); + if (ret_menu < 0){ + stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 500ms); + + if ( + trainerid == "route_216_ace_maria" or + trainerid == "route_216_skier_edward" or + trainerid == "route_217_skier_lexie" or + trainerid == "route_217_blackbelt_luke" or + trainerid == "route_217_ace_olivia" or + trainerid == "verity_lakefront_grunt_1" or + trainerid == "verity_lakefront_grunt_3" + ) { + // Select Bulldoze + pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + } else if ( + trainerid == "route_217_ace_dalton" + ) { + // Select Razor leaf + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + } else if ( + trainerid == "verity_lakefront_grunt_2" or + trainerid == "verity_lakefront_grunt_4" or + trainerid == "verity_lakefront_commander_mars" + ) { + // Select Crunch + } + context.wait_for_all_requests(); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_" + trainerid + ": transition confirmed."); + return true; } static bool leave_gym( @@ -358,7 +571,7 @@ static bool go_through_mount_coronet( use_strength(stream, context); context.wait_for_all_requests(); - pbf_move_left_joystick(context, {0, +1}, 4000ms, 100ms); // 10++ (strength) + pbf_move_left_joystick(context, {0, +1}, 10000ms, 500ms); // 10++ (strength) dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ @@ -368,7 +581,7 @@ static bool go_through_mount_coronet( int ret = run_until( stream, context, [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); }, {{black_screen}} ); @@ -380,14 +593,31 @@ static bool go_through_mount_coronet( }; context.wait_for_all_requests(); - pbf_wait(context, 2000ms); + pbf_wait(context, 3000ms); stream.log("go_through_mount_coronet: continuing through Mt. Coronet..."); - pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ - dpad.last_dir = DPAD_UP; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); - pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_mount_coronet: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_through_mount_coronet: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); @@ -405,36 +635,1319 @@ static bool go_through_mount_coronet( stream.log("go_through_mount_coronet: finished second part of Mt. Coronet", COLOR_GREEN); }; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {+1, 0}, 1200ms, 100ms); // 6+ pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + { + WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{white_screen}} + ); + if (ret < 0){ + stream.log("go_through_mount_coronet: white screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_mount_coronet: exited Mt. Coronet", COLOR_GREEN); + }; + return true; } -void checkpoint_020( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats +static bool go_through_route_216( + VideoStream& stream, + ProControllerContext& context ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - /*if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_route_216: going through the snow..."); + + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 5+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 2+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 9600ms, 100ms); // 48+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_route_216: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_through_route_216: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + MarkDetector ace_maria_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3200ms, 100ms); // 16 + }, + {{ace_maria_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_216_ace_maria: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_216_ace_maria"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_216_ace_maria"); } - if (!go_to_the_library(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + MarkDetector skier_edward_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 7500ms, 100ms); // 25++ (snow) + }, + {{skier_edward_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_216_skier_edward: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_216_skier_edward"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_216_skier_edward"); } - if (!drama_at_the_library(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); + } + } + + context.wait_for_all_requests(); + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 11); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_route_216: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_route_216: entered chalet", COLOR_GREEN); + }; + + return true; +} + +static bool heal_small_house_route_216( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_small_house_route_216: taking a quick nap..."); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + + { + ShortDialogWatcher sleep_in_a_bed(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{sleep_in_a_bed}} + ); + if (ret == 0){ + stream.log("heal_small_house_route_216_sleep_in_a_bed: taking a nap...", COLOR_GREEN); + + const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; + SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 10000ms); + }, + {{arrow}} + ); + if (ret != 0){ + stream.log("heal_small_house_route_216_sleep_in_a_bed: selection arrow not found.", COLOR_RED); + return false; } - if (!talk_fly_and_enter_mount_coronet(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); - }*/ - if (!go_through_mount_coronet(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_mash_button(context, BUTTON_B, 6000ms); + + } else if (ret < 0){ + stream.log("heal_small_house_route_216_sleep_in_a_bed: Didn't find the bed!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("heal_small_house_route_216: black screen not detected!", COLOR_RED); + return false; + } + stream.log("heal_small_house_route_216: exited chalet", COLOR_GREEN); + }; + + return true; +} + +static bool continue_through_route_216( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("continue_through_route_216: continuing through the snow..."); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 800ms, 5); + pbf_move_left_joystick(context, {0, +1}, 2200ms, 100ms); // 11+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 6); // Snow + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("continue_through_route_216: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("continue_through_route_216: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10++ (Snow) + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); // Snow + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 4++ (Snow) + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 1); // Snow + + { + MarkDetector ace_dalton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 7500ms, 100ms); // 25++ (snow) + }, + {{ace_dalton_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("continue_through_route_216_ace_dalton: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_217_ace_dalton"); } } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + pbf_move_left_joystick(context, {0, +1}, 17000ms, 100ms); // 67++ (snow) + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 800ms, 8); // Snow + + { + ShortDialogWatcher pick_up_TM100(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_TM100}} + ); + if (ret == 0){ + stream.log("continue_through_route_216: Picking up TM100.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 10000ms); + } else if (ret < 0){ + stream.log("continue_through_route_216: Didn't detect TM100 dialog box!", COLOR_RED); + return false; + } + } + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 7++ (snow) + + { + MarkDetector skier_lexie_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 6000ms, 100ms); // 20++ (snow) + }, + {{skier_lexie_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("continue_through_route_216_skier_lexie: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_217_skier_lexie"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_217_skier_lexie"); + } + } + } + + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 2); // Snow + repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 3); // Snow + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 2++ (snow) + pbf_move_left_joystick(context, {0, +1}, 2700ms, 100ms); // 9++ (snow) + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 2); // Snow + repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 1); // Snow + + { + MarkDetector blackbelt_luke_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5400ms, 100ms); // 21++ (snow) + }, + {{blackbelt_luke_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("continue_through_route_216_blackbelt_luke: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_217_blackbelt_luke"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_217_blackbelt_luke"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + activate_repel(stream, context); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + MarkDetector ace_olivia_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{ace_olivia_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("continue_through_route_216_ace_olivia: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_217_ace_olivia"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 9+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 11); + pbf_move_left_joystick(context, {+1, 0}, 7800ms, 100ms); // 26++ (snow) + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 2); // Snow + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 8++ (snow) + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 8++ (snow) + pbf_move_left_joystick(context, {+1, 0}, 2700ms, 100ms); // 9++ (snow) + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + fly_to(stream, context, FlyPoint::VeilstoneCity); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("continue_through_route_216: black screen not detected!", COLOR_RED); + return false; + } + stream.log("continue_through_route_216: entered Veilstone Pokemon Center", COLOR_GREEN); + } + + return true; +} + +static bool heal_and_exit_veilstone_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_and_exit_veilstone_pokemon_center: healing and exiting..."); + heal_pokemon(stream, context, "Veilstone city"); + return true; +} + + +static bool go_to_house_south_veilstone( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_house_south_veilstone: heading South..."); + + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ + pbf_move_left_joystick(context, {+1, 0}, 4000ms, 100ms); // 20+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_house_south_veilstone: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_to_house_south_veilstone: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 9); + + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 8 + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 1800ms, 100ms); // 7+ + + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3, true); + + pbf_move_left_joystick(context, {+1, 0}, 2200ms, 100ms); // 15 + context.wait_for_all_requests(); + + { + BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}} + ); + if (ret < 0){ + stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); + return false; + } + } + + return true; + +} + +static bool go_through_house_south_veilstone( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_house_south_veilstone: moving towards exit..."); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_house_south_veilstone: exited the house", COLOR_GREEN); + } + + return true; +} + +static bool walk_through_route_214( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("walk_through_route_214: going through route 214..."); + + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 3800ms, 100ms); // 19+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8); + + activate_repel(stream, context); + + pbf_move_left_joystick(context, {0, -1}, 2600ms, 100ms); // 13+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ + + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); + pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ + + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 3+ + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, true); + + pbf_move_left_joystick(context, {0, -1}, 6200ms, 100ms); // 31+ + context.wait_for_all_requests(); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("walk_through_route_214: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("walk_through_route_214: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("walk_through_route_214: black screen not detected!", COLOR_RED); + return false; + } + stream.log("walk_through_route_214: entered Valor Lakefront", COLOR_GREEN); + } + + return true; +} + +static bool go_through_lake_valor( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_lake_valor: going through the remnants of the lake..."); + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 2+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 3); + + // @GumGum: uncomment this when you've tested your part and made sure that it works. This is for the activate repel running. + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_lake_valor: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_through_lake_valor: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + + // @GumGum: Here comes the rest of the code + + + + // And this comes after having defeated the commander and having exited the cave + + fly_to(stream, context, FlyPoint::TwinleafTown); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_lake_valor: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_lake_valor: back into own's house", COLOR_GREEN); + } + + return true; +} + +static bool heal_at_home( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_at_home: sleeping at mom's..."); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + { + ShortDialogWatcher mom_heal_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{mom_heal_dialog}} + ); + if (ret == 0){ + stream.log("heal_at_home: Sleeping...", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 4000ms); + } else if (ret < 0){ + stream.log("heal_at_home: Didn't detect mom dialog box!", COLOR_RED); + return false; + } + } + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("heal_at_home: black screen not detected!", COLOR_RED); + return false; + } + stream.log("heal_at_home: leaving own's house", COLOR_GREEN); + } + + return true; +} + +static bool go_to_lake_verity( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_lake_verity: heading towards Lake Verity..."); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5); + + pbf_move_left_joystick(context, {0, +1}, 6800ms, 100ms); // 34+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {-1, 0}, 6200ms, 100ms); // 31+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_lake_verity: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_lake_verity: entered Verity lakefront", COLOR_GREEN); + } + return true; +} + +static bool commander_mars_and_snowpoint( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + context.wait_for_all_requests(); + stream.log("commander_mars_and_snowpoint: Dealing with Commander Mars first and then heading to Snowpoint City..."); + + /*{ + ShortDialogWatcher rowan_talking(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_clint}}); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint_rowan_talking: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("commander_mars_and_snowpoint_rowan_talking: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 4000ms); + context.wait_for_all_requests(); + } + }*/ + + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + + { + MarkDetector grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 + }, + {{grunt_1_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint_grunt_1: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "verity_lakefront_grunt_1"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "verity_lakefront_grunt_2"); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 7+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + { + MarkDetector grunt_3_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 + }, + {{grunt_3_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint_grunt_3: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "verity_lakefront_grunt_3"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "verity_lakefront_grunt_4"); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 3+ + + { + ShortDialogWatcher commander_mars_fight(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{commander_mars_fight}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint: Didn't detect Commander Mars dialog box!", COLOR_RED); + return false; + } else if (ret == 0){ + stream.log("commander_mars_and_snowpoint: Battling Commander Mars...", COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint_commander_mars: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "verity_lakefront_commander_mars"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_mash_button(context, BUTTON_B, 10000ms); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + fly_to(stream, context, FlyPoint::SnowpointCity); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); + return false; + } + stream.log("commander_mars_and_snowpoint: entered Snowpoint City Pokemon Center", COLOR_GREEN); + } + + return true; +} + +static bool heal_at_snowpoint_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + context.wait_for_all_requests(); + stream.log("heal_at_snowpoint_pokemon_center: Healing at Snowpoint City Pokemon center and exiting..."); + + heal_pokemon(stream, context, "Snowpoint City"); + + return true; +} + +static bool go_to_snowpoint_gym( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + context.wait_for_all_requests(); + stream.log("go_to_snowpoint_gym: Heading towards Snowpoint City gym..."); + + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 5+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 7); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); + return false; + } + stream.log("commander_mars_and_snowpoint: entered Snowpoint City Pokemon Center", COLOR_GREEN); + } + + return true; +} + + + + +void checkpoint_093(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + } + } + ); +} + +void checkpoint_094(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_the_library(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_095(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!drama_at_the_library(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_096(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!talk_fly_and_enter_mount_coronet(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_097(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_mount_coronet(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_098(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_route_216(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_099(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_small_house_route_216(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_small_house_route_216: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_100(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!continue_through_route_216(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "continue_through_route_216: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit_veilstone_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_house_south_veilstone(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_house_south_veilstone(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_104(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!walk_through_route_214(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_105(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_lake_valor(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_106(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_home(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_home: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_107(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_lake_verity(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_108(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!commander_mars_and_snowpoint(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "commander_mars_and_snowpoint: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_109(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_snowpoint_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_110(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_snowpoint_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + } + }, + false ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h index cb447ac81d..fc4d1e69fd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h @@ -28,7 +28,7 @@ class AutoStory_Segment_07 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_020 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_093 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,15 +40,245 @@ class AutoStory_Checkpoint_020 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; +void checkpoint_093(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -// start: sixth badge acquired -// end: seventh Gym Badge acquired (Snowpoint City) -void checkpoint_020( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +class AutoStory_Checkpoint_094 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_094(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_095 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_095(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_096 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_096(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_097 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_097(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_098 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_098(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_099 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_099(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_100 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_100(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_101 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_102 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_103 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_104 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_104(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_105 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_105(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_106 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_106(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_107 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_107(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_108 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_108(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_109 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_109(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_110 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_110(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } From f9dc39a2d23bacd8a79b1cfa4e8d19f30cedc17e Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Mon, 1 Jun 2026 17:47:31 -0500 Subject: [PATCH 07/39] Updated segements and functions Updated the small house function in segment 07 to not have issues with the ret declaration. optimized some functions in the segment 00 tools added a default to the fly to location list. --- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 1 + ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 429 ++++++++++++------ .../PokemonBDSP_AutoStory_Segment_00_tools.h | 1 + .../PokemonBDSP_AutoStory_Segment_07.cpp | 4 +- 4 files changed, 291 insertions(+), 144 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 05353cf0e7..8d642f452e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -397,6 +397,7 @@ std::string fly_point_name(FlyPoint place) { case FlyPoint::TwinleafTown: return "twinleaf_town"; case FlyPoint::VeilstoneCity: return "veilstone_city"; } + return "unknown"; } bool fly_to( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 4c48707df5..77211683f1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -34,8 +34,11 @@ #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" + #include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" #include "PokemonBDSP/Programs/PokemonBDSP_BasicCatcher.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" using namespace std::chrono_literals; @@ -402,36 +405,65 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- void fight_starly(VideoStream& stream, ProControllerContext& context) { - stream.log("Starting Starly battle..."); - - // Give time for battle transition - pbf_wait(context, 5000ms); + stream.log("[AutoStory] Starting Starly battle...", COLOR_BLUE); - // Mash A to get through intro dialogue - pbf_mash_button(context, BUTTON_A, 5000ms); - - // Use first move repeatedly (Tackle/Scratch/Pound) - for (int i = 0; i < 6; i++) { - pbf_press_button(context, BUTTON_A, 100ms, 2000ms); // Select move - pbf_wait(context, 3000ms); // Wait for animation + damage - } + bool battle_menu_seen = false; - // Mash through faint + post-battle dialogue - pbf_mash_button(context, BUTTON_A, 8000ms); + while (true) { - stream.log("Starly battle complete."); + context.wait_for_all_requests(); - wait_for_dialogue(stream, context, "Phase 9 (post battle text)"); + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + EndBattleWatcher end_battle; - pbf_mash_button(context, BUTTON_B, 20000ms); + int ret = run_until( + stream, + context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + { + {battle_menu}, + battle_menu_seen + ? PeriodicInferenceCallback{end_battle} + : PeriodicInferenceCallback{}, + } + ); - wait_for_dialogue(stream, context, "Phase 9 (post battle text)"); + switch (ret) { - pbf_mash_button(context, BUTTON_B, 5000ms); + case 0: + { + battle_menu_seen = true; + stream.log("[AutoStory] Battle menu detected.", COLOR_BLUE); - context.wait_for_all_requests(); + // Select Fight, then move slot 1. + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + break; + } + case 1: + { + stream.log("[AutoStory] Starly battle complete.", COLOR_GREEN); + + // Mash through post-battle XP / dialog, wait for overworld. + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}} + ); + return; + } + default: + stream.log("[AutoStory] Starly battle timed out.", COLOR_RED); + return; + } + } } // --------------------------------------------------------------------------- @@ -541,14 +573,25 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- void catch_1_pokemon( + SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context ) { stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); + // Save once before attempting to catch. On failure the game + // resets back to this save, so the save lives outside the loop. + stream.log("[AutoStory] Saving game before catch attempt...", COLOR_BLUE); + save_game(stream, context); context.wait_for_all_requests(); pbf_wait(context, 1000ms); + // ------------------------------------------------------- + // Mini-checkpoint loop: retry from the save above if we + // run out of balls or our Pokemon faints. + // ------------------------------------------------------- + while (true) { + BlackScreenWatcher battle_start; BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); BattleDialogWatcher battle_dialog(COLOR_YELLOW); @@ -615,10 +658,11 @@ namespace PokemonAutomation { if (menu_ret < 0) { stream.log( - "[AutoStory] Failed to detect battle menu.", + "[AutoStory] Failed to detect battle menu. Resetting to checkpoint...", COLOR_RED ); - return; + reset_game_from_home(env, env.console, context, true); + continue; } stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); @@ -626,7 +670,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); pbf_wait(context, 500ms); - // Catch the pokemon. + // --- Throw balls --- CatchResults result = basic_catcher( stream, context, @@ -638,26 +682,81 @@ namespace PokemonAutomation { switch (result.result) { case CatchResult::POKEMON_CAUGHT: stream.log( - "[AutoStory] Pokemon successfully caught.", + "[AutoStory] Pokemon successfully caught after " + + std::to_string(result.balls_used) + " ball(s).", COLOR_GREEN ); - break; + { + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}} + ); + } + return; - default: + case CatchResult::POKEMON_FAINTED: + stream.log("[AutoStory] Wild Pokemon fainted. Exiting battle.", COLOR_ORANGE); + { + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}} + ); + } + return; + + case CatchResult::OUT_OF_BALLS: stream.log( - "[AutoStory] Catch attempt failed.", - COLOR_ORANGE + "[AutoStory] Ran out of Poke Balls after " + + std::to_string(result.balls_used) + " throw(s). Resetting to checkpoint...", + COLOR_RED ); - break; - } + reset_game_from_home(env, env.console, context, true); + continue; - context.wait_for_all_requests(); + case CatchResult::OWN_FAINTED: + stream.log( + "[AutoStory] Own Pokemon fainted. Resetting to checkpoint...", + COLOR_RED + ); + reset_game_from_home(env, env.console, context, true); + continue; + + case CatchResult::BALL_LIMIT_REACHED: + stream.log( + "[AutoStory] Ball limit reached (" + + std::to_string(result.balls_used) + " throw(s)). Resetting to checkpoint...", + COLOR_RED + ); + reset_game_from_home(env, env.console, context, true); + continue; - // Force clear any remaining text boxes / summaries. - pbf_mash_button(context, BUTTON_B, 15000ms); + case CatchResult::CANNOT_THROW_BALL: + stream.log( + "[AutoStory] Cannot throw ball (menu timing issue). Resetting to checkpoint...", + COLOR_RED + ); + reset_game_from_home(env, env.console, context, true); + continue; - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); + default: + stream.log( + "[AutoStory] Catch timed out after " + + std::to_string(result.balls_used) + " ball(s). Resetting to checkpoint...", + COLOR_RED + ); + reset_game_from_home(env, env.console, context, true); + continue; + } + + } // end while(true) } // --------------------------------------------------------------------------- @@ -713,27 +812,48 @@ namespace PokemonAutomation { COLOR_ORANGE ); + // If battle_ret == 0 (black screen transition), wait for the + // battle menu to fully appear before attempting to navigate. + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + bool escaped = false; auto deadline = std::chrono::steady_clock::now() + 30s; while (std::chrono::steady_clock::now() < deadline) { - // Advance text. - pbf_press_button(context, BUTTON_B, 40ms, 120ms); + // Advance any dialog / transition text. + pbf_press_button(context, BUTTON_B, 40ms, 200ms); context.wait_for_all_requests(); - // Move cursor to Run. - pbf_move_left_joystick(context, {0, 1}, 80ms, 80ms); + // Wait for the action-selection menu to be visible before navigating. + BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + // Menu not yet visible, loop again. + continue; + } + + // Navigate to Run. Menu is 1x4 (Fight/Bag/Pokemon/Run) and wraps, + // so pressing up once from Fight lands on Run. + pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); // Select Run. - pbf_press_button(context, BUTTON_A, 60ms, 180ms); + pbf_press_button(context, BUTTON_A, 60ms, 0ms); context.wait_for_all_requests(); + // Watch for escape success or "can't escape" return to menu. EndBattleWatcher battle_end; - if (wait_until(stream, context, 1500ms, {{battle_end}}) == 0) { + BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { escaped = true; break; } + // run_result == 1: failed to run, back at battle menu - retry. + // run_result == -1: timed out - retry. } if (!escaped) { @@ -751,9 +871,9 @@ namespace PokemonAutomation { ); } - // Let overworld stabilize. - context.wait_for_all_requests(); - context.wait_for(1000ms); + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); } } @@ -794,40 +914,45 @@ namespace PokemonAutomation { if (battle_ret >= 0) { stream.log( - "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + "[AutoStory] Battle detected during movement.", COLOR_ORANGE ); + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + bool escaped = false; auto deadline = std::chrono::steady_clock::now() + 30s; while (std::chrono::steady_clock::now() < deadline) { - // Spam escape inputs - pbf_press_button(context, BUTTON_B, 40ms, 120ms); + pbf_press_button(context, BUTTON_B, 40ms, 200ms); context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down - pbf_press_button(context, BUTTON_A, 60ms, 180ms); + BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); context.wait_for_all_requests(); EndBattleWatcher battle_end; - if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { escaped = true; break; } } - // Light text advance - pbf_press_button(context, BUTTON_B, 40ms, 120ms); - context.wait_for_all_requests(); - - // Reposition cursor on "Run" - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 180ms); - context.wait_for_all_requests(); - if (!escaped) { - stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); pbf_press_button(context, BUTTON_X, 80ms, 300ms); @@ -840,9 +965,9 @@ namespace PokemonAutomation { ); } - // Critical: let overworld stabilize before next step - context.wait_for_all_requests(); - context.wait_for(1000ms); + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); continue; } @@ -882,40 +1007,45 @@ namespace PokemonAutomation { if (battle_ret >= 0) { stream.log( - "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + "[AutoStory] Battle detected during movement.", COLOR_ORANGE ); + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + bool escaped = false; auto deadline = std::chrono::steady_clock::now() + 30s; while (std::chrono::steady_clock::now() < deadline) { - // Spam escape inputs - pbf_press_button(context, BUTTON_B, 40ms, 120ms); + pbf_press_button(context, BUTTON_B, 40ms, 200ms); context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down - pbf_press_button(context, BUTTON_A, 60ms, 180ms); + BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); context.wait_for_all_requests(); EndBattleWatcher battle_end; - if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { escaped = true; break; } } - // Light text advance - pbf_press_button(context, BUTTON_B, 40ms, 120ms); - context.wait_for_all_requests(); - - // Reposition cursor on "Run" - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 180ms); - context.wait_for_all_requests(); - if (!escaped) { - stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); pbf_press_button(context, BUTTON_X, 80ms, 300ms); @@ -923,14 +1053,14 @@ namespace PokemonAutomation { throw OperationFailedException( ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", + "Failed to run from battle while moving up.", stream ); } - // Critical: let overworld stabilize before next step - context.wait_for_all_requests(); - context.wait_for(1000ms); + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); continue; } @@ -969,40 +1099,45 @@ namespace PokemonAutomation { if (battle_ret >= 0) { stream.log( - "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + "[AutoStory] Battle detected during movement.", COLOR_ORANGE ); + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + bool escaped = false; auto deadline = std::chrono::steady_clock::now() + 30s; while (std::chrono::steady_clock::now() < deadline) { - // Spam escape inputs - pbf_press_button(context, BUTTON_B, 40ms, 120ms); + pbf_press_button(context, BUTTON_B, 40ms, 200ms); context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down - pbf_press_button(context, BUTTON_A, 60ms, 180ms); + BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); context.wait_for_all_requests(); EndBattleWatcher battle_end; - if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { escaped = true; break; } } - // Light text advance - pbf_press_button(context, BUTTON_B, 40ms, 120ms); - context.wait_for_all_requests(); - - // Reposition cursor on "Run" - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 180ms); - context.wait_for_all_requests(); - if (!escaped) { - stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); pbf_press_button(context, BUTTON_X, 80ms, 300ms); @@ -1015,9 +1150,9 @@ namespace PokemonAutomation { ); } - // Critical: let overworld stabilize before next step - context.wait_for_all_requests(); - context.wait_for(1000ms); + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); continue; } @@ -1056,40 +1191,45 @@ namespace PokemonAutomation { if (battle_ret >= 0) { stream.log( - "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + "[AutoStory] Battle detected during movement.", COLOR_ORANGE ); + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + bool escaped = false; auto deadline = std::chrono::steady_clock::now() + 30s; while (std::chrono::steady_clock::now() < deadline) { - // Spam escape inputs - pbf_press_button(context, BUTTON_B, 40ms, 120ms); + pbf_press_button(context, BUTTON_B, 40ms, 200ms); context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); // down - pbf_press_button(context, BUTTON_A, 60ms, 180ms); + BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); context.wait_for_all_requests(); EndBattleWatcher battle_end; - if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { escaped = true; break; } } - // Light text advance - pbf_press_button(context, BUTTON_B, 40ms, 120ms); - context.wait_for_all_requests(); - - // Reposition cursor on "Run" - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 180ms); - context.wait_for_all_requests(); - if (!escaped) { - stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); pbf_press_button(context, BUTTON_X, 80ms, 300ms); @@ -1097,14 +1237,14 @@ namespace PokemonAutomation { throw OperationFailedException( ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", + "Failed to run from battle while moving left.", stream ); } - // Critical: let overworld stabilize before next step - context.wait_for_all_requests(); - context.wait_for(1000ms); + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); continue; } @@ -1143,40 +1283,45 @@ namespace PokemonAutomation { if (battle_ret >= 0) { stream.log( - "[AutoStory] battle detected during movement. ret = " + std::to_string(battle_ret), + "[AutoStory] Battle detected during movement.", COLOR_ORANGE ); + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + bool escaped = false; auto deadline = std::chrono::steady_clock::now() + 30s; while (std::chrono::steady_clock::now() < deadline) { - // Spam escape inputs - pbf_press_button(context, BUTTON_B, 40ms, 120ms); + pbf_press_button(context, BUTTON_B, 40ms, 200ms); context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 180ms); + BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); context.wait_for_all_requests(); EndBattleWatcher battle_end; - if (wait_until(stream, context, 1500ms, { {battle_end} }) == 0) { + BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { escaped = true; break; } } - // Light text advance - pbf_press_button(context, BUTTON_B, 40ms, 120ms); - context.wait_for_all_requests(); - - // Reposition cursor on "Run" - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 180ms); - context.wait_for_all_requests(); - if (!escaped) { - stream.log("[AutoStory] run_from_battle failed. Exiting to HOME.", COLOR_RED); + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); pbf_press_button(context, BUTTON_X, 80ms, 300ms); @@ -1184,14 +1329,14 @@ namespace PokemonAutomation { throw OperationFailedException( ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", + "Failed to run from battle while moving up.", stream ); } - // Critical: let overworld stabilize before next step - context.wait_for_all_requests(); - context.wait_for(1000ms); + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); continue; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h index 494a4f81c3..46dd4641e5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -54,6 +54,7 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- void catch_1_pokemon( + SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context ); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 3f7fe6a9dd..754bedf623 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -817,14 +817,14 @@ static bool heal_small_house_route_216( const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - int ret = run_until( + int arrow_ret = run_until( stream, context, [](ProControllerContext& context){ pbf_mash_button(context, BUTTON_A, 10000ms); }, {{arrow}} ); - if (ret != 0){ + if (arrow_ret != 0){ stream.log("heal_small_house_route_216_sleep_in_a_bed: selection arrow not found.", COLOR_RED); return false; } From 905b81752056927b9adc3558760ffbe075e43f79 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Mon, 1 Jun 2026 20:10:40 -0500 Subject: [PATCH 08/39] updated segments and save_game optimized the save_game function Updated starly fight function and the segment 00 routing --- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 94 +++++++------------ .../PokemonBDSP_AutoStory_Segment_00.cpp | 83 ++++++++-------- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 39 ++++---- 3 files changed, 95 insertions(+), 121 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 8d642f452e..beeedafebf 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -74,56 +74,37 @@ bool fake_save_game( bool save_game( SingleSwitchProgramEnvironment& env, ProControllerContext& context -){ - static constexpr size_t MAX_RETRIES = 10; - - bool success = false; +) { + static constexpr size_t MAX_RETRIES = 3; - for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++){ - try{ + for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++) { + try { env.console.log( "Checkpoint save attempt: " + std::to_string(attempt + 1) ); - // --------------------------- - // Force-close anything weird first. - // --------------------------- + // Only mash B on retries - no need to on first attempt + // if already cleanly in the overworld. + if (attempt > 0) { + pbf_mash_button(context, BUTTON_B, 1000ms); + } + + // Reuse existing feedback-based save: opens menu with MenuWatcher + // + respects GameSettings::OVERWORLD_TO_MENU_DELAY0 internally. + save_game(env.console, context); + + // Return to overworld cleanly. pbf_mash_button(context, BUTTON_B, 1000ms); - // --------------------------- - // Aggressively attempt to open menu. - // No dependence on MenuWatcher. - // --------------------------- - pbf_press_button(context, BUTTON_X, 160ms, 1500ms); - - // --------------------------- - // Trigger save - // --------------------------- - pbf_press_button(context, BUTTON_R, 160ms, 2500ms); - - // --------------------------- - // Confirm save - // --------------------------- - pbf_press_button(context, BUTTON_ZL, 160ms, 5000ms); - - // --------------------------- - // Return to overworld cleanly - // --------------------------- - pbf_mash_button(context, BUTTON_B, 1500ms); - - // --------------------------- - // Verify overworld - // --------------------------- + // Verify overworld. Generous timeout: save animation + fade can be slow. OverworldWatcher overworld; - int ret = wait_until( env.console, context, - std::chrono::seconds(5), - {overworld} + std::chrono::seconds(15), + { overworld } ); - - if (ret < 0){ + if (ret < 0) { throw OperationFailedException( ErrorReport::SEND_ERROR_REPORT, "Failed to detect overworld after save.", @@ -132,39 +113,30 @@ bool save_game( } env.console.log("Checkpoint save verified."); + return true; - success = true; - break; - - }catch (OperationFailedException& e){ + } + catch (OperationFailedException& e) { env.console.log( "Checkpoint save failed: " + e.message(), COLOR_RED ); - context.wait_for(1000ms); } } - // --------------------------- - // Blind fallback - // --------------------------- - if (!success){ - env.console.log( - "All save retries failed. Using blind fallback.", - COLOR_ORANGE - ); - - pbf_mash_button(context, BUTTON_B, 1000ms); - pbf_press_button(context, BUTTON_X, 160ms, 1500ms); - pbf_press_button(context, BUTTON_R, 160ms, 2500ms); - pbf_press_button(context, BUTTON_ZL, 160ms, 5000ms); - pbf_mash_button(context, BUTTON_B, 2000ms); - - context.wait_for_all_requests(); - } + // Blind fallback: reuse the non-feedback save_game(context) from GameNavigation + // rather than duplicating the button sequence manually. + env.console.log( + "All save retries failed. Using blind fallback.", + COLOR_ORANGE + ); + pbf_mash_button(context, BUTTON_B, 1000ms); + save_game(context); // PokemonBDSP_GameNavigation.h - non-feedback overload + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); - return success; + return false; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index bad790aaac..4561024e28 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -162,6 +162,7 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_A, 2500ms); pbf_wait(context, 300ms); + context.wait_for_all_requests(); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 680ms, 0ms); @@ -172,6 +173,8 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2763ms, 0ms); pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 58ms, 0ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1364ms, 0ms); + pbf_wait(context, 4000ms); + context.wait_for_all_requests(); wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); pbf_mash_button(context, BUTTON_A, 6000ms); @@ -181,17 +184,14 @@ namespace PokemonAutomation { pbf_wait(context, 800ms); pbf_wait(context, 1306ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); + pbf_wait(context, 1422ms); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2722ms, 0ms); - pbf_wait(context, 1445ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 161ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 370ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 667ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 130ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 705ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 196ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 684ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 205ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 590ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1600ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 501ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 652ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1817ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 975ms, 0ms); pbf_wait(context, 1422ms); wait_for_dialogue(stream, context, "Phase 8 (Barry 2nd dialog)"); @@ -200,14 +200,11 @@ namespace PokemonAutomation { pbf_wait(context, 856ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 920ms, 0ms); pbf_wait(context, 631ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 972ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 72ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1127ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 82ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1335ms, 0ms); - pbf_wait(context, 1124ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2513ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 51ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 2183ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1076ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 781ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2713ms, 0ms); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5861ms, 0ms); pbf_wait(context, 134ms); @@ -232,13 +229,14 @@ namespace PokemonAutomation { context.wait_for_all_requests(); } // --------------------------------------------------------------------------- - // Segment 00-001 begins - // Navigates into lake Verity to recieve starter - // --------------------------------------------------------------------------- + // Segment 00-001 begins + // Navigates into lake Verity to recieve starter + // --------------------------------------------------------------------------- static void starter_nav(VideoStream& stream, ProControllerContext& context) { // re-sync control pbf_press_button(context, BUTTON_B, 20ms, 200ms); - pbf_wait(context, 500ms); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); // continue movement pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 300ms, 0ms); @@ -281,16 +279,19 @@ namespace PokemonAutomation { stream.log("Mashing B for briefcase to fully open."); pbf_mash_button(context, BUTTON_B, 2000ms); } + static void post_starly_clenaup(VideoStream& stream, ProControllerContext& context) { + pbf_mash_button(context, BUTTON_B, 60000ms); + } + // --------------------------------------------------------------------------- + // Segment 00-002 begins + // Navigates from Exit of lake Verity to receipt of the Pokedex // --------------------------------------------------------------------------- - // Segment 00-002 begins - // Navigates from Exit of lake Verity to receipt of the Pokedex - // --------------------------------------------------------------------------- static void tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + pbf_wait(context, 2000ms); context.wait_for_all_requests(); - context.wait_for_all_requests(); pbf_wait(context, 563ms); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1700ms, 0ms); pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 510ms, 0ms); @@ -298,11 +299,13 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 310ms, 0ms); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5500ms, 0ms); pbf_wait(context, 3963ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Phase 10 (Professor dialog to mom)"); pbf_mash_button(context, BUTTON_B, 40000ms); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 820ms, 0ms); - pbf_wait(context, 1483ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 900ms, 0ms); + pbf_wait(context, 4000ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 800ms, 0ms); pbf_wait(context, 555ms); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 50ms, 0ms); pbf_wait(context, 23ms); @@ -427,7 +430,9 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 70000ms); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - catch_1_pokemon(stream, context); + catch_1_pokemon(env, stream, context); + context.wait_for_all_requests(); + save_game(stream, context); context.wait_for_all_requests(); pbf_wait(context, 1500ms); //-------------------------------------------------------- @@ -524,16 +529,15 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 5000ms); } // --------------------------------------------------------------------------- - // End of Segment 00 - 003 - // Standing in Jubilife after speaking to dawn. - // --------------------------------------------------------------------------- - - // --------------------------------------------------------------------------- - // Begin Segment 00 - 004 - // Standing at the entrance to jubilife after speaking to dawn, will navigate to the poketech challenge. - // Collects poketech and stands in front of the PC to end the tutorial section - // and send the starter to pokemon HOME. - // --------------------------------------------------------------------------- + // End of Segment 00 - 003 + // Standing in Jubilife after speaking to dawn. + // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Begin Segment 00 - 004 + // Standing at the entrance to jubilife after speaking to dawn, will navigate to the poketech challenge. + // Collects poketech and stands in front of the PC to end the tutorial section + // and send the starter to pokemon HOME. + // --------------------------------------------------------------------------- static void tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2580ms, 0ms); @@ -779,6 +783,7 @@ namespace PokemonAutomation { } //Fight Starly fight_starly(env.console, context); + post_starly_clenaup(env.console, context); } ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 77211683f1..52a47b855b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -407,58 +407,55 @@ namespace PokemonAutomation { void fight_starly(VideoStream& stream, ProControllerContext& context) { stream.log("[AutoStory] Starting Starly battle...", COLOR_BLUE); - bool battle_menu_seen = false; + // Declare OUTSIDE the loop so accumulated detector state + // (trigger counts, black-screen transitions) is preserved across frames. + // STARTER type: player has no Pokeballs, so ball-count areas won't + // be white. STARTER skips that check and only verifies status bars + // and the Fight button color. + BattleMenuWatcher battle_menu(BattleType::STARTER, COLOR_YELLOW); + EndBattleWatcher end_battle; while (true) { - context.wait_for_all_requests(); - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - EndBattleWatcher end_battle; - + // Phase 1: wait for either battle menu or battle end. int ret = run_until( - stream, - context, + stream, context, [](ProControllerContext& ctx) { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, - { - {battle_menu}, - battle_menu_seen - ? PeriodicInferenceCallback{end_battle} - : PeriodicInferenceCallback{}, - } - ); + { + {battle_menu}, + {end_battle}, + } + ); switch (ret) { - case 0: { - battle_menu_seen = true; - stream.log("[AutoStory] Battle menu detected.", COLOR_BLUE); + stream.log("[AutoStory] Battle menu detected. Selecting move...", COLOR_BLUE); // Select Fight, then move slot 1. pbf_press_button(context, BUTTON_A, 80ms, 500ms); pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + // Loop back - mash B through animations until next menu or end. break; } - case 1: { stream.log("[AutoStory] Starly battle complete.", COLOR_GREEN); - // Mash through post-battle XP / dialog, wait for overworld. + // Mash through XP / level-up / dialog screens back to overworld. OverworldWatcher overworld; run_until( stream, context, [](ProControllerContext& ctx) { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, - {{overworld}} + { {overworld} } ); return; } - default: stream.log("[AutoStory] Starly battle timed out.", COLOR_RED); return; From e5c7af9e59e778a212eea402e57f0152f5c99e2e Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Tue, 2 Jun 2026 09:17:45 -0500 Subject: [PATCH 09/39] updated segment 00 updated the catch1pokemon function updated grass movement to more efficiently run from battles addressed issues with movement timings --- .../PokemonBDSP_AutoStory_Segment_00.cpp | 19 +- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 2846 +++++++++-------- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 28 +- 3 files changed, 1531 insertions(+), 1362 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 4561024e28..3f25036efa 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -314,9 +314,9 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 745ms, 0ms); DateTime t{ 2026, 1, 1, 0, 0, 50 }; set_time_fast(env, context, t); - walk_right_until_on_path(stream, context); - walk_up_through_grass_2(stream, context); - walk_right_through_grass_2(stream, context); + walk_right_until_on_path(stream, context, BattleType::STARTER); + walk_up_through_grass_2(stream, context, BattleType::STARTER); + walk_right_through_grass_2(stream, context, BattleType::STARTER); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 730ms, 0ms); pbf_wait(context, 941ms); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1950ms, 0ms); @@ -346,7 +346,7 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 2010ms, 0ms); + /*pbf_move_left_joystick(context, {0.000000, -1.000000}, 2010ms, 0ms); wait_for_dialogue(stream, context, "Phase 14 (dawn tour)"); @@ -405,9 +405,9 @@ namespace PokemonAutomation { DateTime t{ 2026, 1, 1, 0, 0, 50 }; set_time_fast(env, context, t); - walk_right_until_on_path(stream, context); - walk_up_through_grass_2(stream, context); - walk_right_through_grass_2(stream, context); + walk_right_until_on_path(stream, context, BattleType::STARTER); + walk_up_through_grass_2(stream, context, BattleType::STARTER); + walk_right_through_grass_2(stream, context, BattleType::STARTER); context.wait_for_all_requests(); pbf_wait(context, 1500ms); @@ -429,8 +429,9 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 70000ms); context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - catch_1_pokemon(env, stream, context); + pbf_wait(context, 2000ms);*/ + catch_1_pokemon(stream, context); + pbf_wait(context, 1500ms); context.wait_for_all_requests(); save_game(stream, context); context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 52a47b855b..3f7eab2e3a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1,1346 +1,1500 @@ -/* BDSP AutoStory - Segment 00 Tools - * - * Helper implementations for Segment 00. - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include -#include "PokemonBDSP_AutoStory_Segment_00_tools.h" - -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/Tools/ErrorDumper.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" - -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" - -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "NintendoSwitch/NintendoSwitch_Settings.h" - -#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" -#include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_HomeToDateTime.h" -#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" - -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" - -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" - -#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" - -#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" -#include "PokemonBDSP/Programs/PokemonBDSP_BasicCatcher.h" -#include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" - -using namespace std::chrono_literals; - -namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - - // --------------------------------------------------------------------------- - // BattleDialogWatcher - // --------------------------------------------------------------------------- - - BattleDialogWatcher::BattleDialogWatcher(Color color) - : DetectorToFinder( - "BattleDialogWatcher", - std::chrono::milliseconds(250), - color - ) - { - } - - // --------------------------------------------------------------------------- - // Poketech Clown dialog helper - // --------------------------------------------------------------------------- - - int detect_clown_choice_arrow( - VideoOverlay& overlay, - VideoSnapshot snapshot - ) { - SelectionArrowFinder top_arrow( - overlay, - { 0.62, 0.37, 0.08, 0.10 }, - COLOR_RED - ); - - SelectionArrowFinder bottom_arrow( - overlay, - { 0.62, 0.48, 0.08, 0.10 }, - COLOR_RED - ); - - if (top_arrow.detect(snapshot)) { - return 0; - } - - if (bottom_arrow.detect(snapshot)) { - return 1; - } - - return -1; - } - - void clown_dialog_helper( - VideoStream& stream, - ProControllerContext& context, - DpadPosition direction - ) { - stream.log( - "[AutoStory] Starting clown dialog helper...", - COLOR_BLUE - ); - - const auto overall_deadline = - std::chrono::steady_clock::now() + 30s; - - // ------------------------------------------------- - // Phase 1: Move + interact until dialog is detected. - // ------------------------------------------------- - ShortDialogWatcher dialog(COLOR_YELLOW); - bool dialog_started = false; - - while (!dialog_started) { - - if (std::chrono::steady_clock::now() > overall_deadline) { - stream.log( - "[AutoStory] Clown helper timed out finding dialog.", - COLOR_RED - ); - return; - } - - context.wait_for_all_requests(); - pbf_press_dpad(context, direction, 80ms, 200ms); - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - - if (wait_until(stream, context, 1500ms, { {dialog} }) == 0) { - dialog_started = true; - stream.log("[AutoStory] Dialog detected.", COLOR_GREEN); - } - } - - // ------------------------------------------------- - // Phase 2: A mash for 2 seconds. - // ------------------------------------------------- - stream.log("[AutoStory] Mashing A to advance dialog...", COLOR_BLUE); - pbf_mash_button(context, BUTTON_A, 4000ms); - context.wait_for_all_requests(); - - // ------------------------------------------------- - // Phase 3: Clear remaining dialog. - // ------------------------------------------------- - pbf_mash_button(context, BUTTON_B, 4000ms); - - context.wait_for_all_requests(); - - stream.log( - "[AutoStory] Clown dialog helper complete.", - COLOR_GREEN - ); - } - // --------------------------------------------------------------------------- - // Time helper - // --------------------------------------------------------------------------- - - void set_time_fast( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - const DateTime& target - ) { - env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); - - // 1) Home - NintendoSwitch::go_home(env.console, context); - - // 2) Navigate to Date/Time settings quickly. - NintendoSwitch::home_to_date_time(env.console, context, true); - - // 3) Enter date editor. - pbf_press_button(context, BUTTON_A, 80ms, 240ms); - - // 4) Wait for date screen and set exact time. - NintendoSwitch::DateChangeWatcher date_reader(env.console); - int ret = wait_until( - env.console, context, 10s, - { date_reader } - ); - if (ret < 0) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Failed to enter Date Change screen.", - env.console - ); - } - - { - VideoOverlaySet overlays(env.console.overlay()); - date_reader.make_overlays(overlays); - date_reader.set_date(env.program_info(), env.console, context, target); - } - - // 5) Commit date. - pbf_press_button(context, BUTTON_A, 160ms, 240ms); - - // 6) Back to game. - pbf_press_button( - context, BUTTON_HOME, 160ms, - ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0 - ); - NintendoSwitch::resume_game_from_home(env.console, context, false); - - context.wait_for_all_requests(); - context.wait_for(500ms); - } - - - // --------------------------------------------------------------------------- - // Natalie battle - // --------------------------------------------------------------------------- - void fight_Natalie( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log( - "[AutoStory] Starting Natalie battle...", - COLOR_BLUE - ); - - // Allow battle transition to finish. - pbf_wait(context, 5000ms); - - bool battle_menu_seen = false; - - // State machine loop. - while (true) { - - context.wait_for_all_requests(); - - BattleMenuWatcher battle_menu(BattleType::TRAINER); - EndBattleWatcher end_battle; - - // Detect move-learning arrows. - SelectionArrowFinder learn_move( - stream.overlay(), - {0.50, 0.62, 0.40, 0.18}, - COLOR_YELLOW - ); - - int ret = run_until( - stream, - context, - [](ProControllerContext& context) { - // Continuously clear: - // EXP gain - // level up - // faint text - // send-out text - // battle narration - pbf_mash_button( - context, - BUTTON_B, - 120000ms - ); - }, - { - {battle_menu}, - battle_menu_seen - ? PeriodicInferenceCallback{end_battle} - : PeriodicInferenceCallback{}, - {learn_move}, - } - ); - - switch (ret) { - - // ------------------------------------------------- - // Battle menu detected. - // ------------------------------------------------- - case 0: - { - stream.log( - "[AutoStory] Battle menu detected.", - COLOR_BLUE - ); - - battle_menu_seen = true; - - // Open Fight menu. - pbf_press_button( - context, - BUTTON_A, - 80ms, - 500ms - ); - - // Hard reset move cursor to top. - pbf_press_dpad( - context, - DPAD_UP, - 80ms, - 150ms - ); - - pbf_press_dpad( - context, - DPAD_UP, - 80ms, - 150ms - ); - - pbf_press_dpad( - context, - DPAD_UP, - 80ms, - 150ms - ); - - // Move to move slot 3. - pbf_press_dpad( - context, - DPAD_DOWN, - 80ms, - 150ms - ); - - pbf_press_dpad( - context, - DPAD_DOWN, - 80ms, - 250ms - ); - - // Use move slot 3. - pbf_press_button( - context, - BUTTON_A, - 80ms, - 3000ms - ); - - break; - } - - // ------------------------------------------------- - // Battle finished. - // ------------------------------------------------- - case 1: - { - stream.log( - "[AutoStory] Natalie battle complete.", - COLOR_GREEN - ); - - pbf_mash_button( - context, - BUTTON_B, - 5000ms - ); - - return; - } - - // ------------------------------------------------- - // Move learning detected. - // ------------------------------------------------- - case 2: - { - stream.log( - "[AutoStory] Learn move detected.", - COLOR_ORANGE - ); - - // Always refuse move learning. - pbf_move_right_joystick( - context, - { 0, -1 }, - 160ms, - 840ms - ); - - pbf_press_button( - context, - BUTTON_A, - 160ms, - 840ms - ); - - break; - } - - // ------------------------------------------------- - // Timeout. - // ------------------------------------------------- - default: - { - stream.log( - "[AutoStory] Natalie battle timed out.", - COLOR_RED - ); - - pbf_mash_button( - context, - BUTTON_B, - 5000ms - ); - - return; - } - - } - } - } - - // --------------------------------------------------------------------------- - // Starly battle - // --------------------------------------------------------------------------- - - void fight_starly(VideoStream& stream, ProControllerContext& context) { - stream.log("[AutoStory] Starting Starly battle...", COLOR_BLUE); - - // Declare OUTSIDE the loop so accumulated detector state - // (trigger counts, black-screen transitions) is preserved across frames. - // STARTER type: player has no Pokeballs, so ball-count areas won't - // be white. STARTER skips that check and only verifies status bars - // and the Fight button color. - BattleMenuWatcher battle_menu(BattleType::STARTER, COLOR_YELLOW); - EndBattleWatcher end_battle; - - while (true) { - context.wait_for_all_requests(); - - // Phase 1: wait for either battle menu or battle end. - int ret = run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - { - {battle_menu}, - {end_battle}, - } - ); - - switch (ret) { - case 0: - { - stream.log("[AutoStory] Battle menu detected. Selecting move...", COLOR_BLUE); - - // Select Fight, then move slot 1. - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 3000ms); - // Loop back - mash B through animations until next menu or end. - break; - } - case 1: - { - stream.log("[AutoStory] Starly battle complete.", COLOR_GREEN); - - // Mash through XP / level-up / dialog screens back to overworld. - OverworldWatcher overworld; - run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - { {overworld} } - ); - return; - } - default: - stream.log("[AutoStory] Starly battle timed out.", COLOR_RED); - return; - } - } - } - - // --------------------------------------------------------------------------- - // Tristan battle - // --------------------------------------------------------------------------- - void fight_tristan( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log( - "[AutoStory] Starting Tristan battle...", - COLOR_BLUE - ); - - // Wait for battle transition to fully finish. - pbf_wait(context, 5000ms); - - bool battle_menu_seen = false; - - while (true) { - - context.wait_for_all_requests(); - - BattleMenuWatcher battle_menu(BattleType::TRAINER); - EndBattleWatcher end_battle; - - int ret = run_until( - stream, - context, - [](ProControllerContext& context) { - // Mash through battle text continuously. - pbf_mash_button( - context, - BUTTON_B, - 10000ms - ); - }, - { - {battle_menu}, - battle_menu_seen - ? PeriodicInferenceCallback{end_battle} - : PeriodicInferenceCallback{}, - } - ); - - switch (ret) { - - // ------------------------------------------------- - // Battle menu detected. - // ------------------------------------------------- - case 0: - battle_menu_seen = true; - - stream.log( - "[AutoStory] Battle menu detected.", - COLOR_BLUE - ); - - // Select Fight. - pbf_press_button( - context, - BUTTON_A, - 120ms, - 500ms - ); - - // Select move slot 1 (Scratch). - pbf_press_button( - context, - BUTTON_A, - 120ms, - 3000ms - ); - - break; - - // ------------------------------------------------- - // End battle detected. - // ------------------------------------------------- - case 1: - stream.log( - "[AutoStory] Tristan battle complete.", - COLOR_GREEN - ); - - pbf_mash_button( - context, - BUTTON_B, - 5000ms - ); - - return; - - default: - stream.log( - "[AutoStory] Tristan battle timeout.", - COLOR_RED - ); - - return; - } - } - } - - // --------------------------------------------------------------------------- - // Catch 1 Pokemon - // --------------------------------------------------------------------------- - - void catch_1_pokemon( - SingleSwitchProgramEnvironment& env, - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); - - // Save once before attempting to catch. On failure the game - // resets back to this save, so the save lives outside the loop. - stream.log("[AutoStory] Saving game before catch attempt...", COLOR_BLUE); - save_game(stream, context); - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - - // ------------------------------------------------------- - // Mini-checkpoint loop: retry from the save above if we - // run out of balls or our Pokemon faints. - // ------------------------------------------------------- - while (true) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - bool encounter_found = false; - - // Wiggle left/right until battle starts. - while (!encounter_found) { - - // Turn left. - pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); - context.wait_for_all_requests(); - - int ret = wait_until( - stream, - context, - 1500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - } - ); - - if (ret >= 0) { - encounter_found = true; - break; - } - - // Turn right. - pbf_move_left_joystick(context, {1, 0}, 120ms, 200ms); - context.wait_for_all_requests(); - - ret = wait_until( - stream, - context, - 1500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - } - ); - - if (ret >= 0) { - encounter_found = true; - break; - } - } - - stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); - - // Wait until the actual battle menu appears. - BattleMenuWatcher confirm_menu(BattleType::STANDARD); - - int menu_ret = wait_until( - stream, - context, - 15s, - { - {confirm_menu}, - } - ); - - if (menu_ret < 0) { - stream.log( - "[AutoStory] Failed to detect battle menu. Resetting to checkpoint...", - COLOR_RED - ); - reset_game_from_home(env, env.console, context, true); - continue; - } - - stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); - - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - - // --- Throw balls --- - CatchResults result = basic_catcher( - stream, - context, - Language::English, - "poke-ball", - 10 - ); - - switch (result.result) { - case CatchResult::POKEMON_CAUGHT: - stream.log( - "[AutoStory] Pokemon successfully caught after " + - std::to_string(result.balls_used) + " ball(s).", - COLOR_GREEN - ); - { - OverworldWatcher overworld; - run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{overworld}} - ); - } - return; - - case CatchResult::POKEMON_FAINTED: - stream.log("[AutoStory] Wild Pokemon fainted. Exiting battle.", COLOR_ORANGE); - { - OverworldWatcher overworld; - run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{overworld}} - ); - } - return; - - case CatchResult::OUT_OF_BALLS: - stream.log( - "[AutoStory] Ran out of Poke Balls after " + - std::to_string(result.balls_used) + " throw(s). Resetting to checkpoint...", - COLOR_RED - ); - reset_game_from_home(env, env.console, context, true); - continue; - - case CatchResult::OWN_FAINTED: - stream.log( - "[AutoStory] Own Pokemon fainted. Resetting to checkpoint...", - COLOR_RED - ); - reset_game_from_home(env, env.console, context, true); - continue; - - case CatchResult::BALL_LIMIT_REACHED: - stream.log( - "[AutoStory] Ball limit reached (" + - std::to_string(result.balls_used) + " throw(s)). Resetting to checkpoint...", - COLOR_RED - ); - reset_game_from_home(env, env.console, context, true); - continue; - - case CatchResult::CANNOT_THROW_BALL: - stream.log( - "[AutoStory] Cannot throw ball (menu timing issue). Resetting to checkpoint...", - COLOR_RED - ); - reset_game_from_home(env, env.console, context, true); - continue; - - default: - stream.log( - "[AutoStory] Catch timed out after " + - std::to_string(result.balls_used) + " ball(s). Resetting to checkpoint...", - COLOR_RED - ); - reset_game_from_home(env, env.console, context, true); - continue; - } - - } // end while(true) - } - - // --------------------------------------------------------------------------- - // Movement Helpers - // --------------------------------------------------------------------------- - - // --------------------------------------------------------------------------- - // Generic Movement Helper - Walk through grass in specified direction for - // specified steps, attempting to run from any battles that are encountered. - // --------------------------------------------------------------------------- - void walk_through_grass( - VideoStream& stream, - ProControllerContext& context, - int8_t x, - int8_t y, - size_t steps, - const std::string& direction_name - ) { - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < steps; step++) { - - context.wait_for_all_requests(); - - // Move. - pbf_move_left_joystick( - context, - {(double)x, (double)y}, - 200ms, - 0ms - ); - - context.wait_for_all_requests(); - - // Check for battle. - int battle_ret = wait_until( - stream, - context, - 2500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - } - ); - - if (battle_ret >= 0) { - - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - // If battle_ret == 0 (black screen transition), wait for the - // battle menu to fully appear before attempting to navigate. - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - - // Advance any dialog / transition text. - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - // Wait for the action-selection menu to be visible before navigating. - BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - // Menu not yet visible, loop again. - continue; - } - - // Navigate to Run. Menu is 1x4 (Fight/Bag/Pokemon/Run) and wraps, - // so pressing up once from Fight lands on Run. - pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - - // Select Run. - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - // Watch for escape success or "can't escape" return to menu. - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - // run_result == 1: failed to run, back at battle menu - retry. - // run_result == -1: timed out - retry. - } - - if (!escaped) { - stream.log( - "[AutoStory] Failed to run from battle.", - COLOR_RED - ); - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - } - } - - stream.log( - "[AutoStory] Completed grass movement: " + direction_name, - COLOR_GREEN - ); - } - - // --------------------------------------------------------------------------- - // Helper function for 1st grass patch - // --------------------------------------------------------------------------- - void walk_right_until_on_path(VideoStream& stream, ProControllerContext& context) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 20; step++) { - - context.wait_for_all_requests(); - - // Move right (fixed step) - pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed right movement (18 steps).", COLOR_GREEN); - } - - // --------------------------------------------------------------------------- - // Helper function to walk up through the second grass patch - // --------------------------------------------------------------------------- - void walk_up_through_grass_2(VideoStream& stream, ProControllerContext& context) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 18; step++) { - - context.wait_for_all_requests(); - - // Move right (fixed step) - pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving up.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); - } - // --------------------------------------------------------------------------- - // Helper function to walk right through the second grass patch - // --------------------------------------------------------------------------- - void walk_right_through_grass_2(VideoStream& stream, ProControllerContext& context) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 10; step++) { - - context.wait_for_all_requests(); - - // Move right (fixed step) - pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed right movement (10 steps).", COLOR_GREEN); - } - // --------------------------------------------------------------------------- - // Helper function to walk left through the third grass patch - // --------------------------------------------------------------------------- - void walk_left_through_grass_3(VideoStream& stream, ProControllerContext& context) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 18; step++) { - - context.wait_for_all_requests(); - - // Move left (fixed step) - pbf_move_left_joystick(context, { -1, 0 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving left.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); - } - // --------------------------------------------------------------------------- - // Helper function to walk up through the third grass patch - // --------------------------------------------------------------------------- - void walk_up_through_grass_3(VideoStream& stream, ProControllerContext& context) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 18; step++) { - - context.wait_for_all_requests(); - - // Move up (fixed step) - pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(BattleType::STANDARD, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(BattleType::STANDARD, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, -1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(BattleType::STANDARD, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving up.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); - } - } - } -} +/* BDSP AutoStory - Segment 00 Tools + * + * Helper implementations for Segment 00. + * + * From: https://github.com/PokemonAutomation/ + * + */ + + +#include +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Tools/ErrorDumper.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" + +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" + +#include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" +#include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_HomeToDateTime.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" + +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" + +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ReceivePokemonDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" + +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" + +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" +#include "PokemonBDSP/Programs/PokemonBDSP_BasicCatcher.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + + // --------------------------------------------------------------------------- + // BattleDialogWatcher + // --------------------------------------------------------------------------- + + BattleDialogWatcher::BattleDialogWatcher(Color color) + : DetectorToFinder( + "BattleDialogWatcher", + std::chrono::milliseconds(250), + color + ) + { + } + + // --------------------------------------------------------------------------- + // Poketech Clown dialog helper + // --------------------------------------------------------------------------- + + int detect_clown_choice_arrow( + VideoOverlay& overlay, + VideoSnapshot snapshot + ) { + SelectionArrowFinder top_arrow( + overlay, + { 0.62, 0.37, 0.08, 0.10 }, + COLOR_RED + ); + + SelectionArrowFinder bottom_arrow( + overlay, + { 0.62, 0.48, 0.08, 0.10 }, + COLOR_RED + ); + + if (top_arrow.detect(snapshot)) { + return 0; + } + + if (bottom_arrow.detect(snapshot)) { + return 1; + } + + return -1; + } + + void clown_dialog_helper( + VideoStream& stream, + ProControllerContext& context, + DpadPosition direction + ) { + stream.log( + "[AutoStory] Starting clown dialog helper...", + COLOR_BLUE + ); + + const auto overall_deadline = + std::chrono::steady_clock::now() + 30s; + + // ------------------------------------------------- + // Phase 1: Move + interact until dialog is detected. + // ------------------------------------------------- + ShortDialogWatcher dialog(COLOR_YELLOW); + bool dialog_started = false; + + while (!dialog_started) { + + if (std::chrono::steady_clock::now() > overall_deadline) { + stream.log( + "[AutoStory] Clown helper timed out finding dialog.", + COLOR_RED + ); + return; + } + + context.wait_for_all_requests(); + pbf_press_dpad(context, direction, 80ms, 200ms); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + + if (wait_until(stream, context, 1500ms, { {dialog} }) == 0) { + dialog_started = true; + stream.log("[AutoStory] Dialog detected.", COLOR_GREEN); + } + } + + // ------------------------------------------------- + // Phase 2: A mash for 2 seconds. + // ------------------------------------------------- + stream.log("[AutoStory] Mashing A to advance dialog...", COLOR_BLUE); + pbf_mash_button(context, BUTTON_A, 4000ms); + context.wait_for_all_requests(); + + // ------------------------------------------------- + // Phase 3: Clear remaining dialog. + // ------------------------------------------------- + pbf_mash_button(context, BUTTON_B, 4000ms); + + context.wait_for_all_requests(); + + stream.log( + "[AutoStory] Clown dialog helper complete.", + COLOR_GREEN + ); + } + // --------------------------------------------------------------------------- + // Time helper + // --------------------------------------------------------------------------- + + void set_time_fast( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + const DateTime& target + ) { + env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); + + // 1) Home + NintendoSwitch::go_home(env.console, context); + + // 2) Navigate to Date/Time settings quickly. + NintendoSwitch::home_to_date_time(env.console, context, true); + + // 3) Enter date editor. + pbf_press_button(context, BUTTON_A, 80ms, 240ms); + + // 4) Wait for date screen and set exact time. + NintendoSwitch::DateChangeWatcher date_reader(env.console); + int ret = wait_until( + env.console, context, 10s, + { date_reader } + ); + if (ret < 0) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to enter Date Change screen.", + env.console + ); + } + + { + VideoOverlaySet overlays(env.console.overlay()); + date_reader.make_overlays(overlays); + date_reader.set_date(env.program_info(), env.console, context, target); + } + + // 5) Commit date. + pbf_press_button(context, BUTTON_A, 160ms, 240ms); + + // 6) Back to game. + pbf_press_button( + context, BUTTON_HOME, 160ms, + ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0 + ); + NintendoSwitch::resume_game_from_home(env.console, context, false); + + context.wait_for_all_requests(); + context.wait_for(500ms); + } + + + // --------------------------------------------------------------------------- + // Natalie battle + // --------------------------------------------------------------------------- + void fight_Natalie( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log( + "[AutoStory] Starting Natalie battle...", + COLOR_BLUE + ); + + // Allow battle transition to finish. + pbf_wait(context, 5000ms); + + bool battle_menu_seen = false; + + // State machine loop. + while (true) { + + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(BattleType::TRAINER); + EndBattleWatcher end_battle; + + // Detect move-learning arrows. + SelectionArrowFinder learn_move( + stream.overlay(), + {0.50, 0.62, 0.40, 0.18}, + COLOR_YELLOW + ); + + int ret = run_until( + stream, + context, + [](ProControllerContext& context) { + // Continuously clear: + // EXP gain + // level up + // faint text + // send-out text + // battle narration + pbf_mash_button( + context, + BUTTON_B, + 120000ms + ); + }, + { + {battle_menu}, + battle_menu_seen + ? PeriodicInferenceCallback{end_battle} + : PeriodicInferenceCallback{}, + {learn_move}, + } + ); + + switch (ret) { + + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: + { + stream.log( + "[AutoStory] Battle menu detected.", + COLOR_BLUE + ); + + battle_menu_seen = true; + + // Open Fight menu. + pbf_press_button( + context, + BUTTON_A, + 80ms, + 500ms + ); + + // Hard reset move cursor to top. + pbf_press_dpad( + context, + DPAD_UP, + 80ms, + 150ms + ); + + pbf_press_dpad( + context, + DPAD_UP, + 80ms, + 150ms + ); + + pbf_press_dpad( + context, + DPAD_UP, + 80ms, + 150ms + ); + + // Move to move slot 3. + pbf_press_dpad( + context, + DPAD_DOWN, + 80ms, + 150ms + ); + + pbf_press_dpad( + context, + DPAD_DOWN, + 80ms, + 250ms + ); + + // Use move slot 3. + pbf_press_button( + context, + BUTTON_A, + 80ms, + 3000ms + ); + + break; + } + + // ------------------------------------------------- + // Battle finished. + // ------------------------------------------------- + case 1: + { + stream.log( + "[AutoStory] Natalie battle complete.", + COLOR_GREEN + ); + + pbf_mash_button( + context, + BUTTON_B, + 5000ms + ); + + return; + } + + // ------------------------------------------------- + // Move learning detected. + // ------------------------------------------------- + case 2: + { + stream.log( + "[AutoStory] Learn move detected.", + COLOR_ORANGE + ); + + // Always refuse move learning. + pbf_move_right_joystick( + context, + { 0, -1 }, + 160ms, + 840ms + ); + + pbf_press_button( + context, + BUTTON_A, + 160ms, + 840ms + ); + + break; + } + + // ------------------------------------------------- + // Timeout. + // ------------------------------------------------- + default: + { + stream.log( + "[AutoStory] Natalie battle timed out.", + COLOR_RED + ); + + pbf_mash_button( + context, + BUTTON_B, + 5000ms + ); + + return; + } + + } + } + } + + // --------------------------------------------------------------------------- + // Starly battle + // --------------------------------------------------------------------------- + + void fight_starly(VideoStream& stream, ProControllerContext& context) { + stream.log("[AutoStory] Starting Starly battle...", COLOR_BLUE); + + // Declare OUTSIDE the loop so accumulated detector state + // (trigger counts, black-screen transitions) is preserved across frames. + // STARTER type: player has no Pokeballs, so ball-count areas won't + // be white. STARTER skips that check and only verifies status bars + // and the Fight button color. + BattleMenuWatcher battle_menu(BattleType::STARTER, COLOR_YELLOW); + EndBattleWatcher end_battle; + + while (true) { + context.wait_for_all_requests(); + + // Phase 1: wait for either battle menu or battle end. + int ret = run_until( + stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + { + {battle_menu}, + {end_battle}, + } + ); + + switch (ret) { + case 0: + { + stream.log("[AutoStory] Battle menu detected. Selecting move...", COLOR_BLUE); + + // Select Fight, then move slot 1. + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + // Loop back - mash B through animations until next menu or end. + break; + } + case 1: + { + stream.log("[AutoStory] Starly battle complete.", COLOR_GREEN); + + // Mash through XP / level-up / dialog screens back to overworld. + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + { {overworld} } + ); + return; + } + default: + stream.log("[AutoStory] Starly battle timed out.", COLOR_RED); + return; + } + } + } + + // --------------------------------------------------------------------------- + // Tristan battle + // --------------------------------------------------------------------------- + void fight_tristan( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log( + "[AutoStory] Starting Tristan battle...", + COLOR_BLUE + ); + + // Wait for battle transition to fully finish. + pbf_wait(context, 5000ms); + + bool battle_menu_seen = false; + + while (true) { + + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(BattleType::TRAINER); + EndBattleWatcher end_battle; + + int ret = run_until( + stream, + context, + [](ProControllerContext& context) { + // Mash through battle text continuously. + pbf_mash_button( + context, + BUTTON_B, + 10000ms + ); + }, + { + {battle_menu}, + battle_menu_seen + ? PeriodicInferenceCallback{end_battle} + : PeriodicInferenceCallback{}, + } + ); + + switch (ret) { + + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: + battle_menu_seen = true; + + stream.log( + "[AutoStory] Battle menu detected.", + COLOR_BLUE + ); + + // Select Fight. + pbf_press_button( + context, + BUTTON_A, + 120ms, + 500ms + ); + + // Select move slot 1 (Scratch). + pbf_press_button( + context, + BUTTON_A, + 120ms, + 3000ms + ); + + break; + + // ------------------------------------------------- + // End battle detected. + // ------------------------------------------------- + case 1: + stream.log( + "[AutoStory] Tristan battle complete.", + COLOR_GREEN + ); + + pbf_mash_button( + context, + BUTTON_B, + 5000ms + ); + + return; + + default: + stream.log( + "[AutoStory] Tristan battle timeout.", + COLOR_RED + ); + + return; + } + } + } + + // --------------------------------------------------------------------------- + // Catch 1 Pokemon + // --------------------------------------------------------------------------- + + void catch_1_pokemon( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + bool encounter_found = false; + + // Wiggle left/right until battle starts. + while (!encounter_found) { + + // Turn left. + pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); + context.wait_for_all_requests(); + + int ret = wait_until( + stream, + context, + 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (ret >= 0) { + encounter_found = true; + break; + } + + // Turn right. + pbf_move_left_joystick(context, {1, 0}, 120ms, 200ms); + context.wait_for_all_requests(); + + ret = wait_until( + stream, + context, + 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (ret >= 0) { + encounter_found = true; + break; + } + } + + stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); + + // Wait until the actual battle menu appears. + BattleMenuWatcher confirm_menu(BattleType::STANDARD); + + int menu_ret = wait_until( + stream, + context, + 15s, + { + {confirm_menu}, + } + ); + + if (menu_ret < 0) { + stream.log( + "[AutoStory] Failed to detect battle menu.", + COLOR_RED + ); + return; + } + + stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + + // Catch the pokemon. + stream.log("[AutoStory] Throwing Poke Ball (manual bag open)...", COLOR_BLUE); + for (int throw_attempt = 0; throw_attempt < 10; throw_attempt++) { + // Open bag from battle menu (X), confirm first slot (ZL). + pbf_press_button(context, BUTTON_X, 160ms, 840ms); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + context.wait_for_all_requests(); + + // Wait to see if the battle menu comes back (ball failed) + // or if the battle ends (caught or fainted). + BattleMenuWatcher menu_back(BattleType::STANDARD, COLOR_YELLOW); + EndBattleWatcher battle_ended; + int throw_ret = wait_until( + stream, context, 30s, + { + {menu_back}, + {battle_ended}, + } + ); + + if (throw_ret == 1) { + // Battle ended - caught or fainted. Either way we're done. + stream.log("[AutoStory] Battle ended after throw.", COLOR_GREEN); + break; + } + // throw_ret == 0: menu came back, ball failed - try again. + // throw_ret < 0: timed out - fall through to B mash cleanup. + if (throw_ret < 0) { + stream.log("[AutoStory] Throw timed out, proceeding to cleanup.", COLOR_ORANGE); + break; + } + stream.log( + "[AutoStory] Ball failed, retrying (attempt " + std::to_string(throw_attempt + 2) + ")...", + COLOR_ORANGE + ); + } + + context.wait_for_all_requests(); + + // Force clear any remaining text boxes / summaries. + pbf_mash_button(context, BUTTON_B, 15000ms); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + } + + // --------------------------------------------------------------------------- + // catch_1_pokemon_v2 - burst wiggle variant for testing. + // Uses WIGGLES_PER_BURST consecutive left/right moves before checking for a + // battle, reducing total wait time compared to checking after every move. + // Commented out - enable for testing when ready. + // --------------------------------------------------------------------------- + // void catch_1_pokemon_v2( + // SingleSwitchProgramEnvironment& env, + // VideoStream& stream, + // ProControllerContext& context + // ) { + // stream.log("[AutoStory] Starting catch_1_pokemon_v2 helper...", COLOR_BLUE); + // + // // Save once before attempting to catch. On failure the game + // // resets back to this save, so the save lives outside the loop. + // stream.log("[AutoStory] Saving game before catch attempt...", COLOR_BLUE); + // { + // static constexpr int SAVE_MAX_RETRIES = 3; + // bool save_succeeded = false; + // for (int save_attempt = 0; save_attempt < SAVE_MAX_RETRIES; save_attempt++) { + // try { + // if (save_attempt > 0) { + // stream.log( + // "[AutoStory] Retrying save (attempt " + std::to_string(save_attempt + 1) + ")...", + // COLOR_ORANGE + // ); + // pbf_mash_button(context, BUTTON_B, 1000ms); + // context.wait_for_all_requests(); + // } + // save_game(stream, context); + // save_succeeded = true; + // break; + // } + // catch (OperationFailedException& e) { + // stream.log( + // "[AutoStory] Save attempt " + std::to_string(save_attempt + 1) + " failed: " + e.message(), + // COLOR_RED + // ); + // context.wait_for(1000ms); + // } + // } + // if (!save_succeeded) { + // stream.log("[AutoStory] All save attempts failed. Using blind fallback save.", COLOR_ORANGE); + // pbf_mash_button(context, BUTTON_B, 1000ms); + // save_game(context); + // pbf_mash_button(context, BUTTON_B, 2000ms); + // } + // } + // context.wait_for_all_requests(); + // pbf_wait(context, 1000ms); + // + // // ------------------------------------------------------- + // // Mini-checkpoint loop: retry from the save above if we + // // run out of balls or our Pokemon faints. + // // ------------------------------------------------------- + // while (true) { + // + // BlackScreenWatcher battle_start; + // BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + // BattleDialogWatcher battle_dialog(COLOR_YELLOW); + // + // bool encounter_found = false; + // + // // Wiggle left/right in bursts, checking for a battle after each burst. + // // Doing multiple moves before checking avoids waiting after every step. + // static constexpr int WIGGLES_PER_BURST = 3; + // while (!encounter_found) { + // for (int i = 0; i < WIGGLES_PER_BURST; i++) { + // pbf_move_left_joystick(context, {-1, 0}, 120ms, 80ms); + // pbf_move_left_joystick(context, { 1, 0}, 120ms, 80ms); + // } + // context.wait_for_all_requests(); + // + // int ret = wait_until( + // stream, + // context, + // 1500ms, + // { + // {battle_start}, + // {battle_menu}, + // {battle_dialog}, + // } + // ); + // + // if (ret >= 0) { + // encounter_found = true; + // } + // } + // + // stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); + // + // // Wait until the actual battle menu appears. + // BattleMenuWatcher confirm_menu(BattleType::STANDARD); + // + // int menu_ret = wait_until( + // stream, + // context, + // 15s, + // { + // {confirm_menu}, + // } + // ); + // + // if (menu_ret < 0) { + // stream.log( + // "[AutoStory] Failed to detect battle menu. Resetting to checkpoint...", + // COLOR_RED + // ); + // reset_game_from_home(env, env.console, context, true); + // continue; + // } + // + // stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); + // + // context.wait_for_all_requests(); + // pbf_wait(context, 500ms); + // + // // --- Throw balls --- + // CatchResults result = basic_catcher( + // stream, + // context, + // Language::English, + // "poke-ball", + // 10 + // ); + // + // switch (result.result) { + // case CatchResult::POKEMON_CAUGHT: + // stream.log( + // "[AutoStory] Pokemon successfully caught after " + + // std::to_string(result.balls_used) + " ball(s).", + // COLOR_GREEN + // ); + // { + // OverworldWatcher overworld; + // run_until( + // stream, context, + // [](ProControllerContext& ctx) { + // pbf_mash_button(ctx, BUTTON_B, 120000ms); + // }, + // {{overworld}} + // ); + // } + // return; + // + // case CatchResult::POKEMON_FAINTED: + // stream.log("[AutoStory] Wild Pokemon fainted. Exiting battle.", COLOR_ORANGE); + // { + // OverworldWatcher overworld; + // run_until( + // stream, context, + // [](ProControllerContext& ctx) { + // pbf_mash_button(ctx, BUTTON_B, 120000ms); + // }, + // {{overworld}} + // ); + // } + // return; + // + // case CatchResult::OUT_OF_BALLS: + // stream.log( + // "[AutoStory] Ran out of Poke Balls after " + + // std::to_string(result.balls_used) + " throw(s). Resetting to checkpoint...", + // COLOR_RED + // ); + // reset_game_from_home(env, env.console, context, true); + // continue; + // + // case CatchResult::OWN_FAINTED: + // stream.log( + // "[AutoStory] Own Pokemon fainted. Resetting to checkpoint...", + // COLOR_RED + // ); + // reset_game_from_home(env, env.console, context, true); + // continue; + // + // case CatchResult::BALL_LIMIT_REACHED: + // stream.log( + // "[AutoStory] Ball limit reached (" + + // std::to_string(result.balls_used) + " throw(s)). Resetting to checkpoint...", + // COLOR_RED + // ); + // reset_game_from_home(env, env.console, context, true); + // continue; + // + // case CatchResult::CANNOT_THROW_BALL: + // stream.log( + // "[AutoStory] Cannot throw ball (menu timing issue). Resetting to checkpoint...", + // COLOR_RED + // ); + // reset_game_from_home(env, env.console, context, true); + // continue; + // + // default: + // stream.log( + // "[AutoStory] Catch timed out after " + + // std::to_string(result.balls_used) + " ball(s). Resetting to checkpoint...", + // COLOR_RED + // ); + // reset_game_from_home(env, env.console, context, true); + // continue; + // } + // + // } // end while(true) + // } + + // --------------------------------------------------------------------------- + // Movement Helpers + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + // Generic Movement Helper - Walk through grass in specified direction for + // specified steps, attempting to run from any battles that are encountered. + // --------------------------------------------------------------------------- + void walk_through_grass( + VideoStream& stream, + ProControllerContext& context, + int8_t x, + int8_t y, + size_t steps, + const std::string& direction_name, + BattleType battle_type + ) { + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < steps; step++) { + + context.wait_for_all_requests(); + + // Move. + pbf_move_left_joystick( + context, + {(double)x, (double)y}, + 200ms, + 0ms + ); + + context.wait_for_all_requests(); + + // Check for battle. + int battle_ret = wait_until( + stream, + context, + 2500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (battle_ret >= 0) { + + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + // If battle_ret == 0 (black screen transition), wait for the + // battle menu to fully appear before attempting to navigate. + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + + // Advance any dialog / transition text. + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + // Wait for the action-selection menu to be visible before navigating. + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + // Menu not yet visible, loop again. + continue; + } + + // Navigate to Run. Menu is 1x4 (Fight/Bag/Pokemon/Run) and wraps, + // so pressing up once from Fight lands on Run. + pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); + + // Select Run. + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + // Watch for escape success or "can't escape" return to menu. + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + // run_result == 1: failed to run, back at battle menu - retry. + // run_result == -1: timed out - retry. + } + + if (!escaped) { + stream.log( + "[AutoStory] Failed to run from battle.", + COLOR_RED + ); + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle.", + stream + ); + } + + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + } + } + + stream.log( + "[AutoStory] Completed grass movement: " + direction_name, + COLOR_GREEN + ); + } + + // --------------------------------------------------------------------------- + // Helper function for 1st grass patch + // --------------------------------------------------------------------------- + void walk_right_until_on_path(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 20; step++) { + + context.wait_for_all_requests(); + + // Move right (fixed step) + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + } + + if (!escaped) { + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed right movement (18 steps).", COLOR_GREEN); + } + + // --------------------------------------------------------------------------- + // Helper function to walk up through the second grass patch + // --------------------------------------------------------------------------- + void walk_up_through_grass_2(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 18; step++) { + + context.wait_for_all_requests(); + + // Move right (fixed step) + pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + } + + if (!escaped) { + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving up.", + stream + ); + } + + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); + } + // --------------------------------------------------------------------------- + // Helper function to walk right through the second grass patch + // --------------------------------------------------------------------------- + void walk_right_through_grass_2(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 10; step++) { + + context.wait_for_all_requests(); + + // Move right (fixed step) + pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + } + + if (!escaped) { + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving right.", + stream + ); + } + + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed right movement (10 steps).", COLOR_GREEN); + } + // --------------------------------------------------------------------------- + // Helper function to walk left through the third grass patch + // --------------------------------------------------------------------------- + void walk_left_through_grass_3(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 18; step++) { + + context.wait_for_all_requests(); + + // Move left (fixed step) + pbf_move_left_joystick(context, { -1, 0 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + } + + if (!escaped) { + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving left.", + stream + ); + } + + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); + } + // --------------------------------------------------------------------------- + // Helper function to walk up through the third grass patch + // --------------------------------------------------------------------------- + void walk_up_through_grass_3(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { + + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < 18; step++) { + + context.wait_for_all_requests(); + + // Move up (fixed step) + pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); + + context.wait_for_all_requests(); + + // Check for battle after movement + int battle_ret = wait_until( + stream, context, + 2500ms, + { + {battle_start}, // 0 + {battle_menu}, // 1 + {battle_dialog}, // 2 + } + ); + + if (battle_ret >= 0) { + stream.log( + "[AutoStory] Battle detected during movement.", + COLOR_ORANGE + ); + + if (battle_ret == 0) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + // Press up once to wrap from Fight to Run. + pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); + + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + } + + if (!escaped) { + stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); + + pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); + pbf_press_button(context, BUTTON_X, 80ms, 300ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from battle while moving up.", + stream + ); + } + + // Wait until truly back in the overworld before the next step. + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + + continue; + } + } + + stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); + } + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h index 46dd4641e5..c823ec6586 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -1,5 +1,6 @@ #pragma once +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP_AutoStory_Segment_00.h" #include "CommonTools/Async/InferenceRoutines.h" @@ -54,11 +55,18 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- void catch_1_pokemon( - SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context ); + // Burst wiggle variant of catch_1_pokemon for testing. + // Commented out in the .cpp - uncomment both to enable. + // void catch_1_pokemon_v2( + // SingleSwitchProgramEnvironment& env, + // VideoStream& stream, + // ProControllerContext& context + // ); + void use_potion_first_pokemon( VideoStream& stream, ProControllerContext& context @@ -70,27 +78,32 @@ namespace PokemonAutomation { void walk_right_until_on_path( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD ); void walk_up_through_grass_2( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD ); void walk_right_through_grass_2( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD ); void walk_left_through_grass_3( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD ); void walk_up_through_grass_3( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD ); void walk_through_grass( VideoStream& stream, @@ -98,7 +111,8 @@ namespace PokemonAutomation { int8_t x, int8_t y, size_t steps, - const std::string& direction_name + const std::string& direction_name, + BattleType battle_type = BattleType::STANDARD ); // --------------------------------------------------------------------------- // Helper function to navigate the poketech challenge clown dialogue. From ee27d5b981959deb63839d692f592bd5299bd1a7 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 2 Jun 2026 17:21:23 +0200 Subject: [PATCH 10/39] Automatic saving when it's the first checkpoint and fixed previous force_save --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 8 + .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 42 +- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 2 + .../PokemonBDSP_AutoStory_Segment_00.cpp | 14 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 7 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 15 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 23 +- .../PokemonBDSP_AutoStory_Segment_04.cpp | 3 +- .../PokemonBDSP_AutoStory_Segment_05.cpp | 3 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 3 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 3 +- .../PokemonBDSP_AutoStory_Segment_08.cpp | 565 +++++++++++++++++- .../PokemonBDSP_AutoStory_Segment_08.h | 99 +++ .../PokemonBDSP_AutoStory_Segment_09.cpp | 3 +- 14 files changed, 763 insertions(+), 27 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 01641ecab4..0fafb15b48 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -137,6 +137,14 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + // Segment 08 (111-118) + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); /*checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index beeedafebf..c881621ee9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -185,8 +185,11 @@ void checkpoint_reattempt_loop( for (size_t i = 0;; i++){ try{ - if (i == 0 && save){ - checkpoint_save(env, context, notif_status_update, stats); + if (i == 0){ + if (save || stats.m_first_run){ + checkpoint_save(env, context, notif_status_update, stats); + } + stats.m_first_run = false; } action(i); @@ -368,6 +371,7 @@ std::string fly_point_name(FlyPoint place) { case FlyPoint::SunyshoreCity: return "sunyshore_city"; case FlyPoint::TwinleafTown: return "twinleaf_town"; case FlyPoint::VeilstoneCity: return "veilstone_city"; + case FlyPoint::CurrentCity: return "current_city"; } return "unknown"; } @@ -388,6 +392,38 @@ bool fly_to( pbf_wait(context, 1000ms); open_menu(stream, context, MenuCursorPosition::MAP, 8); + if (place == FlyPoint::CurrentCity) { + ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; + struct FlyAButtonDetector : VisualInferenceCallback { + ImageFloatBox m_box; + FlyAButtonDetector(const ImageFloatBox& box) + : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} + void make_overlays(VideoOverlaySet&) const override {} + bool process_frame(const ImageViewRGB32& frame, WallClock) override { + ImageStats s = image_stats(extract_box_reference(frame, m_box)); + return s.stddev.sum() > 30.0; + } + }; + FlyAButtonDetector watcher(button_a_box); + int ret = wait_until(stream, context, 3000ms, {{watcher}}); + if (ret < 0) { + stream.log("CurrentCity: A button not found.", COLOR_RED); + return false; + } + BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int fly_ret = run_until( + stream, context, + [](ProControllerContext& context){ pbf_mash_button(context, BUTTON_A, 200000ms); }, + {{flying_black_screen}} + ); + if (fly_ret < 0) { + stream.log("CurrentCity: black screen not detected!", COLOR_RED); + return false; + } + stream.log("CurrentCity: black screen detected, arrived.", COLOR_GREEN); + return true; + } + switch (place) { case FlyPoint::CelesticTown: pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); @@ -484,6 +520,8 @@ bool fly_to( repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8, false); repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 10, false); break; + case FlyPoint::CurrentCity: + break; } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 9754a32211..f274d7cff7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -44,6 +44,7 @@ struct AutoStoryStats : public StatsTracker{ std::atomic& m_checkpoint; std::atomic& m_segment; std::atomic& m_reset; + bool m_first_run = true; }; @@ -263,6 +264,7 @@ enum class FlyPoint { SunyshoreCity, TwinleafTown, VeilstoneCity, + CurrentCity, // cursor already starts on the city; skip all map navigation }; std::string fly_point_name(FlyPoint place); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 3f25036efa..2f0e0475e6 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -763,7 +763,8 @@ namespace PokemonAutomation { [&](size_t /*attempt*/) { // Segment 00-00: Intro and starter selection tutorial_part_1_navigation(env, env.console, context); - } + }, + true ); } static void checkpoint_001( @@ -784,6 +785,8 @@ namespace PokemonAutomation { } //Fight Starly fight_starly(env.console, context); + }, + true post_starly_clenaup(env.console, context); } ); @@ -798,7 +801,8 @@ namespace PokemonAutomation { [&](size_t /*attempt*/) { // Segment 00-002: Pokedex acquisition tutorial_part_2_navigation(env, env.console, context); - } + }, + true ); } static void checkpoint_003( @@ -811,7 +815,8 @@ namespace PokemonAutomation { [&](size_t /*attempt*/) { // Segment 00-003: Poketech acquisition tutorial_part_3_navigation(env, env.console, context); - } + }, + true ); } static void checkpoint_004( @@ -824,7 +829,8 @@ namespace PokemonAutomation { [&](size_t /*attempt*/) { // Segment 00-004: Poketech acquisition tutorial_part_4_navigation(env, env.console, context); - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 16eafad4cb..943683a44f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -250,7 +250,8 @@ namespace PokemonAutomation { [&](size_t /*attempt*/) { jubilife_to_oreburgh_gate(env, env.console, context); oreburgh_gate_nav(env, env.console, context); - } + }, + true ); } @@ -286,7 +287,9 @@ namespace PokemonAutomation { //#. Oreburgh Gym navigation and battle gym_Puzzle_01(env, env.console, context); - } + }, + + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 19c658bc80..3175e4d9be 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -249,7 +249,9 @@ void checkpoint_007( // TODO: implement Segment 02 gameplay logic roark_to_mars(env.console, context); - } + }, + + true ); } std::string AutoStory_checkpoint_008::name() const { return "008 - " + AutoStory_Segment_02().name(); } @@ -277,7 +279,9 @@ void checkpoint_008( // TODO: implement Segment 02 gameplay logic mars_to_eterna_forest(env.console, context); - } + }, + + true ); } @@ -306,7 +310,9 @@ void checkpoint_009( // TODO: implement Segment 02 gameplay logic eterna_forest(env.console, context); - } + }, + + true ); } std::string AutoStory_checkpoint_010::name() const { return "010 - " + AutoStory_Segment_02().name(); } @@ -335,7 +341,8 @@ void checkpoint_010( eterna_forest_exit_to_gym(env.console, context); //#. Eterna Gym navigation and battle gym_Puzzle_02(env.console, context); - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index e135e918e7..314d454786 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -312,7 +312,9 @@ void Checkpoint_011( // TODO: implement Segment 03 gameplay logic eterna_gym_to_jupiter(env.console, context); - } + }, + + true ); } std::string AutoStory_Checkpoint_012::name() const { return "012 - " + AutoStory_Segment_03().name(); } @@ -340,7 +342,9 @@ void Checkpoint_012( // TODO: implement Segment 03 gameplay logic jupiter_to_coronet(env.console, context); - } + }, + + true ); } std::string AutoStory_Checkpoint_013::name() const { return "013 - " + AutoStory_Segment_03().name(); } @@ -368,7 +372,9 @@ void Checkpoint_013( // TODO: implement Segment 03 gameplay logic coronet1(env.console, context); - } + }, + + true ); } std::string AutoStory_Checkpoint_014::name() const { return "014 - " + AutoStory_Segment_03().name(); } @@ -396,7 +402,9 @@ void Checkpoint_014( // TODO: implement Segment 03 gameplay logic coronet_to_heartholme(env.console, context); - } + }, + + true ); } std::string AutoStory_Checkpoint_015::name() const { return "015 - " + AutoStory_Segment_03().name(); } @@ -425,7 +433,9 @@ void Checkpoint_015( heartholme_to_lost_tower(env.console, context); lost_tower(env.console, context); - } + }, + + true ); } std::string AutoStory_Checkpoint_016::name() const { return "016 - " + AutoStory_Segment_03().name(); } @@ -454,7 +464,8 @@ void Checkpoint_016( lost_tower_to_veilstone_gym(env.console, context); //#. Veilstone Gym navigation and battle gym_Puzzle_03(env.console, context); - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 764d010262..e9af487658 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -1011,7 +1011,8 @@ void checkpoint_050(SingleSwitchProgramEnvironment& env, ProControllerContext& c if (!leave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); } - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index 8e59db6199..46d9d54cf1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -1480,7 +1480,8 @@ void checkpoint_067(SingleSwitchProgramEnvironment& env, ProControllerContext& c if (!go_to_pastoria_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); } - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 602b497211..00373af0ea 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -624,7 +624,8 @@ void checkpoint_083(SingleSwitchProgramEnvironment& env, ProControllerContext& c if (!cynthia_and_fly_to_jubilife(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); } - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 754bedf623..6c9acd2929 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -1760,7 +1760,8 @@ void checkpoint_093(SingleSwitchProgramEnvironment& env, ProControllerContext& c if (!leave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); } - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 770162b9d8..171dc94412 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -5,7 +5,18 @@ * */ +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Battle.h" #include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP_AutoStory_Segment_08.h" @@ -80,8 +91,16 @@ void AutoStory_Segment_08::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); - checkpoint_021_legendary(env, context, options, stats); + AutoStory_Checkpoint_111().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_112().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_113().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_114().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_115().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_116().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_117().run_checkpoint(env, context, options, stats); + + /*AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); + checkpoint_021_legendary(env, context, options, stats);*/ context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); @@ -92,6 +111,542 @@ std::string AutoStory_Checkpoint_021::name() const{ return "021 - " + Auto std::string AutoStory_Checkpoint_021::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } std::string AutoStory_Checkpoint_021::end_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } + +// ------------------------------------------------------------------ Functions logic --------------------------------------------------------- // + + +static void move_back_after_unexpected_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + stream.log("Finishing battle with " + trainerid, COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + if (trainerid == "route_216_ace_maria") { + pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 8 + } else if (trainerid == "route_215_skier_edward") { + pbf_move_left_joystick(context, {-1, 0}, 8100ms, 100ms); // 27++ (snow) + } else if (trainerid == "route_217_skier_lexie") { + pbf_move_left_joystick(context, {0, +1}, 2100ms, 100ms); // 7++ (snow) + } else if (trainerid == "route_217_blackbelt_luke") { + pbf_move_left_joystick(context, {0, +1}, 5100ms, 100ms); // 17++ (snow) + } +} + +static bool handle_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + context.wait_for_all_requests(); + stream.log("Battling " + trainerid + "...", COLOR_GREEN); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); + if (ret_menu < 0){ + stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 500ms); + + if ( + trainerid == "route_216_ace_maria" or + trainerid == "route_216_skier_edward" or + trainerid == "route_217_skier_lexie" or + trainerid == "route_217_blackbelt_luke" or + trainerid == "route_217_ace_olivia" or + trainerid == "verity_lakefront_grunt_1" or + trainerid == "verity_lakefront_grunt_3" + ) { + // Select Bulldoze + pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + } else if ( + trainerid == "route_217_ace_dalton" + ) { + // Select Razor leaf + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + } else if ( + trainerid == "verity_lakefront_grunt_2" or + trainerid == "verity_lakefront_grunt_4" or + trainerid == "verity_lakefront_commander_mars" + ) { + // Select Crunch + } + context.wait_for_all_requests(); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_" + trainerid + ": transition confirmed."); + return true; +} + +static bool leave_gym( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("leave_gym: leaving the gym..."); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("leave_gym: black screen not detected!", COLOR_RED); + return false; + } + stream.log("leave_gym: exited gym", COLOR_GREEN); + }; + return true; +} + +static bool heal_at_snowpoint_city_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_at_snowpoint_city_pokemon_center: flying towards the pokemon center..."); + + fly_to(stream, context, FlyPoint::CurrentCity); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("heal_at_snowpoint_city_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + stream.log("heal_at_snowpoint_city_pokemon_center: entered Pokemon Center.", COLOR_GREEN); + }; + return true; +} + +static bool heal_and_exit( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +){ + return heal_pokemon(stream, context, label); +} + +static bool go_to_lake_acuity( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_at_snowpoint_city_pokemon_center: flying towards the pokemon center..."); + + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 5+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8); + pbf_move_left_joystick(context, {0, -1}, 4000ms, 100ms); // 20+ + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 7+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 7+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ + + context.wait_for_all_requests(); + activate_repel(stream, context); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 8400ms, 100ms); // 28++ (snow) + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_lake_acuity: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_lake_acuity: entered Acuity lakefront.", COLOR_GREEN); + }; + return true; +} + +static bool talk_to_clint_then_go_veilstone( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("talk_to_clint_then_go_veilstone: first talking to Clint and then flying to Veilstone city..."); + + { + ShortDialogWatcher listen_to_clint_and_jupiter(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{listen_to_clint_and_jupiter}}); + if (ret < 0){ + stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 22000ms); + context.wait_for_all_requests(); + } + } + + fly_to(stream, context, FlyPoint::VeilstoneCity); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("talk_to_clint_then_go_veilstone: black screen not detected!", COLOR_RED); + return false; + } + stream.log("talk_to_clint_then_go_veilstone: entered Veilstone City Pokemon Center.", COLOR_GREEN); + }; + return true; +} + + +static bool get_key_and_enter_warehouse( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("get_key_and_enter_warehouse: Getting the warehouse key then entering the warehouse..."); + + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_mount_coronet: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_through_mount_coronet: Didn't detect repel dialog box!", COLOR_RED); + return false; + } + } + + pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + ShortDialogWatcher talk_to_grunt(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}} + ); + if (ret == 0){ + stream.log("get_key_and_enter_warehouse: Talking to the grunt.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 9000ms); + } else if (ret < 0){ + stream.log("get_key_and_enter_warehouse: Didn't detect grunt dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + ShortDialogWatcher pick_up_key(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_key}} + ); + if (ret == 0){ + stream.log("get_key_and_enter_warehouse: Got the key.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 5000ms); + } else if (ret < 0){ + stream.log("get_key_and_enter_warehouse: Didn't detect key pickup dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + + pbf_move_left_joystick(context, {0, -1}, 3600ms, 100ms); // 23 + pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 21 + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 9+ + pbf_move_left_joystick(context, {-1, 0}, 2600ms, 100ms); // 13+ + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 13+ + pbf_move_left_joystick(context, {+1, 0}, 2600ms, 100ms); // 12+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("get_key_and_enter_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("get_key_and_enter_warehouse: entered warehouse.", COLOR_GREEN); + }; + return true; +} + + +// ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // + + +void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + } + }, + true + ); +} + +void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Snowpoint City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_lake_acuity(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!talk_to_clint_then_go_veilstone(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Veilstone City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!get_key_and_enter_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); + } + }, + false + ); +} + + + + +std::string AutoStory_Checkpoint_111::name() const{ return "111 - Segment 08 - Gym badge acquired"; } +std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } +void AutoStory_Checkpoint_111::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_111(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_112::name() const{ return "112 - Segment 08 - Left Snowpoint Gym"; } +std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } +std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +void AutoStory_Checkpoint_112::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_112(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_113::name() const{ return "113 - Segment 08 - Arrived at Snowpoint Pokemon Center"; } +std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_113::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_113(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_114::name() const{ return "114 - Segment 08 - Healed at Snowpoint City Pokemon Center"; } +std::string AutoStory_Checkpoint_114::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_114::end_text() const{ return "Entered Lake Acuity Lakefront."; } +void AutoStory_Checkpoint_114::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_114(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_115::name() const{ return "115 - Segment 08 - Entered Acuity Lakefront"; } +std::string AutoStory_Checkpoint_115::start_text() const{ return "Entered Lake Acuity Lakefront."; } +std::string AutoStory_Checkpoint_115::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_115::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_115(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_116::name() const{ return "116 - Segment 08 - Arrived at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_116::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_116::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_116::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_116(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_117::name() const{ return "117 - Segment 08 - Healed at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_117::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_117::end_text() const{ return "Entered Team Galactic Warehouse."; } +void AutoStory_Checkpoint_117::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_117(env, context, options, stats); +} + + + void AutoStory_Checkpoint_021::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -111,7 +666,8 @@ void checkpoint_021( checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 08 gameplay logic (seventh badge area) - } + }, + true ); } @@ -129,7 +685,8 @@ void checkpoint_021_legendary( throw_pokeball_at_legendary(env.console, context); confirm_legendary_catch(env.console, context); exit_legendary_encounter(env.console, context); - } + }, + true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index c15b39b43a..ece6ebca15 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -28,6 +28,105 @@ class AutoStory_Segment_08 : public AutoStory_Segment{ ) const override; }; +class AutoStory_Checkpoint_111 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_112 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_113 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_114 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_115 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_116 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_117 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + + class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 709af0c957..3e04413d49 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -58,7 +58,8 @@ void checkpoint_022( checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 09 gameplay logic - } + }, + true ); } From db242449aae43a79164eea2731a5393f23737eb0 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 6 Jun 2026 17:26:33 +0200 Subject: [PATCH 11/39] Updates to Segment 08 --- .../VisualDetectors/BlackScreenDetector.h | 16 + .../AutoStory/PokemonBDSP_AutoStory.cpp | 12 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 77 + .../AutoStory/PokemonBDSP_AutoStoryTools.h | 15 + .../PokemonBDSP_AutoStory_Segment_08.cpp | 1807 +++++++++++++++-- .../PokemonBDSP_AutoStory_Segment_08.h | 116 ++ 6 files changed, 1873 insertions(+), 170 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h index 229159e2bf..80066081f2 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h @@ -110,6 +110,22 @@ class BlackScreenOverWatcher : public VisualInferenceCallback{ std::atomic m_black_is_over = false; }; +// Detect when a brief flash of black screen (1-2 frames) is over. +// Use instead of BlackScreenOverWatcher when the transition is too short for the 100ms hold. +class BlackScreenFlashWatcher : public BlackScreenOverWatcher{ +public: + BlackScreenFlashWatcher( + Color color = COLOR_RED, + const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, + double max_rgb_sum = 100, + double max_stddev_sum = 10, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(20), + std::chrono::milliseconds release_duration = std::chrono::milliseconds(100) + ) + : BlackScreenOverWatcher(color, box, max_rgb_sum, max_stddev_sum, hold_duration, release_duration) + {} +}; + diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 0fafb15b48..f63b41933c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -137,7 +137,6 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - // Segment 08 (111-118) checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); @@ -145,6 +144,17 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + + checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); /*checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index c881621ee9..9499c12aa7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -18,6 +18,7 @@ #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "CommonTools/Images/SolidColorTest.h" +#include "CommonTools/Images/ImageFilter.h" #include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" @@ -429,6 +430,9 @@ bool fly_to( pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); break; case FlyPoint::HearthomeCity: + case FlyPoint::OreburghCity: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; case FlyPoint::PastoriaCity: case FlyPoint::PokemonLeagueLower: case FlyPoint::PokemonLeagueUpper: @@ -487,6 +491,9 @@ bool fly_to( repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); break; case FlyPoint::OreburghCity: + city_icon_box = {0.566000, 0.702000, 0.012000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 10, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); break; case FlyPoint::PastoriaCity: break; @@ -925,6 +932,76 @@ void use_strength( } +bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLevel /*gear*/){ + static const ImageFloatBox FRONT_BOX{0.440000, 0.470000, 0.035000, 0.054000}; + static const ImageFloatBox BACK_BOX {0.520000, 0.470000, 0.022000, 0.054000}; + // Spark color range: white (R=232,G=255,B=255) down to light green (R=163,G=217,B=181) + static const uint32_t SPARK_MIN = 0xFF000000u | (163u << 16) | (217u << 8) | 181u; + static const uint32_t SPARK_MAX = 0xFFFFFFFFu; + static const size_t SPARK_THRESHOLD = 13; + + stream.log("[AutoStory] get_on_bicycle: mounting bicycle.", COLOR_WHITE); + pbf_press_button(context, BUTTON_PLUS, 80ms, 800ms); + + // Press B, then poll up to 1 s for Fast-gear sparks. + // Logs the peak pixel counts in both boxes for threshold tuning. + // Returns true if sparks are detected (switched TO Fast gear). + auto toggle_and_check = [&]() -> bool { + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_B, 80ms, 800ms); + + size_t max_front = 0, max_back = 0; + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000); + while (std::chrono::steady_clock::now() < deadline){ + context.wait_for(50ms); + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) continue; + + size_t front_pixels = 0, back_pixels = 0; + filter_rgb32_range(front_pixels, extract_box_reference(screen, FRONT_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); + filter_rgb32_range(back_pixels, extract_box_reference(screen, BACK_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); + + if (front_pixels > max_front) max_front = front_pixels; + if (back_pixels > max_back) max_back = back_pixels; + + if (front_pixels >= SPARK_THRESHOLD || back_pixels >= SPARK_THRESHOLD){ + stream.log( + "[AutoStory] get_on_bicycle: sparks detected " + "(front=" + std::to_string(front_pixels) + + " back=" + std::to_string(back_pixels) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_GREEN + ); + return true; + } + } + stream.log( + "[AutoStory] get_on_bicycle: no sparks detected " + "(peak front=" + std::to_string(max_front) + + " back=" + std::to_string(max_back) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_ORANGE + ); + return false; + }; + + if (toggle_and_check()){ + stream.log("[AutoStory] get_on_bicycle: Fast gear confirmed.", COLOR_GREEN); + return true; + } + + // First toggle produced no sparks -> we switched to Slow gear; toggle back to Fast. + stream.log("[AutoStory] get_on_bicycle: Slow gear detected, toggling back to Fast.", COLOR_ORANGE); + if (toggle_and_check()){ + stream.log("[AutoStory] get_on_bicycle: Fast gear confirmed after correction.", COLOR_GREEN); + return true; + } + + stream.log("[AutoStory] get_on_bicycle: Failed to confirm Fast gear after two toggles.", COLOR_RED); + return false; +} + + } } } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index f274d7cff7..6ab1ed3a13 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -292,6 +292,21 @@ void use_potion_first_pokemon( ProControllerContext& context ); +enum class GearLevel { + Fast, +}; + +// Mount the bicycle (press +) and confirm it is in the requested gear. +// Presses B to toggle gear and uses the Fast-gear spark visual effect to +// determine which gear was entered. If the wrong gear is entered, presses B +// again and re-checks. Player must be on foot when called. +// Returns false if gear state cannot be confirmed after two toggles. +bool get_on_bicycle( + VideoStream& stream, + ProControllerContext& context, + GearLevel gear +); + } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 171dc94412..09113d1a3d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -15,9 +15,9 @@ #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "../PokemonBDSP_AutoStory.h" #include "../PokemonBDSP_AutoStoryTools.h" #include "../Utils/PokemonBDSP_AutoStory_Battle.h" -#include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP_AutoStory_Segment_08.h" using namespace std::chrono_literals; @@ -98,6 +98,14 @@ void AutoStory_Segment_08::run_segment( AutoStory_Checkpoint_115().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_116().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_117().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_118().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_119().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_120().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_121().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_122().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_123().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_124().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_125().run_checkpoint(env, context, options, stats); /*AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); checkpoint_021_legendary(env, context, options, stats);*/ @@ -124,14 +132,12 @@ static void move_back_after_unexpected_battle( context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "route_216_ace_maria") { - pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 8 - } else if (trainerid == "route_215_skier_edward") { - pbf_move_left_joystick(context, {-1, 0}, 8100ms, 100ms); // 27++ (snow) - } else if (trainerid == "route_217_skier_lexie") { - pbf_move_left_joystick(context, {0, +1}, 2100ms, 100ms); // 7++ (snow) - } else if (trainerid == "route_217_blackbelt_luke") { - pbf_move_left_joystick(context, {0, +1}, 5100ms, 100ms); // 17++ (snow) + if (trainerid == "veilstone_warehouse_warehouse_grunt_1") { + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + } else if (trainerid == "galactic_hq_grunt_1") { + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + } else if (trainerid == "route_207_hiker_justin") { + pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 3+ } } @@ -153,25 +159,26 @@ static bool handle_battle( pbf_wait(context, 500ms); if ( - trainerid == "route_216_ace_maria" or - trainerid == "route_216_skier_edward" or - trainerid == "route_217_skier_lexie" or - trainerid == "route_217_blackbelt_luke" or - trainerid == "route_217_ace_olivia" or - trainerid == "verity_lakefront_grunt_1" or - trainerid == "verity_lakefront_grunt_3" + trainerid == "veilstone_warehouse_warehouse_grunt_2" or + trainerid == "galactic_hq_grunt_4" or + trainerid == "galactic_hq_grunt_5" ) { // Select Bulldoze pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( - trainerid == "route_217_ace_dalton" + trainerid == "galactic_hq_grunt_1" or + trainerid == "galactic_hq_double_grunts_1" ) { // Select Razor leaf pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( - trainerid == "verity_lakefront_grunt_2" or - trainerid == "verity_lakefront_grunt_4" or - trainerid == "verity_lakefront_commander_mars" + trainerid == "veilstone_warehouse_warehouse_grunt_1" or + trainerid == "galactic_hq_scientist_fredrick" or + trainerid == "galactic_hq_grunt_2" or + trainerid == "galactic_hq_grunt_3" or + trainerid == "galactic_hq_cyrus" or + trainerid == "galactic_hq_commander_saturn" or + trainerid == "route_207_hiker_justin" ) { // Select Crunch } @@ -450,14 +457,14 @@ static bool get_key_and_enter_warehouse( dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); - pbf_move_left_joystick(context, {0, -1}, 3600ms, 100ms); // 23 - pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 21 + pbf_move_left_joystick(context, {0, -1}, 3200ms, 100ms); // 23 + pbf_move_left_joystick(context, {-1, 0}, 2700ms, 100ms); // 21 pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 9+ pbf_move_left_joystick(context, {-1, 0}, 2600ms, 100ms); // 13+ pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 13+ pbf_move_left_joystick(context, {+1, 0}, 2600ms, 100ms); // 12+ dpad.last_dir = DPAD_RIGHT; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); @@ -477,174 +484,1636 @@ static bool get_key_and_enter_warehouse( return true; } +static bool go_through_warehouse( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_warehouse: Going through the warehouse..."); -// ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + { + ShortDialogWatcher open_door(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}} + ); + if (ret == 0){ + stream.log("go_through_warehouse: Opened the door.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 4000ms); + } else if (ret < 0){ + stream.log("go_through_warehouse: Didn't detect door dialog box!", COLOR_RED); + return false; + } + } -void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); - } - }, - true - ); -} + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ -void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); - } - }, - false - ); -} + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the warehouse's B2F.", COLOR_GREEN); + }; -void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Snowpoint City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); - } - }, - false - ); -} + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); -void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!go_to_lake_acuity(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); - } - }, - false - ); -} + pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); -void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!talk_to_clint_then_go_veilstone(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); - } - }, - false - ); -} + { + MarkDetector warehouse_grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); -void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 10800ms, 100ms); // 54+ + }, + {{warehouse_grunt_1_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("veilstone_warehouse_warehouse_grunt_1: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "veilstone_warehouse_warehouse_grunt_1"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "veilstone_warehouse_warehouse_grunt_1"); } - }, - false - ); -} + } + } -void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!get_key_and_enter_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1 + + + { + MarkDetector warehouse_grunt_2_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 13000ms, 100ms); // 54+ + }, + {{warehouse_grunt_2_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("veilstone_warehouse_warehouse_grunt_2: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "veilstone_warehouse_warehouse_grunt_2"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "veilstone_warehouse_warehouse_grunt_2"); } - }, - false - ); -} + } + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the warehouse's B1F.", COLOR_GREEN); + }; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); -std::string AutoStory_Checkpoint_111::name() const{ return "111 - Segment 08 - Gym badge acquired"; } -std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } -void AutoStory_Checkpoint_111::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_111(env, context, options, stats); -} + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ -std::string AutoStory_Checkpoint_112::name() const{ return "112 - Segment 08 - Left Snowpoint Gym"; } -std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } -std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -void AutoStory_Checkpoint_112::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_112(env, context, options, stats); -} + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic HQ 1F, first room.", COLOR_GREEN); + }; -std::string AutoStory_Checkpoint_113::name() const{ return "113 - Segment 08 - Arrived at Snowpoint Pokemon Center"; } -std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_113::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_113(env, context, options, stats); -} + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 6+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 6+ -std::string AutoStory_Checkpoint_114::name() const{ return "114 - Segment 08 - Healed at Snowpoint City Pokemon Center"; } -std::string AutoStory_Checkpoint_114::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_114::end_text() const{ return "Entered Lake Acuity Lakefront."; } -void AutoStory_Checkpoint_114::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_114(env, context, options, stats); -} + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + MarkDetector galactic_hq_grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); -std::string AutoStory_Checkpoint_115::name() const{ return "115 - Segment 08 - Entered Acuity Lakefront"; } -std::string AutoStory_Checkpoint_115::start_text() const{ return "Entered Lake Acuity Lakefront."; } -std::string AutoStory_Checkpoint_115::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_115::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_115(env, context, options, stats); -} + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6 + }, + {{black_screen},{galactic_hq_grunt_1_fight}} + ); + if (ret == 0) { + stream.log("go_through_warehouse: got into Galactic HQ 1F, second room.", COLOR_GREEN); + } else if (ret == 1){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen_2}} + ); + if (ret < 0){ + stream.log("galactic_hq_grunt_1: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_grunt_1"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "galactic_hq_grunt_1"); + } -std::string AutoStory_Checkpoint_116::name() const{ return "116 - Segment 08 - Arrived at Veilstone Pokemon Center"; } -std::string AutoStory_Checkpoint_116::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_116::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_116::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_116(env, context, options, stats); -} + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into Galactic HQ 1F, second room.", COLOR_GREEN); + }; + } + } -std::string AutoStory_Checkpoint_117::name() const{ return "117 - Segment 08 - Healed at Veilstone Pokemon Center"; } -std::string AutoStory_Checkpoint_117::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_117::end_text() const{ return "Entered Team Galactic Warehouse."; } -void AutoStory_Checkpoint_117::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_117(env, context, options, stats); -} + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 4+ + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic HQ 1F, third room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic HQ 2F, first room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 6+ + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic HQ 2F, second room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic HQ 1F, fourth room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + + { + MarkDetector galactic_hq_scientist_fredrick_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 6+ + }, + {{galactic_hq_scientist_fredrick_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_scientist_fredrick: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_scientist_fredrick"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic Warehouse B2F.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 4600ms, 100ms); // 23+ + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 7); + + { + ShortDialogWatcher pick_up_galactic_key(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_galactic_key}} + ); + if (ret == 0){ + stream.log("go_through_warehouse: Picked up the Galactic key.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_through_warehouse: Didn't detect grunt dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 5+ + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 6+ + + { + ShortDialogWatcher open_door(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}} + ); + if (ret == 0){ + stream.log("go_through_warehouse: Opened the first door with the Galactic Key.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 4000ms); + } else if (ret < 0){ + stream.log("go_through_warehouse: Didn't detect door opening dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 8+ + pbf_move_left_joystick(context, {0, -1}, 3200ms, 100ms); // 16+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got into the Galactic Warehouse entrance area.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 5+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_warehouse: got out of the Galactic Warehouse.", COLOR_GREEN); + }; + + return true; +} + + +static bool heal_at_veilstone_pokemon_center( + VideoStream& stream, + ProControllerContext& context +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("heal_at_veilstone_pokemon_center: Flying to Veilstone Pokemon Center and entering..."); + + fly_to(stream, context, FlyPoint::CurrentCity); + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("heal_at_veilstone_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + stream.log("heal_at_veilstone_pokemon_center: entered the Veilstone Pokemon Center.", COLOR_GREEN); + }; + + return true; +} + +static bool go_to_galactic_hq( + VideoStream& stream, + ProControllerContext& context +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_galactic_hq: Going through the warehouse..."); + + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 7); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_galactic_hq: entered the Galactic HQ from the front door.", COLOR_GREEN); + }; + + return true; +} + +static bool go_through_galactic_hq( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_galactic_hq: Going through the HQ again and battling Cyrus..."); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6); + + { + ShortDialogWatcher open_door(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}} + ); + if (ret == 0){ + stream.log("go_through_galactic_hq: Opened the door.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 4000ms); + } else if (ret < 0){ + stream.log("go_through_galactic_hq: Didn't detect door dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: entered Galactic HQ's 2F, first room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ + + + bool grunt_2_done = false; + { + MarkDetector galactic_hq_grunt_2_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + // First run to the right, and see grunt 2 detects you + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + }, + {{galactic_hq_grunt_2_fight}} + ); + if (ret < 0) { + // If it doesn't, move on, but consider that grunt_2 hasn't been done + stream.log("galactic_hq_grunt_2: mark not detected, moving on..."); + } else if (ret == 0){ + // If it does, handle the fight and consider that grunt_2 has been done + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_grunt_2"); + grunt_2_done = true; + } + } + } + + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + // Move to the end against the wall + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 5+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + + if (options.help_mode == HelpMode::OUTSIDE_HELP) { + if (!grunt_2_done) { + { + // Now run downwards and check if either mark is detected (it should) + MarkDetector galactic_hq_grunt_2_3_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 16+ + }, + {{galactic_hq_grunt_2_3_fight}} + ); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + if (ret < 0 ) { + // Mark isn't detected, there is an issue + stream.log("galactic_hq_grunt_2_3: mark not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + context.wait_for_all_requests(); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + handle_battle(stream, context, "galactic_hq_grunt_2"); + } + } + } + } + + context.wait_for_all_requests(); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + context.wait_for_all_requests(); + + handle_battle(stream, context, "galactic_hq_grunt_3"); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 5+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7); + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 13+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: got into the Galactic HQ 2F, third room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: entered Galactic HQ's 3F, first room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + + if (options.help_mode == HelpMode::OUTSIDE_HELP) { + { + MarkDetector galactic_hq_grunt_4_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); // 5+ + }, + {{galactic_hq_grunt_4_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_grunt_4: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_grunt_4"); + } + } + } + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 10000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_grunt_5: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_grunt_5"); + } + } + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {-1, 0}, 4400ms, 100ms); // 22+ + pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 5+ + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: got into the Galactic HQ 1F, fifth room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: entered Galactic HQ's 4F.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); + + { + ShortDialogWatcher open_door(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}} + ); + if (ret == 0){ + stream.log("go_through_galactic_hq: Opened the door with the Galactic Key.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 4000ms); + } else if (ret < 0){ + stream.log("go_through_galactic_hq: Didn't detect door opening dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + { + ShortDialogWatcher cyrus_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 5+ + }, + {{cyrus_dialog}} + ); + if (ret == 0){ + stream.log("go_through_galactic_hq: Cyrus dialog box detected.", COLOR_GREEN); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_cyrus: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_cyrus"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_mash_button(context, BUTTON_A, 17000ms); + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 6+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: got into the Galactic HQ 4F, second room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {+1, 0}, 7600ms, 100ms); // 38+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ + pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 6+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: entered Galactic HQ's 4F Back.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 7+ + + { + ShortDialogWatcher talk_to_commander_saturn(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_commander_saturn}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: Didn't detect Commander Saturn dialog box!", COLOR_RED); + return false; + } else if (ret == 0){ + stream.log("go_through_galactic_hq: Starting the battle with Commander Saturn.", COLOR_GREEN); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("galactic_hq_commander_saturn: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "galactic_hq_commander_saturn"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + { + ShortDialogWatcher unlock_guardians(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{unlock_guardians}} + ); + if (ret == 0){ + stream.log("go_through_galactic_hq: Released the Lake Guardians.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 4000ms); + pbf_mash_button(context, BUTTON_B, 8000ms); + } else if (ret < 0){ + stream.log("go_through_galactic_hq: Didn't detect button dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 7+ + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: exited Galactic HQ's 4F Back.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 8); + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: exited Galactic HQ 4F second room, entering 4F first room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 6+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, -1}, 1800ms, 100ms); // 9+ + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1); + + { + BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: exited Galactic HQ 4F first room, entering 1F sixth room.", COLOR_GREEN); + }; + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + { + WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{white_screen}} + ); + if (ret < 0){ + stream.log("go_through_galactic_hq: white screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_galactic_hq: exited Galactic HQ.", COLOR_GREEN); + }; + + return true; +} + + +static bool fly_and_heal_at_oreburgh( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("fly_and_heal_at_oreburgh: Flying to Oreburgh City and healing pokemon..."); + + fly_to(stream, context, FlyPoint::OreburghCity); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("fly_and_heal_at_oreburgh: black screen not detected!", COLOR_RED); + return false; + } + stream.log("fly_and_heal_at_oreburgh: entered Oreburgh City's Pokemon Center.", COLOR_GREEN); + }; + + return true; +} + +static bool go_to_mount_coronet( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_mount_coronet: Heading towards Mount Coronet..."); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6); + pbf_move_left_joystick(context, {0, +1}, 6000ms, 100ms); // 30+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 7+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + + context.wait_for_all_requests(); + get_on_bicycle(stream, context, GearLevel::Fast); + + pbf_move_left_joystick(context, {0, +1}, 1300ms, 100ms); // 9- (bike fast gear) + pbf_press_button(context, BUTTON_PLUS, 80ms, 300ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 26 + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ + + { + MarkDetector hiker_justin_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5000ms, 100ms); // 11+ + }, + {{hiker_justin_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_mount_coronet, route_207_hiker_justin: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_207_hiker_justin"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_207_hiker_justin"); + } + } + } + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_mount_coronet: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_mount_coronet: entered Mt. Coronet.", COLOR_GREEN); + }; + + return true; +} + + +// ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // + + +void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + } + }, + true + ); +} + +void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Snowpoint City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_lake_acuity(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!talk_to_clint_then_go_veilstone(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Veilstone City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!get_key_and_enter_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_veilstone_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Veilstone City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_galactic_hq(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_galactic_hq(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!fly_and_heal_at_oreburgh(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Oreburgh City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_mount_coronet(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); + } + }, + false + ); +} + + +std::string AutoStory_Checkpoint_111::name() const{ return "111 - Segment 08 - Gym badge acquired"; } +std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } +void AutoStory_Checkpoint_111::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_111(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_112::name() const{ return "112 - Segment 08 - Left Snowpoint Gym"; } +std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } +std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +void AutoStory_Checkpoint_112::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_112(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_113::name() const{ return "113 - Segment 08 - Arrived at Snowpoint Pokemon Center"; } +std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_113::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_113(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_114::name() const{ return "114 - Segment 08 - Healed at Snowpoint City Pokemon Center"; } +std::string AutoStory_Checkpoint_114::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_114::end_text() const{ return "Entered Lake Acuity Lakefront."; } +void AutoStory_Checkpoint_114::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_114(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_115::name() const{ return "115 - Segment 08 - Entered Acuity Lakefront"; } +std::string AutoStory_Checkpoint_115::start_text() const{ return "Entered Lake Acuity Lakefront."; } +std::string AutoStory_Checkpoint_115::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_115::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_115(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_116::name() const{ return "116 - Segment 08 - Arrived at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_116::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_116::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_116::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_116(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_117::name() const{ return "117 - Segment 08 - Healed at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_117::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_117::end_text() const{ return "Entered Team Galactic Warehouse."; } +void AutoStory_Checkpoint_117::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_117(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_118::name() const{ return "118 - Segment 08 - Entered Team Galactic Warehouse"; } +std::string AutoStory_Checkpoint_118::start_text() const{ return "Entered Team Galactic Warehouse."; } +std::string AutoStory_Checkpoint_118::end_text() const{ return "Got out of the warehouse with the key."; } +void AutoStory_Checkpoint_118::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_118(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_119::name() const{ return "119 - Segment 08 - Fly back to Veilstone Pokemon Center for healing"; } +std::string AutoStory_Checkpoint_119::start_text() const{ return "Got out of the warehouse with the key."; } +std::string AutoStory_Checkpoint_119::end_text() const{ return "Entered Veilstone Pokemon Center for healing."; } +void AutoStory_Checkpoint_119::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_119(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_120::name() const{ return "120 - Segment 08 - Heal Pokemon"; } +std::string AutoStory_Checkpoint_120::start_text() const{ return "Entered Veilstone Pokemon Center for healing."; } +std::string AutoStory_Checkpoint_120::end_text() const{ return "Exited Veilstone Pokemon Center."; } +void AutoStory_Checkpoint_120::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_120(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_121::name() const{ return "121 - Segment 08 - Go to Galactic HQ"; } +std::string AutoStory_Checkpoint_121::start_text() const{ return "Exited Veilstone Pokemon Center."; } +std::string AutoStory_Checkpoint_121::end_text() const{ return "Entered Galactic HQ."; } +void AutoStory_Checkpoint_121::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_121(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_122::name() const{ return "122 - Segment 08 - Deal with Cyrus at the Galactic HQ"; } +std::string AutoStory_Checkpoint_122::start_text() const{ return "Entered Galactic HQ."; } +std::string AutoStory_Checkpoint_122::end_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } +void AutoStory_Checkpoint_122::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_122(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_123::name() const{ return "123 - Segment 08 - Fly to Oreburgh City"; } +std::string AutoStory_Checkpoint_123::start_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } +std::string AutoStory_Checkpoint_123::end_text() const{ return "Flew to Oreburgh city and entered Pokemon Center."; } +void AutoStory_Checkpoint_123::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_123(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_124::name() const{ return "124 - Segment 08 - Heal Pokemon at Oreburgh City Pokemon Center"; } +std::string AutoStory_Checkpoint_124::start_text() const{ return "Flew to Oreburgh City and entered Pokemon Center."; } +std::string AutoStory_Checkpoint_124::end_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } +void AutoStory_Checkpoint_124::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_124(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_125::name() const{ return "125 - Segment 08 - Go from Oreburgh City to Mount Coronet entrance"; } +std::string AutoStory_Checkpoint_125::start_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } +std::string AutoStory_Checkpoint_125::end_text() const{ return "Entered Mount Coronet."; } +void AutoStory_Checkpoint_125::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_125(env, context, options, stats); +} void AutoStory_Checkpoint_021::run_checkpoint( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index ece6ebca15..9fa3c8bd0f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -126,6 +126,122 @@ class AutoStory_Checkpoint_117 : public AutoStory_Checkpoint{ }; void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_118 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_119 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_120 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_121 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_122 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_123 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_124 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_125 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + + + + + class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint{ public: From 9628bc80405acdc4339e671d869ff609579fb63e Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sat, 6 Jun 2026 17:55:17 +0200 Subject: [PATCH 12/39] Added white screen detector to fly_to --- .../Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 9499c12aa7..8ac71d1ffe 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -557,19 +557,20 @@ bool fly_to( }; if (ret_1 == 0 and ret_2 == 0){ stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); + WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}) BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); int ret = run_until( stream, context, [](ProControllerContext& context){ pbf_mash_button(context, BUTTON_A, 200000ms); }, - {{flying_black_screen}} + {{flying_white_screen}, {flying_black_screen}} ); if (ret < 0){ - stream.log("Flying to " + name + ": black screen not detected!", COLOR_RED); + stream.log("Flying to " + name + ": white / black screen not detected!", COLOR_RED); return false; - } else if (ret == 0) { - stream.log("Flying to " + name + ": black screen detected!", COLOR_GREEN); + } else if (ret >= 0) { + stream.log("Flying to " + name + ": white / black screen detected!", COLOR_GREEN); return true; } } From 2285a110c801e6076ce4b97a97c4225696f6f556 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Tue, 9 Jun 2026 20:51:21 +0200 Subject: [PATCH 13/39] Finished segment 08 --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 15 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 27 +- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 6 + .../PokemonBDSP_AutoStory_Segment_00.cpp | 1 + .../PokemonBDSP_AutoStory_Segment_08.cpp | 1144 ++++++++++++++--- .../PokemonBDSP_AutoStory_Segment_08.h | 137 ++ 6 files changed, 1147 insertions(+), 183 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index f63b41933c..8ea50d2719 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -137,6 +137,7 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + // Segment 08 (111-) checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); @@ -152,8 +153,18 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - - + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 8ac71d1ffe..7378708323 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -557,7 +557,7 @@ bool fly_to( }; if (ret_1 == 0 and ret_2 == 0){ stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); - WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}) + WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); int ret = run_until( stream, context, @@ -932,6 +932,31 @@ void use_strength( pbf_mash_button(context, BUTTON_B, 6000ms); } +void use_rock_smash( + VideoStream& stream, + ProControllerContext& context +){ + stream.log("[AutoStory] use_rock_smash: pressing A until selection arrow appears."); + const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; + SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 30000ms); + }, + {{arrow}} + ); + if (ret != 0){ + stream.log("[AutoStory] use_rock_smash: selection arrow not found.", COLOR_RED); + return; + } + + stream.log("[AutoStory] use_rock_smash: selection arrow found, confirming.", COLOR_GREEN); + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + pbf_mash_button(context, BUTTON_B, 6000ms); +} + bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLevel /*gear*/){ static const ImageFloatBox FRONT_BOX{0.440000, 0.470000, 0.035000, 0.054000}; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 6ab1ed3a13..cfdd074da7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -237,6 +237,12 @@ void use_strength( ProControllerContext& context ); +// Press A until the Rock Smash selection arrow appears, confirm with A, then mash B for 3 s. +void use_rock_smash( + VideoStream& stream, + ProControllerContext& context +); + // Walk to the nurse, heal the party, and exit the Pokemon Center. // `label` names the center for log messages (e.g. "Veilstone"). // Returns false if the exit black screen was not detected. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 2f0e0475e6..8f10e14ae1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -789,6 +789,7 @@ namespace PokemonAutomation { true post_starly_clenaup(env.console, context); } + true ); } static void checkpoint_002( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 09113d1a3d..86221dbbfa 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -107,6 +107,18 @@ void AutoStory_Segment_08::run_segment( AutoStory_Checkpoint_124().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_125().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_126().run_checkpoint(env, context, options, stats); + + AutoStory_Checkpoint_127().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_128().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_129().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_130().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_131().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_132().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_133().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_134().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_135().run_checkpoint(env, context, options, stats); + /*AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); checkpoint_021_legendary(env, context, options, stats);*/ @@ -167,7 +179,8 @@ static bool handle_battle( pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( trainerid == "galactic_hq_grunt_1" or - trainerid == "galactic_hq_double_grunts_1" + trainerid == "galactic_hq_double_grunts_1" or + trainerid == "route_222_sailor_luther" ) { // Select Razor leaf pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); @@ -1034,7 +1047,7 @@ static bool go_to_galactic_hq( {{black_screen}} ); if (ret < 0){ - stream.log("go_to_galactic_hq: black screen not detected!", COLOR_RED); + stream.log("go_to_galactic_hq, entering the Galactic HQ from the front door: black screen not detected!", COLOR_RED); return false; } stream.log("go_to_galactic_hq: entered the Galactic HQ from the front door.", COLOR_GREEN); @@ -1048,6 +1061,8 @@ static bool go_through_galactic_hq( ProControllerContext& context, AutoStoryOptions options ){ + // ------- This has returned one error and I couldn't reproduce it. It was somewhere at the other comment I left behind ----------- // + DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1071,7 +1086,7 @@ static bool go_through_galactic_hq( stream.log("go_through_galactic_hq: Opened the door.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); } else if (ret < 0){ - stream.log("go_through_galactic_hq: Didn't detect door dialog box!", COLOR_RED); + stream.log("go_through_galactic_hq, opening the door: Didn't detect door dialog box!", COLOR_RED); return false; } } @@ -1090,7 +1105,7 @@ static bool go_through_galactic_hq( {{black_screen}} ); if (ret < 0){ - stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); + stream.log("go_through_galactic_hq, entering Galactic HQ's 2F, first room: black screen not detected!", COLOR_RED); return false; } stream.log("go_through_galactic_hq: entered Galactic HQ's 2F, first room.", COLOR_GREEN); @@ -1344,6 +1359,8 @@ static bool go_through_galactic_hq( stream.log("go_through_galactic_hq: got into the Galactic HQ 1F, fifth room.", COLOR_GREEN); }; + // ---------------------------------------- The error happened around here ---------------------------------------- // + context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1364,6 +1381,8 @@ static bool go_through_galactic_hq( } stream.log("go_through_galactic_hq: entered Galactic HQ's 4F.", COLOR_GREEN); }; + + // ---------------------------------------- Until here ---------------------------------------- // context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1766,204 +1785,848 @@ static bool go_to_mount_coronet( } -// ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // - - -void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); - } - }, - true - ); -} +static bool go_through_mount_coronet( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_mount_coronet: Going through Mount Coronet..."); -void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); - } - }, - false - ); -} + // @GumGum: Here the code, you can add more of these if you need them, but remember to adapt the numbering. -void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Snowpoint City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); - } - }, - false - ); + return true; } -void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!go_to_lake_acuity(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); - } - }, - false - ); -} +static bool fly_and_heal_at_veilstone( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("fly_and_heal_at_veilstone: Flying to Veilstone City and healing there..."); -void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!talk_to_clint_then_go_veilstone(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); - } - }, - false - ); -} + fly_to(stream, context, FlyPoint::VeilstoneCity); -void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); - } - }, - false - ); -} + context.wait_for_all_requests(); -void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!get_key_and_enter_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); - } - }, - false - ); -} + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("fly_and_heal_at_veilstone: black screen not detected!", COLOR_RED); + return false; + } + stream.log("fly_and_heal_at_veilstone: entered Veilstone City Pokemon Center.", COLOR_GREEN); + }; -void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!go_through_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); - } - }, - false - ); + return true; } -void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_at_veilstone_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); - } - }, - false - ); -} +static bool go_to_house_south_veilstone( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_house_south_veilstone: heading South..."); -void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); - } - }, - false - ); -} + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ -void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!go_to_galactic_hq(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); - } - }, - false - ); -} + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 9); -void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!go_through_galactic_hq(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); - } - }, - false - ); -} + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 8 + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 1800ms, 100ms); // 7+ -void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!fly_and_heal_at_oreburgh(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); - } - }, - false - ); -} + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3, true); -void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Oreburgh City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit: transition not detected.", env.console); - } - }, - false - ); -} + pbf_move_left_joystick(context, {+1, 0}, 2200ms, 100ms); // 15 + context.wait_for_all_requests(); -void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - if (!go_to_mount_coronet(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); - } - }, - false - ); + { + BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}} + ); + if (ret < 0){ + stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); + return false; + } + } + + return true; } - -std::string AutoStory_Checkpoint_111::name() const{ return "111 - Segment 08 - Gym badge acquired"; } -std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } -void AutoStory_Checkpoint_111::run_checkpoint( - SingleSwitchProgramEnvironment& env, +static bool go_through_house_south_veilstone( + VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_111(env, context, options, stats); -} + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_house_south_veilstone: moving towards exit..."); -std::string AutoStory_Checkpoint_112::name() const{ return "112 - Segment 08 - Left Snowpoint Gym"; } -std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } -std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -void AutoStory_Checkpoint_112::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_112(env, context, options, stats); + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_house_south_veilstone: exited the house", COLOR_GREEN); + } + + return true; } -std::string AutoStory_Checkpoint_113::name() const{ return "113 - Segment 08 - Arrived at Snowpoint Pokemon Center"; } -std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_113::run_checkpoint( - SingleSwitchProgramEnvironment& env, +static bool go_to_house_west_sunyshore( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_house_west_sunyshore: going towards Sunyshore City..."); + + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 3800ms, 100ms); // 19+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8); + + activate_repel(stream, context); + + pbf_move_left_joystick(context, {0, -1}, 2600ms, 100ms); // 13+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ + + pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ + + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 3+ + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + + pbf_move_left_joystick(context, {0, -1}, 6200ms, 100ms); // 31+ + context.wait_for_all_requests(); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_house_west_sunyshore: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_to_house_west_sunyshore: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 3000ms, 100ms); // 15+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 9); + pbf_move_left_joystick(context, {0, -1}, 8400ms, 100ms); // 42+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 5); + pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 7400ms, 100ms); // 37+ + pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_house_west_sunyshore: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_to_house_west_sunyshore: Didn't detect repel dialog box (2)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 7+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 5200ms, 100ms); // 26+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + + use_rock_smash(stream, context); + + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + + { + MarkDetector route_222_sailor_luther_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); // 5+ + }, + {{route_222_sailor_luther_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_house_west_sunyshore_route_222_sailor_luther: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_222_sailor_luther"); + } + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 6+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_house_west_sunyshore: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_house_west_sunyshore: entered the house", COLOR_GREEN); + } + + return true; +} + +static bool go_through_house_west_sunyshore( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_house_west_sunyshore: moving towards exit..."); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_house_south_veilstone: exited the house", COLOR_GREEN); + } + + return true; +} + +static bool go_to_sunyshore_lighthouse( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_sunyshore_lighthouse: going towards the lighthouse to talk to Volkner..."); + + { + ShortDialogWatcher listen_to_flint(COLOR_BLUE); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{listen_to_flint}}); + if (ret < 0){ + stream.log("go_to_house_west_sunyshore, listen_to_flint: dialog box not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("go_to_house_west_sunyshore, listen_to_flint: dialog box detected.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2); + pbf_move_left_joystick(context, {0, +1}, 3000ms, 100ms); // 15+ + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8); + pbf_move_left_joystick(context, {0, -1}, 3400ms, 100ms); // 17+ + pbf_move_left_joystick(context, {+1, 0}, 7600ms, 100ms); // 38+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_house_west_sunyshore: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_house_west_sunyshore: entered the lighthouse", COLOR_GREEN); + } + + return true; +} + +static bool talk_to_volkner_at_the_lighthouse( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + stream.log("talk_to_volkner_at_the_lighthouse: Going up and talking to Volkner..."); + + { + BlackScreenFlashWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{black_screen}}); + if (ret < 0){ + stream.log("talk_to_volkner_at_the_lighthouse: black screen exiting elevator not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("talk_to_volkner_at_the_lighthouse: exited the elevator.", COLOR_GREEN); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + + { + ShortDialogWatcher talk_to_volkner(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_volkner}} + ); + if (ret == 0){ + stream.log("talk_to_volkner_at_the_lighthouse: Talking to Volkner.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 11000ms); + } else if (ret < 0){ + stream.log("talk_to_volkner_at_the_lighthouse: Didn't detect Volkner dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 7+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("talk_to_volkner_at_the_lighthouse: entering elevator to exit black screen not detected!", COLOR_RED); + return false; + } + stream.log("talk_to_volkner_at_the_lighthouse: entered the elevator", COLOR_GREEN); + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + { + BlackScreenFlashWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = wait_until(stream, context, std::chrono::seconds(30), {{black_screen}}); + if (ret < 0){ + stream.log("talk_to_volkner_at_the_lighthouse: black screen exiting elevator not detected!", COLOR_RED); + return false; + } else if (ret == 0) { + stream.log("talk_to_volkner_at_the_lighthouse: exited the elevator.", COLOR_GREEN); + } + } + + return true; +} + +static bool go_to_sunyshore_gym( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + stream.log("go_to_sunyshore_gym: heading towards Sunyshore City's gym..."); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 14); + pbf_move_left_joystick(context, {0, +1}, 4000ms, 100ms); // 20+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {0, +1}, 5400ms, 100ms); // 27+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 6); + pbf_move_left_joystick(context, {-1, 0}, 6800ms, 100ms); // 34+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + + { + ShortDialogWatcher talk_to_flint(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_flint}} + ); + if (ret == 0){ + stream.log("go_to_sunyshore_gym: Talking to Flint.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 9000ms); + } else if (ret < 0){ + stream.log("go_to_sunyshore_gym: Didn't detect Flint dialog box!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_sunyshore_gym: black screen entering the gym not detected!", COLOR_RED); + return false; + } + stream.log("go_to_sunyshore_gym: entered the gym.", COLOR_GREEN); + } + + return true; +} + + + +// ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // + + +void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!leave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + } + }, + true + ); +} + +void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Snowpoint City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_snowpoint_city: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_lake_acuity(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!talk_to_clint_then_go_veilstone(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Veilstone City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!get_key_and_enter_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_warehouse(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_at_veilstone_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Veilstone City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_galactic_hq(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_galactic_hq(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!fly_and_heal_at_oreburgh(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Oreburgh City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_oreburgh_city: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_mount_coronet(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_126(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_mount_coronet(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_127(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!fly_and_heal_at_veilstone(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_128(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Veilstone City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_129(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_house_south_veilstone(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_130(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_house_south_veilstone(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_131(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_house_west_sunyshore(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_west_sunyshore: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_132(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_house_west_sunyshore(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_133(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_sunyshore_lighthouse(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_lighthouse: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_134(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_135(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_sunyshore_gym(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + } + }, + false + ); +} + + + +std::string AutoStory_Checkpoint_111::name() const{ return "111 - Segment 08 - Gym badge acquired"; } +std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } +void AutoStory_Checkpoint_111::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_111(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_112::name() const{ return "112 - Segment 08 - Left Snowpoint Gym"; } +std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } +std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +void AutoStory_Checkpoint_112::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_112(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_113::name() const{ return "113 - Segment 08 - Arrived at Snowpoint Pokemon Center"; } +std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_113::run_checkpoint( + SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats @@ -2115,6 +2778,127 @@ void AutoStory_Checkpoint_125::run_checkpoint( checkpoint_125(env, context, options, stats); } +std::string AutoStory_Checkpoint_126::name() const{ return "126 - Segment 08 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; } +std::string AutoStory_Checkpoint_126::start_text() const{ return "Entered Mount Coronet."; } +std::string AutoStory_Checkpoint_126::end_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } +void AutoStory_Checkpoint_126::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_126(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_127::name() const{ return "127 - Segment 08 - Fly to Veilstone City"; } +std::string AutoStory_Checkpoint_127::start_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } +std::string AutoStory_Checkpoint_127::end_text() const{ return "Entered the Veilstone Pokemon Center."; } +void AutoStory_Checkpoint_127::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_127(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_128::name() const{ return "126 - Segment 08 - Heal and exit"; } +std::string AutoStory_Checkpoint_128::start_text() const{ return "Entered the Veilstone Pokemon Center."; } +std::string AutoStory_Checkpoint_128::end_text() const{ return "Exited the Veilstone Pokemon Center."; } +void AutoStory_Checkpoint_128::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_128(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_129::name() const{ return "129 - Segment 08 - Go to house South of Veilstone City"; } +std::string AutoStory_Checkpoint_129::start_text() const{ return "Exited the Veilstone Pokemon Center."; } +std::string AutoStory_Checkpoint_129::end_text() const{ return "Arrived at the house South of Veilstone City."; } +void AutoStory_Checkpoint_129::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_129(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_130::name() const{ return "130 - Segment 08 - Go through the house South of Veilstone City"; } +std::string AutoStory_Checkpoint_130::start_text() const{ return "Arrived at the house South of Veilstone City."; } +std::string AutoStory_Checkpoint_130::end_text() const{ return "Exited the house South of Veilstone City."; } +void AutoStory_Checkpoint_130::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_130(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_131::name() const{ return "131 - Segment 08 - Go through routes 214 and 222 to the house West of Sunyshore City"; } +std::string AutoStory_Checkpoint_131::start_text() const{ return "Exited house South of Veilstone City."; } +std::string AutoStory_Checkpoint_131::end_text() const{ return "Entered the house West of Sunyshore City."; } +void AutoStory_Checkpoint_131::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_131(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_132::name() const{ return "132 - Segment 08 - Go through the house West of Sunyshore City"; } +std::string AutoStory_Checkpoint_132::start_text() const{ return "Entered the house West of Sunyshore City."; } +std::string AutoStory_Checkpoint_132::end_text() const{ return "Entered Sunyshore City."; } +void AutoStory_Checkpoint_132::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_132(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_133::name() const{ return "133 - Segment 08 - Go to Sunyshore City's lighthouse"; } +std::string AutoStory_Checkpoint_133::start_text() const{ return "Entered Sunyshore City."; } +std::string AutoStory_Checkpoint_133::end_text() const{ return "Entered Sunyshore City's lighthouse."; } +void AutoStory_Checkpoint_133::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_133(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_134::name() const{ return "134 - Segment 08 - Talk to Volkner at the lighthouse"; } +std::string AutoStory_Checkpoint_134::start_text() const{ return "Entered Sunyshore City's lighthouse."; } +std::string AutoStory_Checkpoint_134::end_text() const{ return "Exited Sunyshore City's lighthouse."; } +void AutoStory_Checkpoint_134::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_134(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_135::name() const{ return "135 - Segment 08 - Go to the Sunyshore City gym"; } +std::string AutoStory_Checkpoint_135::start_text() const{ return "Exited Sunyshore City's lighthouse."; } +std::string AutoStory_Checkpoint_135::end_text() const{ return "Entered Sunyshore City's gym."; } +void AutoStory_Checkpoint_135::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_135(env, context, options, stats); +} + + void AutoStory_Checkpoint_021::run_checkpoint( SingleSwitchProgramEnvironment& env, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index 9fa3c8bd0f..d626df5258 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -238,8 +238,145 @@ class AutoStory_Checkpoint_125 : public AutoStory_Checkpoint{ }; void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_126 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_126(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_127 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_127(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_128 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_128(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_129 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_129(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_130 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_130(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_131 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_131(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_132 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_132(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_133 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_133(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_134 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_134(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_135 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_135(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); From 0482c67fe7f0b3a7b7a02d2f8da32e9c7e713da6 Mon Sep 17 00:00:00 2001 From: Boombaastical Date: Sun, 21 Jun 2026 17:33:29 +0200 Subject: [PATCH 14/39] Segment09 part, draft handle_expert_battle function Segment 09 in part, until having bought all the Full restores with the money left. Draft of the new handle_expert_battle function. --- .../Inference/PokemonBDSP_ShopDetectors.cpp | 273 +++ .../Inference/PokemonBDSP_ShopDetectors.h | 123 ++ .../PokemonBDSP_ShopItemNameReader.cpp | 41 + .../PokemonBDSP_ShopItemNameReader.h | 40 + .../PokemonBDSP_ShopItemPriceReader.cpp | 41 + .../PokemonBDSP_ShopItemPriceReader.h | 40 + .../Data/PokemonBDSP_AutoStory_BDSPData.cpp | 2 + .../Data/PokemonBDSP_AutoStory_BDSPData.h | 2 + .../AutoStory/PokemonBDSP_AutoStory.cpp | 17 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 287 ++- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 62 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_08.cpp | 59 +- .../PokemonBDSP_AutoStory_Segment_08.h | 7 +- .../PokemonBDSP_AutoStory_Segment_09.cpp | 1775 ++++++++++++++++- .../PokemonBDSP_AutoStory_Segment_09.h | 259 ++- .../Utils/PokemonBDSP_AutoStory_Battle.cpp | 596 ++++++ .../Utils/PokemonBDSP_AutoStory_Battle.h | 31 + SerialPrograms/cmake/SourceFiles.cmake | 6 + 19 files changed, 3581 insertions(+), 82 deletions(-) create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.h diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.cpp new file mode 100644 index 0000000000..fc48e45fa5 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.cpp @@ -0,0 +1,273 @@ +/* Shop Detectors + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoOverlay.h" +#include "CommonTools/Images/BinaryImage_FilterRgb32.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Session.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "PokemonBDSP_ShopItemNameReader.h" +#include "PokemonBDSP_ShopItemPriceReader.h" +#include "PokemonBDSP_ShopDetectors.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// ShopBuyingWindowDetector +// --------------------------------------------------------------------------- + +// TODO: Fill in the actual coordinates of the blue bottom border strip. +// At 1280x720, the buying-list bottom bar appears near y ≈ 0.93. +// The colour ratio below matches BoxDetector's bottom bar ({0.190, 0.327, 0.482}). +static const ImageFloatBox SHOP_BUYING_WINDOW_BOX{0.020000, 0.960000, 0.750000, 0.035000}; +static const FloatPixel SHOP_BLUE_RATIO{0.190, 0.327, 0.483}; +static constexpr double SHOP_BLUE_MAX_DISTANCE = 0.10; +static constexpr double SHOP_BLUE_MAX_STDDEV = 15.0; + +ShopBuyingWindowDetector::ShopBuyingWindowDetector(Color color) + : m_color(color) + , m_box(SHOP_BUYING_WINDOW_BOX) +{} +void ShopBuyingWindowDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_box); +} +bool ShopBuyingWindowDetector::detect(const ImageViewRGB32& screen){ + ImageStats stats = image_stats(extract_box_reference(screen, m_box)); + return is_solid(stats, SHOP_BLUE_RATIO, SHOP_BLUE_MAX_DISTANCE, SHOP_BLUE_MAX_STDDEV); +} + + +// --------------------------------------------------------------------------- +// ShopQuantityWindowDetector +// --------------------------------------------------------------------------- + +// TODO: Fill in the coordinates of the yellow quantity-input box. +// It typically appears in the centre-right of the screen when selecting quantity. +static const ImageFloatBox SHOP_QUANTITY_BOX{0.800000, 0.570000, 0.100000, 0.140000}; +// Expected colour ratio for the gold/yellow quantity box. +// High R, high G, low B — adjust if the box colour looks different in your capture. +static const FloatPixel SHOP_YELLOW_RATIO{0.58, 0.38, 0.04}; +static constexpr double SHOP_YELLOW_MAX_DISTANCE = 0.20; +static constexpr double SHOP_YELLOW_MAX_STDDEV = 20.0; + +ShopQuantityWindowDetector::ShopQuantityWindowDetector(Color color) + : m_color(color) + , m_dialog(color) + , m_qty_box(SHOP_QUANTITY_BOX) +{} +void ShopQuantityWindowDetector::make_overlays(VideoOverlaySet& items) const{ + m_dialog.make_overlays(items); + items.add(m_color, m_qty_box); +} +bool ShopQuantityWindowDetector::detect(const ImageViewRGB32& screen){ + if (!m_dialog.detect(screen)){ + return false; + } + ImageStats stats = image_stats(extract_box_reference(screen, m_qty_box)); + return is_solid(stats, SHOP_YELLOW_RATIO, SHOP_YELLOW_MAX_DISTANCE, SHOP_YELLOW_MAX_STDDEV); +} + + +// --------------------------------------------------------------------------- +// ShopItemHoverDetector +// --------------------------------------------------------------------------- + +// Color filter for the red hover rectangle. +static constexpr uint32_t RED_MIN = 0xff800000; // R≥128, G=0, B=0 +static constexpr uint32_t RED_MAX = 0xffff6060; // R≤255, G≤96, B≤96 + +// Minimum pixel area for a red blob to be considered (filters noise). +static constexpr size_t RED_MIN_AREA = 200; + +// The hover rectangle must span at least this fraction of the scan area width. +static constexpr double RED_MIN_WIDTH_RATIO = 0.80; + +// Minimum aspect ratio (width/height) — the row is much wider than tall. +static constexpr double RED_MIN_ASPECT = 4.0; + +// Minimum consecutive frames that must confirm before the detector fires. +static constexpr size_t HOVER_MIN_CONSECUTIVE = 3; + +// OCR region X offsets (as fractions of outline row width). +// Tune NAME_X_OFFSET if the first character of the item name is being cut off. +// Name: [ 5%, 70%] → x_offset=0.05, width_fraction=0.65 +// Price: [70%, 100%] → x_offset=0.70, width_fraction=0.30 +static constexpr double NAME_X_OFFSET = 0.08; +static constexpr double NAME_X_WIDTH = 0.65; +static constexpr double PRICE_X_OFFSET = 0.70; +static constexpr double PRICE_X_WIDTH = 0.30; +// Fixed OCR box height in normalized screen coordinates. The waterfill detects +// only the thin red border lines, so outline_box.height is just the border +// thickness. This constant covers the full item row height instead. +static constexpr double OCR_ROW_HEIGHT = 0.06; + +// Maps item slug → expected price slug. +static const std::map ITEM_PRICE_TABLE{ + {"poke-ball", "200"}, + {"great-ball", "600"}, + {"ultra-ball", "1200"}, + {"heal-ball", "300"}, + {"net-ball", "1000"}, + {"nest-ball", "1000"}, + {"dusk-ball", "1000"}, + {"quick-ball", "1000"}, + {"timer-ball", "1000"}, + {"repeat-ball", "1000"}, + {"luxury-ball", "1000"}, + {"potion", "300"}, + {"super-potion", "700"}, + {"hyper-potion", "1200"}, + {"max-potion", "2500"}, + {"full-restore", "3000"}, + {"revive", "1500"}, + {"antidote", "100"}, + {"paralyze-heal", "200"}, + {"awakening", "250"}, + {"burn-heal", "250"}, + {"ice-heal", "250"}, + {"full-heal", "600"}, + {"escape-rope", "550"}, + {"repel", "350"}, + {"super-repel", "500"}, + {"max-repel", "700"}, +}; + +ShopItemHoverDetector::ShopItemHoverDetector( + Logger& logger, + const ImageFloatBox& scan_area, + const std::string& item_slug, + Language language, + Color color +) + : VisualInferenceCallback("ShopItemHoverDetector") + , m_logger(logger) + , m_color(color) + , m_language(language) + , m_scan_area(scan_area) + , m_item_slug(item_slug) + , m_price_slug(ITEM_PRICE_TABLE.at(item_slug)) +{} + +bool ShopItemHoverDetector::find_outline(const ImageViewRGB32& screen, ImageFloatBox& out_box) const{ + using namespace Kernels::Waterfill; + + const ImageViewRGB32 region = extract_box_reference(screen, m_scan_area); + PackedBinaryMatrix matrix = compress_rgb32_to_binary_range(region, RED_MIN, RED_MAX); + + auto session = make_WaterfillSession(matrix); + auto iter = session->make_iterator(RED_MIN_AREA); + + WaterfillObject object; + while (iter->find_next(object, false)){ + if (object.aspect_ratio() < RED_MIN_ASPECT) continue; + if ((double)object.width() / region.width() < RED_MIN_WIDTH_RATIO) continue; + + out_box = ImageFloatBox{ + m_scan_area.x + (double)object.min_x / region.width() * m_scan_area.width, + m_scan_area.y + (double)object.min_y / region.height() * m_scan_area.height, + (double)object.width() / region.width() * m_scan_area.width, + (double)object.height() / region.height() * m_scan_area.height + }; + return true; + } + return false; +} + +bool ShopItemHoverDetector::match_ocr(const ImageViewRGB32& screen, const ImageFloatBox& outline_box) const{ + m_last_name_box = ImageFloatBox{ + outline_box.x + NAME_X_OFFSET * outline_box.width, + outline_box.y, + NAME_X_WIDTH * outline_box.width, + OCR_ROW_HEIGHT + }; + m_last_price_box = ImageFloatBox{ + outline_box.x + PRICE_X_OFFSET * outline_box.width, + outline_box.y, + PRICE_X_WIDTH * outline_box.width, + OCR_ROW_HEIGHT + }; + const ImageFloatBox& name_box = m_last_name_box; + const ImageFloatBox& price_box = m_last_price_box; + + const ShopItemNameReader& name_reader = ShopItemNameReader::instance(); + OCR::StringMatchResult name_result = name_reader.read_substring( + m_logger, m_language, extract_box_reference(screen, name_box) + ); + name_result.clear_beyond_log10p(ShopItemNameReader::MAX_LOG10P); + if (name_result.results.empty()) return false; + if (name_result.results.begin()->second.token != m_item_slug) return false; + + const ShopItemPriceReader& price_reader = ShopItemPriceReader::instance(); + OCR::StringMatchResult price_result = price_reader.read_substring( + m_logger, m_language, extract_box_reference(screen, price_box) + ); + price_result.clear_beyond_log10p(ShopItemPriceReader::MAX_LOG10P); + if (price_result.results.empty()) return false; + if (price_result.results.begin()->second.token != m_price_slug) return false; + + if (!m_ocr_logged){ + m_ocr_logged = true; + name_result.log(m_logger, ShopItemNameReader::MAX_LOG10P, "want: " + m_item_slug); + price_result.log(m_logger, ShopItemPriceReader::MAX_LOG10P, "want: " + m_price_slug); + } + return true; +} + +bool ShopItemHoverDetector::detect(const ImageViewRGB32& screen){ + ImageFloatBox outline_box; + if (!find_outline(screen, outline_box)){ + m_detected_box = ImageFloatBox{0, 0, 0, 0}; + m_last_name_box = ImageFloatBox{0, 0, 0, 0}; + m_last_price_box = ImageFloatBox{0, 0, 0, 0}; + m_ocr_logged = false; + return false; + } + if (!match_ocr(screen, outline_box)){ + m_last_name_box = ImageFloatBox{0, 0, 0, 0}; + m_last_price_box = ImageFloatBox{0, 0, 0, 0}; + return false; + } + m_detected_box = outline_box; + return true; +} + +void ShopItemHoverDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_scan_area); + if (m_detected_box.width > 0){ + items.add(m_color, m_detected_box); + } + if (m_last_name_box.width > 0){ + items.add(COLOR_BLUE, m_last_name_box); + } + if (m_last_price_box.width > 0){ + items.add(COLOR_GREEN, m_last_price_box); + } +} + +bool ShopItemHoverDetector::process_frame(const ImageViewRGB32& frame, WallClock /*timestamp*/){ + // Only run OCR every 5th frame to reduce CPU load. + if (m_frame_count++ % 5 != 0){ + return false; + } + if (!detect(frame)){ + m_trigger_count = 0; + return false; + } + m_trigger_count++; + return m_trigger_count >= HOVER_MIN_CONSECUTIVE; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h new file mode 100644 index 0000000000..bfe3298e66 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h @@ -0,0 +1,123 @@ +/* Shop Detectors + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_ShopDetectors_H +#define PokemonAutomation_PokemonBDSP_ShopDetectors_H + +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" +#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" +#include "CommonTools/VisualDetector.h" +#include "PokemonBDSP_DialogDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// Detects the blue decorative border at the bottom of the screen that appears +// when the mart buying list is open. +class ShopBuyingWindowDetector : public StaticScreenDetector{ +public: + // box: a thin strip covering the blue bottom border (user fills in coordinates). + ShopBuyingWindowDetector(Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + ImageFloatBox m_box; +}; +class ShopBuyingWindowWatcher : public DetectorToFinder{ +public: + ShopBuyingWindowWatcher(Color color = COLOR_RED) + : DetectorToFinder("ShopBuyingWindowWatcher", std::chrono::milliseconds(250), color) + {} +}; + + +// Detects the quantity-input overlay (yellow/gold box + ShortDialog) that appears +// after selecting an item to buy. +class ShopQuantityWindowDetector : public StaticScreenDetector{ +public: + // box: the area containing the yellow quantity-input box (user fills in coordinates). + ShopQuantityWindowDetector(Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + ShortDialogDetector m_dialog; + ImageFloatBox m_qty_box; +}; +class ShopQuantityWindowWatcher : public DetectorToFinder{ +public: + ShopQuantityWindowWatcher(Color color = COLOR_RED) + : DetectorToFinder("ShopQuantityWindowWatcher", std::chrono::milliseconds(250), color) + {} +}; + + +// Scans `scan_area` to dynamically locate the red selection outline, then +// verifies the highlighted item by running OCR on the item name and price +// regions within the row and comparing against the expected `item_slug`. +// Fires only when the outline is found AND both OCR checks pass. +class ShopItemHoverDetector : public VisualInferenceCallback{ +public: + // scan_area: normalized screen region that covers the entire shop item list. + // item_slug: identifies the expected item (e.g. "full-restore"). Must match + // a key in ShopItemNameOCR.json and have a known price in the + // internal lookup table. + // language: language used for OCR dictionary lookup. + ShopItemHoverDetector( + Logger& logger, + const ImageFloatBox& scan_area, + const std::string& item_slug, + Language language = Language::English, + Color color = COLOR_CYAN + ); + + bool detect(const ImageViewRGB32& screen); + + // Returns the last outline box confirmed by detect() / process_frame(). + // Valid only after a successful detection; width == 0 if nothing detected yet. + const ImageFloatBox& detected_box() const { return m_detected_box; } + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; + +private: + // Scans scan_area for a horizontal band of red edges (left + right) that + // indicate the selection outline. Fills out_box with the outline's screen- + // normalized bounds and returns true when found. + bool find_outline(const ImageViewRGB32& screen, ImageFloatBox& out_box) const; + + // Runs OCR on the name and price sub-regions of outline_box. + // Returns true when both match the expected item slug and its price. + bool match_ocr(const ImageViewRGB32& screen, const ImageFloatBox& outline_box) const; + + Logger& m_logger; + Color m_color; + Language m_language; + ImageFloatBox m_scan_area; + std::string m_item_slug; + std::string m_price_slug; + mutable ImageFloatBox m_detected_box{0, 0, 0, 0}; + mutable ImageFloatBox m_last_name_box{0, 0, 0, 0}; + mutable ImageFloatBox m_last_price_box{0, 0, 0, 0}; + mutable bool m_ocr_logged = false; + size_t m_trigger_count = 0; + size_t m_frame_count = 0; +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.cpp new file mode 100644 index 0000000000..96fa48eae0 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.cpp @@ -0,0 +1,41 @@ +/* Shop Item Name Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/OCR/OCR_Routines.h" +#include "PokemonBDSP_ShopItemNameReader.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +ShopItemNameReader::ShopItemNameReader() + : SmallDictionaryMatcher("PokemonBDSP/ShopItemNameOCR.json") +{} + +const ShopItemNameReader& ShopItemNameReader::instance(){ + static ShopItemNameReader reader; + return reader; +} + +OCR::StringMatchResult ShopItemNameReader::read_substring( + Logger& logger, + Language language, + const ImageViewRGB32& image, + double min_text_ratio, double max_text_ratio +) const{ + return match_substring_from_image_multifiltered( + &logger, language, image, + OCR::BLACK_TEXT_FILTERS(), + MAX_LOG10P, MAX_LOG10P_SPREAD, + min_text_ratio, max_text_ratio + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h new file mode 100644 index 0000000000..ae32b1dda5 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h @@ -0,0 +1,40 @@ +/* Shop Item Name Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_ShopItemNameReader_H +#define PokemonAutomation_PokemonBDSP_ShopItemNameReader_H + +#include "CommonFramework/Language.h" +#include "CommonTools/OCR/OCR_SmallDictionaryMatcher.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class ShopItemNameReader : public OCR::SmallDictionaryMatcher{ +public: + static constexpr double MAX_LOG10P = -1.40; + static constexpr double MAX_LOG10P_SPREAD = 0.50; + +public: + ShopItemNameReader(); + + static const ShopItemNameReader& instance(); + + OCR::StringMatchResult read_substring( + Logger& logger, + Language language, + const ImageViewRGB32& image, + double min_text_ratio = 0.01, double max_text_ratio = 0.50 + ) const; +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.cpp new file mode 100644 index 0000000000..82d6cfba3d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.cpp @@ -0,0 +1,41 @@ +/* Shop Item Price Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/OCR/OCR_Routines.h" +#include "PokemonBDSP_ShopItemPriceReader.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +ShopItemPriceReader::ShopItemPriceReader() + : SmallDictionaryMatcher("PokemonBDSP/ShopItemPriceOCR.json") +{} + +const ShopItemPriceReader& ShopItemPriceReader::instance(){ + static ShopItemPriceReader reader; + return reader; +} + +OCR::StringMatchResult ShopItemPriceReader::read_substring( + Logger& logger, + Language language, + const ImageViewRGB32& image, + double min_text_ratio, double max_text_ratio +) const{ + return match_substring_from_image_multifiltered( + &logger, language, image, + OCR::BLACK_TEXT_FILTERS(), + MAX_LOG10P, MAX_LOG10P_SPREAD, + min_text_ratio, max_text_ratio + ); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.h b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.h new file mode 100644 index 0000000000..0d5cc49303 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.h @@ -0,0 +1,40 @@ +/* Shop Item Price Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_ShopItemPriceReader_H +#define PokemonAutomation_PokemonBDSP_ShopItemPriceReader_H + +#include "CommonFramework/Language.h" +#include "CommonTools/OCR/OCR_SmallDictionaryMatcher.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +class ShopItemPriceReader : public OCR::SmallDictionaryMatcher{ +public: + static constexpr double MAX_LOG10P = -1.40; + static constexpr double MAX_LOG10P_SPREAD = 0.50; + +public: + ShopItemPriceReader(); + + static const ShopItemPriceReader& instance(); + + OCR::StringMatchResult read_substring( + Logger& logger, + Language language, + const ImageViewRGB32& image, + double min_text_ratio = 0.01, double max_text_ratio = 0.50 + ) const; +}; + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp index 23385d64da..788156b93e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp @@ -87,6 +87,7 @@ struct BDSPMoveDatabase{ move.base_power = (uint8_t)obj.get_integer_throw("base_power", path); move.pp = (uint8_t)obj.get_integer_throw("pp", path); move.spread = obj.get_boolean_throw("spread", path); + move.accuracy = (uint8_t)obj.get_integer_default("accuracy", 100); } } }; @@ -208,6 +209,7 @@ struct BDSPTrainerDatabase{ }; parse_stat_set(tp.ivs, "ivs"); parse_stat_set(tp.evs, "evs"); + tp.has_intimidate = p_obj.get_boolean_default("intimidate", false); trainer.pokemon.push_back(std::move(tp)); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h index 9a3de6b20d..0872c25479 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h @@ -30,6 +30,7 @@ struct BDSPMoveData{ uint8_t base_power; uint8_t pp; bool spread; + uint8_t accuracy; // 0 = never-miss (e.g. Swift); otherwise 1-100 }; // Returns move data for the given slug (e.g. "earthquake"). @@ -79,6 +80,7 @@ struct BDSPTrainerPokemon{ uint8_t level; BDSPStatSet ivs; BDSPStatSet evs; + bool has_intimidate = false; }; struct BDSPTrainer{ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 8ea50d2719..1835d2e0b3 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -163,11 +163,26 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + //checkpoint_list.emplace_back(std::make_unique()); + // Segment 09 (137-) + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); /*checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 7378708323..dd03c16e90 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -18,6 +18,7 @@ #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "CommonTools/Images/SolidColorTest.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" #include "CommonTools/Images/ImageFilter.h" #include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" @@ -57,6 +58,13 @@ const char* torterra_move_slug(TorterraMove move){ return "unknown"; } +const char* trainer_slug(Trainer t){ + switch (t){ + case Trainer::Veilstone_GalacticGrunts_2v2: return "Veilstone_GalacticGrunts_2v2"; + } + return "unknown"; +} + bool fake_save_game( SingleSwitchProgramEnvironment& env, ProControllerContext& context @@ -316,6 +324,248 @@ void repeat_dpad( } } +static const char* shop_item_name(ShopItem item){ + switch (item){ + case ShopItem::FullRestore: return "Full restore"; + case ShopItem::MaxPotion: return "Max potion"; + case ShopItem::HyperPotion: return "Hyper potion"; + case ShopItem::SuperPotion: return "Super potion"; + case ShopItem::Potion: return "Potion"; + case ShopItem::Antidote: return "Antidote"; + case ShopItem::ParalyzHeal: return "Paralyz heal"; + case ShopItem::Awakening: return "Awakening"; + case ShopItem::Repel: return "Repel"; + case ShopItem::SuperRepel: return "Super repel"; + case ShopItem::MaxRepel: return "Max repel"; + case ShopItem::EscapeRope: return "Escape rope"; + default: return "Unknown"; + } +} + +// Returns the OCR slug used as the dictionary key in ShopItemNameOCR.json. +static const char* shop_item_slug(ShopItem item){ + switch (item){ + case ShopItem::FullRestore: return "full-restore"; + case ShopItem::MaxPotion: return "max-potion"; + case ShopItem::HyperPotion: return "hyper-potion"; + case ShopItem::SuperPotion: return "super-potion"; + case ShopItem::Potion: return "potion"; + case ShopItem::Antidote: return "antidote"; + case ShopItem::ParalyzHeal: return "paralyze-heal"; + case ShopItem::Awakening: return "awakening"; + case ShopItem::Repel: return "repel"; + case ShopItem::SuperRepel: return "super-repel"; + case ShopItem::MaxRepel: return "max-repel"; + case ShopItem::EscapeRope: return "escape-rope"; + default: return "unknown"; + } +} + +// {MaxRepel, 2, 4}, {FullRestore, 0, 3} → "4 Max repel, 3 Full restore" +static std::string make_buy_label(const std::vector& items){ + std::string label; + for (size_t i = 0; i < items.size(); i++){ + if (i > 0) label += ", "; + if (items[i].quantity == BUY_MAX){ + label += "max "; + } else { + label += std::to_string(items[i].quantity) + " "; + } + label += shop_item_name(items[i].item); + } + return label; +} + +bool buy_items( + VideoStream& stream, + ProControllerContext& context, + const std::vector& items, + Language language +){ + if (items.empty()){ + return true; + } + + const std::string label = make_buy_label(items); + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + context.wait_for_all_requests(); + + + // ── Coordinate constants ───────────────────────────────────────────────── + + // Area where the selection arrow appears at the vendor greeting menu (Buy/Sell/Leave). + static const ImageFloatBox VENDOR_ARROW_BOX{0.630000, 0.550000, 0.070000, 0.120000}; + + // Area where the selection arrow appears at the YES/NO confirmation dialog. + static const ImageFloatBox CONFIRM_ARROW_BOX{0.700000, 0.620000, 0.070000, 0.120000}; + + // Full area containing all item rows in the shop list. + // The hover detector scans this region to dynamically locate the red outline. + static const ImageFloatBox ITEM_SCAN_AREA{0.417000, 0.100000, 0.540000, 0.625000}; + + static constexpr size_t MAX_OUTER = 50; + static constexpr size_t MAX_ITEM_SEARCH = 50; // max iterations per item before giving up + + // ── Outer state machine ────────────────────────────────────────────────── + for (size_t outer = 0; outer < MAX_OUTER; outer++){ + SelectionArrowFinder arrow_vendor(stream.overlay(), VENDOR_ARROW_BOX, COLOR_GREEN); + ShopBuyingWindowWatcher buying_window; + ShortDialogWatcher dialog(COLOR_RED); + + // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. + stream.log("Starting to loop over items"); + int state = run_until( + stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 5; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + } + }, + {{arrow_vendor}, {buying_window}, {dialog}} + ); + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + + switch (state){ + + case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" + stream.log("Found the selection arrow, pressing A to enter the buying window...", COLOR_GREEN); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + break; + + case 1: // case 2: buying window open → buy each requested item + stream.log("Found the buying window, searching for the right item...", COLOR_GREEN); + { + bool buying_failed = false; + + for (const ItemToBuy& item : items){ + stream.log("Buying " + std::to_string(item.quantity) + "x " + shop_item_slug(item.item)); + if (buying_failed) break; + + // Detector fires only when the red outline is found AND both + // name and price OCR match the expected item slug. + ShopItemHoverDetector hover(stream.logger(), ITEM_SCAN_AREA, + shop_item_slug(item.item), language, COLOR_CYAN); + ShopQuantityWindowWatcher qty_window; + SelectionArrowFinder confirm_arrow(stream.overlay(), CONFIRM_ARROW_BOX, COLOR_GREEN); + + bool item_bought = false; + bool hover_confirmed = false; + for (size_t inner = 0; inner < MAX_ITEM_SEARCH && !item_bought; inner++){ + context.wait_for_all_requests(); + + int sub = run_until( + stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(1000ms); + }, + {{confirm_arrow}, {qty_window}, {hover}} + ); + + context.wait_for_all_requests(); + + stream.log("buy_items: sub=" + std::to_string(sub), COLOR_WHITE); + + switch (sub){ + + case 0: // YES/NO confirmation arrow → confirm purchase + stream.log("Found the confirmation to buy arrow, pressing A to buy", COLOR_GREEN); + { + context.wait_for_all_requests(); + ShopBuyingWindowWatcher back_in_shop; + run_until( + stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 10; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); + } + }, + {{back_in_shop}} + ); + item_bought = true; + break; + } + + case 1: // quantity window + if (!hover_confirmed){ + // Quantity window appeared before we confirmed the right item — + // an earlier A press accidentally opened the wrong item's selector. + // Press B to go back to the list and continue searching. + stream.log("Unexpected quantity window (wrong item), pressing B...", COLOR_YELLOW); + pbf_press_button(context, BUTTON_B, 80ms, 800ms); + break; + } + stream.log("Found the quantity window", COLOR_GREEN); + if (item.quantity == BUY_MAX){ + stream.log("Buying max quantity"); + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } else { + stream.log("Buying " + std::to_string(item.quantity) + " of the item"); + for (int q = 1; q < item.quantity; q++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } + } + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 1000ms); + break; + + case 2: // correct item highlighted → press A to open quantity selector + stream.log("Found the right item, pressing A to buy a certain quantity...", COLOR_GREEN); + hover_confirmed = true; + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 1000ms); + break; + + default: // outline not found or wrong item → advance to next row + stream.log("Not on the right item, moving...", COLOR_GREEN); + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + pbf_wait(context, 300ms); + break; + } + } + + if (!item_bought){ + stream.log(label + ": buy_items: could not locate " + + shop_item_name(item.item) + " in shop list.", COLOR_RED); + buying_failed = true; + } + } + + if (!buying_failed){ + stream.log("All items bought, exiting shop...", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 5000ms); + return true; + } + break; + } + + case 2: // case 3: unexpected ShortDialog → press B until back in buying window + { + ShopBuyingWindowWatcher back_in_shop; + run_until( + stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 5000ms); + }, + {{back_in_shop}} + ); + break; + } + + default: // outer default: unrecognised state → press B to recover + pbf_press_button(context, BUTTON_B, 80ms, 3000ms); + break; + } + } + + stream.log(label + ": buy_items: failed to complete after max iterations.", COLOR_RED); + return false; +} + bool heal_pokemon( VideoStream& stream, @@ -907,36 +1157,13 @@ bool activate_repel( } -void use_strength( +void use_HM( VideoStream& stream, - ProControllerContext& context -){ - stream.log("[AutoStory] use_strength: pressing A until selection arrow appears."); - const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; - SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 30000ms); - }, - {{arrow}} - ); - if (ret != 0){ - stream.log("[AutoStory] use_strength: selection arrow not found.", COLOR_RED); - return; - } - - stream.log("[AutoStory] use_strength: selection arrow found, confirming.", COLOR_GREEN); - pbf_press_button(context, BUTTON_A, 80ms, 200ms); - pbf_mash_button(context, BUTTON_B, 6000ms); -} + ProControllerContext& context, + const std::string& label -void use_rock_smash( - VideoStream& stream, - ProControllerContext& context ){ - stream.log("[AutoStory] use_rock_smash: pressing A until selection arrow appears."); + stream.log("Using " + label); const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); @@ -948,12 +1175,12 @@ void use_rock_smash( {{arrow}} ); if (ret != 0){ - stream.log("[AutoStory] use_rock_smash: selection arrow not found.", COLOR_RED); + stream.log("Using " + label + ": selection arrow not found.", COLOR_RED); return; } - stream.log("[AutoStory] use_rock_smash: selection arrow found, confirming.", COLOR_GREEN); - pbf_press_button(context, BUTTON_A, 80ms, 200ms); + stream.log("Using " + label + ": selection arrow found, confirming.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 1000ms); pbf_mash_button(context, BUTTON_B, 6000ms); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index cfdd074da7..01781a11f2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "Common/Cpp/Options/EnumDropdownOption.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" @@ -87,6 +88,14 @@ enum class TorterraMove{ // Returns the JSON slug for a TorterraMove (e.g. "earthquake"). const char* torterra_move_slug(TorterraMove move); +enum class Trainer { + Veilstone_GalacticGrunts_2v2, + // Extend with new trainer battle IDs as the AutoStory adds them. +}; + +// Returns the trainers.json battle-ID string for a Trainer value. +const char* trainer_slug(Trainer t); + struct OutsideHelpConfig{ OutsideHelpPokemon pokemon; TorterraMove attacks[4]; @@ -231,16 +240,11 @@ bool activate_repel( int num_icons = 8 ); -// Press A until the Strength selection arrow appears, confirm with A, then mash B for 3 s. -void use_strength( - VideoStream& stream, - ProControllerContext& context -); - -// Press A until the Rock Smash selection arrow appears, confirm with A, then mash B for 3 s. -void use_rock_smash( +// Press A until the any HM selection arrow appears, confirm with A, then mash B for 3 s. +void use_HM( VideoStream& stream, - ProControllerContext& context + ProControllerContext& context, + const std::string& label ); // Walk to the nurse, heal the party, and exit the Pokemon Center. @@ -302,6 +306,46 @@ enum class GearLevel { Fast, }; + +// --------------------------------------------------------------------------- +// Mart buying +// --------------------------------------------------------------------------- + +enum class ShopItem { + FullRestore, + MaxPotion, + HyperPotion, + SuperPotion, + Potion, + Antidote, + ParalyzHeal, + Awakening, + Repel, + SuperRepel, + MaxRepel, + EscapeRope, +}; + +// Pass as the `quantity` field to buy the maximum available amount. +static constexpr int BUY_MAX = -1; + +struct ItemToBuy { + ShopItem item; + int quantity; // positive count to buy, or BUY_MAX +}; + +// Walk up to a vendor (already talking to them), navigate through the Buy menu, +// and purchase each item in `items` in order. The function presses DPAD_DOWN +// until the matching item name is found via OCR, so items may appear in any +// list order. Returns true on success. +bool buy_items( + VideoStream& stream, + ProControllerContext& context, + const std::vector& items, + Language language = Language::English +); + + // Mount the bicycle (press +) and confirm it is in the requested gear. // Presses B to toggle gear and uses the Fast-gear spark visual effect to // determine which gear was entered. If the wrong gear is entered, presses B diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 6c9acd2929..746230eb56 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -568,7 +568,7 @@ static bool go_through_mount_coronet( pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 1+ pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ - use_strength(stream, context); + use_HM(stream, context, "Strength"); context.wait_for_all_requests(); pbf_move_left_joystick(context, {0, +1}, 10000ms, 500ms); // 10++ (strength) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 86221dbbfa..8208ef1b82 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -2011,7 +2011,7 @@ static bool go_to_house_west_sunyshore( pbf_move_left_joystick(context, {+1, 0}, 5200ms, 100ms); // 26+ pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ - use_rock_smash(stream, context); + use_HM(stream, context, "Rock Smash"); context.wait_for_all_requests(); @@ -2585,7 +2585,12 @@ void checkpoint_134(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_135(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_135( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ @@ -2598,7 +2603,7 @@ void checkpoint_135(SingleSwitchProgramEnvironment& env, ProControllerContext& c -std::string AutoStory_Checkpoint_111::name() const{ return "111 - Segment 08 - Gym badge acquired"; } +std::string AutoStory_Checkpoint_111::name() const{ return "111 - S08 - Gym badge acquired"; } std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } void AutoStory_Checkpoint_111::run_checkpoint( @@ -2610,7 +2615,7 @@ void AutoStory_Checkpoint_111::run_checkpoint( checkpoint_111(env, context, options, stats); } -std::string AutoStory_Checkpoint_112::name() const{ return "112 - Segment 08 - Left Snowpoint Gym"; } +std::string AutoStory_Checkpoint_112::name() const{ return "112 - S08 - Left Snowpoint Gym"; } std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } void AutoStory_Checkpoint_112::run_checkpoint( @@ -2622,7 +2627,7 @@ void AutoStory_Checkpoint_112::run_checkpoint( checkpoint_112(env, context, options, stats); } -std::string AutoStory_Checkpoint_113::name() const{ return "113 - Segment 08 - Arrived at Snowpoint Pokemon Center"; } +std::string AutoStory_Checkpoint_113::name() const{ return "113 - S08 - Arrived at Snowpoint Pokemon Center"; } std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } void AutoStory_Checkpoint_113::run_checkpoint( @@ -2634,7 +2639,7 @@ void AutoStory_Checkpoint_113::run_checkpoint( checkpoint_113(env, context, options, stats); } -std::string AutoStory_Checkpoint_114::name() const{ return "114 - Segment 08 - Healed at Snowpoint City Pokemon Center"; } +std::string AutoStory_Checkpoint_114::name() const{ return "114 - S08 - Healed at Snowpoint City Pokemon Center"; } std::string AutoStory_Checkpoint_114::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } std::string AutoStory_Checkpoint_114::end_text() const{ return "Entered Lake Acuity Lakefront."; } void AutoStory_Checkpoint_114::run_checkpoint( @@ -2646,7 +2651,7 @@ void AutoStory_Checkpoint_114::run_checkpoint( checkpoint_114(env, context, options, stats); } -std::string AutoStory_Checkpoint_115::name() const{ return "115 - Segment 08 - Entered Acuity Lakefront"; } +std::string AutoStory_Checkpoint_115::name() const{ return "115 - S08 - Entered Acuity Lakefront"; } std::string AutoStory_Checkpoint_115::start_text() const{ return "Entered Lake Acuity Lakefront."; } std::string AutoStory_Checkpoint_115::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } void AutoStory_Checkpoint_115::run_checkpoint( @@ -2658,7 +2663,7 @@ void AutoStory_Checkpoint_115::run_checkpoint( checkpoint_115(env, context, options, stats); } -std::string AutoStory_Checkpoint_116::name() const{ return "116 - Segment 08 - Arrived at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_116::name() const{ return "116 - S08 - Arrived at Veilstone Pokemon Center"; } std::string AutoStory_Checkpoint_116::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } std::string AutoStory_Checkpoint_116::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } void AutoStory_Checkpoint_116::run_checkpoint( @@ -2670,7 +2675,7 @@ void AutoStory_Checkpoint_116::run_checkpoint( checkpoint_116(env, context, options, stats); } -std::string AutoStory_Checkpoint_117::name() const{ return "117 - Segment 08 - Healed at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_117::name() const{ return "117 - S08 - Healed at Veilstone Pokemon Center"; } std::string AutoStory_Checkpoint_117::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } std::string AutoStory_Checkpoint_117::end_text() const{ return "Entered Team Galactic Warehouse."; } void AutoStory_Checkpoint_117::run_checkpoint( @@ -2682,7 +2687,7 @@ void AutoStory_Checkpoint_117::run_checkpoint( checkpoint_117(env, context, options, stats); } -std::string AutoStory_Checkpoint_118::name() const{ return "118 - Segment 08 - Entered Team Galactic Warehouse"; } +std::string AutoStory_Checkpoint_118::name() const{ return "118 - S08 - Entered Team Galactic Warehouse"; } std::string AutoStory_Checkpoint_118::start_text() const{ return "Entered Team Galactic Warehouse."; } std::string AutoStory_Checkpoint_118::end_text() const{ return "Got out of the warehouse with the key."; } void AutoStory_Checkpoint_118::run_checkpoint( @@ -2694,7 +2699,7 @@ void AutoStory_Checkpoint_118::run_checkpoint( checkpoint_118(env, context, options, stats); } -std::string AutoStory_Checkpoint_119::name() const{ return "119 - Segment 08 - Fly back to Veilstone Pokemon Center for healing"; } +std::string AutoStory_Checkpoint_119::name() const{ return "119 - S08 - Fly back to Veilstone Pokemon Center for healing"; } std::string AutoStory_Checkpoint_119::start_text() const{ return "Got out of the warehouse with the key."; } std::string AutoStory_Checkpoint_119::end_text() const{ return "Entered Veilstone Pokemon Center for healing."; } void AutoStory_Checkpoint_119::run_checkpoint( @@ -2706,7 +2711,7 @@ void AutoStory_Checkpoint_119::run_checkpoint( checkpoint_119(env, context, options, stats); } -std::string AutoStory_Checkpoint_120::name() const{ return "120 - Segment 08 - Heal Pokemon"; } +std::string AutoStory_Checkpoint_120::name() const{ return "120 - S08 - Heal Pokemon"; } std::string AutoStory_Checkpoint_120::start_text() const{ return "Entered Veilstone Pokemon Center for healing."; } std::string AutoStory_Checkpoint_120::end_text() const{ return "Exited Veilstone Pokemon Center."; } void AutoStory_Checkpoint_120::run_checkpoint( @@ -2718,7 +2723,7 @@ void AutoStory_Checkpoint_120::run_checkpoint( checkpoint_120(env, context, options, stats); } -std::string AutoStory_Checkpoint_121::name() const{ return "121 - Segment 08 - Go to Galactic HQ"; } +std::string AutoStory_Checkpoint_121::name() const{ return "121 - S08 - Go to Galactic HQ"; } std::string AutoStory_Checkpoint_121::start_text() const{ return "Exited Veilstone Pokemon Center."; } std::string AutoStory_Checkpoint_121::end_text() const{ return "Entered Galactic HQ."; } void AutoStory_Checkpoint_121::run_checkpoint( @@ -2730,7 +2735,7 @@ void AutoStory_Checkpoint_121::run_checkpoint( checkpoint_121(env, context, options, stats); } -std::string AutoStory_Checkpoint_122::name() const{ return "122 - Segment 08 - Deal with Cyrus at the Galactic HQ"; } +std::string AutoStory_Checkpoint_122::name() const{ return "122 - S08 - Deal with Cyrus at the Galactic HQ"; } std::string AutoStory_Checkpoint_122::start_text() const{ return "Entered Galactic HQ."; } std::string AutoStory_Checkpoint_122::end_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } void AutoStory_Checkpoint_122::run_checkpoint( @@ -2742,7 +2747,7 @@ void AutoStory_Checkpoint_122::run_checkpoint( checkpoint_122(env, context, options, stats); } -std::string AutoStory_Checkpoint_123::name() const{ return "123 - Segment 08 - Fly to Oreburgh City"; } +std::string AutoStory_Checkpoint_123::name() const{ return "123 - S08 - Fly to Oreburgh City"; } std::string AutoStory_Checkpoint_123::start_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } std::string AutoStory_Checkpoint_123::end_text() const{ return "Flew to Oreburgh city and entered Pokemon Center."; } void AutoStory_Checkpoint_123::run_checkpoint( @@ -2754,7 +2759,7 @@ void AutoStory_Checkpoint_123::run_checkpoint( checkpoint_123(env, context, options, stats); } -std::string AutoStory_Checkpoint_124::name() const{ return "124 - Segment 08 - Heal Pokemon at Oreburgh City Pokemon Center"; } +std::string AutoStory_Checkpoint_124::name() const{ return "124 - S08 - Heal Pokemon at Oreburgh City Pokemon Center"; } std::string AutoStory_Checkpoint_124::start_text() const{ return "Flew to Oreburgh City and entered Pokemon Center."; } std::string AutoStory_Checkpoint_124::end_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } void AutoStory_Checkpoint_124::run_checkpoint( @@ -2766,7 +2771,7 @@ void AutoStory_Checkpoint_124::run_checkpoint( checkpoint_124(env, context, options, stats); } -std::string AutoStory_Checkpoint_125::name() const{ return "125 - Segment 08 - Go from Oreburgh City to Mount Coronet entrance"; } +std::string AutoStory_Checkpoint_125::name() const{ return "125 - S08 - Go from Oreburgh City to Mount Coronet entrance"; } std::string AutoStory_Checkpoint_125::start_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } std::string AutoStory_Checkpoint_125::end_text() const{ return "Entered Mount Coronet."; } void AutoStory_Checkpoint_125::run_checkpoint( @@ -2778,7 +2783,7 @@ void AutoStory_Checkpoint_125::run_checkpoint( checkpoint_125(env, context, options, stats); } -std::string AutoStory_Checkpoint_126::name() const{ return "126 - Segment 08 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; } +std::string AutoStory_Checkpoint_126::name() const{ return "126 - S08 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; } std::string AutoStory_Checkpoint_126::start_text() const{ return "Entered Mount Coronet."; } std::string AutoStory_Checkpoint_126::end_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } void AutoStory_Checkpoint_126::run_checkpoint( @@ -2790,7 +2795,7 @@ void AutoStory_Checkpoint_126::run_checkpoint( checkpoint_126(env, context, options, stats); } -std::string AutoStory_Checkpoint_127::name() const{ return "127 - Segment 08 - Fly to Veilstone City"; } +std::string AutoStory_Checkpoint_127::name() const{ return "127 - S08 - Fly to Veilstone City"; } std::string AutoStory_Checkpoint_127::start_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } std::string AutoStory_Checkpoint_127::end_text() const{ return "Entered the Veilstone Pokemon Center."; } void AutoStory_Checkpoint_127::run_checkpoint( @@ -2802,7 +2807,7 @@ void AutoStory_Checkpoint_127::run_checkpoint( checkpoint_127(env, context, options, stats); } -std::string AutoStory_Checkpoint_128::name() const{ return "126 - Segment 08 - Heal and exit"; } +std::string AutoStory_Checkpoint_128::name() const{ return "126 - S08 - Heal and exit"; } std::string AutoStory_Checkpoint_128::start_text() const{ return "Entered the Veilstone Pokemon Center."; } std::string AutoStory_Checkpoint_128::end_text() const{ return "Exited the Veilstone Pokemon Center."; } void AutoStory_Checkpoint_128::run_checkpoint( @@ -2814,7 +2819,7 @@ void AutoStory_Checkpoint_128::run_checkpoint( checkpoint_128(env, context, options, stats); } -std::string AutoStory_Checkpoint_129::name() const{ return "129 - Segment 08 - Go to house South of Veilstone City"; } +std::string AutoStory_Checkpoint_129::name() const{ return "129 - S08 - Go to house South of Veilstone City"; } std::string AutoStory_Checkpoint_129::start_text() const{ return "Exited the Veilstone Pokemon Center."; } std::string AutoStory_Checkpoint_129::end_text() const{ return "Arrived at the house South of Veilstone City."; } void AutoStory_Checkpoint_129::run_checkpoint( @@ -2826,7 +2831,7 @@ void AutoStory_Checkpoint_129::run_checkpoint( checkpoint_129(env, context, options, stats); } -std::string AutoStory_Checkpoint_130::name() const{ return "130 - Segment 08 - Go through the house South of Veilstone City"; } +std::string AutoStory_Checkpoint_130::name() const{ return "130 - S08 - Go through the house South of Veilstone City"; } std::string AutoStory_Checkpoint_130::start_text() const{ return "Arrived at the house South of Veilstone City."; } std::string AutoStory_Checkpoint_130::end_text() const{ return "Exited the house South of Veilstone City."; } void AutoStory_Checkpoint_130::run_checkpoint( @@ -2838,7 +2843,7 @@ void AutoStory_Checkpoint_130::run_checkpoint( checkpoint_130(env, context, options, stats); } -std::string AutoStory_Checkpoint_131::name() const{ return "131 - Segment 08 - Go through routes 214 and 222 to the house West of Sunyshore City"; } +std::string AutoStory_Checkpoint_131::name() const{ return "131 - S08 - Go through routes 214 and 222 to the house West of Sunyshore City"; } std::string AutoStory_Checkpoint_131::start_text() const{ return "Exited house South of Veilstone City."; } std::string AutoStory_Checkpoint_131::end_text() const{ return "Entered the house West of Sunyshore City."; } void AutoStory_Checkpoint_131::run_checkpoint( @@ -2850,7 +2855,7 @@ void AutoStory_Checkpoint_131::run_checkpoint( checkpoint_131(env, context, options, stats); } -std::string AutoStory_Checkpoint_132::name() const{ return "132 - Segment 08 - Go through the house West of Sunyshore City"; } +std::string AutoStory_Checkpoint_132::name() const{ return "132 - S08 - Go through the house West of Sunyshore City"; } std::string AutoStory_Checkpoint_132::start_text() const{ return "Entered the house West of Sunyshore City."; } std::string AutoStory_Checkpoint_132::end_text() const{ return "Entered Sunyshore City."; } void AutoStory_Checkpoint_132::run_checkpoint( @@ -2862,7 +2867,7 @@ void AutoStory_Checkpoint_132::run_checkpoint( checkpoint_132(env, context, options, stats); } -std::string AutoStory_Checkpoint_133::name() const{ return "133 - Segment 08 - Go to Sunyshore City's lighthouse"; } +std::string AutoStory_Checkpoint_133::name() const{ return "133 - S08 - Go to Sunyshore City's lighthouse"; } std::string AutoStory_Checkpoint_133::start_text() const{ return "Entered Sunyshore City."; } std::string AutoStory_Checkpoint_133::end_text() const{ return "Entered Sunyshore City's lighthouse."; } void AutoStory_Checkpoint_133::run_checkpoint( @@ -2874,7 +2879,7 @@ void AutoStory_Checkpoint_133::run_checkpoint( checkpoint_133(env, context, options, stats); } -std::string AutoStory_Checkpoint_134::name() const{ return "134 - Segment 08 - Talk to Volkner at the lighthouse"; } +std::string AutoStory_Checkpoint_134::name() const{ return "134 - S08 - Talk to Volkner at the lighthouse"; } std::string AutoStory_Checkpoint_134::start_text() const{ return "Entered Sunyshore City's lighthouse."; } std::string AutoStory_Checkpoint_134::end_text() const{ return "Exited Sunyshore City's lighthouse."; } void AutoStory_Checkpoint_134::run_checkpoint( @@ -2886,7 +2891,7 @@ void AutoStory_Checkpoint_134::run_checkpoint( checkpoint_134(env, context, options, stats); } -std::string AutoStory_Checkpoint_135::name() const{ return "135 - Segment 08 - Go to the Sunyshore City gym"; } +std::string AutoStory_Checkpoint_135::name() const{ return "135 - S08 - Go to the Sunyshore City gym"; } std::string AutoStory_Checkpoint_135::start_text() const{ return "Exited Sunyshore City's lighthouse."; } std::string AutoStory_Checkpoint_135::end_text() const{ return "Entered Sunyshore City's gym."; } void AutoStory_Checkpoint_135::run_checkpoint( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index d626df5258..abc2097442 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -40,7 +40,12 @@ class AutoStory_Checkpoint_111 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_111( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); class AutoStory_Checkpoint_112 : public AutoStory_Checkpoint{ public: diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 3e04413d49..662101aec0 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -5,7 +5,21 @@ * */ +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" +#include "../PokemonBDSP_AutoStory.h" #include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Battle.h" #include "PokemonBDSP_AutoStory_Segment_09.h" namespace PokemonAutomation{ @@ -28,28 +42,1520 @@ void AutoStory_Segment_09::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_022().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_137().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_138().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_139().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_140().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_141().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_142().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_143().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_144().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_145().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_146().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_147().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_148().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_149().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_150().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } +static void move_back_after_unexpected_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + stream.log("Finishing battle with " + trainerid, COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + if (trainerid == "route_223_swimmer_miranda") { + pbf_move_left_joystick(context, {0, +1}, 3800ms, 100ms); // 19+ + } else if (trainerid == "route_223_swimmer_crystal") { + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + } else if (trainerid == "route_223_swimmer_gabrielle") { + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + } else if (trainerid == "route_223_swimmer_troy") { + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + } +} + +static bool handle_battle( + VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid +){ + context.wait_for_all_requests(); + stream.log("Battling " + trainerid + "...", COLOR_GREEN); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); + if (ret_menu < 0){ + stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 500ms); + + if ( + trainerid == "victory_road_ace_mariah" or + trainerid == "victory_road_blackbelt_miles" or + trainerid == "victory_road_dragontamer_andre" or + trainerid == "victory_road_veteran_edgar" + ) { + // Select Bulldoze + pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); + } else if ( + trainerid == "route_223_swimmer_miranda" or + trainerid == "route_223_swimmer_oscar" or + trainerid == "route_223_swimmer_crystal" or + trainerid == "route_223_swimmer_gabrielle" or + trainerid == "route_223_swimmer_troy" or + trainerid == "victory_road_ace_sydney" or + trainerid == "victory_road_psychic_valencia" + ) { + // Select Razor leaf + pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); + } else if ( + trainerid == "victory_road_ace_omar" or + trainerid == "victory_road_veteran_clayton" or + trainerid == "victory_road_dragontamer_clinton" + ) { + // Select Crunch + } + context.wait_for_all_requests(); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + stream.log("handle_battle_" + trainerid + ": transition confirmed."); + return true; +} + +static bool enter_sunyshore_pokemon_center( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("enter_sunyshore_pokemon_center: flying to the Pokemon Center and entering..."); + + fly_to(stream, context, FlyPoint::CurrentCity); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("enter_sunyshore_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + stream.log("enter_sunyshore_pokemon_center: entered the Pokemon Center.", COLOR_GREEN); + } + + return true; +} + +static bool go_to_victory_road_pokemon_center( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_to_victory_road_pokemon_center: going through Victory Road's outdoor and heading to the Pokemon Center..."); + + // Going to the lady who gives you HM Waterfall + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5); + pbf_move_left_joystick(context, {0, +1}, 3400ms, 100ms); // 17+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); + + { + MarkDetector jasmine_dialog(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 6800ms, 100ms); // 34+ + }, + {{jasmine_dialog}} + ); + if (ret == 0){ + stream.log("go_to_victory_road_pokemon_center: talking to Jasmine.", COLOR_GREEN); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_mash_button(context, BUTTON_B, 14000ms); + } else if (ret < 0) { + stream.log("go_to_victory_road_pokemon_center: Jasmine dialog box not detected!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + + use_HM(stream, context, "Surf"); + context.wait_for_all_requests(); + + activate_repel(stream, context); + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + { + MarkDetector route_223_swimmer_miranda_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{route_223_swimmer_miranda_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_miranda: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_223_swimmer_miranda"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_223_swimmer_miranda"); + } + } + } + + context.wait_for_all_requests(); + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + { + MarkDetector route_223_swimmer_oscar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{route_223_swimmer_oscar_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_oscar: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_223_swimmer_oscar"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 9+ + pbf_move_left_joystick(context, {+1, 0}, 1800ms, 100ms); // 9+ + + { + MarkDetector route_223_swimmer_crystal_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ + }, + {{route_223_swimmer_crystal_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_crystal: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_223_swimmer_crystal"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_223_swimmer_crystal"); + } + } + } + + context.wait_for_all_requests(); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_victory_road_pokemon_center: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 4++ (getting out of the water) + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Surf"); + + context.wait_for_all_requests(); + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5); + pbf_move_left_joystick(context, {0, +1}, 3000ms, 100ms); // 10++ (getting out of the water) + + context.wait_for_all_requests(); + use_HM(stream, context, "Surf"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); + + { + MarkDetector route_223_swimmer_gabrielle_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + DpadState dpad; + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + }, + {{route_223_swimmer_gabrielle_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_gabrielle: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_223_swimmer_gabrielle"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_223_swimmer_gabrielle"); + } + } + } + + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); + + { + MarkDetector route_223_swimmer_troy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + }, + {{route_223_swimmer_troy_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_troy: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "route_223_swimmer_troy"); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + move_back_after_unexpected_battle(stream, context, "route_223_swimmer_troy"); + } + } + } + + context.wait_for_all_requests(); + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); + pbf_move_left_joystick(context, {0, +1}, 3000ms, 100ms); // 15+ + + context.wait_for_all_requests(); + + use_HM(stream, context, "Waterfall"); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 5++ (getting out of the water) + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_to_victory_road_pokemon_center: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + stream.log("go_to_victory_road_pokemon_center: entered the Pokemon Center.", COLOR_GREEN); + } + + return true; +} + +static bool heal_and_exit( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +){ + return heal_pokemon(stream, context, label); +} + +static bool enter_victory_road_cave( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("enter_victory_road_cave: entering the cave..."); + + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 9); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("enter_victory_road_cave: black screen not detected!", COLOR_RED); + return false; + } + stream.log("enter_victory_road_cave: entered the Victory Road cave.", COLOR_GREEN); + } + + return true; +} + +static bool go_through_victory_road_cave( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("go_through_victory_road_cave: going through Victory Road's cave and heading to the Elite 4..."); + + activate_repel(stream, context); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16+ + pbf_move_left_joystick(context, {+1, 0}, 3800ms, 100ms); // 19+ + pbf_move_left_joystick(context, {0, +1}, 3000ms, 100ms); // 15+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + + use_HM(stream, context, "Rock Climb"); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12); + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 13+ + + { + MarkDetector victory_road_ace_mariah_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_mariah_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_ace_mariah: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_ace_mariah"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 1+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave: black screen not detected (1)!", COLOR_RED); + return false; + } + stream.log("go_through_victory_road_cave: entered the second part of the cave.", COLOR_GREEN); + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Rock Smash"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Strength"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 1500ms, 100ms); // 1++ (move boulders) + pbf_move_left_joystick(context, {-1, 0}, 1500ms, 100ms); // 1++ (move boulders) + pbf_move_left_joystick(context, {0, -1}, 4000ms, 100ms); // 3++ (move boulders) + + { + MarkDetector victory_road_ace_omar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_omar_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_ace_omar: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_ace_omar"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 1++ (move boulders) + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Rock Smash"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 4000ms, 100ms); // 2++ (move boulders) + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {+1, 0}, 1500ms, 100ms); // 4++ (move after boulder moving) + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Rock Smash"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); + + { + MarkDetector victory_road_ace_sydney_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_sydney_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_ace_sydney: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_ace_sydney"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {+1, 0}, 1500ms, 100ms); // 2++ (move boulders) + + { + MarkDetector victory_road_veteran_clayton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_veteran_clayton_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_veteran_clayton: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_veteran_clayton"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 1+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 9+ + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave: black screen not detected (2)!", COLOR_RED); + return false; + } + stream.log("go_through_victory_road_cave: entered the third part of the cave.", COLOR_GREEN); + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {+1, 0}, 3600ms, 100ms); // 18+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Rock Climb"); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + + { + MarkDetector victory_road_blackbelt_miles_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_blackbelt_miles_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_blackbelt_miles: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_blackbelt_miles"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave: black screen not detected (3)!", COLOR_RED); + return false; + } + stream.log("go_through_victory_road_cave: entered the fourth part of the cave.", COLOR_GREEN); + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1); + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Surf"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 3+ + + { + MarkDetector victory_road_psychic_valencia_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_psychic_valencia_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_psychic_valencia: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_psychic_valencia"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Surf"); + + context.wait_for_all_requests(); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (2)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {+1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, +1}, 2200ms, 100ms); // 11+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Waterfall"); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + + { + MarkDetector victory_road_dragontamer_andre_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_dragontamer_andre_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_dragontamer_andre: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_dragontamer_andre"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 1+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Surf"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 17++ (getting out of the water) + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave: black screen not detected (4)!", COLOR_RED); + return false; + } + stream.log("go_through_victory_road_cave: entered the fifth part of the cave.", COLOR_GREEN); + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3000ms, 100ms); // 15+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 3000ms); + } else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (2)!", COLOR_RED); + return false; + } + } + + pbf_move_left_joystick(context, {-1, 0}, 3000ms, 100ms); // 15+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); + + { + MarkDetector victory_road_veteran_edgar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_veteran_edgar_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_veteran_edgar: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_veteran_edgar"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, -1}, 2600ms, 100ms); // 13+ + dpad.last_dir = DPAD_DOWN; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1); + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 9); + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Rock Climb"); + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + { + MarkDetector victory_road_dragontamer_clinton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_dragontamer_clinton_fight}} + ); + if (ret == 0){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave, victory_road_dragontamer_clinton: black screen not detected!", COLOR_RED); + return false; + } else if (ret == 0){ + handle_battle(stream, context, "victory_road_dragontamer_clinton"); + } + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); + + { + WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{white_screen}} + ); + if (ret < 0){ + stream.log("go_through_victory_road_cave: white screen not detected!", COLOR_RED); + return false; + } + stream.log("go_through_victory_road_cave: exited Victory Road's cave.", COLOR_GREEN); + } + + return true; +} + +static bool enter_elite_4_pokemon_center( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + //pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("enter_elite_4_pokemon_center: Entering the Elite 4 building..."); + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6); + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + + context.wait_for_all_requests(); + use_HM(stream, context, "Surf"); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + context.wait_for_all_requests(); + use_HM(stream, context, "Waterfall"); + + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + + { + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("enter_elite_4_pokemon_center: black screen not detected!", COLOR_RED); + return false; + } + stream.log("enter_elite_4_pokemon_center: entered the Elite 4 building.", COLOR_GREEN); + } + + return true; +} + +static bool ready_in_front_of_elite_4( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 6000ms); + context.wait_for_all_requests(); + stream.log("ready_in_front_of_elite_4: stocked up and healed up, ready for the Elite 4..."); + + { + ShortDialogWatcher repel_dialog(COLOR_RED); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}} + ); + if (ret == 0){ + stream.log("ready_in_front_of_elite_4: Not reactivating repel.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 3000ms); + } else if (ret < 0){ + stream.log("ready_in_front_of_elite_4: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; + } + } + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1); + + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 7+ + context.wait_for_all_requests(); + + pbf_mash_button(context, BUTTON_A, 5000ms); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + wait_for_dialogue(stream, context, "Elite 4 Pokemon Center - Pokemon healing"); + mash_until_dialogue_ends(stream, context, BUTTON_B); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + + + buy_items(stream, context, + { + {ShopItem::FullRestore, BUY_MAX} + }, + options.language + ); + + return true; +} + +static bool get_into_the_elite_4_entrance( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("get_into_the_elite_4_entrance: entered the Elite 4..."); + + return true; +} + +static bool deal_with_aaron_elite_4( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("deal_with_aaron_elite_4: battling Aaron of the Elite 4 and passing the gate..."); + + return true; +} + +static bool deal_with_bertha_elite_4( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("deal_with_bertha_elite_4: battling Bertha of the Elite 4 and passing the gate..."); + + return true; +} + +static bool deal_with_flint_elite_4( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("deal_with_flint_elite_4: battling Flint of the Elite 4 and passing the gate..."); + + return true; +} + +static bool deal_with_lucian_elite_4( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("deal_with_lucian_elite_4: battling Lucian of the Elite 4 and passing the gate..."); + + return true; +} + +static bool deal_with_cynthia_and_finish( + VideoStream& stream, + ProControllerContext& context, + AutoStoryOptions options +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + stream.log("deal_with_cynthia_and_finish: battling Cynthia and finishing the game..."); -std::string AutoStory_Checkpoint_022::name() const{ return "022 - " + AutoStory_Segment_09().name(); } -std::string AutoStory_Checkpoint_022::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } -std::string AutoStory_Checkpoint_022::end_text() const{ return "Elite Four and Champion defeated, credits roll."; } + return true; +} -void AutoStory_Checkpoint_022::run_checkpoint( +void checkpoint_137( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats -) const{ - checkpoint_022(env, context, options, stats); +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!enter_sunyshore_pokemon_center(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_sunyshore_pokemon_center: transition not detected.", env.console); + } + }, + true + ); +} + +void checkpoint_138( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Sunyshore City")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_1: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_139( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_victory_road_pokemon_center(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_140( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!heal_and_exit(env.console, context, "Victory Road")){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_2: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_141( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!enter_victory_road_cave(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_victory_road_cave: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_142( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_through_victory_road_cave(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_victory_road_cave: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_143( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!enter_elite_4_pokemon_center(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_elite_4_pokemon_center: transition not detected.", env.console); + } + }, + false + ); } +void checkpoint_144( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!ready_in_front_of_elite_4(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "ready_in_front_of_elite_4: transition not detected.", env.console); + } + }, + false + ); +} -void checkpoint_022( +void checkpoint_145( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -57,12 +1563,263 @@ void checkpoint_022( ){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ - // TODO: implement Segment 09 gameplay logic + if (!get_into_the_elite_4_entrance(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_the_elite_4_entrance: transition not detected.", env.console); + } }, true ); } +void checkpoint_146( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!deal_with_aaron_elite_4(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_aaron_elite_4: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_147( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!deal_with_bertha_elite_4(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_bertha_elite_4: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_148( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!deal_with_flint_elite_4(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_flint_elite_4: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_149( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!deal_with_lucian_elite_4(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_lucian_elite_4: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_150( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!deal_with_cynthia_and_finish(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_cynthia_and_finish: transition not detected.", env.console); + } + }, + false + ); +} + +std::string AutoStory_Checkpoint_137::name() const{ return "137 - S09 - Leave the gym and fly to the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_137::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } +std::string AutoStory_Checkpoint_137::end_text() const{ return "Enter the Sunyshore Pokemon Center."; } +void AutoStory_Checkpoint_137::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_137(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_138::name() const{ return "138 - S09 - Heal the Pokemon at the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_138::start_text() const{ return "Entered the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_138::end_text() const{ return "Exit the Sunyshore Pokemon Center."; } +void AutoStory_Checkpoint_138::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_138(env, context, options, stats); +} + + +std::string AutoStory_Checkpoint_139::name() const{ return "139 - S09 - Start at the Sunyshore Pokemon Center and go to the Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_139::start_text() const{ return "Exited the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_139::end_text() const{ return "Enter the Victory Road Pokemon Center."; } +void AutoStory_Checkpoint_139::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_139(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_140::name() const{ return "140 - S09 - Heal at the Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_140::start_text() const{ return "Entered Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_140::end_text() const{ return "Exit Victory Road Pokemon Center."; } +void AutoStory_Checkpoint_140::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_140(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_141::name() const{ return "141 - S09 - Enter the Victory Road cave."; } +std::string AutoStory_Checkpoint_141::start_text() const{ return "Exited Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_141::end_text() const{ return "Enter the Victory Road cave."; } +void AutoStory_Checkpoint_141::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_141(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_142::name() const{ return "142 - S09 - Go through the Victory Road cave."; } +std::string AutoStory_Checkpoint_142::start_text() const{ return "Entered Victory Road cave."; } +std::string AutoStory_Checkpoint_142::end_text() const{ return "Exit Victory Road cave."; } +void AutoStory_Checkpoint_142::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_142(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_143::name() const{ return "143 - S09 - Go through the end of Victory Road into the Elite 4 building"; } +std::string AutoStory_Checkpoint_143::start_text() const{ return "Exited Victory Road cave."; } +std::string AutoStory_Checkpoint_143::end_text() const{ return "Enter the Elite 4 building."; } +void AutoStory_Checkpoint_143::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_143(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_144::name() const{ return "144 - S09 - Enter the Elite 4 building and get ready to enter."; } +std::string AutoStory_Checkpoint_144::start_text() const{ return "Entered the Elite 4 building."; } +std::string AutoStory_Checkpoint_144::end_text() const{ return "Ready in front of the Elite 4."; } +void AutoStory_Checkpoint_144::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_144(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_145::name() const{ return "145 - S09 - Enter the first room of the Elite 4."; } +std::string AutoStory_Checkpoint_145::start_text() const{ return "Readied in front of the Elite 4."; } +std::string AutoStory_Checkpoint_145::end_text() const{ return "Enter the Elite 4's first room."; } +void AutoStory_Checkpoint_145::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_145(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_146::name() const{ return "146 - S09 - Deal with Aaron and go to the second room."; } +std::string AutoStory_Checkpoint_146::start_text() const{ return "Entered the Elite 4's first room."; } +std::string AutoStory_Checkpoint_146::end_text() const{ return "Deal with Aaron and enter the second room."; } +void AutoStory_Checkpoint_146::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_146(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_147::name() const{ return "147 - S09 - Deal with Bertha and go to the third room."; } +std::string AutoStory_Checkpoint_147::start_text() const{ return "Dealt with Aaron and entered the second room."; } +std::string AutoStory_Checkpoint_147::end_text() const{ return "Deal with Bertha and enter the third room."; } +void AutoStory_Checkpoint_147::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_147(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_148::name() const{ return "148 - S09 - Deal with Flint and go to the fourth room."; } +std::string AutoStory_Checkpoint_148::start_text() const{ return "Dealt with Bertha and entered the third room."; } +std::string AutoStory_Checkpoint_148::end_text() const{ return "Deal with Flint and enter the fourth room."; } +void AutoStory_Checkpoint_148::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_148(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_149::name() const{ return "149 - S09 - Deal with Lucian and go to the last room."; } +std::string AutoStory_Checkpoint_149::start_text() const{ return "Dealt with Flint and entered the fourth room."; } +std::string AutoStory_Checkpoint_149::end_text() const{ return "Deal with Lucian and enter the last room."; } +void AutoStory_Checkpoint_149::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_149(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_150::name() const{ return "150 - S09 - Deal with Cynthia and finish the game."; } +std::string AutoStory_Checkpoint_150::start_text() const{ return "Dealt with Lucian and entered the last room."; } +std::string AutoStory_Checkpoint_150::end_text() const{ return "Deal with Cynthia and finish the game."; } +void AutoStory_Checkpoint_150::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_150(env, context, options, stats); +} + } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h index b01813e1a4..7b3364437e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h @@ -28,7 +28,7 @@ class AutoStory_Segment_09 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_022 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_137 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,10 +40,261 @@ class AutoStory_Checkpoint_022 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; +void checkpoint_137( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_138 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_138( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_139 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_139( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_140 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_140( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_141 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_141( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + -// start: eighth badge acquired -// end: Elite Four and Champion defeated, credits roll -void checkpoint_022( +class AutoStory_Checkpoint_142 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_142( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +class AutoStory_Checkpoint_143 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_143( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +class AutoStory_Checkpoint_144 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_144( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +class AutoStory_Checkpoint_145 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_145( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +class AutoStory_Checkpoint_146 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_146( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +class AutoStory_Checkpoint_147 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_147( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +class AutoStory_Checkpoint_148 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_148( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_149 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_149( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_150 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_150( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp index a95af419b7..faa27a431f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -5,6 +5,8 @@ */ #include +#include +#include #include #include #include @@ -13,15 +15,18 @@ #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/Images/SolidColorTest.h" #include "CommonTools/OCR/OCR_RawOCR.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "Pokemon/Pokemon_StatsCalculation.h" +#include "Pokemon/Inference/Pokemon_NameReader.h" #include "PokemonSwSh/Resources/PokemonSwSh_TypeMatchup.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h" #include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h" #include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" #include "PokemonBDSP_AutoStory_Battle.h" @@ -348,6 +353,597 @@ void handle_battle_with_outside_help( } +// --------------------------------------------------------------------------- +// Internal helpers for handle_expert_battle +// --------------------------------------------------------------------------- + +// ⚠️ Estimated player-side HP bar region — calibrate against a real capture. +static const ImageFloatBox PLAYER_HP_BAR {0.057, 0.652, 0.168, 0.008}; + +// ⚠️ Estimated player-side status badge region — calibrate against a real capture. +static const ImageFloatBox PLAYER_STATUS_BOX{0.140, 0.710, 0.080, 0.018}; + +// ⚠️ Estimated battle-bag item name region (highlighted row) — calibrate. +static const ImageFloatBox BATTLE_BAG_ITEM_NAME_BOX{0.470, 0.150, 0.350, 0.050}; + +// Returns the fraction [0.0, 1.0] of the player's HP bar that is filled. +// Scans each pixel column; a column with high colour saturation counts as "HP present". +static double read_player_hp_ratio(const ImageViewRGB32& frame){ + ImageViewRGB32 strip = extract_box_reference(frame, PLAYER_HP_BAR); + int total = (int)strip.width(); + int mid_row = (int)(strip.height() / 2); + if (total <= 0) return 1.0; + + int colored_cols = 0; + for (int x = 0; x < total; x++){ + uint32_t px = strip.pixel(x, mid_row); + uint8_t r = (px >> 16) & 0xFF; + uint8_t g = (px >> 8) & 0xFF; + uint8_t b = px & 0xFF; + int mx = std::max({(int)r, (int)g, (int)b}); + int mn = std::min({(int)r, (int)g, (int)b}); + if (mx - mn > 30) colored_cols++; + } + return (double)colored_cols / total; +} + +// Returns true if a coloured status badge is visible on the player's active pokemon. +static bool detect_player_status(const ImageViewRGB32& frame){ + return !is_white(extract_box_reference(frame, PLAYER_STATUS_BOX)); +} + +// Cycle through battle-bag pockets with BUTTON_R until the highlighted item +// matches "full-restore", then use it on the first pokemon. +// Called while the battle-bag is already open (navigate to Bag from battle menu first). +static void use_full_restore_in_open_bag( + VideoStream& stream, + ProControllerContext& context, + Language language +){ + Logger& logger = stream.logger(); + static constexpr size_t MAX_POCKETS = 9; + static constexpr size_t MAX_SCROLL = 25; + + // Cycle pockets until we see Full Restore in the highlighted row + bool pocket_found = false; + for (size_t tab = 0; tab < MAX_POCKETS; tab++){ + context.wait_for(500ms); + VideoSnapshot frame = stream.video().snapshot(); + ImageViewRGB32 region = extract_box_reference(frame, BATTLE_BAG_ITEM_NAME_BOX); + OCR::StringMatchResult result = ShopItemNameReader::instance().read_substring( + logger, language, region + ); + for (const auto& [score, match] : result.results){ + if (match.token == "full-restore" && score <= ShopItemNameReader::MAX_LOG10P){ + pocket_found = true; + break; + } + } + if (pocket_found) break; + pbf_press_button(context, BUTTON_R, 10ms, 400ms); + } + + if (!pocket_found){ + logger.log("Full Restore pocket not found — scanning list", COLOR_YELLOW); + } + + // Scroll down within the pocket until the highlighted row is Full Restore + for (size_t i = 0; i < MAX_SCROLL; i++){ + context.wait_for(400ms); + VideoSnapshot frame = stream.video().snapshot(); + ImageViewRGB32 region = extract_box_reference(frame, BATTLE_BAG_ITEM_NAME_BOX); + OCR::StringMatchResult result = ShopItemNameReader::instance().read_substring( + logger, language, region + ); + bool found = false; + for (const auto& [score, match] : result.results){ + if (match.token == "full-restore" && score <= ShopItemNameReader::MAX_LOG10P){ + found = true; + break; + } + } + if (found){ + pbf_press_button(context, BUTTON_A, 10ms, 600ms); // select item + pbf_press_button(context, BUTTON_A, 10ms, 600ms); // "Use" + pbf_press_button(context, BUTTON_A, 10ms, 800ms); // confirm on first pokemon + context.wait_for(500ms); + logger.log("Full Restore used", COLOR_WHITE); + pbf_mash_button(context, BUTTON_B, 1500ms); + return; + } + pbf_press_dpad(context, DPAD_DOWN, 10ms, 200ms); + } + + logger.log("Full Restore not found in bag — skipping", COLOR_YELLOW); + pbf_mash_button(context, BUTTON_B, 1500ms); +} + +// From the battle menu (Fight/Pokemon/Bag/Run), navigate to Bag and open it. +// In BDSP the battle menu is a 2×2 grid: Fight(top-left) Bag(top-right) +// Pokemon(bottom-left) Run(bottom-right). Cursor starts at Fight. +static void open_battle_bag(ProControllerContext& context){ + pbf_press_dpad(context, DPAD_RIGHT, 10ms, 200ms); + pbf_press_button(context, BUTTON_A, 10ms, 800ms); + context.wait_for(500ms); +} + +// Use a Full Restore on the first party pokemon from the battle menu. +// Returns immediately after item use; the caller is responsible for resuming the turn. +static void use_full_restore_in_battle( + VideoStream& stream, + ProControllerContext& context, + Language language +){ + open_battle_bag(context); + use_full_restore_in_open_bag(stream, context, language); + // Mash B to ensure we're back at the battle menu + context.wait_for(300ms); +} + +// OCR the opponent name box and restrict matches to `candidates`. +// Returns the best matching slug in candidates, or the best unrestricted match +// if no candidate qualifies. +static std::string ocr_opponent_restricted( + Logger& logger, + Language language, + const ImageViewRGB32& frame, + const ImageFloatBox& box, + const std::vector& candidates +){ + using namespace Pokemon; + ImageViewRGB32 cropped = extract_box_reference(frame, box); + OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( + logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS() + ); + if (result.results.empty()) return ""; + + // Prefer the best candidate-restricted match + for (const auto& [score, match] : result.results){ + if (score > PokemonNameReader::MAX_LOG10P) break; + for (const std::string& c : candidates){ + if (match.token == c) return c; + } + } + // Fallback: best unrestricted match (if it meets threshold) + const auto& best = result.results.begin(); + if (best->first <= PokemonNameReader::MAX_LOG10P) return best->second.token; + return ""; +} + + +// --------------------------------------------------------------------------- +// Pre-plan computation +// --------------------------------------------------------------------------- + +struct BattlePlan { + // species slug → move slot to use against that pokemon (for current trainer) + std::map move_for_species; + // Estimated remaining PP after the full trainer sequence is played out + std::array estimated_pp_after{}; +}; + +// Returns the Intimidate attack-stage multiplier for `count` Intimidate activations. +// Stage formula: 2 / (2 + count), capped at 6 drops. +static double intimidate_atk_mult(int count){ + if (count <= 0) return 1.0; + int capped = std::min(count, 6); + return 2.0 / (2.0 + capped); +} + +// Score move `slot` (0-3) against a single opponent pokemon. +// Returns {lower_damage, upper_damage, is_ohko}. +static std::tuple score_move_expert( + const BDSPMoveData& mdata, + const BDSPTrainerPokemon& tp, + int effective_atk, + int effective_spatk, + bool doubles +){ + if (mdata.category == MoveCategory::STATUS) return {0, 0, false}; + + const BDSPPokemonData& pdata = bdsp_pokemon(tp.species); + uint16_t hp = calc_stats_hp(pdata.hp, tp.level, tp.ivs.hp, tp.evs.hp); + + int atk_stat = (mdata.category == MoveCategory::PHYSICAL) ? effective_atk : effective_spatk; + uint16_t def = (mdata.category == MoveCategory::PHYSICAL) + ? calc_stats_nonhp(pdata.defense, tp.level, tp.ivs.defense, tp.evs.defense, NatureAdjustment::NEUTRAL) + : calc_stats_nonhp(pdata.spdef, tp.level, tp.ivs.spdef, tp.evs.spdef, NatureAdjustment::NEUTRAL); + + double mult = type_multiplier(mdata.type, pdata.type[0], pdata.type[1]); + if (mdata.spread && doubles) mult *= 0.75; + + // Attacker level is always 100 (helper Pokemon); defender level is tp.level + auto [lo, hi] = damage_range(100, (int)mdata.base_power, atk_stat, (int)def, mult); + bool ohko = (lo >= (int)hp); + return {lo, hi, ohko}; +} + +static BattlePlan build_expert_plan( + Logger& logger, + const std::array& move_slugs, + const std::array& current_pp, + const std::vector& all_trainers, + Trainer current_trainer, + int base_atk, + int base_spatk +){ + // Running PP budget across the full sequence + std::array pp = current_pp; + + BattlePlan plan; + plan.estimated_pp_after = pp; + + int intimidate_count = 0; + + // Iterate through all listed trainers in sequence order. + // For current_trainer, populate move_for_species. + for (Trainer t : all_trainers){ + const BDSPTrainerData& tdata = bdsp_trainer(trainer_slug(t)); + bool is_current = (t == current_trainer); + bool doubles = (tdata.battle_type == BDSPBattleType::DOUBLES); + + for (const BDSPTrainer& side : tdata.trainers){ + // Check first-out pokemon for Intimidate (activates before we attack) + if (!side.pokemon.empty() && side.pokemon[0].has_intimidate){ + intimidate_count++; + } + } + + int eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); + int eff_spatk = base_spatk; // Intimidate doesn't affect SpAtk + + for (const BDSPTrainer& side : tdata.trainers){ + for (const BDSPTrainerPokemon& tp : side.pokemon){ + // Find best move for this pokemon + int best_slot = -1; + int best_hi = -1; + bool best_ohko = false; + + for (int i = 0; i < 4; i++){ + if (pp[i] <= 0) continue; + const BDSPMoveData& mdata = bdsp_move(move_slugs[i]); + if (mdata.category == MoveCategory::STATUS) continue; + + auto [lo, hi, ohko] = score_move_expert(mdata, tp, eff_atk, eff_spatk, doubles); + (void)lo; + + // Log adjusted accuracy (informational only, not used in OHKO decisions) + uint8_t acc = mdata.accuracy; + if (acc > 0 && acc < 100){ + int adj = 100 - 2 * (100 - acc); + logger.log( + move_slugs[i] + " vs " + tp.species + ": accuracy=" + + std::to_string(acc) + "% (adjusted=" + std::to_string(adj) + "%)", + COLOR_YELLOW + ); + } + + bool prefer = (best_slot < 0) + || (ohko && !best_ohko) // new OHKO > non-OHKO + || (ohko && best_ohko && hi < best_hi) // both OHKO: lower overkill + || (!ohko && !best_ohko && hi > best_hi); // no OHKO: higher damage + + if (prefer){ + best_slot = i; + best_hi = hi; + best_ohko = ohko; + } + } + + if (best_slot < 0){ + // All PP at 0 — Struggle will be used in practice + logger.log("PP exhausted for " + tp.species + " — Struggle expected", COLOR_YELLOW); + } else { + pp[best_slot]--; + logger.log( + std::string(is_current ? "[plan] " : "[pre-plan] ") + + tp.species + " → slot " + std::to_string(best_slot) + + " (" + move_slugs[best_slot] + ")" + + (best_ohko ? " [OHKO]" : " [best effort, hi=" + std::to_string(best_hi) + "]"), + COLOR_WHITE + ); + if (is_current){ + // Only override if this species hasn't been planned yet + // (in case the same species appears twice — first occurrence wins) + if (plan.move_for_species.find(tp.species) == plan.move_for_species.end()){ + plan.move_for_species[tp.species] = best_slot; + } + } + } + + // Track Intimidate for subsequent pokemon (after the initial send-out) + if (tp.has_intimidate){ + intimidate_count++; + eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); + } + } + } + } + + plan.estimated_pp_after = pp; + return plan; +} + + +// --------------------------------------------------------------------------- +// handle_expert_battle +// --------------------------------------------------------------------------- + +// Snapshot the current frame, OCR on-field names, and update `on_field`. +static void update_on_field( + Logger& logger, + Language language, + VideoStream& stream, + BDSPBattleType battle_type, + const std::vector& candidates, + std::vector& on_field +){ + VideoSnapshot frame = stream.video().snapshot(); + if (battle_type == BDSPBattleType::SINGLES){ + static const ImageFloatBox BOX(0.505, 0.010, 0.420, 0.085); + std::string name = ocr_opponent_restricted(logger, language, frame, BOX, candidates); + if (!name.empty() && !on_field.empty()) on_field[0] = name; + } else { + static const ImageFloatBox BOX_L(0.505, 0.010, 0.200, 0.085); + static const ImageFloatBox BOX_R(0.710, 0.010, 0.260, 0.085); + std::string l = ocr_opponent_restricted(logger, language, frame, BOX_L, candidates); + std::string r = ocr_opponent_restricted(logger, language, frame, BOX_R, candidates); + if (!l.empty() && on_field.size() > 0) on_field[0] = l; + if (!r.empty() && on_field.size() > 1) on_field[1] = r; + } +} + + +void handle_expert_battle( + const ProgramInfo& /*info*/, + VideoStream& stream, + ProControllerContext& context, + const std::vector& all_trainers, + Trainer current_trainer, + const AutoStoryOptions& options +){ + using namespace Pokemon; + + Logger& logger = stream.logger(); + const BDSPTrainerData& trainer_data = bdsp_trainer(trainer_slug(current_trainer)); + const OutsideHelpConfig& help = options.outside_help; + bool doubles = (trainer_data.battle_type == BDSPBattleType::DOUBLES); + + // Move slugs for the helper's 4 moves + std::array move_slugs; + for (int i = 0; i < 4; i++) move_slugs[i] = torterra_move_slug(help.attacks[i]); + + // Helper stats — 0IV/0EV at level 100, neutral nature + const BDSPPokemonData& helper_base = bdsp_pokemon("torterra"); + int base_atk = (int)calc_stats_nonhp(helper_base.attack, 100, 0, 0, NatureAdjustment::NEUTRAL); + int base_spatk = (int)calc_stats_nonhp(helper_base.spatk, 100, 0, 0, NatureAdjustment::NEUTRAL); + + // Candidate species slugs for restricted OCR (all pokemon in current trainer's party) + std::vector candidates; + for (const BDSPTrainer& side : trainer_data.trainers){ + for (const BDSPTrainerPokemon& tp : side.pokemon){ + candidates.push_back(tp.species); + } + } + + // PP tracking — initialised to max PP from JSON; OCR updates each turn + std::array pp_remaining; + for (int i = 0; i < 4; i++) pp_remaining[i] = (int)bdsp_move(move_slugs[i]).pp; + + // Intimidate tracking across turns + int intimidate_count = 0; + std::set intimidate_seen; + + // Current on-field opponents (initialised to first pokemon of each side) + std::vector on_field; + for (const BDSPTrainer& side : trainer_data.trainers){ + on_field.push_back(side.pokemon.empty() ? "" : side.pokemon[0].species); + } + + // plan is built on the first turn after PP are OCR'd + BattlePlan plan; + bool plan_built = false; + + // ── Wait for the initial battle menu ───────────────────────────────────── + bool battle_active = true; + { + BattleMenuWatcher menu(BattleType::TRAINER); + EndBattleWatcher end; + int r = wait_until(stream, context, 60s, {{menu}, {end}}); + if (r != 0 || r < 0) battle_active = false; + } + + // ── Main battle loop ────────────────────────────────────────────────────── + // Each iteration starts with the battle menu (Fight/Pokemon/Bag/Run) visible. + // We check HP/status, open the fight menu, OCR PP, identify the opponent, + // select a move, then wait for the next event. + while (battle_active){ + + // 1. Check HP / status → use Full Restore if needed + { + VideoSnapshot frame = stream.video().snapshot(); + double hp = read_player_hp_ratio(frame); + bool has_status = detect_player_status(frame); + if (hp < 0.33 || has_status){ + logger.log( + "Full Restore needed (HP=" + std::to_string((int)(hp * 100)) + + "%, status=" + (has_status ? "yes" : "no") + ")", + COLOR_WHITE + ); + use_full_restore_in_battle(stream, context, options.language); + } + } + + // 2. Open fight menu and re-read PP + pbf_press_button(context, BUTTON_A, 10ms, 60ms); + context.wait_for_all_requests(); + context.wait_for(300ms); + { + auto ocr_pp = read_all_pp(logger, stream); + for (int i = 0; i < 4; i++){ + if (ocr_pp[i] >= 0) pp_remaining[i] = ocr_pp[i]; + else logger.log("PP OCR failed slot " + std::to_string(i), COLOR_YELLOW); + } + } + + // 3. Build plan on first turn (after we have real PP values from OCR) + if (!plan_built){ + plan = build_expert_plan( + logger, move_slugs, pp_remaining, + all_trainers, current_trainer, + base_atk, base_spatk + ); + plan_built = true; + } + + // 4. Identify current on-field opponent(s) via restricted OCR + // (Fight menu is open at bottom; opponent info boxes are at the top — still readable) + update_on_field(logger, options.language, stream, trainer_data.battle_type, candidates, on_field); + + // 5. Track Intimidate from newly-observed opponents + { + int eff_atk = base_atk; + for (const std::string& slug : on_field){ + if (slug.empty() || intimidate_seen.count(slug)) continue; + for (const BDSPTrainer& side : trainer_data.trainers){ + for (const BDSPTrainerPokemon& tp : side.pokemon){ + if (tp.species == slug && tp.has_intimidate){ + intimidate_count++; + intimidate_seen.insert(slug); + eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); + logger.log( + slug + " has Intimidate — ATK stage -" + + std::to_string(intimidate_count) + + " (effective ATK=" + std::to_string(eff_atk) + ")", + COLOR_YELLOW + ); + } + } + } + } + } + + // 6. Select move — prefer pre-plan; fall back to live scoring if plan misses + int best_slot = -1; + if (!on_field.empty() && !on_field[0].empty()){ + auto it = plan.move_for_species.find(on_field[0]); + if (it != plan.move_for_species.end() && pp_remaining[it->second] > 0) + best_slot = it->second; + } + + if (best_slot < 0){ + logger.log("Plan miss for " + (on_field.empty() ? "?" : on_field[0]) + + " — live scoring", COLOR_YELLOW); + int eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); + int eff_spatk = base_spatk; + int best_hi = -1; + bool best_ohko = false; + + for (int i = 0; i < 4; i++){ + if (pp_remaining[i] <= 0) continue; + const BDSPMoveData& mdata = bdsp_move(move_slugs[i]); + if (mdata.category == MoveCategory::STATUS) continue; + + int hi = 0; bool ohko = false; + if (!on_field.empty() && !on_field[0].empty()){ + for (const BDSPTrainer& side : trainer_data.trainers){ + for (const BDSPTrainerPokemon& tp : side.pokemon){ + if (tp.species != on_field[0]) continue; + auto [lo, h, ok] = score_move_expert(mdata, tp, eff_atk, eff_spatk, doubles); + hi = h; ohko = ok; + } + } + } + + bool prefer = (best_slot < 0) + || (ohko && !best_ohko) + || (ohko && best_ohko && hi < best_hi) + || (!ohko && !best_ohko && hi > best_hi); + + if (prefer){ best_slot = i; best_hi = hi; best_ohko = ohko; } + } + // Last resort: first move with any PP remaining + if (best_slot < 0){ + for (int i = 0; i < 4; i++){ + if (pp_remaining[i] > 0){ best_slot = i; break; } + } + } + } + + // 7. Execute selected move (or Struggle if all PP are gone) + if (best_slot < 0){ + logger.log("All PP at 0 — Struggle", COLOR_YELLOW); + pbf_press_button(context, BUTTON_B, 10ms, 60ms); // close fight menu + context.wait_for_all_requests(); + } else { + logger.log( + "Move slot " + std::to_string(best_slot) + + " (" + move_slugs[best_slot] + ") vs " + + (on_field.empty() ? "?" : on_field[0]), + COLOR_WHITE + ); + navigate_to_slot(context, best_slot); + pbf_mash_button(context, BUTTON_A, 500ms); + context.wait_for_all_requests(); + pp_remaining[best_slot]--; + + // Single-target move in doubles: confirm default target + if (!bdsp_move(move_slugs[best_slot]).spread && doubles){ + pbf_press_button(context, BUTTON_A, 10ms, 60ms); + context.wait_for_all_requests(); + } + // Mash through target confirm and partner's move selection + pbf_mash_button(context, BUTTON_A, 3000ms); + context.wait_for_all_requests(); + } + + // 8. Wait for next event: next battle menu, XP gain, or battle end + { + BattleMenuWatcher menu2(BattleType::TRAINER); + ExperienceGainWatcher exp; + EndBattleWatcher end; + + int event = wait_until(stream, context, 60s, {{menu2}, {exp}, {end}}); + + if (event == 2 || event < 0){ + // Battle over + break; + } else if (event == 1){ + // XP gain — mash until the NEXT battle menu appears (or battle ends) + BattleMenuWatcher menu3(BattleType::TRAINER); + EndBattleWatcher end2; + int after = run_until( + stream, context, + [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, + {{menu3}, {end2}} + ); + if (after != 0){ + // Battle ended during XP screens + break; + } + // Update on-field now that the next pokemon is visible + update_on_field(logger, options.language, stream, + trainer_data.battle_type, candidates, on_field); + for (size_t i = 0; i < on_field.size(); i++){ + logger.log("On-field[" + std::to_string(i) + "] = " + on_field[i], COLOR_WHITE); + } + // Continue loop — we're now at the next battle menu + } + // event == 0: next battle menu appeared (mid-turn switch or next turn) + // Loop continues from the top, which is the battle menu state + } + } + + // Mash through any remaining XP/dialog until we're back in the overworld + OverworldWatcher overworld; + run_until( + stream, context, + [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, + {{overworld}} + ); +} + + } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h index fc97a4b63b..94dacd0804 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h @@ -16,6 +16,7 @@ #include #include +#include #include "CommonFramework/Tools/VideoStream.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" @@ -83,6 +84,36 @@ void handle_battle_with_outside_help( ); + + +// --------------------------------------------------------------------------- +// Expert battle handler (sequence-aware, per-turn status/HP check) +// --------------------------------------------------------------------------- + +// Handle one scripted trainer battle with intelligent per-turn move selection. +// +// `all_trainers` — the full upcoming sequence of trainer battles. Used to +// pre-plan PP budget so moves are allocated efficiently across the sequence. +// `current_trainer` — the trainer being fought in this call. +// +// Each turn the function: +// 1. Checks player HP (<33%) and status condition; uses a Full Restore if needed. +// 2. OCRs current PP for all 4 configured moves. +// 3. Identifies the current opponent via restricted-dictionary OCR. +// 4. Tracks Intimidate activations and reduces effective ATK accordingly. +// 5. Selects the pre-planned optimal move and executes it. +// 6. After XP gain, OCRs the new opponent and loops. +// Returns when the battle ends and the player is back in the overworld. +void handle_expert_battle( + const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + const std::vector& all_trainers, + Trainer current_trainer, + const AutoStoryOptions& options +); + + } } } diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index ac86abd8b6..746cb31e14 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1523,6 +1523,12 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/Inference/PokemonBDSP_ReceivePokemonDetector.h Source/PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.cpp Source/PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h + Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.cpp + Source/PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h + Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.cpp + Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h + Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.cpp + Source/PokemonBDSP/Inference/PokemonBDSP_ShopItemPriceReader.h Source/PokemonBDSP/Inference/PokemonBDSP_VSSeekerReaction.cpp Source/PokemonBDSP/Inference/PokemonBDSP_VSSeekerReaction.h Source/PokemonBDSP/Inference/Rng/PokemonBDSP_BlinkExtraction.cpp From 06a3898e8d7c7ed5d1576da5827b6d2acaa4c18f Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Mon, 22 Jun 2026 18:17:10 -0500 Subject: [PATCH 15/39] Update PokemonBDSP_AutoStory_Segment_08.cpp resolved naming issues that prevented compiling the program --- .../StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 8208ef1b82..7161cba421 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -718,15 +718,15 @@ static bool go_through_warehouse( pbf_wait(context, 1000ms); { - BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( + BlackScreenFlashWatcher room_transition(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); + int ret1 = run_until( stream, context, [](ProControllerContext& context){ pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); }, - {{black_screen}} + {{room_transition}} ); - if (ret < 0){ + if (ret1 < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; } From da279b85dec6f6676f8f4c71064c75c4943a9b5b Mon Sep 17 00:00:00 2001 From: GumGumDragoon <51164935+GumGumDragoon@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:40:39 +0100 Subject: [PATCH 16/39] Checkpoint Renaming Renamed checkpoints to better associate them with certain parts of code and makes adding new ones easier. --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 268 +++++++------ .../PokemonBDSP_AutoStory_Segment_00.cpp | 80 ++-- .../PokemonBDSP_AutoStory_Segment_00.h | 10 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 73 +++- .../PokemonBDSP_AutoStory_Segment_01.h | 30 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 85 ++-- .../PokemonBDSP_AutoStory_Segment_02.h | 32 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 111 ++++-- .../PokemonBDSP_AutoStory_Segment_03.h | 46 ++- .../PokemonBDSP_AutoStory_Segment_04.cpp | 238 +++++------ .../PokemonBDSP_AutoStory_Segment_04.h | 68 ++-- .../PokemonBDSP_AutoStory_Segment_05.cpp | 242 +++++------ .../PokemonBDSP_AutoStory_Segment_05.h | 78 ++-- .../PokemonBDSP_AutoStory_Segment_06.cpp | 159 ++++---- .../PokemonBDSP_AutoStory_Segment_06.h | 53 ++- .../PokemonBDSP_AutoStory_Segment_07.cpp | 275 +++++++------ .../PokemonBDSP_AutoStory_Segment_07.h | 85 ++-- .../PokemonBDSP_AutoStory_Segment_08.cpp | 376 +++++++++--------- .../PokemonBDSP_AutoStory_Segment_08.h | 113 +++--- .../PokemonBDSP_AutoStory_Segment_09.cpp | 196 ++++----- .../PokemonBDSP_AutoStory_Segment_09.h | 56 +-- 21 files changed, 1487 insertions(+), 1187 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 1835d2e0b3..c715c42a84 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -55,134 +55,152 @@ static const std::vector>& ALL_SEGMENTS(){ static std::vector> make_checkpoint_list(){ std::vector> checkpoint_list; - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - // Segment 04 (050-066) - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - // Segment 05 (067-082) - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - // Segment 06 (083-092) - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - // Segment 07 (093-110) - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - // Segment 08 (111-) - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - //checkpoint_list.emplace_back(std::make_unique()); - // Segment 09 (137-) - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); + //Segment 00 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + //Segment 01 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + //Segment 02 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + //Segment 03 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 04 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 05 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 06 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 07 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 08 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 09 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); + //checkpoint_list.emplace_back(std::make_unique()); /*checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 8f10e14ae1..ba02b9d09e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -743,17 +743,17 @@ namespace PokemonAutomation { context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_000().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_001().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_002().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_003().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_004().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0001().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0002().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0003().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0004().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } - static void checkpoint_000( + static void checkpoint_0001( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -767,7 +767,7 @@ namespace PokemonAutomation { true ); } - static void checkpoint_001( + static void checkpoint_0002( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -792,7 +792,7 @@ namespace PokemonAutomation { true ); } - static void checkpoint_002( + static void checkpoint_0003( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -806,7 +806,7 @@ namespace PokemonAutomation { true ); } - static void checkpoint_003( + static void checkpoint_0004( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -820,7 +820,7 @@ namespace PokemonAutomation { true ); } - static void checkpoint_004( + static void checkpoint_0005( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -836,86 +836,86 @@ namespace PokemonAutomation { } // ======================== - // Checkpoint 000 Wrapper + // Checkpoint 0001 Wrapper // ======================== - std::string AutoStory_Checkpoint_000::name() const { return "000 - " + AutoStory_Segment_00().name(); } - std::string AutoStory_Checkpoint_000::start_text() const { return "Game launched, Standing in the player's room after tv stops playing"; } - std::string AutoStory_Checkpoint_000::end_text() const { return "Standing outside Lake Verity before picking starter."; } + std::string AutoStory_Checkpoint_0001::name() const { return "00.01 - After TV"; } + std::string AutoStory_Checkpoint_0001::start_text() const { return "Game launched, Standing in the player's room after tv stops playing"; } + std::string AutoStory_Checkpoint_0001::end_text() const { return "Standing outside Lake Verity before picking starter."; } - void AutoStory_Checkpoint_000::run_checkpoint( + void AutoStory_Checkpoint_0001::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_000(env, context, options, stats); + checkpoint_0001(env, context, options, stats); } // ======================== - // Checkpoint 001 Wrapper + // Checkpoint 0002 Wrapper // ======================== - std::string AutoStory_Checkpoint_001::name() const { return "001 - " + AutoStory_Segment_00().name(); } - std::string AutoStory_Checkpoint_001::start_text() const { return AutoStory_Checkpoint_000().end_text(); } - std::string AutoStory_Checkpoint_001::end_text() const { return "Standing outside Lake Verity after picking starter."; } + std::string AutoStory_Checkpoint_0002::name() const { return "00.02 - Outside Verity before starter"; } + std::string AutoStory_Checkpoint_0002::start_text() const { return AutoStory_Checkpoint_0001().end_text(); } + std::string AutoStory_Checkpoint_0002::end_text() const { return "Standing outside Lake Verity after picking starter."; } - void AutoStory_Checkpoint_001::run_checkpoint( + void AutoStory_Checkpoint_0002::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_001(env, context, options, stats); + checkpoint_0002(env, context, options, stats); } // ======================== - // Checkpoint 002 Wrapper + // Checkpoint 0003 Wrapper // ======================== - std::string AutoStory_Checkpoint_002::name() const { return "002 - " + AutoStory_Segment_00().name(); } - std::string AutoStory_Checkpoint_002::start_text() const { return AutoStory_Checkpoint_001().end_text(); } - std::string AutoStory_Checkpoint_002::end_text() const { return "Standing in front of The professor after recieving the Pokedex"; } + std::string AutoStory_Checkpoint_0003::name() const { return "00.03 - Outside Verity after starter"; } + std::string AutoStory_Checkpoint_0003::start_text() const { return AutoStory_Checkpoint_0002().end_text(); } + std::string AutoStory_Checkpoint_0003::end_text() const { return "Standing in front of The professor after recieving the Pokedex"; } - void AutoStory_Checkpoint_002::run_checkpoint( + void AutoStory_Checkpoint_0003::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_002(env, context, options, stats); + checkpoint_0003(env, context, options, stats); } // ======================== - // Checkpoint 003 Wrapper + // Checkpoint 0004 Wrapper // ======================== - std::string AutoStory_Checkpoint_003::name() const { return "003 - " + AutoStory_Segment_00().name(); } - std::string AutoStory_Checkpoint_003::start_text() const { return AutoStory_Checkpoint_002().end_text(); } - std::string AutoStory_Checkpoint_003::end_text() const { return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; } + std::string AutoStory_Checkpoint_0004::name() const { return "00.04 - After Pokedex"; } + std::string AutoStory_Checkpoint_0004::start_text() const { return AutoStory_Checkpoint_0003().end_text(); } + std::string AutoStory_Checkpoint_0004::end_text() const { return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; } - void AutoStory_Checkpoint_003::run_checkpoint( + void AutoStory_Checkpoint_0004::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_003(env, context, options, stats); + checkpoint_0004(env, context, options, stats); } // ======================== - // Checkpoint 004 Wrapper + // Checkpoint 0005 Wrapper // ======================== - std::string AutoStory_Checkpoint_004::name() const { return "004 - " + AutoStory_Segment_00().name(); } - std::string AutoStory_Checkpoint_004::start_text() const { return AutoStory_Checkpoint_003().end_text(); } - std::string AutoStory_Checkpoint_004::end_text() const { return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; } + std::string AutoStory_Checkpoint_0005::name() const { return "00.05 - Jubilife After Dawn"; } + std::string AutoStory_Checkpoint_0005::start_text() const { return AutoStory_Checkpoint_0004().end_text(); } + std::string AutoStory_Checkpoint_0005::end_text() const { return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; } - void AutoStory_Checkpoint_004::run_checkpoint( + void AutoStory_Checkpoint_0005::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_004(env, context, options, stats); + checkpoint_0005(env, context, options, stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h index 01e0ad3bb6..806e3889ba 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h @@ -28,7 +28,7 @@ class AutoStory_Segment_00 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_000 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -41,7 +41,7 @@ class AutoStory_Checkpoint_000 : public AutoStory_Checkpoint{ ) const override; }; -class AutoStory_Checkpoint_001 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -54,7 +54,7 @@ class AutoStory_Checkpoint_001 : public AutoStory_Checkpoint{ ) const override; }; -class AutoStory_Checkpoint_002 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -67,7 +67,7 @@ class AutoStory_Checkpoint_002 : public AutoStory_Checkpoint{ ) const override; }; -class AutoStory_Checkpoint_003 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -80,7 +80,7 @@ class AutoStory_Checkpoint_003 : public AutoStory_Checkpoint{ ) const override; }; -class AutoStory_Checkpoint_004 : public AutoStory_Checkpoint { +class AutoStory_Checkpoint_0005 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 943683a44f..11348c32bd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -217,30 +217,31 @@ namespace PokemonAutomation { context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_checkpoint_005().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_006().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0101().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0102().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0103().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } - std::string AutoStory_checkpoint_005::name() const { return "005 - " + AutoStory_Segment_01().name(); } - std::string AutoStory_checkpoint_005::start_text() const { return "Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_checkpoint_005::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } + std::string AutoStory_checkpoint_0101::name() const { return "01.01 - Maxed Starter from Home"; } + std::string AutoStory_checkpoint_0101::start_text() const { return "Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_checkpoint_0101::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } // ======================== - // Checkpoint 005 Wrapper + // Checkpoint 0101 Wrapper // ======================== - void AutoStory_checkpoint_005::run_checkpoint( + void AutoStory_checkpoint_0101::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_005(env, context, options, stats); + checkpoint_0101(env, context, options, stats); } - void checkpoint_005( + void checkpoint_0101( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -256,24 +257,61 @@ namespace PokemonAutomation { } // ======================== - // Checkpoint 006 Wrapper + // Checkpoint 0102 Wrapper // ======================== - std::string AutoStory_checkpoint_006::name() const { return "006 - " + AutoStory_Segment_01().name(); } - std::string AutoStory_checkpoint_006::start_text() const { return AutoStory_checkpoint_005().end_text(); } - std::string AutoStory_checkpoint_006::end_text() const { return "Standing At the first gym leader after defeating him."; } + std::string AutoStory_checkpoint_0102::name() const { return "01.02 - Oreburgh Gate"; } + std::string AutoStory_checkpoint_0102::start_text() const { return AutoStory_checkpoint_0101().end_text(); } + std::string AutoStory_checkpoint_0102::end_text() const { return "Standing At the first gym leader after defeating him."; } - void AutoStory_checkpoint_006::run_checkpoint( + void AutoStory_checkpoint_0102::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_006(env, context, options, stats); + checkpoint_0102(env, context, options, stats); } - void checkpoint_006( + void checkpoint_0102( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + //oreburgh_gate_to_mine(env, env.console, context); + //oreburgh_mine_nav(env, env.console, context); + //oreburgh_mine_to_gym(env, env.console, context); + //#. Oreburgh Gym navigation and battle + + }, + + true + ); + } + + // ======================== + // Checkpoint 0103 Wrapper + // ======================== + + std::string AutoStory_checkpoint_0103::name() const { return "01.03 - Oreburgh Gym"; } + std::string AutoStory_checkpoint_0103::start_text() const { return AutoStory_checkpoint_0102().end_text(); } + std::string AutoStory_checkpoint_0103::end_text() const { return "Standing At the first gym leader after defeating him."; } + + void AutoStory_checkpoint_0103::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0103(env, context, options, stats); + } + + + void checkpoint_0103( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -281,9 +319,6 @@ namespace PokemonAutomation { ) { checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/) { - oreburgh_gate_to_mine(env, env.console, context); - oreburgh_mine_nav(env, env.console, context); - oreburgh_mine_to_gym(env, env.console, context); //#. Oreburgh Gym navigation and battle gym_Puzzle_01(env, env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h index 52fd409af8..c560945c77 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -28,7 +28,7 @@ class AutoStory_Segment_01 : public AutoStory_Segment{ ) const override; }; -class AutoStory_checkpoint_005 : public AutoStory_Checkpoint{ +class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -43,13 +43,13 @@ class AutoStory_checkpoint_005 : public AutoStory_Checkpoint{ // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_005( +void checkpoint_0101( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_006 : public AutoStory_Checkpoint { +class AutoStory_checkpoint_0102 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -64,7 +64,29 @@ class AutoStory_checkpoint_006 : public AutoStory_Checkpoint { // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_006( +void checkpoint_0102( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_checkpoint_0103 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle +// end: first Gym Badge acquired (Oreburgh City) +void checkpoint_0103( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 3175e4d9be..6f8f8a9c4f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -217,28 +217,29 @@ void AutoStory_Segment_02::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_checkpoint_007().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_008().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_009().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0201().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0202().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0203().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0204().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_checkpoint_007::name() const { return "007 - " + AutoStory_Segment_02().name(); } -std::string AutoStory_checkpoint_007::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_007::end_text() const { return "Standing in front of the scientist in Valley windworks after beating Mars."; } +std::string AutoStory_checkpoint_0201::name() const { return "02.01 - Outside Gym 1"; } +std::string AutoStory_checkpoint_0201::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_0201::end_text() const { return "Standing in front of the scientist in Valley windworks after beating Mars."; } -void AutoStory_checkpoint_007::run_checkpoint( +void AutoStory_checkpoint_0201::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_007(env, context, options, stats); + checkpoint_0201(env, context, options, stats); } -void checkpoint_007( +void checkpoint_0201( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -254,21 +255,21 @@ void checkpoint_007( true ); } -std::string AutoStory_checkpoint_008::name() const { return "008 - " + AutoStory_Segment_02().name(); } -std::string AutoStory_checkpoint_008::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_008::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_checkpoint_0202::name() const { return "02.02 - TBC"; } +std::string AutoStory_checkpoint_0202::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_0202::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } -void AutoStory_checkpoint_008::run_checkpoint( +void AutoStory_checkpoint_0202::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_008(env, context, options, stats); + checkpoint_0202(env, context, options, stats); } -void checkpoint_008( +void checkpoint_0202( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -285,21 +286,21 @@ void checkpoint_008( ); } -std::string AutoStory_checkpoint_009::name() const{ return "009 - " + AutoStory_Segment_02().name(); } -std::string AutoStory_checkpoint_009::start_text() const{ return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_009::end_text() const{ return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_checkpoint_0203::name() const{ return "02.03 - TBC"; } +std::string AutoStory_checkpoint_0203::start_text() const{ return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_0203::end_text() const{ return "Second Gym Badge acquired (Eterna City)."; } -void AutoStory_checkpoint_009::run_checkpoint( +void AutoStory_checkpoint_0203::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_009(env, context, options, stats); + checkpoint_0203(env, context, options, stats); } -void checkpoint_009( +void checkpoint_0203( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -315,21 +316,22 @@ void checkpoint_009( true ); } -std::string AutoStory_checkpoint_010::name() const { return "010 - " + AutoStory_Segment_02().name(); } -std::string AutoStory_checkpoint_010::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_010::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } -void AutoStory_checkpoint_010::run_checkpoint( +std::string AutoStory_checkpoint_0204::name() const { return "02.04 - TBC"; } +std::string AutoStory_checkpoint_0204::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_0204::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } + +void AutoStory_checkpoint_0204::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - checkpoint_010(env, context, options, stats); + checkpoint_0204(env, context, options, stats); } -void checkpoint_010( +void checkpoint_0204( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -339,6 +341,35 @@ void checkpoint_010( [&](size_t /*attempt*/) { // TODO: implement Segment 02 gameplay logic eterna_forest_exit_to_gym(env.console, context); + + }, + true + ); +} + +std::string AutoStory_checkpoint_0205::name() const { return "02.05 - Inside Gym"; } +std::string AutoStory_checkpoint_0205::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } +std::string AutoStory_checkpoint_0205::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } + +void AutoStory_checkpoint_0205::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + checkpoint_0205(env, context, options, stats); +} + + +void checkpoint_0205( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { + //#. Eterna Gym navigation and battle gym_Puzzle_02(env.console, context); }, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h index 4210a110cc..a76d2774e8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h @@ -28,7 +28,7 @@ class AutoStory_Segment_02 : public AutoStory_Segment{ ) const override; }; -class AutoStory_checkpoint_007 : public AutoStory_Checkpoint{ +class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -43,22 +43,35 @@ class AutoStory_checkpoint_007 : public AutoStory_Checkpoint{ // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_007( +void checkpoint_0201( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); +class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_008( +void checkpoint_0202( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_008 : public AutoStory_Checkpoint { +class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -73,13 +86,14 @@ class AutoStory_checkpoint_008 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_008( +void checkpoint_0203( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_009 : public AutoStory_Checkpoint { + +class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -94,14 +108,14 @@ class AutoStory_checkpoint_009 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_009( +void checkpoint_0204( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_010 : public AutoStory_Checkpoint { +class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -116,7 +130,7 @@ class AutoStory_checkpoint_010 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_010( +void checkpoint_0205( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 314d454786..155769e01c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -277,31 +277,32 @@ void AutoStory_Segment_03::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_011().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_012().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_013().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_014().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_015().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0301().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0302().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0303().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0304().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0305().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0306().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_011::name() const{ return "011 - " + AutoStory_Segment_03().name(); } -std::string AutoStory_Checkpoint_011::start_text() const{ return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_011::end_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_0301::name() const{ return "03.01 - TBC"; } +std::string AutoStory_Checkpoint_0301::start_text() const{ return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0301::end_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_011::run_checkpoint( +void AutoStory_Checkpoint_0301::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - Checkpoint_011(env, context, options, stats); + Checkpoint_0301(env, context, options, stats); } -void Checkpoint_011( +void Checkpoint_0301( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -317,21 +318,21 @@ void Checkpoint_011( true ); } -std::string AutoStory_Checkpoint_012::name() const { return "012 - " + AutoStory_Segment_03().name(); } -std::string AutoStory_Checkpoint_012::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_012::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_0302::name() const { return "03.02 - TBC"; } +std::string AutoStory_Checkpoint_0302::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0302::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_012::run_checkpoint( +void AutoStory_Checkpoint_0302::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - Checkpoint_012(env, context, options, stats); + Checkpoint_0302(env, context, options, stats); } -void Checkpoint_012( +void Checkpoint_0302( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -347,21 +348,21 @@ void Checkpoint_012( true ); } -std::string AutoStory_Checkpoint_013::name() const { return "013 - " + AutoStory_Segment_03().name(); } -std::string AutoStory_Checkpoint_013::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_013::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_0303::name() const { return "03.03 - TBC"; } +std::string AutoStory_Checkpoint_0303::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0303::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_013::run_checkpoint( +void AutoStory_Checkpoint_0303::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - Checkpoint_013(env, context, options, stats); + Checkpoint_0303(env, context, options, stats); } -void Checkpoint_013( +void Checkpoint_0303( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -377,21 +378,21 @@ void Checkpoint_013( true ); } -std::string AutoStory_Checkpoint_014::name() const { return "014 - " + AutoStory_Segment_03().name(); } -std::string AutoStory_Checkpoint_014::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_014::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_0304::name() const { return "03.04 - TBC"; } +std::string AutoStory_Checkpoint_0304::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0304::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_014::run_checkpoint( +void AutoStory_Checkpoint_0304::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - Checkpoint_014(env, context, options, stats); + Checkpoint_0304(env, context, options, stats); } -void Checkpoint_014( +void Checkpoint_0304( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -407,21 +408,21 @@ void Checkpoint_014( true ); } -std::string AutoStory_Checkpoint_015::name() const { return "015 - " + AutoStory_Segment_03().name(); } -std::string AutoStory_Checkpoint_015::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_015::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - TBC"; } +std::string AutoStory_Checkpoint_0305::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0305::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_015::run_checkpoint( +void AutoStory_Checkpoint_0305::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - Checkpoint_015(env, context, options, stats); + Checkpoint_0305(env, context, options, stats); } -void Checkpoint_015( +void Checkpoint_0305( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -438,21 +439,22 @@ void Checkpoint_015( true ); } -std::string AutoStory_Checkpoint_016::name() const { return "016 - " + AutoStory_Segment_03().name(); } -std::string AutoStory_Checkpoint_016::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_016::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_016::run_checkpoint( +std::string AutoStory_Checkpoint_0306::name() const { return "03.06 - TBC"; } +std::string AutoStory_Checkpoint_0306::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0306::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_0306::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const { - Checkpoint_016(env, context, options, stats); + Checkpoint_0306(env, context, options, stats); } -void Checkpoint_016( +void Checkpoint_0306( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -462,6 +464,33 @@ void Checkpoint_016( [&](size_t /*attempt*/) { // TODO: implement Segment 03 gameplay logic lost_tower_to_veilstone_gym(env.console, context); + }, + true + ); +} + +std::string AutoStory_Checkpoint_0307::name() const { return "03.07 - Inside Gym 3"; } +std::string AutoStory_Checkpoint_0307::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Checkpoint_0307::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } + +void AutoStory_Checkpoint_0307::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0307(env, context, options, stats); +} + + +void Checkpoint_0307( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/) { //#. Veilstone Gym navigation and battle gym_Puzzle_03(env.console, context); }, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h index af86147546..1c51870efb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h @@ -28,7 +28,7 @@ class AutoStory_Segment_03 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_011 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0301 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -43,13 +43,13 @@ class AutoStory_Checkpoint_011 : public AutoStory_Checkpoint{ // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_011( +void Checkpoint_0301( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_012 : public AutoStory_Checkpoint { +class AutoStory_Checkpoint_0302 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -64,14 +64,14 @@ class AutoStory_Checkpoint_012 : public AutoStory_Checkpoint { // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_012( +void Checkpoint_0302( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_013 : public AutoStory_Checkpoint { +class AutoStory_Checkpoint_0303 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -86,14 +86,14 @@ class AutoStory_Checkpoint_013 : public AutoStory_Checkpoint { // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_013( +void Checkpoint_0303( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_014 : public AutoStory_Checkpoint { +class AutoStory_Checkpoint_0304 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -108,14 +108,14 @@ class AutoStory_Checkpoint_014 : public AutoStory_Checkpoint { // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_014( +void Checkpoint_0304( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_015 : public AutoStory_Checkpoint { +class AutoStory_Checkpoint_0305 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -130,14 +130,14 @@ class AutoStory_Checkpoint_015 : public AutoStory_Checkpoint { // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_015( +void Checkpoint_0305( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_016 : public AutoStory_Checkpoint { +class AutoStory_Checkpoint_0306 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -152,7 +152,29 @@ class AutoStory_Checkpoint_016 : public AutoStory_Checkpoint { // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_016( +void Checkpoint_0306( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_Checkpoint_0307 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: second badge acquired +// end: third Gym Badge acquired (Hearthome City) +void Checkpoint_0307( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index e9af487658..057b494a7f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -42,146 +42,146 @@ void AutoStory_Segment_04::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_050().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_051().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_052().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_053().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_054().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_055().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_056().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_057().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_058().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_059().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_060().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_061().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_062().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_063().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_064().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_065().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_066().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0401().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0402().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0403().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0404().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0405().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0406().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0407().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0408().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0409().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0410().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0411().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0412().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0413().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0414().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0415().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0416().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0417().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_050::name() const{ return "004.01 - leave_gym"; } -std::string AutoStory_Checkpoint_050::start_text() const{ return "Inside Hearthome City gym after third badge."; } -std::string AutoStory_Checkpoint_050::end_text() const{ return "Exited Hearthome City gym."; } -void AutoStory_Checkpoint_050::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_050(env, context, options, stats); +std::string AutoStory_Checkpoint_0401::name() const{ return "04.01 - leave_gym"; } +std::string AutoStory_Checkpoint_0401::start_text() const{ return "Inside Hearthome City gym after third badge."; } +std::string AutoStory_Checkpoint_0401::end_text() const{ return "Exited Hearthome City gym."; } +void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0401(env, context, options, stats); } -std::string AutoStory_Checkpoint_051::name() const{ return "004.02 - go_to_veilstone_pokemon_center"; } -std::string AutoStory_Checkpoint_051::start_text() const{ return "Outside Hearthome City gym."; } -std::string AutoStory_Checkpoint_051::end_text() const{ return "Entered Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_051::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_051(env, context, options, stats); +std::string AutoStory_Checkpoint_0402::name() const{ return "04.02 - go_to_veilstone_pokemon_center"; } +std::string AutoStory_Checkpoint_0402::start_text() const{ return "Outside Hearthome City gym."; } +std::string AutoStory_Checkpoint_0402::end_text() const{ return "Entered Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0402(env, context, options, stats); } -std::string AutoStory_Checkpoint_052::name() const{ return "004.03 - heal_at_pokemon_center (Veilstone)"; } -std::string AutoStory_Checkpoint_052::start_text() const{ return "Inside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_052::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_052::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_052(env, context, options, stats); +std::string AutoStory_Checkpoint_0403::name() const{ return "04.03 - heal_at_pokemon_center (Veilstone)"; } +std::string AutoStory_Checkpoint_0403::start_text() const{ return "Inside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_0403::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0403(env, context, options, stats); } -std::string AutoStory_Checkpoint_053::name() const{ return "004.04 - go_to_galactic_warehouse"; } -std::string AutoStory_Checkpoint_053::start_text() const{ return "Outside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_053::end_text() const{ return "Reached Galactic Warehouse, battle started."; } -void AutoStory_Checkpoint_053::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_053(env, context, options, stats); +std::string AutoStory_Checkpoint_0404::name() const{ return "04.04 - go_to_galactic_warehouse"; } +std::string AutoStory_Checkpoint_0404::start_text() const{ return "Outside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_0404::end_text() const{ return "Reached Galactic Warehouse, battle started."; } +void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0404(env, context, options, stats); } -std::string AutoStory_Checkpoint_054::name() const{ return "004.05 - handle_battle (veilstone_galactic_grunts_1)"; } -std::string AutoStory_Checkpoint_054::start_text() const{ return "In battle against Galactic Grunt at Veilstone."; } -std::string AutoStory_Checkpoint_054::end_text() const{ return "Defeated Galactic Grunt at Veilstone."; } -void AutoStory_Checkpoint_054::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_054(env, context, options, stats); +std::string AutoStory_Checkpoint_0405::name() const{ return "04.05 - handle_battle (veilstone_galactic_grunts_1)"; } +std::string AutoStory_Checkpoint_0405::start_text() const{ return "In battle against Galactic Grunt at Veilstone."; } +std::string AutoStory_Checkpoint_0405::end_text() const{ return "Defeated Galactic Grunt at Veilstone."; } +void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0405(env, context, options, stats); } -std::string AutoStory_Checkpoint_055::name() const{ return "004.06 - enter_galactic_warehouse"; } -std::string AutoStory_Checkpoint_055::start_text() const{ return "Outside Galactic Warehouse."; } -std::string AutoStory_Checkpoint_055::end_text() const{ return "Entered Galactic Warehouse."; } -void AutoStory_Checkpoint_055::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_055(env, context, options, stats); +std::string AutoStory_Checkpoint_0406::name() const{ return "04.06 - enter_galactic_warehouse"; } +std::string AutoStory_Checkpoint_0406::start_text() const{ return "Outside Galactic Warehouse."; } +std::string AutoStory_Checkpoint_0406::end_text() const{ return "Entered Galactic Warehouse."; } +void AutoStory_Checkpoint_0406::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0406(env, context, options, stats); } -std::string AutoStory_Checkpoint_056::name() const{ return "004.07 - exit_galactic_warehouse"; } -std::string AutoStory_Checkpoint_056::start_text() const{ return "Inside Galactic Warehouse."; } -std::string AutoStory_Checkpoint_056::end_text() const{ return "Exited Galactic Warehouse with TM Fly."; } -void AutoStory_Checkpoint_056::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_056(env, context, options, stats); +std::string AutoStory_Checkpoint_0407::name() const{ return "04.07 - exit_galactic_warehouse"; } +std::string AutoStory_Checkpoint_0407::start_text() const{ return "Inside Galactic Warehouse."; } +std::string AutoStory_Checkpoint_0407::end_text() const{ return "Exited Galactic Warehouse with TM Fly."; } +void AutoStory_Checkpoint_0407::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0407(env, context, options, stats); } -std::string AutoStory_Checkpoint_057::name() const{ return "004.08 - go_to_route_214"; } -std::string AutoStory_Checkpoint_057::start_text() const{ return "Outside Galactic Warehouse."; } -std::string AutoStory_Checkpoint_057::end_text() const{ return "Arrived at Route 214, battle started."; } -void AutoStory_Checkpoint_057::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_057(env, context, options, stats); +std::string AutoStory_Checkpoint_0408::name() const{ return "04.08 - go_to_route_214"; } +std::string AutoStory_Checkpoint_0408::start_text() const{ return "Outside Galactic Warehouse."; } +std::string AutoStory_Checkpoint_0408::end_text() const{ return "Arrived at Route 214, battle started."; } +void AutoStory_Checkpoint_0408::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0408(env, context, options, stats); } -std::string AutoStory_Checkpoint_058::name() const{ return "004.09 - handle_battle (route_214_psychic_abigail)"; } -std::string AutoStory_Checkpoint_058::start_text() const{ return "In battle against Psychic Abigail on Route 214."; } -std::string AutoStory_Checkpoint_058::end_text() const{ return "Defeated Psychic Abigail on Route 214."; } -void AutoStory_Checkpoint_058::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_058(env, context, options, stats); +std::string AutoStory_Checkpoint_0409::name() const{ return "04.09 - handle_battle (route_214_psychic_abigail)"; } +std::string AutoStory_Checkpoint_0409::start_text() const{ return "In battle against Psychic Abigail on Route 214."; } +std::string AutoStory_Checkpoint_0409::end_text() const{ return "Defeated Psychic Abigail on Route 214."; } +void AutoStory_Checkpoint_0409::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0409(env, context, options, stats); } -std::string AutoStory_Checkpoint_059::name() const{ return "004.10 - walk_through_route_214"; } -std::string AutoStory_Checkpoint_059::start_text() const{ return "On Route 214 after Psychic Abigail."; } -std::string AutoStory_Checkpoint_059::end_text() const{ return "Walked through Route 214."; } -void AutoStory_Checkpoint_059::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_059(env, context, options, stats); +std::string AutoStory_Checkpoint_0410::name() const{ return "04.10 - walk_through_route_214"; } +std::string AutoStory_Checkpoint_0410::start_text() const{ return "On Route 214 after Psychic Abigail."; } +std::string AutoStory_Checkpoint_0410::end_text() const{ return "Walked through Route 214."; } +void AutoStory_Checkpoint_0410::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0410(env, context, options, stats); } -std::string AutoStory_Checkpoint_060::name() const{ return "004.11 - pass_by_valor_lakefront"; } -std::string AutoStory_Checkpoint_060::start_text() const{ return "End of Route 214."; } -std::string AutoStory_Checkpoint_060::end_text() const{ return "Passed Valor Lakefront, entered building."; } -void AutoStory_Checkpoint_060::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_060(env, context, options, stats); +std::string AutoStory_Checkpoint_0411::name() const{ return "04.11 - pass_by_valor_lakefront"; } +std::string AutoStory_Checkpoint_0411::start_text() const{ return "End of Route 214."; } +std::string AutoStory_Checkpoint_0411::end_text() const{ return "Passed Valor Lakefront, entered building."; } +void AutoStory_Checkpoint_0411::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0411(env, context, options, stats); } -std::string AutoStory_Checkpoint_061::name() const{ return "004.12 - go_through_building_route_213"; } -std::string AutoStory_Checkpoint_061::start_text() const{ return "Inside building between Route 214 and Route 213."; } -std::string AutoStory_Checkpoint_061::end_text() const{ return "Exited building onto Route 213."; } -void AutoStory_Checkpoint_061::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_061(env, context, options, stats); +std::string AutoStory_Checkpoint_0412::name() const{ return "04.12 - go_through_building_route_213"; } +std::string AutoStory_Checkpoint_0412::start_text() const{ return "Inside building between Route 214 and Route 213."; } +std::string AutoStory_Checkpoint_0412::end_text() const{ return "Exited building onto Route 213."; } +void AutoStory_Checkpoint_0412::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0412(env, context, options, stats); } -std::string AutoStory_Checkpoint_062::name() const{ return "004.13 - go_through_route_213"; } -std::string AutoStory_Checkpoint_062::start_text() const{ return "On Route 213."; } -std::string AutoStory_Checkpoint_062::end_text() const{ return "Walked through Route 213, entered east Pastoria building."; } -void AutoStory_Checkpoint_062::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_062(env, context, options, stats); +std::string AutoStory_Checkpoint_0413::name() const{ return "04.13 - go_through_route_213"; } +std::string AutoStory_Checkpoint_0413::start_text() const{ return "On Route 213."; } +std::string AutoStory_Checkpoint_0413::end_text() const{ return "Walked through Route 213, entered east Pastoria building."; } +void AutoStory_Checkpoint_0413::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0413(env, context, options, stats); } -std::string AutoStory_Checkpoint_063::name() const{ return "004.14 - go_through_house_east_pastoria"; } -std::string AutoStory_Checkpoint_063::start_text() const{ return "Inside east Pastoria building."; } -std::string AutoStory_Checkpoint_063::end_text() const{ return "Exited east Pastoria building into Pastoria City."; } -void AutoStory_Checkpoint_063::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_063(env, context, options, stats); +std::string AutoStory_Checkpoint_0414::name() const{ return "04.14 - go_through_house_east_pastoria"; } +std::string AutoStory_Checkpoint_0414::start_text() const{ return "Inside east Pastoria building."; } +std::string AutoStory_Checkpoint_0414::end_text() const{ return "Exited east Pastoria building into Pastoria City."; } +void AutoStory_Checkpoint_0414::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0414(env, context, options, stats); } -std::string AutoStory_Checkpoint_064::name() const{ return "004.15 - go_through_pastoria"; } -std::string AutoStory_Checkpoint_064::start_text() const{ return "In Pastoria City."; } -std::string AutoStory_Checkpoint_064::end_text() const{ return "Entered Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_064::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_064(env, context, options, stats); +std::string AutoStory_Checkpoint_0415::name() const{ return "04.15 - go_through_pastoria"; } +std::string AutoStory_Checkpoint_0415::start_text() const{ return "In Pastoria City."; } +std::string AutoStory_Checkpoint_0415::end_text() const{ return "Entered Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_0415::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0415(env, context, options, stats); } -std::string AutoStory_Checkpoint_065::name() const{ return "004.16 - heal_at_pokemon_center (Pastoria)"; } -std::string AutoStory_Checkpoint_065::start_text() const{ return "Inside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_065::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_065::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_065(env, context, options, stats); +std::string AutoStory_Checkpoint_0416::name() const{ return "04.16 - heal_at_pokemon_center (Pastoria)"; } +std::string AutoStory_Checkpoint_0416::start_text() const{ return "Inside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_0416::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0416(env, context, options, stats); } -std::string AutoStory_Checkpoint_066::name() const{ return "004.17 - go_to_pastoria_gym"; } -std::string AutoStory_Checkpoint_066::start_text() const{ return "Outside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_066::end_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } -void AutoStory_Checkpoint_066::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_066(env, context, options, stats); +std::string AutoStory_Checkpoint_0417::name() const{ return "04.17 - go_to_pastoria_gym"; } +std::string AutoStory_Checkpoint_0417::start_text() const{ return "Outside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_0417::end_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0417(env, context, options, stats); } static void move_back_after_unexpected_battle( @@ -1005,7 +1005,7 @@ static bool go_to_pastoria_gym( return true; } -void checkpoint_050(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ @@ -1016,7 +1016,7 @@ void checkpoint_050(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_051(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_veilstone_pokemon_center(env.console, context)){ @@ -1027,7 +1027,7 @@ void checkpoint_051(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_052(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ @@ -1038,7 +1038,7 @@ void checkpoint_052(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_053(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_warehouse(env.console, context)){ @@ -1049,7 +1049,7 @@ void checkpoint_053(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_054(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ @@ -1060,7 +1060,7 @@ void checkpoint_054(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_055(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!enter_galactic_warehouse(env.console, context)){ @@ -1071,7 +1071,7 @@ void checkpoint_055(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_056(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!exit_galactic_warehouse(env.console, context)){ @@ -1082,7 +1082,7 @@ void checkpoint_056(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_057(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_route_214(env.console, context)){ @@ -1093,7 +1093,7 @@ void checkpoint_057(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_058(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ @@ -1104,7 +1104,7 @@ void checkpoint_058(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_059(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ @@ -1115,7 +1115,7 @@ void checkpoint_059(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_060(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!pass_by_valor_lakefront(env.console, context)){ @@ -1126,7 +1126,7 @@ void checkpoint_060(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_061(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ @@ -1137,7 +1137,7 @@ void checkpoint_061(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_062(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_route_213(env.console, context)){ @@ -1148,7 +1148,7 @@ void checkpoint_062(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_063(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ @@ -1159,7 +1159,7 @@ void checkpoint_063(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_064(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_pastoria(env.console, context)){ @@ -1170,7 +1170,7 @@ void checkpoint_064(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_065(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ @@ -1181,7 +1181,7 @@ void checkpoint_065(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_066(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_gym(env.console, context)){ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h index 78ed57c732..abbd1503a5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -28,7 +28,7 @@ class AutoStory_Segment_04 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_050 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0401 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,9 +40,9 @@ class AutoStory_Checkpoint_050 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_050(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_051 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0402 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -54,9 +54,9 @@ class AutoStory_Checkpoint_051 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_051(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_052 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0403 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -68,9 +68,9 @@ class AutoStory_Checkpoint_052 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_052(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_053 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0404 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -82,9 +82,9 @@ class AutoStory_Checkpoint_053 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_053(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_054 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0405 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -96,9 +96,9 @@ class AutoStory_Checkpoint_054 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_054(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_055 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0406 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -110,9 +110,9 @@ class AutoStory_Checkpoint_055 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_055(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_056 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0407 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -124,9 +124,9 @@ class AutoStory_Checkpoint_056 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_056(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_057 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0408 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -138,9 +138,9 @@ class AutoStory_Checkpoint_057 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_057(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_058 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0409 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -152,9 +152,9 @@ class AutoStory_Checkpoint_058 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_058(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_059 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0410 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -166,9 +166,9 @@ class AutoStory_Checkpoint_059 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_059(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_060 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0411 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -180,9 +180,9 @@ class AutoStory_Checkpoint_060 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_060(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_061 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0412 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -194,9 +194,9 @@ class AutoStory_Checkpoint_061 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_061(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_062 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0413 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -208,9 +208,9 @@ class AutoStory_Checkpoint_062 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_062(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_063 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0414 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -222,9 +222,9 @@ class AutoStory_Checkpoint_063 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_063(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_064 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0415 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -236,9 +236,9 @@ class AutoStory_Checkpoint_064 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_064(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_065 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0416 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -250,9 +250,9 @@ class AutoStory_Checkpoint_065 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_065(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_066 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0417 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -264,7 +264,7 @@ class AutoStory_Checkpoint_066 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_066(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index 46d9d54cf1..8a553bd3a1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -39,138 +39,146 @@ void AutoStory_Segment_05::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_067().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_068().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_069().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_070().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_071().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_072().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_073().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_074().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_075().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_076().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_077().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_078().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_079().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_080().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_081().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_082().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0501().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0502().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0503().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0504().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0505().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0506().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0507().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0508().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0509().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0510().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0511().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0512().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0513().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0514().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0515().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0516().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0517().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_067::name() const{ return "005.01 - go_to_pastoria_pokemon_center"; } -std::string AutoStory_Checkpoint_067::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } -std::string AutoStory_Checkpoint_067::end_text() const{ return "Entered Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_067::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_067(env, context, options, stats); +std::string AutoStory_Checkpoint_0501::name() const{ return "05.01 - go_to_pastoria_pokemon_center"; } +std::string AutoStory_Checkpoint_0501::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_Checkpoint_0501::end_text() const{ return "Entered Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0501(env, context, options, stats); } -std::string AutoStory_Checkpoint_068::name() const{ return "005.02 - exit_pastoria_pokemon_center"; } -std::string AutoStory_Checkpoint_068::start_text() const{ return "Inside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_068::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_068::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_068(env, context, options, stats); +std::string AutoStory_Checkpoint_0502::name() const{ return "05.02 - exit_pastoria_pokemon_center"; } +std::string AutoStory_Checkpoint_0502::start_text() const{ return "Inside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_0502::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } +void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0502(env, context, options, stats); } -std::string AutoStory_Checkpoint_069::name() const{ return "005.03 - pastoria_city_drama"; } -std::string AutoStory_Checkpoint_069::start_text() const{ return "Outside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_069::end_text() const{ return "Pastoria City drama complete, exited east building."; } -void AutoStory_Checkpoint_069::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_069(env, context, options, stats); +std::string AutoStory_Checkpoint_0503::name() const{ return "05.03 - pastoria_city_drama"; } +std::string AutoStory_Checkpoint_0503::start_text() const{ return "Outside Pastoria City Pokemon Center."; } +std::string AutoStory_Checkpoint_0503::end_text() const{ return "Pastoria City drama complete, exited east building."; } +void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0503(env, context, options, stats); } -std::string AutoStory_Checkpoint_070::name() const{ return "005.04 - go_through_house_east_pastoria"; } -std::string AutoStory_Checkpoint_070::start_text() const{ return "Inside east Pastoria building."; } -std::string AutoStory_Checkpoint_070::end_text() const{ return "Exited east Pastoria building onto Route 213."; } -void AutoStory_Checkpoint_070::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_070(env, context, options, stats); +std::string AutoStory_Checkpoint_0504::name() const{ return "05.04 - go_through_house_east_pastoria"; } +std::string AutoStory_Checkpoint_0504::start_text() const{ return "Inside east Pastoria building."; } +std::string AutoStory_Checkpoint_0504::end_text() const{ return "Exited east Pastoria building onto Route 213."; } +void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0504(env, context, options, stats); } -std::string AutoStory_Checkpoint_071::name() const{ return "005.05 - go_through_route_213_chasing"; } -std::string AutoStory_Checkpoint_071::start_text() const{ return "On Route 213 chasing Galactic Grunts."; } -std::string AutoStory_Checkpoint_071::end_text() const{ return "Entered east Route 213 building."; } -void AutoStory_Checkpoint_071::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_071(env, context, options, stats); +std::string AutoStory_Checkpoint_0505::name() const{ return "05.05 - go_through_route_213_chasing"; } +std::string AutoStory_Checkpoint_0505::start_text() const{ return "On Route 213 chasing Galactic Grunts."; } +std::string AutoStory_Checkpoint_0505::end_text() const{ return "Entered east Route 213 building."; } +void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0505(env, context, options, stats); } -std::string AutoStory_Checkpoint_072::name() const{ return "005.06 - go_through_building_route_213"; } -std::string AutoStory_Checkpoint_072::start_text() const{ return "Inside building on Route 213."; } -std::string AutoStory_Checkpoint_072::end_text() const{ return "Exited building onto Route 214."; } -void AutoStory_Checkpoint_072::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_072(env, context, options, stats); +std::string AutoStory_Checkpoint_0506::name() const{ return "05.06 - go_through_building_route_213"; } +std::string AutoStory_Checkpoint_0506::start_text() const{ return "Inside building on Route 213."; } +std::string AutoStory_Checkpoint_0506::end_text() const{ return "Exited building onto Route 214."; } +void AutoStory_Checkpoint_0506::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0506(env, context, options, stats); } -std::string AutoStory_Checkpoint_073::name() const{ return "005.07 - go_through_route_214"; } -std::string AutoStory_Checkpoint_073::start_text() const{ return "On Route 214 chasing Galactic Grunts."; } -std::string AutoStory_Checkpoint_073::end_text() const{ return "Flew to Solaceon Town."; } -void AutoStory_Checkpoint_073::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_073(env, context, options, stats); +std::string AutoStory_Checkpoint_0507::name() const{ return "05.07 - go_through_route_214"; } +std::string AutoStory_Checkpoint_0507::start_text() const{ return "On Route 214 chasing Galactic Grunts."; } +std::string AutoStory_Checkpoint_0507::end_text() const{ return "Flew to Solaceon Town."; } +void AutoStory_Checkpoint_0507::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0507(env, context, options, stats); } -std::string AutoStory_Checkpoint_074::name() const{ return "005.08 - go_inside_solaceon_pokemon_center"; } -std::string AutoStory_Checkpoint_074::start_text() const{ return "In Solaceon Town."; } -std::string AutoStory_Checkpoint_074::end_text() const{ return "Entered Solaceon Town Pokemon Center."; } -void AutoStory_Checkpoint_074::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_074(env, context, options, stats); +std::string AutoStory_Checkpoint_0508::name() const{ return "05.08 - go_inside_solaceon_pokemon_center"; } +std::string AutoStory_Checkpoint_0508::start_text() const{ return "In Solaceon Town."; } +std::string AutoStory_Checkpoint_0508::end_text() const{ return "Entered Solaceon Town Pokemon Center."; } +void AutoStory_Checkpoint_0508::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0508(env, context, options, stats); } -std::string AutoStory_Checkpoint_075::name() const{ return "005.09 - heal_at_solaceon_pokemon_center"; } -std::string AutoStory_Checkpoint_075::start_text() const{ return "Inside Solaceon Town Pokemon Center."; } -std::string AutoStory_Checkpoint_075::end_text() const{ return "Healed and exited Solaceon Town Pokemon Center."; } -void AutoStory_Checkpoint_075::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_075(env, context, options, stats); +std::string AutoStory_Checkpoint_0509::name() const{ return "05.09 - heal_at_solaceon_pokemon_center"; } +std::string AutoStory_Checkpoint_0509::start_text() const{ return "Inside Solaceon Town Pokemon Center."; } +std::string AutoStory_Checkpoint_0509::end_text() const{ return "Healed and exited Solaceon Town Pokemon Center."; } +void AutoStory_Checkpoint_0509::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0509(env, context, options, stats); } -std::string AutoStory_Checkpoint_076::name() const{ return "005.10 - go_to_the_psyduck_route_210"; } -std::string AutoStory_Checkpoint_076::start_text() const{ return "Outside Solaceon Town Pokemon Center."; } -std::string AutoStory_Checkpoint_076::end_text() const{ return "Reached Celestic Town Pokemon Center via Route 210."; } -void AutoStory_Checkpoint_076::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_076(env, context, options, stats); +std::string AutoStory_Checkpoint_0510::name() const{ return "05.10 - go_to_the_psyduck_route_210"; } +std::string AutoStory_Checkpoint_0510::start_text() const{ return "Outside Solaceon Town Pokemon Center."; } +std::string AutoStory_Checkpoint_0510::end_text() const{ return "Reached Celestic Town Pokemon Center via Route 210."; } +void AutoStory_Checkpoint_0510::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0510(env, context, options, stats); } -std::string AutoStory_Checkpoint_077::name() const{ return "005.11 - heal_at_celestic_pokemon_center"; } -std::string AutoStory_Checkpoint_077::start_text() const{ return "Inside Celestic Town Pokemon Center."; } -std::string AutoStory_Checkpoint_077::end_text() const{ return "Healed and exited Celestic Town Pokemon Center."; } -void AutoStory_Checkpoint_077::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_077(env, context, options, stats); +std::string AutoStory_Checkpoint_0511::name() const{ return "05.11 - heal_at_celestic_pokemon_center"; } +std::string AutoStory_Checkpoint_0511::start_text() const{ return "Inside Celestic Town Pokemon Center."; } +std::string AutoStory_Checkpoint_0511::end_text() const{ return "Healed and exited Celestic Town Pokemon Center."; } +void AutoStory_Checkpoint_0511::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0511(env, context, options, stats); } -std::string AutoStory_Checkpoint_078::name() const{ return "005.12 - celestic_town_drama"; } -std::string AutoStory_Checkpoint_078::start_text() const{ return "In Celestic Town."; } -std::string AutoStory_Checkpoint_078::end_text() const{ return "Entered Celestic Town cave."; } -void AutoStory_Checkpoint_078::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_078(env, context, options, stats); +std::string AutoStory_Checkpoint_0512::name() const{ return "05.12 - celestic_town_drama"; } +std::string AutoStory_Checkpoint_0512::start_text() const{ return "In Celestic Town."; } +std::string AutoStory_Checkpoint_0512::end_text() const{ return "Entered Celestic Town cave."; } +void AutoStory_Checkpoint_0512::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0512(env, context, options, stats); } -std::string AutoStory_Checkpoint_079::name() const{ return "005.13 - discover_the_paintings"; } -std::string AutoStory_Checkpoint_079::start_text() const{ return "Inside Celestic Town cave."; } -std::string AutoStory_Checkpoint_079::end_text() const{ return "Discovered paintings and exited cave."; } -void AutoStory_Checkpoint_079::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_079(env, context, options, stats); +std::string AutoStory_Checkpoint_0513::name() const{ return "05.13 - discover_the_paintings"; } +std::string AutoStory_Checkpoint_0513::start_text() const{ return "Inside Celestic Town cave."; } +std::string AutoStory_Checkpoint_0513::end_text() const{ return "Discovered paintings and exited cave."; } +void AutoStory_Checkpoint_0513::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0513(env, context, options, stats); } -std::string AutoStory_Checkpoint_080::name() const{ return "005.14 - talk_to_cyrus_and_fly_hearthome"; } -std::string AutoStory_Checkpoint_080::start_text() const{ return "Outside Celestic Town cave."; } -std::string AutoStory_Checkpoint_080::end_text() const{ return "Entered Hearthome City Pokemon Center."; } -void AutoStory_Checkpoint_080::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_080(env, context, options, stats); +std::string AutoStory_Checkpoint_0514::name() const{ return "05.14 - talk_to_cyrus_and_fly_hearthome"; } +std::string AutoStory_Checkpoint_0514::start_text() const{ return "Outside Celestic Town cave."; } +std::string AutoStory_Checkpoint_0514::end_text() const{ return "Entered Hearthome City Pokemon Center."; } +void AutoStory_Checkpoint_0514::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0514(env, context, options, stats); } -std::string AutoStory_Checkpoint_081::name() const{ return "005.15 - heal_at_hearthome_pokemon_center"; } -std::string AutoStory_Checkpoint_081::start_text() const{ return "Inside Hearthome City Pokemon Center."; } -std::string AutoStory_Checkpoint_081::end_text() const{ return "Healed and exited Hearthome City Pokemon Center."; } -void AutoStory_Checkpoint_081::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_081(env, context, options, stats); +std::string AutoStory_Checkpoint_0515::name() const{ return "05.15 - heal_at_hearthome_pokemon_center"; } +std::string AutoStory_Checkpoint_0515::start_text() const{ return "Inside Hearthome City Pokemon Center."; } +std::string AutoStory_Checkpoint_0515::end_text() const{ return "Healed and exited Hearthome City Pokemon Center."; } +void AutoStory_Checkpoint_0515::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0515(env, context, options, stats); } -std::string AutoStory_Checkpoint_082::name() const{ return "005.16 - get_into_hearthome_gym"; } -std::string AutoStory_Checkpoint_082::start_text() const{ return "Outside Hearthome City gym."; } -std::string AutoStory_Checkpoint_082::end_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_082::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_082(env, context, options, stats); +std::string AutoStory_Checkpoint_0516::name() const{ return "05.16 - get_into_hearthome_gym"; } +std::string AutoStory_Checkpoint_0516::start_text() const{ return "Outside Hearthome City gym."; } +std::string AutoStory_Checkpoint_0516::end_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } +void AutoStory_Checkpoint_0516::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0516(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_0517::name() const{ return "05.17 - Hearthome Gym Puzzle"; } +std::string AutoStory_Checkpoint_0517::start_text() const{ return "Outside Hearthome City gym."; } +std::string AutoStory_Checkpoint_0517::end_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } +void AutoStory_Checkpoint_0517::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0517(env, context, options, stats); } static void move_back_after_unexpected_battle( @@ -1474,7 +1482,7 @@ static bool get_into_hearthome_gym( return true; } -void checkpoint_067(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_pokemon_center(env.console, context)){ @@ -1485,7 +1493,7 @@ void checkpoint_067(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_068(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!exit_pastoria_pokemon_center(env.console, context)){ @@ -1496,7 +1504,7 @@ void checkpoint_068(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_069(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!pastoria_city_drama(env.console, context)){ @@ -1507,7 +1515,7 @@ void checkpoint_069(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_070(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ @@ -1518,7 +1526,7 @@ void checkpoint_070(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_071(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_route_213_chasing(env.console, context)){ @@ -1529,7 +1537,7 @@ void checkpoint_071(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_072(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ @@ -1540,7 +1548,7 @@ void checkpoint_072(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_073(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_route_214(env.console, context)){ @@ -1551,7 +1559,7 @@ void checkpoint_073(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_074(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_inside_solaceon_pokemon_center(env.console, context)){ @@ -1562,7 +1570,7 @@ void checkpoint_074(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_075(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_solaceon_pokemon_center(env.console, context)){ @@ -1573,7 +1581,7 @@ void checkpoint_075(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_076(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_the_psyduck_route_210(env.console, context)){ @@ -1584,7 +1592,7 @@ void checkpoint_076(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_077(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_celestic_pokemon_center(env.console, context)){ @@ -1595,7 +1603,7 @@ void checkpoint_077(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_078(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!celestic_town_drama(env.console, context)){ @@ -1606,7 +1614,7 @@ void checkpoint_078(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_079(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!discover_the_paintings(env.console, context)){ @@ -1617,7 +1625,7 @@ void checkpoint_079(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_080(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ @@ -1628,7 +1636,7 @@ void checkpoint_080(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_081(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_hearthome_pokemon_center(env.console, context)){ @@ -1639,7 +1647,7 @@ void checkpoint_081(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_082(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ @@ -1650,6 +1658,16 @@ void checkpoint_082(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } +void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!get_into_hearthome_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); + } + }, + false + ); +} } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h index 91df97f59c..b454a9da65 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h @@ -28,7 +28,7 @@ class AutoStory_Segment_05 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_067 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0501 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,9 +40,9 @@ class AutoStory_Checkpoint_067 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_067(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_068 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0502 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -54,9 +54,9 @@ class AutoStory_Checkpoint_068 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_068(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_069 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0503 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -68,9 +68,9 @@ class AutoStory_Checkpoint_069 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_069(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_070 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0504 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -82,9 +82,9 @@ class AutoStory_Checkpoint_070 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_070(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_071 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0505 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -96,9 +96,9 @@ class AutoStory_Checkpoint_071 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_071(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_072 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0506 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -110,9 +110,9 @@ class AutoStory_Checkpoint_072 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_072(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_073 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0507 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -124,9 +124,9 @@ class AutoStory_Checkpoint_073 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_073(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_074 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0508 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -138,9 +138,9 @@ class AutoStory_Checkpoint_074 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_074(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_075 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0509 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -152,9 +152,9 @@ class AutoStory_Checkpoint_075 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_075(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_076 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0510 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -166,9 +166,9 @@ class AutoStory_Checkpoint_076 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_076(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_077 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0511 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -180,9 +180,9 @@ class AutoStory_Checkpoint_077 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_077(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_078 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0512 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -194,9 +194,9 @@ class AutoStory_Checkpoint_078 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_078(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_079 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0513 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -208,9 +208,9 @@ class AutoStory_Checkpoint_079 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_079(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_080 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0514 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -222,9 +222,9 @@ class AutoStory_Checkpoint_080 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_080(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_081 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0515 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -236,9 +236,9 @@ class AutoStory_Checkpoint_081 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_081(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_082 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0516 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -250,7 +250,21 @@ class AutoStory_Checkpoint_082 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_082(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); + +class AutoStory_Checkpoint_0517 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 00373af0ea..8d148ce592 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -39,92 +39,101 @@ void AutoStory_Segment_06::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_083().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_084().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_085().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_086().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_087().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_088().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_089().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_090().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_091().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_092().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0601().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0602().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0603().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0604().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0605().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0606().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0607().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0608().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0609().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0610().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0611().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_083::name() const{ return "006.01 - cynthia_and_fly_to_jubilife"; } -std::string AutoStory_Checkpoint_083::start_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } -std::string AutoStory_Checkpoint_083::end_text() const{ return "Flew to Jubilife City."; } -void AutoStory_Checkpoint_083::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_083(env, context, options, stats); +std::string AutoStory_Checkpoint_0601::name() const{ return "06.01 - cynthia_and_fly_to_jubilife"; } +std::string AutoStory_Checkpoint_0601::start_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Checkpoint_0601::end_text() const{ return "Flew to Jubilife City."; } +void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0601(env, context, options, stats); } -std::string AutoStory_Checkpoint_084::name() const{ return "006.02 - enter_jubilife_pokemon_center"; } -std::string AutoStory_Checkpoint_084::start_text() const{ return "In Jubilife City."; } -std::string AutoStory_Checkpoint_084::end_text() const{ return "Entered Jubilife City Pokemon Center."; } -void AutoStory_Checkpoint_084::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_084(env, context, options, stats); +std::string AutoStory_Checkpoint_0602::name() const{ return "06.02 - enter_jubilife_pokemon_center"; } +std::string AutoStory_Checkpoint_0602::start_text() const{ return "In Jubilife City."; } +std::string AutoStory_Checkpoint_0602::end_text() const{ return "Entered Jubilife City Pokemon Center."; } +void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0602(env, context, options, stats); } -std::string AutoStory_Checkpoint_085::name() const{ return "006.03 - heal_and_exit_jubilife_pokemon_center"; } -std::string AutoStory_Checkpoint_085::start_text() const{ return "Inside Jubilife City Pokemon Center."; } -std::string AutoStory_Checkpoint_085::end_text() const{ return "Healed and exited Jubilife City Pokemon Center."; } -void AutoStory_Checkpoint_085::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_085(env, context, options, stats); +std::string AutoStory_Checkpoint_0603::name() const{ return "06.03 - heal_and_exit_jubilife_pokemon_center"; } +std::string AutoStory_Checkpoint_0603::start_text() const{ return "Inside Jubilife City Pokemon Center."; } +std::string AutoStory_Checkpoint_0603::end_text() const{ return "Healed and exited Jubilife City Pokemon Center."; } +void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0603(env, context, options, stats); } -std::string AutoStory_Checkpoint_086::name() const{ return "006.04 - move_to_jubilife_west_exit"; } -std::string AutoStory_Checkpoint_086::start_text() const{ return "Outside Jubilife City Pokemon Center."; } -std::string AutoStory_Checkpoint_086::end_text() const{ return "Entered west exit building of Jubilife City."; } -void AutoStory_Checkpoint_086::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_086(env, context, options, stats); +std::string AutoStory_Checkpoint_0604::name() const{ return "06.04 - move_to_jubilife_west_exit"; } +std::string AutoStory_Checkpoint_0604::start_text() const{ return "Outside Jubilife City Pokemon Center."; } +std::string AutoStory_Checkpoint_0604::end_text() const{ return "Entered west exit building of Jubilife City."; } +void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0604(env, context, options, stats); } -std::string AutoStory_Checkpoint_087::name() const{ return "006.05 - go_through_jubilife_west_exit"; } -std::string AutoStory_Checkpoint_087::start_text() const{ return "Inside Jubilife City west exit building."; } -std::string AutoStory_Checkpoint_087::end_text() const{ return "Exited onto Route 218."; } -void AutoStory_Checkpoint_087::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_087(env, context, options, stats); +std::string AutoStory_Checkpoint_0605::name() const{ return "06.05 - go_through_jubilife_west_exit"; } +std::string AutoStory_Checkpoint_0605::start_text() const{ return "Inside Jubilife City west exit building."; } +std::string AutoStory_Checkpoint_0605::end_text() const{ return "Exited onto Route 218."; } +void AutoStory_Checkpoint_0605::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0605(env, context, options, stats); } -std::string AutoStory_Checkpoint_088::name() const{ return "006.06 - go_through_route_218"; } -std::string AutoStory_Checkpoint_088::start_text() const{ return "On Route 218."; } -std::string AutoStory_Checkpoint_088::end_text() const{ return "Entered building at end of Route 218."; } -void AutoStory_Checkpoint_088::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_088(env, context, options, stats); +std::string AutoStory_Checkpoint_0606::name() const{ return "06.06 - go_through_route_218"; } +std::string AutoStory_Checkpoint_0606::start_text() const{ return "On Route 218."; } +std::string AutoStory_Checkpoint_0606::end_text() const{ return "Entered building at end of Route 218."; } +void AutoStory_Checkpoint_0606::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0606(env, context, options, stats); } -std::string AutoStory_Checkpoint_089::name() const{ return "006.07 - go_through_building_route_218"; } -std::string AutoStory_Checkpoint_089::start_text() const{ return "Inside building at end of Route 218."; } -std::string AutoStory_Checkpoint_089::end_text() const{ return "Exited building into Canalave City."; } -void AutoStory_Checkpoint_089::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_089(env, context, options, stats); +std::string AutoStory_Checkpoint_0607::name() const{ return "06.07 - go_through_building_route_218"; } +std::string AutoStory_Checkpoint_0607::start_text() const{ return "Inside building at end of Route 218."; } +std::string AutoStory_Checkpoint_0607::end_text() const{ return "Exited building into Canalave City."; } +void AutoStory_Checkpoint_0607::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0607(env, context, options, stats); } -std::string AutoStory_Checkpoint_090::name() const{ return "006.08 - go_to_clint_and_pokemon_center"; } -std::string AutoStory_Checkpoint_090::start_text() const{ return "In Canalave City."; } -std::string AutoStory_Checkpoint_090::end_text() const{ return "Battled Clint and entered Canalave City Pokemon Center."; } -void AutoStory_Checkpoint_090::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_090(env, context, options, stats); +std::string AutoStory_Checkpoint_0608::name() const{ return "06.08 - go_to_clint_and_pokemon_center"; } +std::string AutoStory_Checkpoint_0608::start_text() const{ return "In Canalave City."; } +std::string AutoStory_Checkpoint_0608::end_text() const{ return "Battled Clint and entered Canalave City Pokemon Center."; } +void AutoStory_Checkpoint_0608::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0608(env, context, options, stats); } -std::string AutoStory_Checkpoint_091::name() const{ return "006.09 - heal_and_exit_canalave_pokemon_center"; } -std::string AutoStory_Checkpoint_091::start_text() const{ return "Inside Canalave City Pokemon Center."; } -std::string AutoStory_Checkpoint_091::end_text() const{ return "Healed and exited Canalave City Pokemon Center."; } -void AutoStory_Checkpoint_091::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_091(env, context, options, stats); +std::string AutoStory_Checkpoint_0609::name() const{ return "06.09 - heal_and_exit_canalave_pokemon_center"; } +std::string AutoStory_Checkpoint_0609::start_text() const{ return "Inside Canalave City Pokemon Center."; } +std::string AutoStory_Checkpoint_0609::end_text() const{ return "Healed and exited Canalave City Pokemon Center."; } +void AutoStory_Checkpoint_0609::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0609(env, context, options, stats); } -std::string AutoStory_Checkpoint_092::name() const{ return "006.10 - go_to_canalave_gym"; } -std::string AutoStory_Checkpoint_092::start_text() const{ return "Outside Canalave City Pokemon Center."; } -std::string AutoStory_Checkpoint_092::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } -void AutoStory_Checkpoint_092::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_092(env, context, options, stats); +std::string AutoStory_Checkpoint_0610::name() const{ return "06.10 - go_to_canalave_gym"; } +std::string AutoStory_Checkpoint_0610::start_text() const{ return "Outside Canalave City Pokemon Center."; } +std::string AutoStory_Checkpoint_0610::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } +void AutoStory_Checkpoint_0610::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0610(env, context, options, stats); } +std::string AutoStory_Checkpoint_0611::name() const{ return "06.11 - Canalave Gym Puzzle"; } +std::string AutoStory_Checkpoint_0611::start_text() const{ return "Outside Canalave City Pokemon Center."; } +std::string AutoStory_Checkpoint_0611::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } +void AutoStory_Checkpoint_0611::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0611(env, context, options, stats); +} + + static void move_back_after_unexpected_battle( VideoStream& stream, ProControllerContext& context, @@ -615,7 +624,7 @@ static bool go_to_canalave_gym( -void checkpoint_083(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ /*if (!test(env.console, context)){ @@ -629,7 +638,7 @@ void checkpoint_083(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_084(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!enter_jubilife_pokemon_center(env.console, context)){ @@ -640,7 +649,7 @@ void checkpoint_084(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_085(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ @@ -651,7 +660,7 @@ void checkpoint_085(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_086(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ @@ -662,7 +671,7 @@ void checkpoint_086(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_087(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ @@ -673,7 +682,7 @@ void checkpoint_087(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_088(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_route_218(env.console, context)){ @@ -684,7 +693,7 @@ void checkpoint_088(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_089(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ @@ -695,7 +704,7 @@ void checkpoint_089(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_090(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_clint_and_pokemon_center(env.console, context)){ @@ -706,7 +715,7 @@ void checkpoint_090(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_091(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ @@ -717,7 +726,7 @@ void checkpoint_091(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_092(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ @@ -728,6 +737,16 @@ void checkpoint_092(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } +void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_canalave_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + } + }, + false + ); +} } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h index 55f84af77d..81e8fd423d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h @@ -28,7 +28,7 @@ class AutoStory_Segment_06 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_083 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0601 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,9 +40,9 @@ class AutoStory_Checkpoint_083 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_083(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_084 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0602 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -54,9 +54,9 @@ class AutoStory_Checkpoint_084 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_084(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_085 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0603 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -68,9 +68,9 @@ class AutoStory_Checkpoint_085 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_085(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_086 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0604 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -82,9 +82,9 @@ class AutoStory_Checkpoint_086 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_086(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_087 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0605 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -96,9 +96,9 @@ class AutoStory_Checkpoint_087 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_087(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_088 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0606 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -110,9 +110,9 @@ class AutoStory_Checkpoint_088 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_088(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_089 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0607 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -124,9 +124,9 @@ class AutoStory_Checkpoint_089 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_089(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_090 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0608 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -138,9 +138,9 @@ class AutoStory_Checkpoint_090 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_090(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_091 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0609 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -152,9 +152,9 @@ class AutoStory_Checkpoint_091 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_091(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_092 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0610 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -166,8 +166,21 @@ class AutoStory_Checkpoint_092 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_092(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_0611 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 746230eb56..1553291c20 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -40,154 +40,162 @@ void AutoStory_Segment_07::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_093().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_094().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_095().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_096().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_097().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_098().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_099().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_100().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_101().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_102().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_103().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_104().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_105().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_106().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_107().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_108().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_109().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_110().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0701().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0702().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0703().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0704().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0705().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0706().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0707().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0708().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0709().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0710().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0711().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0712().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0713().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0714().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0715().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0716().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0717().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0718().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0719().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } -std::string AutoStory_Checkpoint_093::name() const{ return "007.01 - leave_gym"; } -std::string AutoStory_Checkpoint_093::start_text() const{ return "Inside Canalave City gym after sixth badge."; } -std::string AutoStory_Checkpoint_093::end_text() const{ return "Exited Canalave City gym."; } -void AutoStory_Checkpoint_093::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_093(env, context, options, stats); +std::string AutoStory_Checkpoint_0701::name() const{ return "07.01 - leave_gym"; } +std::string AutoStory_Checkpoint_0701::start_text() const{ return "Inside Canalave City gym after sixth badge."; } +std::string AutoStory_Checkpoint_0701::end_text() const{ return "Exited Canalave City gym."; } +void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0701(env, context, options, stats); } -std::string AutoStory_Checkpoint_094::name() const{ return "007.02 - go_to_the_library"; } -std::string AutoStory_Checkpoint_094::start_text() const{ return "Outside Canalave City gym."; } -std::string AutoStory_Checkpoint_094::end_text() const{ return "Entered Canalave City library."; } -void AutoStory_Checkpoint_094::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_094(env, context, options, stats); +std::string AutoStory_Checkpoint_0702::name() const{ return "07.02 - go_to_the_library"; } +std::string AutoStory_Checkpoint_0702::start_text() const{ return "Outside Canalave City gym."; } +std::string AutoStory_Checkpoint_0702::end_text() const{ return "Entered Canalave City library."; } +void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0702(env, context, options, stats); } -std::string AutoStory_Checkpoint_095::name() const{ return "007.03 - drama_at_the_library"; } -std::string AutoStory_Checkpoint_095::start_text() const{ return "Inside Canalave City library."; } -std::string AutoStory_Checkpoint_095::end_text() const{ return "Library drama complete, exited library."; } -void AutoStory_Checkpoint_095::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_095(env, context, options, stats); +std::string AutoStory_Checkpoint_0703::name() const{ return "07.03 - drama_at_the_library"; } +std::string AutoStory_Checkpoint_0703::start_text() const{ return "Inside Canalave City library."; } +std::string AutoStory_Checkpoint_0703::end_text() const{ return "Library drama complete, exited library."; } +void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0703(env, context, options, stats); } -std::string AutoStory_Checkpoint_096::name() const{ return "007.04 - talk_fly_and_enter_mount_coronet"; } -std::string AutoStory_Checkpoint_096::start_text() const{ return "Outside Canalave City library."; } -std::string AutoStory_Checkpoint_096::end_text() const{ return "Entered Mt. Coronet."; } -void AutoStory_Checkpoint_096::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_096(env, context, options, stats); +std::string AutoStory_Checkpoint_0704::name() const{ return "07.04 - talk_fly_and_enter_mount_coronet"; } +std::string AutoStory_Checkpoint_0704::start_text() const{ return "Outside Canalave City library."; } +std::string AutoStory_Checkpoint_0704::end_text() const{ return "Entered Mt. Coronet."; } +void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0704(env, context, options, stats); } -std::string AutoStory_Checkpoint_097::name() const{ return "007.05 - go_through_mount_coronet"; } -std::string AutoStory_Checkpoint_097::start_text() const{ return "Inside Mt. Coronet."; } -std::string AutoStory_Checkpoint_097::end_text() const{ return "Exited Mt. Coronet."; } -void AutoStory_Checkpoint_097::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_097(env, context, options, stats); +std::string AutoStory_Checkpoint_0705::name() const{ return "07.05 - go_through_mount_coronet"; } +std::string AutoStory_Checkpoint_0705::start_text() const{ return "Inside Mt. Coronet."; } +std::string AutoStory_Checkpoint_0705::end_text() const{ return "Exited Mt. Coronet."; } +void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0705(env, context, options, stats); } -std::string AutoStory_Checkpoint_098::name() const{ return "007.06 - go_through_route_216"; } -std::string AutoStory_Checkpoint_098::start_text() const{ return "On Route 216."; } -std::string AutoStory_Checkpoint_098::end_text() const{ return "Entered small chalet on Route 216."; } -void AutoStory_Checkpoint_098::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_098(env, context, options, stats); +std::string AutoStory_Checkpoint_0706::name() const{ return "07.06 - go_through_route_216"; } +std::string AutoStory_Checkpoint_0706::start_text() const{ return "On Route 216."; } +std::string AutoStory_Checkpoint_0706::end_text() const{ return "Entered small chalet on Route 216."; } +void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0706(env, context, options, stats); } -std::string AutoStory_Checkpoint_099::name() const{ return "007.07 - heal_small_house_route_216"; } -std::string AutoStory_Checkpoint_099::start_text() const{ return "Inside small chalet on Route 216."; } -std::string AutoStory_Checkpoint_099::end_text() const{ return "Rested in chalet and exited."; } -void AutoStory_Checkpoint_099::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_099(env, context, options, stats); +std::string AutoStory_Checkpoint_0707::name() const{ return "07.07 - heal_small_house_route_216"; } +std::string AutoStory_Checkpoint_0707::start_text() const{ return "Inside small chalet on Route 216."; } +std::string AutoStory_Checkpoint_0707::end_text() const{ return "Rested in chalet and exited."; } +void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0707(env, context, options, stats); } -std::string AutoStory_Checkpoint_100::name() const{ return "007.08 - continue_through_route_216"; } -std::string AutoStory_Checkpoint_100::start_text() const{ return "Outside chalet on Route 216."; } -std::string AutoStory_Checkpoint_100::end_text() const{ return "Entered Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_100::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_100(env, context, options, stats); +std::string AutoStory_Checkpoint_0708::name() const{ return "07.08 - continue_through_route_216"; } +std::string AutoStory_Checkpoint_0708::start_text() const{ return "Outside chalet on Route 216."; } +std::string AutoStory_Checkpoint_0708::end_text() const{ return "Entered Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_0708::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0708(env, context, options, stats); } -std::string AutoStory_Checkpoint_101::name() const{ return "007.09 - heal_and_exit_veilstone_pokemon_center"; } -std::string AutoStory_Checkpoint_101::start_text() const{ return "Inside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_101::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_101::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_101(env, context, options, stats); +std::string AutoStory_Checkpoint_0709::name() const{ return "07.09 - heal_and_exit_veilstone_pokemon_center"; } +std::string AutoStory_Checkpoint_0709::start_text() const{ return "Inside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_0709::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_0709::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0709(env, context, options, stats); } -std::string AutoStory_Checkpoint_102::name() const{ return "007.10 - go_to_house_south_veilstone"; } -std::string AutoStory_Checkpoint_102::start_text() const{ return "Outside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_102::end_text() const{ return "Entered house south of Veilstone City."; } -void AutoStory_Checkpoint_102::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_102(env, context, options, stats); +std::string AutoStory_Checkpoint_0710::name() const{ return "07.10 - go_to_house_south_veilstone"; } +std::string AutoStory_Checkpoint_0710::start_text() const{ return "Outside Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_0710::end_text() const{ return "Entered house south of Veilstone City."; } +void AutoStory_Checkpoint_0710::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0710(env, context, options, stats); } -std::string AutoStory_Checkpoint_103::name() const{ return "007.11 - go_through_house_south_veilstone"; } -std::string AutoStory_Checkpoint_103::start_text() const{ return "Inside house south of Veilstone City."; } -std::string AutoStory_Checkpoint_103::end_text() const{ return "Exited house onto Route 214."; } -void AutoStory_Checkpoint_103::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_103(env, context, options, stats); +std::string AutoStory_Checkpoint_0711::name() const{ return "07.11 - go_through_house_south_veilstone"; } +std::string AutoStory_Checkpoint_0711::start_text() const{ return "Inside house south of Veilstone City."; } +std::string AutoStory_Checkpoint_0711::end_text() const{ return "Exited house onto Route 214."; } +void AutoStory_Checkpoint_0711::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0711(env, context, options, stats); } -std::string AutoStory_Checkpoint_104::name() const{ return "007.12 - walk_through_route_214"; } -std::string AutoStory_Checkpoint_104::start_text() const{ return "On Route 214 heading south."; } -std::string AutoStory_Checkpoint_104::end_text() const{ return "Entered Valor Lakefront."; } -void AutoStory_Checkpoint_104::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_104(env, context, options, stats); +std::string AutoStory_Checkpoint_0712::name() const{ return "07.12 - walk_through_route_214"; } +std::string AutoStory_Checkpoint_0712::start_text() const{ return "On Route 214 heading south."; } +std::string AutoStory_Checkpoint_0712::end_text() const{ return "Entered Valor Lakefront."; } +void AutoStory_Checkpoint_0712::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0712(env, context, options, stats); } -std::string AutoStory_Checkpoint_105::name() const{ return "007.13 - go_through_lake_valor"; } -std::string AutoStory_Checkpoint_105::start_text() const{ return "At Valor Lakefront."; } -std::string AutoStory_Checkpoint_105::end_text() const{ return "Flew to Twinleaf Town and entered own house."; } -void AutoStory_Checkpoint_105::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_105(env, context, options, stats); +std::string AutoStory_Checkpoint_0713::name() const{ return "07.13 - go_through_lake_valor"; } +std::string AutoStory_Checkpoint_0713::start_text() const{ return "At Valor Lakefront."; } +std::string AutoStory_Checkpoint_0713::end_text() const{ return "Flew to Twinleaf Town and entered own house."; } +void AutoStory_Checkpoint_0713::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0713(env, context, options, stats); } -std::string AutoStory_Checkpoint_106::name() const{ return "007.14 - heal_at_home"; } -std::string AutoStory_Checkpoint_106::start_text() const{ return "Inside own house in Twinleaf Town."; } -std::string AutoStory_Checkpoint_106::end_text() const{ return "Healed at home and exited."; } -void AutoStory_Checkpoint_106::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_106(env, context, options, stats); +std::string AutoStory_Checkpoint_0714::name() const{ return "07.14 - heal_at_home"; } +std::string AutoStory_Checkpoint_0714::start_text() const{ return "Inside own house in Twinleaf Town."; } +std::string AutoStory_Checkpoint_0714::end_text() const{ return "Healed at home and exited."; } +void AutoStory_Checkpoint_0714::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0714(env, context, options, stats); } -std::string AutoStory_Checkpoint_107::name() const{ return "007.15 - go_to_lake_verity"; } -std::string AutoStory_Checkpoint_107::start_text() const{ return "Outside own house in Twinleaf Town."; } -std::string AutoStory_Checkpoint_107::end_text() const{ return "Entered Verity Lakefront."; } -void AutoStory_Checkpoint_107::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_107(env, context, options, stats); +std::string AutoStory_Checkpoint_0715::name() const{ return "07.15 - go_to_lake_verity"; } +std::string AutoStory_Checkpoint_0715::start_text() const{ return "Outside own house in Twinleaf Town."; } +std::string AutoStory_Checkpoint_0715::end_text() const{ return "Entered Verity Lakefront."; } +void AutoStory_Checkpoint_0715::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0715(env, context, options, stats); } -std::string AutoStory_Checkpoint_108::name() const{ return "007.16 - commander_mars_and_snowpoint"; } -std::string AutoStory_Checkpoint_108::start_text() const{ return "At Lake Verity."; } -std::string AutoStory_Checkpoint_108::end_text() const{ return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_108::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_108(env, context, options, stats); +std::string AutoStory_Checkpoint_0716::name() const{ return "07.16 - commander_mars_and_snowpoint"; } +std::string AutoStory_Checkpoint_0716::start_text() const{ return "At Lake Verity."; } +std::string AutoStory_Checkpoint_0716::end_text() const{ return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_0716::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0716(env, context, options, stats); } -std::string AutoStory_Checkpoint_109::name() const{ return "007.17 - heal_at_snowpoint_pokemon_center"; } -std::string AutoStory_Checkpoint_109::start_text() const{ return "Inside Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_109::end_text() const{ return "Healed and exited Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_109::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_109(env, context, options, stats); +std::string AutoStory_Checkpoint_0717::name() const{ return "07.17 - heal_at_snowpoint_pokemon_center"; } +std::string AutoStory_Checkpoint_0717::start_text() const{ return "Inside Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_0717::end_text() const{ return "Healed and exited Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_0717::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0717(env, context, options, stats); } -std::string AutoStory_Checkpoint_110::name() const{ return "007.18 - go_to_snowpoint_gym"; } -std::string AutoStory_Checkpoint_110::start_text() const{ return "Outside Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_110::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -void AutoStory_Checkpoint_110::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ - checkpoint_110(env, context, options, stats); +std::string AutoStory_Checkpoint_0718::name() const{ return "07.18 - go_to_snowpoint_gym"; } +std::string AutoStory_Checkpoint_0718::start_text() const{ return "Outside Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_0718::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +void AutoStory_Checkpoint_0718::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0718(env, context, options, stats); +} + +std::string AutoStory_Checkpoint_0719::name() const{ return "07.19 - Snowpoint Gym Battle"; } +std::string AutoStory_Checkpoint_0719::start_text() const{ return "Outside Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_0719::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +void AutoStory_Checkpoint_0719::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ + checkpoint_0719(env, context, options, stats); } static void move_back_after_unexpected_battle( @@ -1754,7 +1762,7 @@ static bool go_to_snowpoint_gym( -void checkpoint_093(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ @@ -1765,7 +1773,7 @@ void checkpoint_093(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_094(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_the_library(env.console, context)){ @@ -1776,7 +1784,7 @@ void checkpoint_094(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_095(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!drama_at_the_library(env.console, context)){ @@ -1787,7 +1795,7 @@ void checkpoint_095(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_096(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!talk_fly_and_enter_mount_coronet(env.console, context)){ @@ -1798,7 +1806,7 @@ void checkpoint_096(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_097(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context)){ @@ -1809,7 +1817,7 @@ void checkpoint_097(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_098(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_route_216(env.console, context)){ @@ -1820,7 +1828,7 @@ void checkpoint_098(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_099(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_small_house_route_216(env.console, context)){ @@ -1831,7 +1839,7 @@ void checkpoint_099(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_100(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!continue_through_route_216(env.console, context)){ @@ -1842,18 +1850,18 @@ void checkpoint_100(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ - if (!heal_and_exit_veilstone_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_pokemon_center: transition not detected.", env.console); + if (!heal_at_snowpoint_pokemon_center(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); } }, false ); } -void checkpoint_102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context)){ @@ -1864,7 +1872,7 @@ void checkpoint_102(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context)){ @@ -1875,7 +1883,7 @@ void checkpoint_103(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_104(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ @@ -1886,7 +1894,7 @@ void checkpoint_104(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_105(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_lake_valor(env.console, context)){ @@ -1897,7 +1905,7 @@ void checkpoint_105(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_106(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_home(env.console, context)){ @@ -1908,7 +1916,7 @@ void checkpoint_106(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_107(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_lake_verity(env.console, context)){ @@ -1919,7 +1927,7 @@ void checkpoint_107(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_108(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!commander_mars_and_snowpoint(env.console, context)){ @@ -1930,7 +1938,7 @@ void checkpoint_108(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_109(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ @@ -1941,7 +1949,18 @@ void checkpoint_109(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_110(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_snowpoint_gym(env.console, context)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + } + }, + false + ); +} + +void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h index fc4d1e69fd..d7d1f93c8b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h @@ -28,7 +28,7 @@ class AutoStory_Segment_07 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_093 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0701 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,9 +40,9 @@ class AutoStory_Checkpoint_093 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_093(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_094 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0702 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -54,9 +54,9 @@ class AutoStory_Checkpoint_094 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_094(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_095 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0703 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -68,9 +68,9 @@ class AutoStory_Checkpoint_095 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_095(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_096 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0704 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -82,9 +82,9 @@ class AutoStory_Checkpoint_096 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_096(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_097 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0705 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -96,9 +96,9 @@ class AutoStory_Checkpoint_097 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_097(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_098 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0706 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -110,9 +110,9 @@ class AutoStory_Checkpoint_098 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_098(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_099 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0707 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -124,9 +124,9 @@ class AutoStory_Checkpoint_099 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_099(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_100 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0708 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -138,9 +138,9 @@ class AutoStory_Checkpoint_100 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_100(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_101 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0709 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -152,9 +152,9 @@ class AutoStory_Checkpoint_101 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_102 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0710 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -166,9 +166,9 @@ class AutoStory_Checkpoint_102 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_103 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0711 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -180,9 +180,9 @@ class AutoStory_Checkpoint_103 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_104 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0712 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -194,9 +194,9 @@ class AutoStory_Checkpoint_104 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_104(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_105 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0713 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -208,9 +208,9 @@ class AutoStory_Checkpoint_105 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_105(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_106 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0714 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -222,9 +222,9 @@ class AutoStory_Checkpoint_106 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_106(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_107 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0715 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -236,9 +236,9 @@ class AutoStory_Checkpoint_107 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_107(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_108 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0716 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -250,9 +250,9 @@ class AutoStory_Checkpoint_108 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_108(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_109 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0717 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -264,9 +264,9 @@ class AutoStory_Checkpoint_109 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_109(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_110 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0718 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -278,8 +278,21 @@ class AutoStory_Checkpoint_110 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_110(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_0719 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 7161cba421..91528bce6b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -91,33 +91,27 @@ void AutoStory_Segment_08::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_111().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_112().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_113().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_114().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_115().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_116().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_117().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_118().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_119().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_120().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_121().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_122().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_123().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_124().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_125().run_checkpoint(env, context, options, stats); - - AutoStory_Checkpoint_126().run_checkpoint(env, context, options, stats); - - AutoStory_Checkpoint_127().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_128().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_129().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_130().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_131().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_132().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_133().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_134().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_135().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0801().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0802().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0803().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0804().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0805().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0806().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0807().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0808().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0809().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0810().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0811().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0812().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0813().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0814().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0815().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0816().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0817().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0818().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0819().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0820().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0821().run_checkpoint(env, context, options, stats); /*AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); checkpoint_021_legendary(env, context, options, stats);*/ @@ -2321,7 +2315,7 @@ static bool go_to_sunyshore_gym( // ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // -void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ @@ -2332,7 +2326,7 @@ void checkpoint_111(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ @@ -2343,7 +2337,7 @@ void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Snowpoint City")){ @@ -2354,7 +2348,7 @@ void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_lake_acuity(env.console, context)){ @@ -2365,7 +2359,7 @@ void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!talk_to_clint_then_go_veilstone(env.console, context)){ @@ -2376,7 +2370,7 @@ void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ @@ -2387,7 +2381,7 @@ void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!get_key_and_enter_warehouse(env.console, context)){ @@ -2398,7 +2392,7 @@ void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_warehouse(env.console, context)){ @@ -2409,7 +2403,7 @@ void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_at_veilstone_pokemon_center(env.console, context)){ @@ -2420,7 +2414,7 @@ void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ @@ -2431,7 +2425,7 @@ void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_hq(env.console, context)){ @@ -2442,7 +2436,7 @@ void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_galactic_hq(env.console, context, options)){ @@ -2453,7 +2447,7 @@ void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_oreburgh(env.console, context, options)){ @@ -2464,7 +2458,7 @@ void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Oreburgh City")){ @@ -2475,7 +2469,7 @@ void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_mount_coronet(env.console, context, options)){ @@ -2486,7 +2480,7 @@ void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_126(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context, options)){ @@ -2497,7 +2491,7 @@ void checkpoint_126(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_127(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_veilstone(env.console, context, options)){ @@ -2508,7 +2502,7 @@ void checkpoint_127(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_128(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ @@ -2519,7 +2513,7 @@ void checkpoint_128(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_129(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context, options)){ @@ -2530,7 +2524,7 @@ void checkpoint_129(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_130(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context, options)){ @@ -2541,7 +2535,7 @@ void checkpoint_130(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_131(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_house_west_sunyshore(env.console, context, options)){ @@ -2552,7 +2546,7 @@ void checkpoint_131(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_132(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_through_house_west_sunyshore(env.console, context, options)){ @@ -2563,7 +2557,7 @@ void checkpoint_132(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_133(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_lighthouse(env.console, context, options)){ @@ -2574,7 +2568,7 @@ void checkpoint_133(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_134(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/){ if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ @@ -2585,7 +2579,7 @@ void checkpoint_134(SingleSwitchProgramEnvironment& env, ProControllerContext& c ); } -void checkpoint_135( +void checkpoint_0825( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -2601,309 +2595,335 @@ void checkpoint_135( ); } +void checkpoint_0826( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + [&](size_t /*attempt*/){ + if (!go_to_sunyshore_gym(env.console, context, options)){ + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + } + }, + false + ); +} -std::string AutoStory_Checkpoint_111::name() const{ return "111 - S08 - Gym badge acquired"; } -std::string AutoStory_Checkpoint_111::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -std::string AutoStory_Checkpoint_111::end_text() const{ return "Leave Snowpoint Gym."; } -void AutoStory_Checkpoint_111::run_checkpoint( + +std::string AutoStory_Checkpoint_0801::name() const{ return "08.01 - Gym badge acquired"; } +std::string AutoStory_Checkpoint_0801::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Checkpoint_0801::end_text() const{ return "Leave Snowpoint Gym."; } +void AutoStory_Checkpoint_0801::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_111(env, context, options, stats); + checkpoint_0801(env, context, options, stats); } -std::string AutoStory_Checkpoint_112::name() const{ return "112 - S08 - Left Snowpoint Gym"; } -std::string AutoStory_Checkpoint_112::start_text() const{ return "Left Snowpoint Gym."; } -std::string AutoStory_Checkpoint_112::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -void AutoStory_Checkpoint_112::run_checkpoint( +std::string AutoStory_Checkpoint_0802::name() const{ return "08.02 - Left Snowpoint Gym"; } +std::string AutoStory_Checkpoint_0802::start_text() const{ return "Left Snowpoint Gym."; } +std::string AutoStory_Checkpoint_0802::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +void AutoStory_Checkpoint_0802::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_112(env, context, options, stats); + checkpoint_0802(env, context, options, stats); } -std::string AutoStory_Checkpoint_113::name() const{ return "113 - S08 - Arrived at Snowpoint Pokemon Center"; } -std::string AutoStory_Checkpoint_113::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -std::string AutoStory_Checkpoint_113::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_113::run_checkpoint( +std::string AutoStory_Checkpoint_0803::name() const{ return "08.03 - Arrived at Snowpoint Pokemon Center"; } +std::string AutoStory_Checkpoint_0803::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } +std::string AutoStory_Checkpoint_0803::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +void AutoStory_Checkpoint_0803::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_113(env, context, options, stats); + checkpoint_0803(env, context, options, stats); } -std::string AutoStory_Checkpoint_114::name() const{ return "114 - S08 - Healed at Snowpoint City Pokemon Center"; } -std::string AutoStory_Checkpoint_114::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_114::end_text() const{ return "Entered Lake Acuity Lakefront."; } -void AutoStory_Checkpoint_114::run_checkpoint( +std::string AutoStory_Checkpoint_0804::name() const{ return "08.04 - Healed at Snowpoint City Pokemon Center"; } +std::string AutoStory_Checkpoint_0804::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } +std::string AutoStory_Checkpoint_0804::end_text() const{ return "Entered Lake Acuity Lakefront."; } +void AutoStory_Checkpoint_0804::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_114(env, context, options, stats); + checkpoint_0804(env, context, options, stats); } -std::string AutoStory_Checkpoint_115::name() const{ return "115 - S08 - Entered Acuity Lakefront"; } -std::string AutoStory_Checkpoint_115::start_text() const{ return "Entered Lake Acuity Lakefront."; } -std::string AutoStory_Checkpoint_115::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_115::run_checkpoint( +std::string AutoStory_Checkpoint_0805::name() const{ return "08.05 - Entered Acuity Lakefront"; } +std::string AutoStory_Checkpoint_0805::start_text() const{ return "Entered Lake Acuity Lakefront."; } +std::string AutoStory_Checkpoint_0805::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_0805::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_115(env, context, options, stats); + checkpoint_0805(env, context, options, stats); } -std::string AutoStory_Checkpoint_116::name() const{ return "116 - S08 - Arrived at Veilstone Pokemon Center"; } -std::string AutoStory_Checkpoint_116::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_116::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_116::run_checkpoint( +std::string AutoStory_Checkpoint_0806::name() const{ return "08.06 - Arrived at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_0806::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_0806::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } +void AutoStory_Checkpoint_0806::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_116(env, context, options, stats); + checkpoint_0806(env, context, options, stats); } -std::string AutoStory_Checkpoint_117::name() const{ return "117 - S08 - Healed at Veilstone Pokemon Center"; } -std::string AutoStory_Checkpoint_117::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_117::end_text() const{ return "Entered Team Galactic Warehouse."; } -void AutoStory_Checkpoint_117::run_checkpoint( +std::string AutoStory_Checkpoint_0807::name() const{ return "08.07 - Healed at Veilstone Pokemon Center"; } +std::string AutoStory_Checkpoint_0807::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } +std::string AutoStory_Checkpoint_0807::end_text() const{ return "Entered Team Galactic Warehouse."; } +void AutoStory_Checkpoint_0807::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_117(env, context, options, stats); + checkpoint_0807(env, context, options, stats); } -std::string AutoStory_Checkpoint_118::name() const{ return "118 - S08 - Entered Team Galactic Warehouse"; } -std::string AutoStory_Checkpoint_118::start_text() const{ return "Entered Team Galactic Warehouse."; } -std::string AutoStory_Checkpoint_118::end_text() const{ return "Got out of the warehouse with the key."; } -void AutoStory_Checkpoint_118::run_checkpoint( +std::string AutoStory_Checkpoint_0808::name() const{ return "08.08 - Entered Team Galactic Warehouse"; } +std::string AutoStory_Checkpoint_0808::start_text() const{ return "Entered Team Galactic Warehouse."; } +std::string AutoStory_Checkpoint_0808::end_text() const{ return "Got out of the warehouse with the key."; } +void AutoStory_Checkpoint_0808::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_118(env, context, options, stats); + checkpoint_0808(env, context, options, stats); } -std::string AutoStory_Checkpoint_119::name() const{ return "119 - S08 - Fly back to Veilstone Pokemon Center for healing"; } -std::string AutoStory_Checkpoint_119::start_text() const{ return "Got out of the warehouse with the key."; } -std::string AutoStory_Checkpoint_119::end_text() const{ return "Entered Veilstone Pokemon Center for healing."; } -void AutoStory_Checkpoint_119::run_checkpoint( +std::string AutoStory_Checkpoint_0809::name() const{ return "08.09 - Fly back to Veilstone Pokemon Center for healing"; } +std::string AutoStory_Checkpoint_0809::start_text() const{ return "Got out of the warehouse with the key."; } +std::string AutoStory_Checkpoint_0809::end_text() const{ return "Entered Veilstone Pokemon Center for healing."; } +void AutoStory_Checkpoint_0809::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_119(env, context, options, stats); + checkpoint_0809(env, context, options, stats); } -std::string AutoStory_Checkpoint_120::name() const{ return "120 - S08 - Heal Pokemon"; } -std::string AutoStory_Checkpoint_120::start_text() const{ return "Entered Veilstone Pokemon Center for healing."; } -std::string AutoStory_Checkpoint_120::end_text() const{ return "Exited Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_120::run_checkpoint( +std::string AutoStory_Checkpoint_0810::name() const{ return "08.10 - Heal Pokemon"; } +std::string AutoStory_Checkpoint_0810::start_text() const{ return "Entered Veilstone Pokemon Center for healing."; } +std::string AutoStory_Checkpoint_0810::end_text() const{ return "Exited Veilstone Pokemon Center."; } +void AutoStory_Checkpoint_0810::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_120(env, context, options, stats); + checkpoint_0810(env, context, options, stats); } -std::string AutoStory_Checkpoint_121::name() const{ return "121 - S08 - Go to Galactic HQ"; } -std::string AutoStory_Checkpoint_121::start_text() const{ return "Exited Veilstone Pokemon Center."; } -std::string AutoStory_Checkpoint_121::end_text() const{ return "Entered Galactic HQ."; } -void AutoStory_Checkpoint_121::run_checkpoint( +std::string AutoStory_Checkpoint_0811::name() const{ return "08.11 - Go to Galactic HQ"; } +std::string AutoStory_Checkpoint_0811::start_text() const{ return "Exited Veilstone Pokemon Center."; } +std::string AutoStory_Checkpoint_0811::end_text() const{ return "Entered Galactic HQ."; } +void AutoStory_Checkpoint_0811::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_121(env, context, options, stats); + checkpoint_0811(env, context, options, stats); } -std::string AutoStory_Checkpoint_122::name() const{ return "122 - S08 - Deal with Cyrus at the Galactic HQ"; } -std::string AutoStory_Checkpoint_122::start_text() const{ return "Entered Galactic HQ."; } -std::string AutoStory_Checkpoint_122::end_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } -void AutoStory_Checkpoint_122::run_checkpoint( +std::string AutoStory_Checkpoint_0812::name() const{ return "08.12 - Deal with Cyrus at the Galactic HQ"; } +std::string AutoStory_Checkpoint_0812::start_text() const{ return "Entered Galactic HQ."; } +std::string AutoStory_Checkpoint_0812::end_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } +void AutoStory_Checkpoint_0812::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_122(env, context, options, stats); + checkpoint_0812(env, context, options, stats); } -std::string AutoStory_Checkpoint_123::name() const{ return "123 - S08 - Fly to Oreburgh City"; } -std::string AutoStory_Checkpoint_123::start_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } -std::string AutoStory_Checkpoint_123::end_text() const{ return "Flew to Oreburgh city and entered Pokemon Center."; } -void AutoStory_Checkpoint_123::run_checkpoint( +std::string AutoStory_Checkpoint_0813::name() const{ return "08.13 - Fly to Oreburgh City"; } +std::string AutoStory_Checkpoint_0813::start_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } +std::string AutoStory_Checkpoint_0813::end_text() const{ return "Flew to Oreburgh city and entered Pokemon Center."; } +void AutoStory_Checkpoint_0813::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_123(env, context, options, stats); + checkpoint_0813(env, context, options, stats); } -std::string AutoStory_Checkpoint_124::name() const{ return "124 - S08 - Heal Pokemon at Oreburgh City Pokemon Center"; } -std::string AutoStory_Checkpoint_124::start_text() const{ return "Flew to Oreburgh City and entered Pokemon Center."; } -std::string AutoStory_Checkpoint_124::end_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } -void AutoStory_Checkpoint_124::run_checkpoint( +std::string AutoStory_Checkpoint_0814::name() const{ return "08.14 - Heal Pokemon at Oreburgh City Pokemon Center"; } +std::string AutoStory_Checkpoint_0814::start_text() const{ return "Flew to Oreburgh City and entered Pokemon Center."; } +std::string AutoStory_Checkpoint_0814::end_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } +void AutoStory_Checkpoint_0814::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_124(env, context, options, stats); + checkpoint_0814(env, context, options, stats); } -std::string AutoStory_Checkpoint_125::name() const{ return "125 - S08 - Go from Oreburgh City to Mount Coronet entrance"; } -std::string AutoStory_Checkpoint_125::start_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } -std::string AutoStory_Checkpoint_125::end_text() const{ return "Entered Mount Coronet."; } -void AutoStory_Checkpoint_125::run_checkpoint( +std::string AutoStory_Checkpoint_0815::name() const{ return "08.15 - Go from Oreburgh City to Mount Coronet entrance"; } +std::string AutoStory_Checkpoint_0815::start_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } +std::string AutoStory_Checkpoint_0815::end_text() const{ return "Entered Mount Coronet."; } +void AutoStory_Checkpoint_0815::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_125(env, context, options, stats); + checkpoint_0815(env, context, options, stats); } -std::string AutoStory_Checkpoint_126::name() const{ return "126 - S08 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; } -std::string AutoStory_Checkpoint_126::start_text() const{ return "Entered Mount Coronet."; } -std::string AutoStory_Checkpoint_126::end_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } -void AutoStory_Checkpoint_126::run_checkpoint( +std::string AutoStory_Checkpoint_0816::name() const{ return "08.16 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; } +std::string AutoStory_Checkpoint_0816::start_text() const{ return "Entered Mount Coronet."; } +std::string AutoStory_Checkpoint_0816::end_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } +void AutoStory_Checkpoint_0816::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_126(env, context, options, stats); + checkpoint_0816(env, context, options, stats); } -std::string AutoStory_Checkpoint_127::name() const{ return "127 - S08 - Fly to Veilstone City"; } -std::string AutoStory_Checkpoint_127::start_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } -std::string AutoStory_Checkpoint_127::end_text() const{ return "Entered the Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_127::run_checkpoint( +std::string AutoStory_Checkpoint_0817::name() const{ return "08.17 - Fly to Veilstone City"; } +std::string AutoStory_Checkpoint_0817::start_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } +std::string AutoStory_Checkpoint_0817::end_text() const{ return "Entered the Veilstone Pokemon Center."; } +void AutoStory_Checkpoint_0817::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_127(env, context, options, stats); + checkpoint_0817(env, context, options, stats); } -std::string AutoStory_Checkpoint_128::name() const{ return "126 - S08 - Heal and exit"; } -std::string AutoStory_Checkpoint_128::start_text() const{ return "Entered the Veilstone Pokemon Center."; } -std::string AutoStory_Checkpoint_128::end_text() const{ return "Exited the Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_128::run_checkpoint( +std::string AutoStory_Checkpoint_0818::name() const{ return "08.18 - Heal and exit"; } +std::string AutoStory_Checkpoint_0818::start_text() const{ return "Entered the Veilstone Pokemon Center."; } +std::string AutoStory_Checkpoint_0818::end_text() const{ return "Exited the Veilstone Pokemon Center."; } +void AutoStory_Checkpoint_0818::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_128(env, context, options, stats); + checkpoint_0818(env, context, options, stats); } -std::string AutoStory_Checkpoint_129::name() const{ return "129 - S08 - Go to house South of Veilstone City"; } -std::string AutoStory_Checkpoint_129::start_text() const{ return "Exited the Veilstone Pokemon Center."; } -std::string AutoStory_Checkpoint_129::end_text() const{ return "Arrived at the house South of Veilstone City."; } -void AutoStory_Checkpoint_129::run_checkpoint( +std::string AutoStory_Checkpoint_0819::name() const{ return "08.19 - Go to house South of Veilstone City"; } +std::string AutoStory_Checkpoint_0819::start_text() const{ return "Exited the Veilstone Pokemon Center."; } +std::string AutoStory_Checkpoint_0819::end_text() const{ return "Arrived at the house South of Veilstone City."; } +void AutoStory_Checkpoint_0819::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_129(env, context, options, stats); + checkpoint_0819(env, context, options, stats); } -std::string AutoStory_Checkpoint_130::name() const{ return "130 - S08 - Go through the house South of Veilstone City"; } -std::string AutoStory_Checkpoint_130::start_text() const{ return "Arrived at the house South of Veilstone City."; } -std::string AutoStory_Checkpoint_130::end_text() const{ return "Exited the house South of Veilstone City."; } -void AutoStory_Checkpoint_130::run_checkpoint( +std::string AutoStory_Checkpoint_0820::name() const{ return "08.20 - Go through the house South of Veilstone City"; } +std::string AutoStory_Checkpoint_0820::start_text() const{ return "Arrived at the house South of Veilstone City."; } +std::string AutoStory_Checkpoint_0820::end_text() const{ return "Exited the house South of Veilstone City."; } +void AutoStory_Checkpoint_0820::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_130(env, context, options, stats); + checkpoint_0820(env, context, options, stats); } -std::string AutoStory_Checkpoint_131::name() const{ return "131 - S08 - Go through routes 214 and 222 to the house West of Sunyshore City"; } -std::string AutoStory_Checkpoint_131::start_text() const{ return "Exited house South of Veilstone City."; } -std::string AutoStory_Checkpoint_131::end_text() const{ return "Entered the house West of Sunyshore City."; } -void AutoStory_Checkpoint_131::run_checkpoint( +std::string AutoStory_Checkpoint_0821::name() const{ return "08.21 - Go through routes 214 and 222 to the house West of Sunyshore City"; } +std::string AutoStory_Checkpoint_0821::start_text() const{ return "Exited house South of Veilstone City."; } +std::string AutoStory_Checkpoint_0821::end_text() const{ return "Entered the house West of Sunyshore City."; } +void AutoStory_Checkpoint_0821::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_131(env, context, options, stats); + checkpoint_0821(env, context, options, stats); } -std::string AutoStory_Checkpoint_132::name() const{ return "132 - S08 - Go through the house West of Sunyshore City"; } -std::string AutoStory_Checkpoint_132::start_text() const{ return "Entered the house West of Sunyshore City."; } -std::string AutoStory_Checkpoint_132::end_text() const{ return "Entered Sunyshore City."; } -void AutoStory_Checkpoint_132::run_checkpoint( +std::string AutoStory_Checkpoint_0822::name() const{ return "08.22 - Go through the house West of Sunyshore City"; } +std::string AutoStory_Checkpoint_0822::start_text() const{ return "Entered the house West of Sunyshore City."; } +std::string AutoStory_Checkpoint_0822::end_text() const{ return "Entered Sunyshore City."; } +void AutoStory_Checkpoint_0822::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_132(env, context, options, stats); + checkpoint_0822(env, context, options, stats); } -std::string AutoStory_Checkpoint_133::name() const{ return "133 - S08 - Go to Sunyshore City's lighthouse"; } -std::string AutoStory_Checkpoint_133::start_text() const{ return "Entered Sunyshore City."; } -std::string AutoStory_Checkpoint_133::end_text() const{ return "Entered Sunyshore City's lighthouse."; } -void AutoStory_Checkpoint_133::run_checkpoint( +std::string AutoStory_Checkpoint_0823::name() const{ return "08.23 - Go to Sunyshore City's lighthouse"; } +std::string AutoStory_Checkpoint_0823::start_text() const{ return "Entered Sunyshore City."; } +std::string AutoStory_Checkpoint_0823::end_text() const{ return "Entered Sunyshore City's lighthouse."; } +void AutoStory_Checkpoint_0823::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_133(env, context, options, stats); + checkpoint_0823(env, context, options, stats); } -std::string AutoStory_Checkpoint_134::name() const{ return "134 - S08 - Talk to Volkner at the lighthouse"; } -std::string AutoStory_Checkpoint_134::start_text() const{ return "Entered Sunyshore City's lighthouse."; } -std::string AutoStory_Checkpoint_134::end_text() const{ return "Exited Sunyshore City's lighthouse."; } -void AutoStory_Checkpoint_134::run_checkpoint( +std::string AutoStory_Checkpoint_0824::name() const{ return "08.24 - Talk to Volkner at the lighthouse"; } +std::string AutoStory_Checkpoint_0824::start_text() const{ return "Entered Sunyshore City's lighthouse."; } +std::string AutoStory_Checkpoint_0824::end_text() const{ return "Exited Sunyshore City's lighthouse."; } +void AutoStory_Checkpoint_0824::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_134(env, context, options, stats); + checkpoint_0824(env, context, options, stats); } -std::string AutoStory_Checkpoint_135::name() const{ return "135 - S08 - Go to the Sunyshore City gym"; } -std::string AutoStory_Checkpoint_135::start_text() const{ return "Exited Sunyshore City's lighthouse."; } -std::string AutoStory_Checkpoint_135::end_text() const{ return "Entered Sunyshore City's gym."; } -void AutoStory_Checkpoint_135::run_checkpoint( +std::string AutoStory_Checkpoint_0825::name() const{ return "08.25 - Go to the Sunyshore City gym"; } +std::string AutoStory_Checkpoint_0825::start_text() const{ return "Exited Sunyshore City's lighthouse."; } +std::string AutoStory_Checkpoint_0825::end_text() const{ return "Entered Sunyshore City's gym."; } +void AutoStory_Checkpoint_0825::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_135(env, context, options, stats); + checkpoint_0825(env, context, options, stats); } - +std::string AutoStory_Checkpoint_0826::name() const{ return "08.26 - Sunnyshore Gym Puzzle"; } +std::string AutoStory_Checkpoint_0826::start_text() const{ return "Exited Sunyshore City's lighthouse."; } +std::string AutoStory_Checkpoint_0826::end_text() const{ return "Entered Sunyshore City's gym."; } +void AutoStory_Checkpoint_0826::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + checkpoint_0826(env, context, options, stats); +} void AutoStory_Checkpoint_021::run_checkpoint( SingleSwitchProgramEnvironment& env, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index abc2097442..39fff41a6f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -28,7 +28,7 @@ class AutoStory_Segment_08 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_111 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0801 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,14 +40,14 @@ class AutoStory_Checkpoint_111 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_111( +void checkpoint_0801( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_112 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0802 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -59,9 +59,9 @@ class AutoStory_Checkpoint_112 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_112(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_113 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0803 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -73,9 +73,9 @@ class AutoStory_Checkpoint_113 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_113(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_114 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0804 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -87,9 +87,9 @@ class AutoStory_Checkpoint_114 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_114(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_115 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0805 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -101,9 +101,9 @@ class AutoStory_Checkpoint_115 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_115(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_116 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0806 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -115,9 +115,9 @@ class AutoStory_Checkpoint_116 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_116(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_117 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0807 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -129,9 +129,9 @@ class AutoStory_Checkpoint_117 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_117(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_118 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0808 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -143,9 +143,9 @@ class AutoStory_Checkpoint_118 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_118(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_119 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0809 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -157,9 +157,9 @@ class AutoStory_Checkpoint_119 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_119(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_120 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0810 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -171,9 +171,9 @@ class AutoStory_Checkpoint_120 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_120(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_121 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0811 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -185,9 +185,9 @@ class AutoStory_Checkpoint_121 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_121(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_122 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0812 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -199,9 +199,9 @@ class AutoStory_Checkpoint_122 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_122(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_123 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0813 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -213,9 +213,9 @@ class AutoStory_Checkpoint_123 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_123(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_124 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0814 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -227,9 +227,9 @@ class AutoStory_Checkpoint_124 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_124(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_125 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0815 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -241,9 +241,9 @@ class AutoStory_Checkpoint_125 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_125(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_126 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0816 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -255,9 +255,9 @@ class AutoStory_Checkpoint_126 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_126(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_127 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0817 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -269,9 +269,9 @@ class AutoStory_Checkpoint_127 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_127(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_128 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0818 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -283,9 +283,9 @@ class AutoStory_Checkpoint_128 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_128(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_129 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0819 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -297,9 +297,9 @@ class AutoStory_Checkpoint_129 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_129(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_130 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0820 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -311,9 +311,9 @@ class AutoStory_Checkpoint_130 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_130(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_131 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0821 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -325,9 +325,9 @@ class AutoStory_Checkpoint_131 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_131(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_132 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0822 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -339,9 +339,9 @@ class AutoStory_Checkpoint_132 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_132(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_133 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0823 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -353,9 +353,9 @@ class AutoStory_Checkpoint_133 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_133(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_134 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0824 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -367,9 +367,9 @@ class AutoStory_Checkpoint_134 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_134(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_135 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0825 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -381,8 +381,21 @@ class AutoStory_Checkpoint_135 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_135(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0825(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_0826 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; +void checkpoint_0826(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint{ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 662101aec0..c28427e8ab 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -42,20 +42,20 @@ void AutoStory_Segment_09::run_segment( context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); - AutoStory_Checkpoint_137().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_138().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_139().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_140().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_141().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_142().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_143().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_144().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_145().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_146().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_147().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_148().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_149().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_150().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0901().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0902().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0903().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0904().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0905().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0906().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0907().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0908().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0909().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0910().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0911().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0912().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0913().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0914().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); @@ -1427,7 +1427,7 @@ static bool deal_with_cynthia_and_finish( return true; } -void checkpoint_137( +void checkpoint_0901( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1443,7 +1443,7 @@ void checkpoint_137( ); } -void checkpoint_138( +void checkpoint_0902( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1459,7 +1459,7 @@ void checkpoint_138( ); } -void checkpoint_139( +void checkpoint_0903( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1475,7 +1475,7 @@ void checkpoint_139( ); } -void checkpoint_140( +void checkpoint_0904( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1491,7 +1491,7 @@ void checkpoint_140( ); } -void checkpoint_141( +void checkpoint_0905( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1507,7 +1507,7 @@ void checkpoint_141( ); } -void checkpoint_142( +void checkpoint_0906( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1523,7 +1523,7 @@ void checkpoint_142( ); } -void checkpoint_143( +void checkpoint_0907( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1539,7 +1539,7 @@ void checkpoint_143( ); } -void checkpoint_144( +void checkpoint_0908( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1555,7 +1555,7 @@ void checkpoint_144( ); } -void checkpoint_145( +void checkpoint_0909( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1571,7 +1571,7 @@ void checkpoint_145( ); } -void checkpoint_146( +void checkpoint_0910( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1587,7 +1587,7 @@ void checkpoint_146( ); } -void checkpoint_147( +void checkpoint_0911( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1603,7 +1603,7 @@ void checkpoint_147( ); } -void checkpoint_148( +void checkpoint_0912( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1619,7 +1619,7 @@ void checkpoint_148( ); } -void checkpoint_149( +void checkpoint_0913( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1635,7 +1635,7 @@ void checkpoint_149( ); } -void checkpoint_150( +void checkpoint_0914( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1651,173 +1651,173 @@ void checkpoint_150( ); } -std::string AutoStory_Checkpoint_137::name() const{ return "137 - S09 - Leave the gym and fly to the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_137::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } -std::string AutoStory_Checkpoint_137::end_text() const{ return "Enter the Sunyshore Pokemon Center."; } -void AutoStory_Checkpoint_137::run_checkpoint( +std::string AutoStory_Checkpoint_0901::name() const{ return "09.01 - Leave the gym and fly to the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_0901::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } +std::string AutoStory_Checkpoint_0901::end_text() const{ return "Enter the Sunyshore Pokemon Center."; } +void AutoStory_Checkpoint_0901::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_137(env, context, options, stats); + checkpoint_0901(env, context, options, stats); } -std::string AutoStory_Checkpoint_138::name() const{ return "138 - S09 - Heal the Pokemon at the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_138::start_text() const{ return "Entered the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_138::end_text() const{ return "Exit the Sunyshore Pokemon Center."; } -void AutoStory_Checkpoint_138::run_checkpoint( +std::string AutoStory_Checkpoint_0902::name() const{ return "09.02 - Heal the Pokemon at the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_0902::start_text() const{ return "Entered the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_0902::end_text() const{ return "Exit the Sunyshore Pokemon Center."; } +void AutoStory_Checkpoint_0902::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_138(env, context, options, stats); + checkpoint_0902(env, context, options, stats); } -std::string AutoStory_Checkpoint_139::name() const{ return "139 - S09 - Start at the Sunyshore Pokemon Center and go to the Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_139::start_text() const{ return "Exited the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_139::end_text() const{ return "Enter the Victory Road Pokemon Center."; } -void AutoStory_Checkpoint_139::run_checkpoint( +std::string AutoStory_Checkpoint_0903::name() const{ return "09.03 - Start at the Sunyshore Pokemon Center and go to the Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_0903::start_text() const{ return "Exited the Sunyshore Pokemon Center."; } +std::string AutoStory_Checkpoint_0903::end_text() const{ return "Enter the Victory Road Pokemon Center."; } +void AutoStory_Checkpoint_0903::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_139(env, context, options, stats); + checkpoint_0903(env, context, options, stats); } -std::string AutoStory_Checkpoint_140::name() const{ return "140 - S09 - Heal at the Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_140::start_text() const{ return "Entered Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_140::end_text() const{ return "Exit Victory Road Pokemon Center."; } -void AutoStory_Checkpoint_140::run_checkpoint( +std::string AutoStory_Checkpoint_0904::name() const{ return "09.04 - Heal at the Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_0904::start_text() const{ return "Entered Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_0904::end_text() const{ return "Exit Victory Road Pokemon Center."; } +void AutoStory_Checkpoint_0904::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_140(env, context, options, stats); + checkpoint_0904(env, context, options, stats); } -std::string AutoStory_Checkpoint_141::name() const{ return "141 - S09 - Enter the Victory Road cave."; } -std::string AutoStory_Checkpoint_141::start_text() const{ return "Exited Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_141::end_text() const{ return "Enter the Victory Road cave."; } -void AutoStory_Checkpoint_141::run_checkpoint( +std::string AutoStory_Checkpoint_0905::name() const{ return "09.05 - Enter the Victory Road cave."; } +std::string AutoStory_Checkpoint_0905::start_text() const{ return "Exited Victory Road Pokemon Center."; } +std::string AutoStory_Checkpoint_0905::end_text() const{ return "Enter the Victory Road cave."; } +void AutoStory_Checkpoint_0905::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_141(env, context, options, stats); + checkpoint_0905(env, context, options, stats); } -std::string AutoStory_Checkpoint_142::name() const{ return "142 - S09 - Go through the Victory Road cave."; } -std::string AutoStory_Checkpoint_142::start_text() const{ return "Entered Victory Road cave."; } -std::string AutoStory_Checkpoint_142::end_text() const{ return "Exit Victory Road cave."; } -void AutoStory_Checkpoint_142::run_checkpoint( +std::string AutoStory_Checkpoint_0906::name() const{ return "09.06 - Go through the Victory Road cave."; } +std::string AutoStory_Checkpoint_0906::start_text() const{ return "Entered Victory Road cave."; } +std::string AutoStory_Checkpoint_0906::end_text() const{ return "Exit Victory Road cave."; } +void AutoStory_Checkpoint_0906::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_142(env, context, options, stats); + checkpoint_0906(env, context, options, stats); } -std::string AutoStory_Checkpoint_143::name() const{ return "143 - S09 - Go through the end of Victory Road into the Elite 4 building"; } -std::string AutoStory_Checkpoint_143::start_text() const{ return "Exited Victory Road cave."; } -std::string AutoStory_Checkpoint_143::end_text() const{ return "Enter the Elite 4 building."; } -void AutoStory_Checkpoint_143::run_checkpoint( +std::string AutoStory_Checkpoint_0907::name() const{ return "09.07 - Go through the end of Victory Road into the Elite 4 building"; } +std::string AutoStory_Checkpoint_0907::start_text() const{ return "Exited Victory Road cave."; } +std::string AutoStory_Checkpoint_0907::end_text() const{ return "Enter the Elite 4 building."; } +void AutoStory_Checkpoint_0907::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_143(env, context, options, stats); + checkpoint_0907(env, context, options, stats); } -std::string AutoStory_Checkpoint_144::name() const{ return "144 - S09 - Enter the Elite 4 building and get ready to enter."; } -std::string AutoStory_Checkpoint_144::start_text() const{ return "Entered the Elite 4 building."; } -std::string AutoStory_Checkpoint_144::end_text() const{ return "Ready in front of the Elite 4."; } -void AutoStory_Checkpoint_144::run_checkpoint( +std::string AutoStory_Checkpoint_0908::name() const{ return "09.08 - Enter the Elite 4 building and get ready to enter."; } +std::string AutoStory_Checkpoint_0908::start_text() const{ return "Entered the Elite 4 building."; } +std::string AutoStory_Checkpoint_0908::end_text() const{ return "Ready in front of the Elite 4."; } +void AutoStory_Checkpoint_0908::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_144(env, context, options, stats); + checkpoint_0908(env, context, options, stats); } -std::string AutoStory_Checkpoint_145::name() const{ return "145 - S09 - Enter the first room of the Elite 4."; } -std::string AutoStory_Checkpoint_145::start_text() const{ return "Readied in front of the Elite 4."; } -std::string AutoStory_Checkpoint_145::end_text() const{ return "Enter the Elite 4's first room."; } -void AutoStory_Checkpoint_145::run_checkpoint( +std::string AutoStory_Checkpoint_0909::name() const{ return "09.09 - Enter the first room of the Elite 4."; } +std::string AutoStory_Checkpoint_0909::start_text() const{ return "Readied in front of the Elite 4."; } +std::string AutoStory_Checkpoint_0909::end_text() const{ return "Enter the Elite 4's first room."; } +void AutoStory_Checkpoint_0909::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_145(env, context, options, stats); + checkpoint_0909(env, context, options, stats); } -std::string AutoStory_Checkpoint_146::name() const{ return "146 - S09 - Deal with Aaron and go to the second room."; } -std::string AutoStory_Checkpoint_146::start_text() const{ return "Entered the Elite 4's first room."; } -std::string AutoStory_Checkpoint_146::end_text() const{ return "Deal with Aaron and enter the second room."; } -void AutoStory_Checkpoint_146::run_checkpoint( +std::string AutoStory_Checkpoint_0910::name() const{ return "09.10 - Deal with Aaron and go to the second room."; } +std::string AutoStory_Checkpoint_0910::start_text() const{ return "Entered the Elite 4's first room."; } +std::string AutoStory_Checkpoint_0910::end_text() const{ return "Deal with Aaron and enter the second room."; } +void AutoStory_Checkpoint_0910::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_146(env, context, options, stats); + checkpoint_0910(env, context, options, stats); } -std::string AutoStory_Checkpoint_147::name() const{ return "147 - S09 - Deal with Bertha and go to the third room."; } -std::string AutoStory_Checkpoint_147::start_text() const{ return "Dealt with Aaron and entered the second room."; } -std::string AutoStory_Checkpoint_147::end_text() const{ return "Deal with Bertha and enter the third room."; } -void AutoStory_Checkpoint_147::run_checkpoint( +std::string AutoStory_Checkpoint_0911::name() const{ return "09.11 - Deal with Bertha and go to the third room."; } +std::string AutoStory_Checkpoint_0911::start_text() const{ return "Dealt with Aaron and entered the second room."; } +std::string AutoStory_Checkpoint_0911::end_text() const{ return "Deal with Bertha and enter the third room."; } +void AutoStory_Checkpoint_0911::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_147(env, context, options, stats); + checkpoint_0911(env, context, options, stats); } -std::string AutoStory_Checkpoint_148::name() const{ return "148 - S09 - Deal with Flint and go to the fourth room."; } -std::string AutoStory_Checkpoint_148::start_text() const{ return "Dealt with Bertha and entered the third room."; } -std::string AutoStory_Checkpoint_148::end_text() const{ return "Deal with Flint and enter the fourth room."; } -void AutoStory_Checkpoint_148::run_checkpoint( +std::string AutoStory_Checkpoint_0912::name() const{ return "09.12 - Deal with Flint and go to the fourth room."; } +std::string AutoStory_Checkpoint_0912::start_text() const{ return "Dealt with Bertha and entered the third room."; } +std::string AutoStory_Checkpoint_0912::end_text() const{ return "Deal with Flint and enter the fourth room."; } +void AutoStory_Checkpoint_0912::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_148(env, context, options, stats); + checkpoint_0912(env, context, options, stats); } -std::string AutoStory_Checkpoint_149::name() const{ return "149 - S09 - Deal with Lucian and go to the last room."; } -std::string AutoStory_Checkpoint_149::start_text() const{ return "Dealt with Flint and entered the fourth room."; } -std::string AutoStory_Checkpoint_149::end_text() const{ return "Deal with Lucian and enter the last room."; } -void AutoStory_Checkpoint_149::run_checkpoint( +std::string AutoStory_Checkpoint_0913::name() const{ return "09.13 - Deal with Lucian and go to the last room."; } +std::string AutoStory_Checkpoint_0913::start_text() const{ return "Dealt with Flint and entered the fourth room."; } +std::string AutoStory_Checkpoint_0913::end_text() const{ return "Deal with Lucian and enter the last room."; } +void AutoStory_Checkpoint_0913::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_149(env, context, options, stats); + checkpoint_0913(env, context, options, stats); } -std::string AutoStory_Checkpoint_150::name() const{ return "150 - S09 - Deal with Cynthia and finish the game."; } -std::string AutoStory_Checkpoint_150::start_text() const{ return "Dealt with Lucian and entered the last room."; } -std::string AutoStory_Checkpoint_150::end_text() const{ return "Deal with Cynthia and finish the game."; } -void AutoStory_Checkpoint_150::run_checkpoint( +std::string AutoStory_Checkpoint_0914::name() const{ return "09.14 - Deal with Cynthia and finish the game."; } +std::string AutoStory_Checkpoint_0914::start_text() const{ return "Dealt with Lucian and entered the last room."; } +std::string AutoStory_Checkpoint_0914::end_text() const{ return "Deal with Cynthia and finish the game."; } +void AutoStory_Checkpoint_0914::run_checkpoint( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) const{ - checkpoint_150(env, context, options, stats); + checkpoint_0914(env, context, options, stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h index 7b3364437e..3010af027e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h @@ -28,7 +28,7 @@ class AutoStory_Segment_09 : public AutoStory_Segment{ ) const override; }; -class AutoStory_Checkpoint_137 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0901 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -40,14 +40,14 @@ class AutoStory_Checkpoint_137 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_137( +void checkpoint_0901( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_138 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0902 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -59,14 +59,14 @@ class AutoStory_Checkpoint_138 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_138( +void checkpoint_0902( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_139 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0903 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -78,14 +78,14 @@ class AutoStory_Checkpoint_139 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_139( +void checkpoint_0903( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_140 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0904 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -97,14 +97,14 @@ class AutoStory_Checkpoint_140 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_140( +void checkpoint_0904( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_141 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0905 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -116,7 +116,7 @@ class AutoStory_Checkpoint_141 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_141( +void checkpoint_0905( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -124,7 +124,7 @@ void checkpoint_141( ); -class AutoStory_Checkpoint_142 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0906 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -136,7 +136,7 @@ class AutoStory_Checkpoint_142 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_142( +void checkpoint_0906( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -144,7 +144,7 @@ void checkpoint_142( ); -class AutoStory_Checkpoint_143 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0907 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -156,7 +156,7 @@ class AutoStory_Checkpoint_143 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_143( +void checkpoint_0907( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -164,7 +164,7 @@ void checkpoint_143( ); -class AutoStory_Checkpoint_144 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0908 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -176,7 +176,7 @@ class AutoStory_Checkpoint_144 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_144( +void checkpoint_0908( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -184,7 +184,7 @@ void checkpoint_144( ); -class AutoStory_Checkpoint_145 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0909 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -196,7 +196,7 @@ class AutoStory_Checkpoint_145 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_145( +void checkpoint_0909( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -204,7 +204,7 @@ void checkpoint_145( ); -class AutoStory_Checkpoint_146 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0910 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -216,7 +216,7 @@ class AutoStory_Checkpoint_146 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_146( +void checkpoint_0910( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -224,7 +224,7 @@ void checkpoint_146( ); -class AutoStory_Checkpoint_147 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0911 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -236,7 +236,7 @@ class AutoStory_Checkpoint_147 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_147( +void checkpoint_0911( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -244,7 +244,7 @@ void checkpoint_147( ); -class AutoStory_Checkpoint_148 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0912 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -256,14 +256,14 @@ class AutoStory_Checkpoint_148 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_148( +void checkpoint_0912( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_149 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0913 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -275,14 +275,14 @@ class AutoStory_Checkpoint_149 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_149( +void checkpoint_0913( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_Checkpoint_150 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0914 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -294,7 +294,7 @@ class AutoStory_Checkpoint_150 : public AutoStory_Checkpoint{ AutoStoryStats& stats ) const override; }; -void checkpoint_150( +void checkpoint_0914( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, From ee9190c7282d63de94bd862f0397dacd78437e89 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Tue, 29 Sep 2026 22:08:21 -0500 Subject: [PATCH 17/39] Fix BDSP AutoStory build errors --- .../StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp | 5 +---- .../AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index ba02b9d09e..56a04ff3a5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -783,12 +783,9 @@ namespace PokemonAutomation { case StarterChoice::CHIMCHAR: select_chimchar(env.console, context); break; case StarterChoice::PIPLUP: select_piplup(env.console, context); break; } - //Fight Starly fight_starly(env.console, context); + post_starly_clenaup(env.console, context); }, - true - post_starly_clenaup(env.console, context); - } true ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp index faa27a431f..ac2e61fa78 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -16,7 +16,7 @@ #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/Images/SolidColorTest.h" -#include "CommonTools/OCR/OCR_RawOCR.h" +#include "CommonTools/OCR/OCR_Routines.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Inference/Pokemon_NameReader.h" From a5b9933262626e081c2a91f443b4282eec9abc3a Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 4 Oct 2026 11:09:19 -0500 Subject: [PATCH 18/39] Improve AutoStory route handling and detection This commit adds safer Poketch visibility checks, stronger black/white screen handling, and broader battle/dialog recovery for the Jubilife/Oreburgh story route. It also introduces movement and doorway detection helpers to better navigate exits, trainer encounters, and the Oreburgh Mine path while reducing false negatives and checkpoint retries. --- .github/copilot-instructions.md | 4 + ...okemonBDSP_AutoStory_OverworldDetector.cpp | 68 + .../PokemonBDSP_AutoStory_OverworldDetector.h | 29 + ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 66 +- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 17 + .../PokemonBDSP_AutoStory_Segment_01.cpp | 3570 ++++++++++++++++- 6 files changed, 3715 insertions(+), 39 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..fd71411423 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,4 @@ +# Copilot Instructions + +## Project Guidelines +- Avoid running PowerShell terminal commands in this workspace; they frequently crash/close the project because they aren't run in the correct directory. Use file-reading, search and edit tools instead. \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp index 8cb97c5179..ce23be29b7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp @@ -4,8 +4,14 @@ * */ +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/Images/SolidColorTest.h" #include "PokemonBDSP_AutoStory_OverworldDetector.h" +using namespace std::chrono_literals; + namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ @@ -53,6 +59,68 @@ bool OverworldWatcher::process_frame(const ImageViewRGB32& frame, WallClock /*ti } +// --------------------------------------------------------------------------- +// PoketchButtonDetector +// --------------------------------------------------------------------------- + +PoketchButtonDetector::PoketchButtonDetector(Color color) + : m_color(color) + , m_button(0.968, 0.170, 0.022, 0.120) +{} + +void PoketchButtonDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_button); +} + +bool PoketchButtonDetector::detect(const ImageViewRGB32& screen){ + ImageStats stats = image_stats(extract_box_reference(screen, m_button)); + // The button is a glossy red with a highlight, so allow a wide stddev. + return is_solid(stats, {0.74, 0.10, 0.16}, 0.12, 80); +} + +PoketchButtonWatcher::PoketchButtonWatcher(Color color) + : PoketchButtonDetector(color) + , VisualInferenceCallback("PoketchButtonWatcher") +{} + +void PoketchButtonWatcher::make_overlays(VideoOverlaySet& items) const{ + PoketchButtonDetector::make_overlays(items); +} + +bool PoketchButtonWatcher::process_frame(const ImageViewRGB32& frame, WallClock){ + return detect(frame); +} + + +static bool look_for_poketch_button(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + PoketchButtonWatcher watcher; + return wait_until(stream, context, 2s, {{watcher}}) == 0; +} + +bool ensure_poketch_visible(VideoStream& stream, ProControllerContext& context){ + for (size_t attempt = 0; attempt < 3; attempt++){ + if (look_for_poketch_button(stream, context)){ + return true; + } + stream.log("[AutoStory] Poketch button not found. Attempt " + std::to_string(attempt + 1) + " of 3.", COLOR_ORANGE); + + if (attempt == 0){ + // Most likely hidden: one tap shows it. + pbf_press_button(context, BUTTON_R, 205ms, 0ms); + }else{ + // Long press hides it, then a tap shows it at the normal size. + pbf_press_button(context, BUTTON_R, 1548ms, 0ms); + pbf_wait(context, 1418ms); + pbf_press_button(context, BUTTON_R, 143ms, 0ms); + } + } + + // Check the result of the last press sequence. + return look_for_poketch_button(stream, context); +} + + } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h index aa0bddff24..044f75a8cf 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h @@ -21,6 +21,8 @@ #include "Common/Cpp/Color.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" @@ -59,6 +61,33 @@ class OverworldWatcher : public OverworldDetector, public VisualInferenceCallbac }; +// Detects the big red button on the right edge of the Poketch overlay. +// Present only while the Poketch is shown at its normal size. +class PoketchButtonDetector : public StaticScreenDetector{ +public: + PoketchButtonDetector(Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + ImageFloatBox m_button; +}; + +class PoketchButtonWatcher : public PoketchButtonDetector, public VisualInferenceCallback{ +public: + PoketchButtonWatcher(Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; +}; + +// Tries up to 3 times to get the Poketch button on screen using R presses. +// Returns true if the button was seen. +bool ensure_poketch_visible(VideoStream& stream, ProControllerContext& context); + + } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 3f7eab2e3a..12d9282b00 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -913,6 +913,65 @@ namespace PokemonAutomation { // Movement Helpers // --------------------------------------------------------------------------- + bool detect_black_screen_after_movement( + VideoStream& stream, + ProControllerContext& context, + Milliseconds timeout + ) { + BlackScreenWatcher black_screen; + return wait_until(stream, context, timeout, {{black_screen}}) == 0; + } + + bool try_right_down_exit_search( + VideoStream& stream, + ProControllerContext& context, + size_t iterations + ) { + for (size_t i = 0; i < iterations; i++) { + pbf_move_left_joystick(context, { 1.0, 0.0 }, 80ms, 0ms); + pbf_move_left_joystick(context, { 0.0, -1.0 }, 220ms, 0ms); + + if (detect_black_screen_after_movement(stream, context, 1000ms)) { + return true; + } + } + return false; + } + + void leave_pokemon_center_before_barry_1( + VideoStream& stream, + ProControllerContext& context + ) { + // Original down movement to leave Pokemon Center. + pbf_move_left_joystick(context, { 0.0, -1.0 }, 814ms, 0ms); + + if (detect_black_screen_after_movement(stream, context, 1200ms)) { + pbf_wait(context, 2340ms); + return; + } + + stream.log("[AutoStory] Pokemon Center exit black screen not detected. Searching with right/down movement.", COLOR_ORANGE); + + if (try_right_down_exit_search(stream, context, 20)) { + pbf_wait(context, 2340ms); + return; + } + + stream.log("[AutoStory] First exit search failed. Repositioning left and retrying.", COLOR_ORANGE); + pbf_move_left_joystick(context, { -1.0, 0.0 }, 5000ms, 0ms); + + if (try_right_down_exit_search(stream, context, 30)) { + pbf_wait(context, 2340ms); + return; + } + + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to find Pokemon Center exit black screen before Barry 1. Triggering checkpoint retry.", + stream + ); + } + // --------------------------------------------------------------------------- // Generic Movement Helper - Walk through grass in specified direction for // specified steps, attempting to run from any battles that are encountered. @@ -927,6 +986,7 @@ namespace PokemonAutomation { BattleType battle_type ) { BlackScreenWatcher battle_start; + WhiteScreenOverWatcher white_screen(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}); BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); BattleDialogWatcher battle_dialog(COLOR_YELLOW); @@ -950,8 +1010,8 @@ namespace PokemonAutomation { context, 2500ms, { + {white_screen}, {battle_start}, - {battle_menu}, {battle_dialog}, } ); @@ -963,9 +1023,9 @@ namespace PokemonAutomation { COLOR_ORANGE ); - // If battle_ret == 0 (black screen transition), wait for the + // If battle_ret == 0/1 (black/white transition), wait for the // battle menu to fully appear before attempting to navigate. - if (battle_ret == 0) { + if (battle_ret == 0 || battle_ret == 1) { BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); wait_until(stream, context, 10s, {{wait_for_menu}}); context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h index c823ec6586..3bafa3d457 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -114,6 +114,23 @@ namespace PokemonAutomation { const std::string& direction_name, BattleType battle_type = BattleType::STANDARD ); + + bool detect_black_screen_after_movement( + VideoStream& stream, + ProControllerContext& context, + Milliseconds timeout + ); + + bool try_right_down_exit_search( + VideoStream& stream, + ProControllerContext& context, + size_t iterations + ); + + void leave_pokemon_center_before_barry_1( + VideoStream& stream, + ProControllerContext& context + ); // --------------------------------------------------------------------------- // Helper function to navigate the poketech challenge clown dialogue. // Example usage diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 11348c32bd..7dc8790bfd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -5,12 +5,20 @@ * */ +#include +#include +#include #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTools/ImageDiff.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" #include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP_AutoStory_Segment_01.h" #include "PokemonBDSP_AutoStory_Segment_00_tools.h" @@ -20,6 +28,185 @@ using namespace std::chrono_literals; namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { + enum class MoveDirection { UP, DOWN, LEFT, RIGHT }; + + // Left joystick vector for a direction (matches the game's joystick convention used in this file). + static std::pair joystick_for(MoveDirection direction) { + switch (direction) { + case MoveDirection::UP: return { 0.0, 1.0 }; + case MoveDirection::DOWN: return { 0.0, -1.0 }; + case MoveDirection::LEFT: return { -1.0, 0.0 }; + case MoveDirection::RIGHT: return { 1.0, 0.0 }; + } + return { 0.0, 0.0 }; + } + + // Runs from a wild battle that is already in progress (menu visible or about to be). + // Throws OperationFailedException if it cannot escape within `timeout`. + static void escape_wild_battle( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD, + std::chrono::seconds timeout = 30s + ) { + bool escaped = false; + auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + continue; + } + + pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}) == 0) { + escaped = true; + break; + } + } + if (!escaped) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "escape_wild_battle: Failed to run from battle.", + stream + ); + } + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + } + + // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. + // Returns false (without throwing) only if every attempt times out, so callers can skip + // an optional interaction. Multiple attempts guard against a single missed detection. + static bool try_detect_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label, + size_t attempts = 3, + std::chrono::milliseconds window = 5000ms + ) { + for (size_t attempt = 0; attempt < attempts; attempt++) { + ShortDialogWatcher watcher_cyan(COLOR_CYAN); + ShortDialogWatcher watcher_red(COLOR_RED); + if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0) { + stream.log(label + " dialogue detected."); + return true; + } + stream.log( + label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + std::to_string(attempts) + ").", + COLOR_ORANGE + ); + } + return false; + } + + struct DoorwayWalkOptions { + BattleType battle_type = BattleType::STANDARD; + // Active movement per step. Time spent in battles or waiting is never counted. + std::chrono::milliseconds step = 200ms; + // Extra walking allowed beyond `travel_time` before giving up. + std::chrono::milliseconds grace = 1600ms; + // How long to watch for a black screen after each step. + std::chrono::milliseconds black_screen_window = 1500ms; + // After a black screen, how long to wait for a battle menu before deciding it was a doorway. + std::chrono::milliseconds battle_menu_window = 8000ms; + size_t max_battles = 10; + // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). + bool detect_white_screen = false; + }; + + // Reusable: walk in `direction` for a cumulative `travel_time` of active movement until + // a black screen NOT followed by a battle menu (a doorway/area transition) is seen. + // Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are + // escaped and do not consume the time budget. + // Throws OperationFailedException if the doorway is not found within + // travel_time + options.grace, if more than options.max_battles occur, or if a battle + // cannot be escaped. + // Returns the total active walking time used. + static std::chrono::milliseconds walk_until_doorway( + VideoStream& stream, + ProControllerContext& context, + MoveDirection direction, + std::chrono::milliseconds travel_time, + const DoorwayWalkOptions& options = DoorwayWalkOptions() + ) { + enum class NavState { WALKING, BLACK_SCREEN, IN_BATTLE, DONE }; + + const auto [x, y] = joystick_for(direction); + const std::chrono::milliseconds max_travel = travel_time + options.grace; + + std::chrono::milliseconds walked = 0ms; + size_t battles = 0; + NavState state = NavState::WALKING; + + while (state != NavState::DONE) { + switch (state) { + case NavState::WALKING: { + if (walked >= max_travel) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "walk_until_doorway: Doorway not found within time budget.", + stream + ); + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {x, y}, options.step, 0ms); + context.wait_for_all_requests(); + walked += options.step; + + BlackScreenWatcher black_screen; + WhiteScreenWatcher white_screen; + if (options.detect_white_screen) { + int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); + if (ret == 0) { + state = NavState::BLACK_SCREEN; + } else if (ret == 1) { + stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); + state = NavState::DONE; + } + } else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0) { + state = NavState::BLACK_SCREEN; + } + break; + } + case NavState::BLACK_SCREEN: { + stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); + BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); + if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0) { + state = NavState::IN_BATTLE; + } else { + state = NavState::DONE; + } + break; + } + case NavState::IN_BATTLE: { + if (++battles > options.max_battles) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "walk_until_doorway: Too many battles while walking.", + stream + ); + } + stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + std::to_string(travel_time.count()) + "ms.", COLOR_ORANGE); + escape_wild_battle(stream, context, options.battle_type); + state = NavState::WALKING; + break; + } + case NavState::DONE: + break; + } + } + stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", COLOR_GREEN); + return walked; + } + static void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { stream.log("Starting Barry 1 battle..."); @@ -65,6 +252,161 @@ namespace PokemonAutomation { ); } + static void handle_unexpected_trainer_battle( + SingleSwitchProgramEnvironment& env, + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("Unexpected trainer dialogue detected.", COLOR_ORANGE); + + // Clear the white dialogue boxes before entering battle logic. + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + + Barry_1_fight(env, stream, context); + + ShortDialogWatcher post_battle_dialog_cyan(COLOR_CYAN); + ShortDialogWatcher post_battle_dialog_red(COLOR_RED); + while (true) { + int dialog_ret = wait_until( + stream, + context, + 3s, + { + {post_battle_dialog_cyan}, + {post_battle_dialog_red}, + } + ); + if (dialog_ret < 0) { + break; + } + pbf_mash_button(context, BUTTON_B, 1200ms); + context.wait_for_all_requests(); + } + + pbf_move_left_joystick(context, {0, -1}, 1000ms, 0ms); + walk_through_grass(stream, context, 1, 0, 10, "right through grass"); + } + + static void walk_through_grass_with_trainer_watch( + SingleSwitchProgramEnvironment& env, + VideoStream& stream, + ProControllerContext& context, + int8_t x, + int8_t y, + size_t steps, + const std::string& direction_name, + BattleType battle_type = BattleType::STANDARD + ) { + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + for (size_t step = 0; step < steps; step++) { + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {(double)x, (double)y}, 200ms, 0ms); + + context.wait_for_all_requests(); + + ShortDialogWatcher trainer_dialog_cyan(COLOR_CYAN); + ShortDialogWatcher trainer_dialog_red(COLOR_RED); + int battle_ret = wait_until( + stream, + context, + 4500ms, + { + {trainer_dialog_cyan}, + {trainer_dialog_red}, + {battle_start}, + {battle_menu}, + {battle_dialog}, + } + ); + + if (battle_ret == 0 || battle_ret == 1) { + handle_unexpected_trainer_battle(env, stream, context); + return; + } + + if (battle_ret >= 2) { + stream.log("[AutoStory] Wild battle detected during movement.", COLOR_ORANGE); + + if (battle_ret == 2) { + BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); + wait_until(stream, context, 10s, {{wait_for_menu}}); + context.wait_for_all_requests(); + } + + bool escaped = false; + size_t overworld_streak = 0; + auto deadline = std::chrono::steady_clock::now() + 30s; + + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + // No battle menu for 3s. If the overworld is showing on + // two consecutive checks, the battle is already over. + OverworldWatcher overworld_check; + if (wait_until(stream, context, 1s, {{overworld_check}}) == 0) { + overworld_streak++; + } else { + overworld_streak = 0; + } + if (overworld_streak >= 2) { + stream.log("[AutoStory] Overworld detected without battle menu. Battle already over.", COLOR_ORANGE); + escaped = true; + break; + } + continue; + } + overworld_streak = 0; + + // Cursor direction can drift; try both vertical directions. + pbf_move_left_joystick(context, {0, 1}, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end; + BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); + if (run_result == 0) { + escaped = true; + break; + } + + if (run_result == 1) { + pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); + pbf_press_button(context, BUTTON_A, 60ms, 0ms); + context.wait_for_all_requests(); + + EndBattleWatcher battle_end_alt; + BattleMenuWatcher back_to_menu_alt(battle_type, COLOR_YELLOW); + int run_result_alt = wait_until(stream, context, 4s, {{battle_end_alt}, {back_to_menu_alt}}); + if (run_result_alt == 0) { + escaped = true; + break; + } + } + } + + if (!escaped) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to run from wild battle while moving " + direction_name + ".", + stream + ); + } + + OverworldWatcher overworld; + wait_until(stream, context, 5s, {{overworld}}); + } + } + } + // ---------------------------------------------------------------- // Segment 1 Part 1: Jubilife City to Oreburgh Gate // Navigates the player from Jubilife City to the Oreburgh Gate, including the @@ -80,7 +422,7 @@ namespace PokemonAutomation { pbf_wait(context, 127ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); pbf_wait(context, 698ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 814ms, 0ms); + leave_pokemon_center_before_barry_1(stream, context); pbf_wait(context, 2340ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); pbf_wait(context, 348ms); @@ -95,21 +437,84 @@ namespace PokemonAutomation { context.wait_for_all_requests(); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); pbf_wait(context, 1647ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 415ms, 0ms); - pbf_wait(context, 390ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 321ms, 0ms); - walk_through_grass(stream, context, 1, 0, 16, "right through grass"); - walk_through_grass(stream, context, 0, -1, 3, "down through grass"); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1952ms, 0ms); - walk_through_grass(stream, context, 1, 0, 16, "right through grass"); - walk_through_grass(stream, context, 0, 1, 16, "up through grass"); - pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + wait_for_dialogue(stream, context, "Dallas fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Dallas + context.wait_for_all_requests(); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); + walk_through_grass(stream, context, 1, 0, 10, "right through grass"); + //there is a small chance that the player dodges sSebastian, so this + //function addresses that edge case + if (try_detect_dialogue(stream, context, "Sebastian fight")) { + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Sebastian + context.wait_for_all_requests(); + } else { + stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); + } + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2000ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { + BlackScreenWatcher black_screen; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + for (size_t i = 0; i < 10; i++) { + pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + } + pbf_move_left_joystick(context, { 0, 1 }, 10000ms, 0ms); + }, + { black_screen } + ); + if (ret == 0) { + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + + if (!black_screen_found) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", + stream + ); + } } static void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {1.000000, -0.000000 }, 879ms, 0ms); + wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); + pbf_mash_button(context, BUTTON_B, 15000ms); + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + //Similar to sebastian, an edge case exists where we run into too many + //wild battles and don't reach curtis or diana. This detect dialog function solves that. + while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)) { + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Curtis + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)) { + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Diana + // Walk right until the gate exit transition (white screen, or black screen without a battle menu). + // Wild battles are escaped without using up the budget. Throws on failure so the + // checkpoint is retried. + DoorwayWalkOptions gate_exit_options; + gate_exit_options.detect_white_screen = true; + walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); + context.wait_for_all_requests(); } // --------------------------------------------------------------------------- // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine @@ -121,37 +526,3129 @@ namespace PokemonAutomation { pbf_wait(context, 2000ms); context.wait_for_all_requests(); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - wait_for_dialogue(stream, context, "Phase 2 (oreburgh tour dialog)"); + while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)) { + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); + } pbf_mash_button(context, BUTTON_B, 12000ms); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); pbf_wait(context, 23ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { + BlackScreenWatcher black_screen; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); + }, + { black_screen } + ); + if (ret == 0) { + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + if (!black_screen_found) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "oreburgh_gate_to_mine: No black screen after 3 attempts.", + stream + ); + } + + // Let the mine entrance transition fully finish so the next step's black screen + // watcher can't pick up the tail of this one. + BlackScreenOverWatcher black_over; + wait_until(stream, context, 6000ms, {{black_over}}); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); } - static void oreburgh_mine_nav(SingleSwitchProgramEnvironment& env, + // Aligns the player between the two rocks using template matching in fixed ROIs, + // then faces up and presses A to talk to the NPC. + static void align_between_rocks_and_talk( VideoStream& stream, ProControllerContext& context) { + auto rock1_image = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/rock_smash_rock1.png"); + auto rock2_image = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/rock_smash_rock2.png"); + auto helmet_image = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/roark_helmet.png"); + const ImageFloatBox rock1_box(0.390, 0.370, 0.075, 0.130); + const ImageFloatBox rock2_box(0.529, 0.370, 0.075, 0.130); + const ImageFloatBox helmet_box(0.474, 0.32, 0.035, 0.065); + const double max_rmsd = 60; + ImageMatchDetector rock1(rock1_image, rock1_box, max_rmsd, true); + ImageMatchDetector rock2(rock2_image, rock2_box, max_rmsd, true); + ImageMatchDetector helmet(helmet_image, helmet_box, max_rmsd, true); + + auto score = [&]() { + context.wait_for_all_requests(); + VideoSnapshot frame = stream.video().snapshot(); + return std::array{rock1.rmsd(frame), rock2.rmsd(frame), helmet.rmsd(frame)}; + }; + auto total = [](const std::array& s) { return s[0] + s[1] + s[2]; }; + + const size_t MAX_ATTEMPTS = 12; + int8_t dir_x = 1; + bool aligned = false; + auto prev = score(); + for (size_t attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + if (prev[0] <= max_rmsd && prev[1] <= max_rmsd && prev[2] <= max_rmsd) { + aligned = true; + break; + } + walk_through_grass( + stream, context, dir_x, 0, 1, + dir_x > 0 ? "right (rock align)" : "left (rock align)" + ); + pbf_wait(context, 300ms); + auto cur = score(); + if (total(cur) > total(prev)) { + dir_x = (int8_t)-dir_x; + } + prev = cur; + } + if (!aligned) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to align between the two rocks and Roark's helmet.", + stream + ); + } + // Face north (up) without moving off the spot: a tap turns the character. + pbf_move_left_joystick(context, { 0.0, 1.0 }, 100ms, 300ms); + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + pbf_mash_button(context, BUTTON_A, 4000ms); + pbf_wait(context, 4873ms); } - // --------------------------------------------------------------------------- - // Segment 1 Part 3: Oreburgh Mine to Gym - // Assumes that escape rope was picked up and used to exit the mine. - // Navigates to the gym. - // ---------------------------------------------------------------------------- - static void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4443ms, 0ms); - pbf_wait(context, 1165ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 540ms, 0ms); - pbf_wait(context, 281ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 858ms, 0ms); - pbf_wait(context, 764ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2972ms, 0ms); - pbf_wait(context, 677ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 160ms, 0ms); - pbf_press_button(context, BUTTON_UP, 180ms, 0ms); + + // Diagnostic only: identifies the entrance start without moving the player. + // Thresholds are initial values; use the logged scores to calibrate on live video. + enum class MineStartPosition { + UNKNOWN, + LEFT, + CENTER, + RIGHT + }; + + static MineStartPosition detect_mine_start( + VideoStream& stream, ProControllerContext& context + ) { + const std::array names = {"Left", "Center", "Right"}; + const std::array files = { + "mine_start_left.png", "mine_start_center.png", "mine_start_right.png" + }; + // Wooden beam and side walls: exclude the animated player and shadow. + const std::array boxes = { + ImageFloatBox(0.24, 0.24, 0.52, 0.12), + ImageFloatBox(0.18, 0.37, 0.20, 0.23), + ImageFloatBox(0.64, 0.37, 0.18, 0.23) + }; + std::array, 3>, 3> detectors; + for (size_t candidate = 0; candidate < names.size(); candidate++) { + auto reference = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + files[candidate] + ); + for (size_t region = 0; region < boxes.size(); region++) { + detectors[candidate][region] = std::make_unique( + reference, boxes[region], 60.0, true + ); + } + } + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + int previous = -1; + size_t streak = 0; + for (size_t attempt = 0; attempt < 12; attempt++) { + const VideoSnapshot frame = stream.video().snapshot(); + std::array scores = {}; + std::array regions_ok = {true, true, true}; + for (size_t candidate = 0; candidate < names.size(); candidate++) { + for (size_t region = 0; region < boxes.size(); region++) { + const double score = detectors[candidate][region]->rmsd(frame); + scores[candidate] += score / boxes.size(); + regions_ok[candidate] = regions_ok[candidate] && score <= 60.0; + } + } + size_t best = 0; + for (size_t candidate = 1; candidate < names.size(); candidate++) { + if (scores[candidate] < scores[best]) best = candidate; + } + double second = 10000.0; + for (size_t candidate = 0; candidate < names.size(); candidate++) { + if (candidate != best && scores[candidate] < second) second = scores[candidate]; + } + stream.log("[Mine start] Scores (lower is better): Left=" + + std::to_string(scores[0]) + ", Center=" + std::to_string(scores[1]) + + ", Right=" + std::to_string(scores[2])); + + const int detected = regions_ok[best] && scores[best] <= 40.0 && + second - scores[best] >= 8.0 ? static_cast(best) : -1; + if (detected >= 0) { + streak = detected == previous ? streak + 1 : 1; + if (streak >= 3) { + stream.log( + std::string("[Mine start] Detected: ") + names[best], + COLOR_GREEN + ); + + switch (best) { + case 0: return MineStartPosition::LEFT; + case 1: return MineStartPosition::CENTER; + case 2: return MineStartPosition::RIGHT; + } + + return MineStartPosition::UNKNOWN; + } + } else { + streak = 0; + } + previous = detected; + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + } + stream.log("[Mine start] Detected: Unsure.", COLOR_ORANGE); + return MineStartPosition::UNKNOWN; + } + // Keeps the original picture-only test available. + static void oreburgh_mine_localization_test( + VideoStream& stream, + ProControllerContext& context + ) { + detect_mine_start(stream, context); + } + struct MineCenterOffset { + bool valid; + double offset; + double score; + }; + + // Finds the sideways shift of stationary scenery relative to the center picture. + // Positive offset: scenery shifted right, so the player is left of center. + static MineCenterOffset measure_mine_center_offset( + const ImageViewRGB32& reference, + const ImageViewRGB32& live + ) { + if (!reference || !live) { + return { false, 0.0, 1000.0 }; + } + + // Use a fixed working size so the search works at different capture resolutions. + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const ImageRGB32 live_scaled = live.scale_to(960, 540); + + // Entrance beam and both posts. Avoid the player and shadow. + const std::array boxes = { + ImageFloatBox(0.35, 0.285, 0.30, 0.07), + ImageFloatBox(0.355, 0.37, 0.035, 0.23), + ImageFloatBox(0.615, 0.37, 0.035, 0.23) + }; + + std::array scores = {}; + std::array worst_regions = {}; + + int best_shift = 0; + double best_score = 1000.0; + + // Search up to 10% of the screen width in either direction. + for (int shift = -96; shift <= 96; shift++) { + double total = 0.0; + double worst = 0.0; + + for (const ImageFloatBox& box : boxes) { + const ImageViewRGB32 reference_crop = + extract_box_reference(reference_scaled, box); + + // Translate the same crop horizontally in the live image. + const ImageViewRGB32 live_crop = + extract_box_reference(live_scaled, box, shift, 0); + + const double score = + ImageMatch::pixel_RMSD(reference_crop, live_crop); + + total += score; + if (score > worst) { + worst = score; + } + } + + const double average = total / boxes.size(); + const size_t index = static_cast(shift + 96); + + scores[index] = average; + worst_regions[index] = worst; + + if (average < best_score) { + best_score = average; + best_shift = shift; + } + } + + // Reject another similarly good match well away from the chosen position. + double alternative_score = 1000.0; + + for (int shift = -96; shift <= 96; shift++) { + if (std::abs(shift - best_shift) <= 12) { + continue; + } + + const double score = scores[static_cast(shift + 96)]; + if (score < alternative_score) { + alternative_score = score; + } + } + + const size_t best_index = static_cast(best_shift + 96); + + // Initial limits: live testing will tell us whether these need adjustment. + const bool valid = + best_score <= 20.0 && + worst_regions[best_index] <= 30.0 && + alternative_score - best_score >= 3.0 && + std::abs(best_shift) < 96; + + return { + valid, + static_cast(best_shift) / 960.0, + best_score + }; + } + // Test only: align with the entrance center, then stop. + static bool oreburgh_mine_alignment_test( + VideoStream& stream, + ProControllerContext& context + ) { + const ImageRGB32 reference( + RESOURCE_PATH() + + "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_start_center.png" + ); + + constexpr size_t MAX_MOVEMENTS = 12; + constexpr double CENTER_TOLERANCE = 0.008; + + size_t movements = 0; + size_t center_streak = 0; + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + while (true) { + const VideoSnapshot frame = stream.video().snapshot(); + + const MineCenterOffset measurement = + measure_mine_center_offset(reference, frame); + + stream.log( + "[Mine align] Offset=" + + std::to_string(measurement.offset) + + ", score=" + std::to_string(measurement.score) + + (measurement.valid ? ", trusted" : ", unsure") + ); + + if (!measurement.valid) { + stream.log( + "[Mine align] Entrance position unsure. Stopping.", + COLOR_ORANGE + ); + return false; + } + + if (std::abs(measurement.offset) <= CENTER_TOLERANCE) { + center_streak++; + + if (center_streak >= 3) { + stream.log( + "[Mine align] Center reached. Test complete.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; + } + + center_streak = 0; + + // Check the latest position before enforcing the movement limit. + if (movements >= MAX_MOVEMENTS) { + stream.log( + "[Mine align] Movement limit reached. Stopping.", + COLOR_ORANGE + ); + return false; + } + + const double x = measurement.offset > 0.0 ? 1.0 : -1.0; + + // Smaller movements when close to the center. + const auto duration = + std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; + + stream.log( + x > 0.0 + ? "[Mine align] Moving a little right." + : "[Mine align] Moving a little left." + ); + + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + + const int result = run_until( + stream, + context, + [x, duration](ProControllerContext& ctx) { + pbf_move_left_joystick( + ctx, { x, 0.0 }, duration, 0ms + ); + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + }, + { black, menu, dialog } + ); + + if (result >= 0) { + stream.log( + "[Mine align] Battle or transition detected. " + "Stopping the alignment test.", + COLOR_ORANGE + ); + return false; + } + + movements++; + } + } + // Confirm a battle before using the existing escape helper. + static bool mine_escape_and_settle( + VideoStream& stream, + ProControllerContext& context + ) { + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + + if (wait_until(stream, context, 12s, { {menu} }) != 0) { + stream.log( + "[Mine encounter] Battle menu not confirmed. Stopping.", + COLOR_ORANGE + ); + return false; + } + + stream.log("[Mine encounter] Battle confirmed. Escaping."); + escape_wild_battle(stream, context); + context.wait_for_all_requests(); + + // Check the return waits instead of ignoring their results. + BlackScreenWatcher visible( + COLOR_YELLOW, + ImageFloatBox(0.1, 0.1, 0.8, 0.8), + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms + ); + + if (wait_until(stream, context, 8s, { {visible} }) != 0) { + stream.log( + "[Mine encounter] Screen did not return. Stopping.", + COLOR_ORANGE + ); + return false; + } + + OverworldWatcher overworld; + + if (wait_until(stream, context, 5s, { {overworld} }) != 0) { + stream.log( + "[Mine encounter] Overworld not detected. Stopping.", + COLOR_ORANGE + ); + return false; + } + + pbf_wait(context, 800ms); + context.wait_for_all_requests(); + + stream.log( + "[Mine encounter] Escaped. Returning to the rock-walking loop.", + COLOR_GREEN + ); + return true; } + + // Temporary test: wiggle along the corridor until a battle is detected. + static bool mine_force_encounter_test( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine encounter] Starting forced encounter wiggle."); + + const auto deadline = + std::chrono::steady_clock::now() + 60s; + + size_t pulses = 0; + + while (std::chrono::steady_clock::now() < deadline) { + // Alternate down and up. Do not move sideways. + const double y = pulses % 2 == 0 ? -1.0 : 1.0; + + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + + const int result = run_until( + stream, + context, + [y](ProControllerContext& ctx) { + pbf_move_left_joystick( + ctx, { 0.0, y }, 120ms, 0ms + ); + pbf_wait(ctx, 400ms); + ctx.wait_for_all_requests(); + }, + { black, menu, dialog } + ); + + if (result >= 0) { + stream.log( + "[Mine encounter] Wiggle interrupted. Checking for battle." + ); + return mine_escape_and_settle(stream, context); + } + + pulses++; + } + + stream.log( + "[Mine encounter] No encounter within 60 seconds. " + "Test incomplete; stopping.", + COLOR_ORANGE + ); + return false; + } + // Walk toward the first rock checkpoint, then stop. + // This test stops on battles; escaping and resuming come next. +// Previous implementation retained during consolidation. +// static bool oreburgh_mine_walk_to_first_rock( +// VideoStream& stream, +// ProControllerContext& context, +// bool force_encounter = false +// ) { +// const ImageRGB32 reference( +// RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_01.png" +// ); +// const ImageRGB32 reference_scaled = reference.scale_to(960, 540); +// +// // Five small rocks and the large upper-right rock. +// // Boxes cover rock surfaces, avoiding the character and shadows. +// const std::array rock_boxes = { +// ImageFloatBox(0.482, 0.585, 0.055, 0.055), // Central rock +// ImageFloatBox(0.335, 0.670, 0.050, 0.055), // Left pair: left +// ImageFloatBox(0.412, 0.670, 0.050, 0.055), // Left pair: right +// ImageFloatBox(0.570, 0.752, 0.045, 0.055), // Right pair: left +// ImageFloatBox(0.645, 0.752, 0.045, 0.055), // Right pair: right +// ImageFloatBox(0.570, 0.290, 0.080, 0.120) // Large upper-right rock +// }; +// bool forced_encounter_completed = false; +// size_t encounters = 0; +// +// constexpr size_t MAX_MOVEMENTS = 40; +// size_t movements = 0; +// size_t matching_frames = 0; +// +// while (true) { +// context.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// +// if (!frame) { +// stream.log( +// "[Mine path 01] No video frame. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// const ImageRGB32 live_scaled = frame->scale_to(960, 540); +// +// std::array best_scores = {}; +// double best_average = 1000.0; +// int best_shift = 0; +// int best_vertical_shift = 0; +// +// // Small horizontal and vertical search. +// // All six rocks must match at the SAME pair of offsets. +// for (int vertical = -6; vertical <= 6; vertical++) { +// for (int horizontal = -24; horizontal <= 24; horizontal++) { +// std::array scores = {}; +// double total = 0.0; +// +// for (size_t rock = 0; rock < rock_boxes.size(); rock++) { +// const ImageViewRGB32 reference_crop = +// extract_box_reference( +// reference_scaled, rock_boxes[rock] +// ); +// +// const ImageViewRGB32 live_crop = +// extract_box_reference( +// live_scaled, +// rock_boxes[rock], +// horizontal, +// vertical +// ); +// +// scores[rock] = +// ImageMatch::pixel_RMSD(reference_crop, live_crop); +// +// total += scores[rock]; +// } +// +// const double average = total / rock_boxes.size(); +// +// if (average < best_average) { +// best_average = average; +// best_scores = scores; +// best_shift = horizontal; +// best_vertical_shift = vertical; +// } +// } +// } +// +// stream.log( +// "[Mine path 01] Horizontal=" + std::to_string(best_shift) + +// ", vertical=" + std::to_string(best_vertical_shift) + +// ", central=" + std::to_string(best_scores[0]) + +// ", left1=" + std::to_string(best_scores[1]) + +// ", left2=" + std::to_string(best_scores[2]) + +// ", right1=" + std::to_string(best_scores[3]) + +// ", right2=" + std::to_string(best_scores[4]) + +// ", large=" + std::to_string(best_scores[5]) +// ); +// +// // Initial limits: each rock must match, plus a good overall score. +// bool at_target = best_average <= 20.0; +// +// for (double score : best_scores) { +// if (score > 30.0) { +// at_target = false; +// } +// } +// +// if (at_target) { +// matching_frames++; +// +// if (matching_frames >= 3) { +// stream.log( +// "[Mine path 01] First rock reached. Stopping.", +// COLOR_GREEN +// ); +// return true; +// } +// +// pbf_wait(context, 250ms); +// context.wait_for_all_requests(); +// continue; +// } +// +// matching_frames = 0; +// +// // Trigger once, partway down the corridor. +// if (force_encounter && +// !forced_encounter_completed && +// movements >= 4) { +// +// if (!mine_force_encounter_test(stream, context)) { +// return false; +// } +// +// forced_encounter_completed = true; +// +// stream.log( +// "[Mine path 01] Forced encounter completed. Resuming navigation.", +// COLOR_GREEN +// ); +// +// // Take a fresh picture before sending another movement. +// continue; +// } +// +// if (movements >= MAX_MOVEMENTS) { +// stream.log( +// "[Mine path 01] Movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// BlackScreenWatcher black; +// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); +// BattleDialogWatcher dialog(COLOR_YELLOW); +// +// const int result = run_until( +// stream, +// context, +// [](ProControllerContext& ctx) { +// pbf_move_left_joystick( +// ctx, { 0.0, -1.0 }, 80ms, 0ms +// ); +// pbf_wait(ctx, 500ms); +// ctx.wait_for_all_requests(); +// }, +// { black, menu, dialog } +// ); +// +// // Count commands as a safety budget, including interrupted commands. +// // This counter does not represent the player's position. +// movements++; +// +// if (result >= 0) { +// matching_frames = 0; +// +// if (++encounters > 10) { +// stream.log( +// "[Mine path 01] Encounter limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// if (!mine_escape_and_settle(stream, context)) { +// return false; +// } +// +// stream.log("[Mine path 01] Resuming after encounter."); +// continue; +// } +// +// } +// } + // Move right until either accepted endpoint is recognized. +// Previous implementation retained during consolidation. +// static bool oreburgh_mine_move_right( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const ImageRGB32 reference1( +// RESOURCE_PATH() + +// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_01_1.png" +// ); +// const ImageRGB32 reference2( +// RESOURCE_PATH() + +// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_01_2.png" +// ); +// +// const std::array references = { +// reference1.scale_to(960, 540), +// reference2.scale_to(960, 540) +// }; +// +// // Six rock regions for each endpoint. +// // The upper-right rock uses a higher patch to avoid the character. +// const std::array, 2> boxes = { { +// {{ +// ImageFloatBox(0.400, 0.585, 0.055, 0.055), +// ImageFloatBox(0.245, 0.670, 0.050, 0.055), +// ImageFloatBox(0.322, 0.670, 0.050, 0.055), +// ImageFloatBox(0.480, 0.752, 0.045, 0.055), +// ImageFloatBox(0.555, 0.752, 0.045, 0.055), +// ImageFloatBox(0.485, 0.290, 0.080, 0.090) +// }}, +// {{ +// ImageFloatBox(0.325, 0.585, 0.055, 0.055), +// ImageFloatBox(0.170, 0.670, 0.050, 0.055), +// ImageFloatBox(0.244, 0.670, 0.050, 0.055), +// ImageFloatBox(0.395, 0.752, 0.045, 0.055), +// ImageFloatBox(0.470, 0.752, 0.045, 0.055), +// ImageFloatBox(0.415, 0.290, 0.080, 0.090) +// }} +// } }; +// +// size_t movements = 0; +// size_t encounters = 0; +// size_t matching_frames = 0; +// int previous_endpoint = -1; +// +// while (true) { +// context.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// +// if (!frame) { +// stream.log("[Mine right] No video frame. Stopping.", COLOR_ORANGE); +// return false; +// } +// +// const ImageRGB32 live = frame->scale_to(960, 540); +// int accepted_endpoint = -1; +// double accepted_score = 1000.0; +// +// for (size_t endpoint = 0; endpoint < references.size(); endpoint++) { +// double best_average = 1000.0; +// double best_worst = 1000.0; +// +// // Small tolerance around each accepted endpoint. +// for (int vertical = -6; vertical <= 6; vertical++) { +// for (int horizontal = -12; horizontal <= 12; horizontal++) { +// double total = 0.0; +// double worst = 0.0; +// +// for (const ImageFloatBox& box : boxes[endpoint]) { +// const ImageViewRGB32 reference_crop = +// extract_box_reference(references[endpoint], box); +// +// const ImageViewRGB32 live_crop = +// extract_box_reference( +// live, box, horizontal, vertical +// ); +// +// const double score = +// ImageMatch::pixel_RMSD(reference_crop, live_crop); +// +// total += score; +// if (score > worst) { +// worst = score; +// } +// } +// +// const double average = total / boxes[endpoint].size(); +// +// if (average < best_average) { +// best_average = average; +// best_worst = worst; +// } +// } +// } +// +// stream.log( +// "[Mine right] Endpoint " + std::to_string(endpoint + 1) + +// ": average=" + std::to_string(best_average) + +// ", worst rock=" + std::to_string(best_worst) +// ); +// +// if (best_average <= 20.0 && +// best_worst <= 30.0 && +// best_average < accepted_score) { +// +// accepted_endpoint = static_cast(endpoint); +// accepted_score = best_average; +// } +// } +// +// if (accepted_endpoint >= 0) { +// matching_frames = +// accepted_endpoint == previous_endpoint +// ? matching_frames + 1 +// : 1; +// +// previous_endpoint = accepted_endpoint; +// +// if (matching_frames >= 3) { +// stream.log( +// "[Mine right] Endpoint " + +// std::to_string(accepted_endpoint + 1) + +// " reached. Stopping.", +// COLOR_GREEN +// ); +// return true; +// } +// +// pbf_wait(context, 250ms); +// context.wait_for_all_requests(); +// continue; +// } +// +// matching_frames = 0; +// previous_endpoint = -1; +// +// if (movements >= 20) { +// stream.log( +// "[Mine right] Movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// BlackScreenWatcher black; +// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); +// BattleDialogWatcher dialog(COLOR_YELLOW); +// +// const int result = run_until( +// stream, +// context, +// [](ProControllerContext& ctx) { +// pbf_move_left_joystick( +// ctx, { 1.0, 0.0 }, 40ms, 0ms +// ); +// pbf_wait(ctx, 500ms); +// ctx.wait_for_all_requests(); +// }, +// { black, menu, dialog } +// ); +// +// movements++; +// +// if (result >= 0) { +// if (++encounters > 10) { +// stream.log( +// "[Mine right] Encounter limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// if (!mine_escape_and_settle(stream, context)) { +// return false; +// } +// +// stream.log("[Mine right] Resuming after encounter."); +// } +// } +// } + // Walk down past the central rock to either accepted stopping point. +// Previous implementation retained during consolidation. +// static bool oreburgh_mine_down_past_central_rock( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const ImageRGB32 reference1( +// RESOURCE_PATH() + +// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_02_1.png" +// ); +// const ImageRGB32 reference2( +// RESOURCE_PATH() + +// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_02_2.png" +// ); +// +// const std::array references = { +// reference1.scale_to(960, 540), +// reference2.scale_to(960, 540) +// }; +// +// // Rock surfaces in each reference picture. +// // Avoid the player standing above the lower-right pair. +// const std::array, 2> boxes = { { +// {{ +// ImageFloatBox(0.345, 0.420, 0.040, 0.045), // Central rock +// ImageFloatBox(0.195, 0.500, 0.040, 0.045), // Left pair: left +// ImageFloatBox(0.270, 0.500, 0.040, 0.045), // Left pair: right +// ImageFloatBox(0.410, 0.590, 0.040, 0.045), // Lower pair: left +// ImageFloatBox(0.480, 0.590, 0.040, 0.045), // Lower pair: right +// ImageFloatBox(0.445, 0.180, 0.060, 0.090) // Large upper rock +// }}, +// {{ +// ImageFloatBox(0.420, 0.420, 0.040, 0.045), +// ImageFloatBox(0.270, 0.500, 0.040, 0.045), +// ImageFloatBox(0.345, 0.500, 0.040, 0.045), +// ImageFloatBox(0.485, 0.590, 0.040, 0.045), +// ImageFloatBox(0.555, 0.590, 0.040, 0.045), +// ImageFloatBox(0.520, 0.180, 0.060, 0.090) +// }} +// } }; +// +// size_t movements = 0; +// size_t encounters = 0; +// size_t matching_frames = 0; +// int previous_endpoint = -1; +// +// while (true) { +// context.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// +// if (!frame) { +// stream.log("[Mine down 02] No video frame. Stopping.", COLOR_ORANGE); +// return false; +// } +// +// const ImageRGB32 live = frame->scale_to(960, 540); +// int accepted_endpoint = -1; +// double accepted_score = 1000.0; +// +// for (size_t endpoint = 0; endpoint < references.size(); endpoint++) { +// double best_average = 1000.0; +// double best_worst = 1000.0; +// +// // Allow small differences in the stopping position. +// for (int vertical = -6; vertical <= 6; vertical++) { +// for (int horizontal = -12; horizontal <= 12; horizontal++) { +// double total = 0.0; +// double worst = 0.0; +// +// for (const ImageFloatBox& box : boxes[endpoint]) { +// const ImageViewRGB32 reference_crop = +// extract_box_reference(references[endpoint], box); +// +// const ImageViewRGB32 live_crop = +// extract_box_reference( +// live, box, horizontal, vertical +// ); +// +// const double score = +// ImageMatch::pixel_RMSD(reference_crop, live_crop); +// +// total += score; +// +// if (score > worst) { +// worst = score; +// } +// } +// +// const double average = total / boxes[endpoint].size(); +// +// if (average < best_average) { +// best_average = average; +// best_worst = worst; +// } +// } +// } +// +// stream.log( +// "[Mine down 02] Endpoint " + std::to_string(endpoint + 1) + +// ": average=" + std::to_string(best_average) + +// ", worst rock=" + std::to_string(best_worst) +// ); +// +// if (best_average <= 20.0 && +// best_worst <= 30.0 && +// best_average < accepted_score) { +// +// accepted_endpoint = static_cast(endpoint); +// accepted_score = best_average; +// } +// } +// +// if (accepted_endpoint >= 0) { +// matching_frames = +// accepted_endpoint == previous_endpoint +// ? matching_frames + 1 +// : 1; +// +// previous_endpoint = accepted_endpoint; +// +// if (matching_frames >= 3) { +// stream.log( +// "[Mine down 02] Endpoint " + +// std::to_string(accepted_endpoint + 1) + +// " reached. Stopping.", +// COLOR_GREEN +// ); +// return true; +// } +// +// pbf_wait(context, 250ms); +// context.wait_for_all_requests(); +// continue; +// } +// +// matching_frames = 0; +// previous_endpoint = -1; +// +// if (movements >= 30) { +// stream.log( +// "[Mine down 02] Movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// BlackScreenWatcher black; +// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); +// BattleDialogWatcher dialog(COLOR_YELLOW); +// +// const int result = run_until( +// stream, +// context, +// [](ProControllerContext& ctx) { +// pbf_move_left_joystick( +// ctx, { 0.0, -1.0 }, 40ms, 0ms +// ); +// pbf_wait(ctx, 500ms); +// ctx.wait_for_all_requests(); +// }, +// { black, menu, dialog } +// ); +// +// movements++; +// +// if (result >= 0) { +// if (++encounters > 10) { +// stream.log( +// "[Mine down 02] Encounter limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// if (!mine_escape_and_settle(stream, context)) { +// return false; +// } +// +// stream.log("[Mine down 02] Resuming after encounter."); +// } +// } +// } + + // Move left into the central gap, then stop. +// Previous implementation retained during consolidation. +// static bool oreburgh_mine_move_left_to_gap( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const ImageRGB32 reference( +// RESOURCE_PATH() + +// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_03.png" +// ); +// const ImageRGB32 reference_scaled = reference.scale_to(960, 540); +// +// // Four visible small rocks and the large upper-right rock. +// // Exclude the central rock hidden behind the character. +// const std::array boxes = { +// ImageFloatBox(0.340, 0.495, 0.040, 0.045), // Left pair: left +// ImageFloatBox(0.410, 0.495, 0.040, 0.045), // Left pair: right +// ImageFloatBox(0.555, 0.580, 0.040, 0.045), // Right pair: left +// ImageFloatBox(0.625, 0.580, 0.040, 0.045), // Right pair: right +// ImageFloatBox(0.580, 0.180, 0.060, 0.090) // Large upper-right rock +// }; +// +// size_t movements = 0; +// size_t encounters = 0; +// size_t matching_frames = 0; +// +// while (true) { +// context.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// +// if (!frame) { +// stream.log("[Mine left 03] No video frame. Stopping.", COLOR_ORANGE); +// return false; +// } +// +// const ImageRGB32 live = frame->scale_to(960, 540); +// +// double best_average = 1000.0; +// double best_worst = 1000.0; +// int best_horizontal = 0; +// int best_vertical = 0; +// +// for (int vertical = -6; vertical <= 6; vertical++) { +// for (int horizontal = -12; horizontal <= 12; horizontal++) { +// double total = 0.0; +// double worst = 0.0; +// +// for (const ImageFloatBox& box : boxes) { +// const ImageViewRGB32 reference_crop = +// extract_box_reference(reference_scaled, box); +// +// const ImageViewRGB32 live_crop = +// extract_box_reference( +// live, box, horizontal, vertical +// ); +// +// const double score = +// ImageMatch::pixel_RMSD(reference_crop, live_crop); +// +// total += score; +// +// if (score > worst) { +// worst = score; +// } +// } +// +// const double average = total / boxes.size(); +// +// if (average < best_average) { +// best_average = average; +// best_worst = worst; +// best_horizontal = horizontal; +// best_vertical = vertical; +// } +// } +// } +// +// stream.log( +// "[Mine left 03] Horizontal=" + +// std::to_string(best_horizontal) + +// ", vertical=" + std::to_string(best_vertical) + +// ", average=" + std::to_string(best_average) + +// ", worst rock=" + std::to_string(best_worst) +// ); +// +// const bool at_target = +// best_average <= 20.0 && +// best_worst <= 30.0; +// +// if (at_target) { +// matching_frames++; +// +// if (matching_frames >= 3) { +// stream.log( +// "[Mine left 03] Central gap reached. Stopping.", +// COLOR_GREEN +// ); +// return true; +// } +// +// pbf_wait(context, 250ms); +// context.wait_for_all_requests(); +// continue; +// } +// +// matching_frames = 0; +// +// if (movements >= 30) { +// stream.log( +// "[Mine left 03] Movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// BlackScreenWatcher black; +// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); +// BattleDialogWatcher dialog(COLOR_YELLOW); +// +// const int result = run_until( +// stream, +// context, +// [](ProControllerContext& ctx) { +// pbf_move_left_joystick( +// ctx, { -1.0, 0.0 }, 40ms, 0ms +// ); +// pbf_wait(ctx, 500ms); +// ctx.wait_for_all_requests(); +// }, +// { black, menu, dialog } +// ); +// +// movements++; +// +// if (result >= 0) { +// if (++encounters > 10) { +// stream.log( +// "[Mine left 03] Encounter limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// if (!mine_escape_and_settle(stream, context)) { +// return false; +// } +// +// stream.log("[Mine left 03] Resuming after encounter."); +// } +// } +// } + + // Watch throughout an action, including its movement and picture checks. + // After an encounter, restart the action using fresh watcher state. + template + static bool mine_run_with_encounters( + VideoStream& stream, + ProControllerContext& context, + Action action + ) { + for (size_t encounters = 0;;) { + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + bool completed = false; + + const int result = run_until( + stream, context, + [&](ProControllerContext& ctx) { + completed = action(ctx); + }, + { black, menu, dialog } + ); + + if (result < 0) { + return completed; + } + + if (++encounters > 10) { + stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); + return false; + } + + if (!mine_escape_and_settle(stream, context)) { + return false; + } + + stream.log("[Mine encounter] Escaped; resuming interrupted action."); + } + } + + static bool mine_timed_walk( + VideoStream& stream, + ProControllerContext& context, + double x, + double y, + std::chrono::milliseconds duration + ) { + auto remaining = duration; + + return mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + while (remaining > 0ms) { + const auto pulse = remaining < 100ms ? remaining : 100ms; + pbf_move_left_joystick(ctx, { x, y }, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + + // An interrupted pulse is retried after escaping. + // This is a timing budget, not exact distance tracking. + remaining -= pulse; + } + return true; + } + ); + } + + // Compatibility helper for callers such as alignment at position 18. + static bool mine_watch_while_checking( + VideoStream& stream, + ProControllerContext& context + ) { + return mine_run_with_encounters( + stream, context, + [](ProControllerContext& ctx) { + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + return true; + } + ); + } + +// Previous four-region scorer retained for comparison. +// struct MinePictureScore { +// double average = 1000.0; +// double worst = 1000.0; +// }; +// +// // Pure picture comparison: no movement, waits, or battle handling here. +// static MinePictureScore mine_score_picture( +// const ImageRGB32& reference, +// const ImageRGB32& live, +// const std::array& boxes +// ) { +// MinePictureScore best; +// +// for (int vertical = -8; vertical <= 8; vertical++) { +// for (int horizontal = -16; horizontal <= 16; horizontal++) { +// double total = 0.0; +// double worst = 0.0; +// +// for (const ImageFloatBox& box : boxes) { +// const ImageViewRGB32 reference_crop = +// extract_box_reference(reference, box); +// const ImageViewRGB32 live_crop = +// extract_box_reference(live, box, horizontal, vertical); +// const double score = +// ImageMatch::pixel_RMSD(reference_crop, live_crop); +// +// total += score; +// if (score > worst) { +// worst = score; +// } +// } +// +// const double average = total / boxes.size(); +// if (average < best.average) { +// best.average = average; +// best.worst = worst; +// } +// } +// } +// +// return best; +// } + +// Replace the existing MinePictureScore and mine_score_picture with this block. +struct MinePictureScore { + double average = 1000.0; + double worst = 1000.0; + int horizontal = 0; + int vertical = 0; +}; + +// Supports four, five, or six regions. Existing four-region callers keep +// their original search ranges through the default arguments. +template +static MinePictureScore mine_score_picture( + const ImageRGB32& reference, + const ImageRGB32& live, + const std::array& boxes, + int horizontal_range = 16, + int vertical_range = 8 +) { + MinePictureScore best; + for (int y = -vertical_range; y <= vertical_range; y++) { + for (int x = -horizontal_range; x <= horizontal_range; x++) { + double total = 0.0; + double worst = 0.0; + for (const ImageFloatBox& box : boxes) { + const ImageViewRGB32 ref = extract_box_reference(reference, box); + const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); + const double score = ImageMatch::pixel_RMSD(ref, patch); + total += score; + if (score > worst) worst = score; + } + const double average = total / boxes.size(); + if (average < best.average) { + best.average = average; + best.worst = worst; + best.horizontal = x; + best.vertical = y; + } + } + } + return best; +} + + + static bool mine_check_picture( + VideoStream& stream, + ProControllerContext& context, + const char* filename, + const std::array& boxes, + const std::string& label, + size_t max_attempts = 8, + double max_average = 20.0, + double max_patch = 30.0 + ) { + const ImageRGB32 reference( + RESOURCE_PATH() + + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + filename + ); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + + const bool confirmed = mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + // The watcher stays active throughout settling and sampling. + // There is no separate 500ms watcher wait before each sample. + ctx.wait_for_all_requests(); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + + // Restart confirmation after a battle; do not reuse old matches. + size_t matching_frames = 0; + + for (size_t attempt = 0; attempt < max_attempts; attempt++) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + const MinePictureScore score = + mine_score_picture(reference_scaled, live, boxes); + + stream.log( + label + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + ); + + if (score.average <= max_average && score.worst <= max_patch) { + if (++matching_frames >= 3) { + return true; + } + } + else { + matching_frames = 0; + } + + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + } + + return false; + } + ); + + stream.log( + label + (confirmed ? ": confirmed." : ": not confirmed."), + confirmed ? COLOR_GREEN : COLOR_ORANGE + ); + return confirmed; + } + +// Paste this block AFTER mine_check_picture and BEFORE oreburgh_mine_confirm_05. +// Keep the four old early-route functions commented out above this block. + +// Previous early-stage engine retained for comparison. +// struct MineEarlyWalkOptions { +// double x = 0.0; +// double y = 0.0; +// std::chrono::milliseconds pulse = 40ms; +// std::chrono::milliseconds settle = 500ms; +// size_t max_movements = 30; +// int horizontal_range = 12; +// int vertical_range = 6; +// double max_average = 20.0; +// double max_patch = 30.0; +// }; +// +// template +// static bool mine_walk_early_endpoints( +// VideoStream& stream, +// ProControllerContext& context, +// const std::array& files, +// const std::array, Endpoints>& boxes, +// const MineEarlyWalkOptions& options, +// const std::string& label, +// bool force_encounter = false +// ) { +// std::array, Endpoints> references; +// for (size_t index = 0; index < Endpoints; index++) { +// const ImageRGB32 image( +// RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + files[index] +// ); +// references[index] = std::make_unique(image.scale_to(960, 540)); +// } +// +// size_t movements = 0; +// bool forced_completed = false; +// +// while (true) { +// bool request_forced_test = false; +// const bool reached = mine_run_with_encounters( +// stream, context, +// [&](ProControllerContext& ctx) { +// // Reset the match history whenever an encounter interrupts us. +// size_t matching_frames = 0; +// int previous_endpoint = -1; +// +// while (true) { +// ctx.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// if (!frame) { +// stream.log(label + ": no video frame.", COLOR_ORANGE); +// return false; +// } +// +// const ImageRGB32 live = frame->scale_to(960, 540); +// int accepted = -1; +// double accepted_average = 1000.0; +// +// for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { +// const MinePictureScore score = mine_score_picture( +// *references[endpoint], live, boxes[endpoint], +// options.horizontal_range, options.vertical_range +// ); +// stream.log( +// label + " Endpoint " + std::to_string(endpoint + 1) + +// ": average=" + std::to_string(score.average) + +// ", worst patch=" + std::to_string(score.worst) + +// ", offset=(" + std::to_string(score.horizontal) + +// "," + std::to_string(score.vertical) + ")" +// ); +// +// if (score.average <= options.max_average && +// score.worst <= options.max_patch && +// score.average < accepted_average) { +// accepted = static_cast(endpoint); +// accepted_average = score.average; +// } +// } +// +// if (accepted >= 0) { +// matching_frames = accepted == previous_endpoint +// ? matching_frames + 1 : 1; +// previous_endpoint = accepted; +// if (matching_frames >= 3) { +// stream.log( +// label + " Endpoint " + std::to_string(accepted + 1) + +// " confirmed.", COLOR_GREEN +// ); +// return true; +// } +// pbf_wait(ctx, 250ms); +// ctx.wait_for_all_requests(); +// continue; +// } +// +// matching_frames = 0; +// previous_endpoint = -1; +// +// // Exit the guard before invoking the diagnostic wiggle: +// // it has its own encounter watcher and must not be nested. +// if (force_encounter && !forced_completed && movements >= 4) { +// request_forced_test = true; +// return false; +// } +// +// if (movements >= options.max_movements) { +// stream.log(label + ": movement limit reached.", COLOR_ORANGE); +// return false; +// } +// +// // Count issued commands, including interrupted commands. +// movements++; +// pbf_move_left_joystick( +// ctx, { options.x, options.y }, options.pulse, 0ms +// ); +// pbf_wait(ctx, options.settle); +// ctx.wait_for_all_requests(); +// } +// } +// ); +// +// if (reached) return true; +// if (!request_forced_test) return false; +// if (!mine_force_encounter_test(stream, context)) return false; +// forced_completed = true; +// stream.log(label + ": forced encounter completed; resuming."); +// } +// } +// +// static bool oreburgh_mine_walk_to_first_rock( +// VideoStream& stream, +// ProControllerContext& context, +// bool force_encounter = false +// ) { +// const std::array files = { "mine_path_01.png" }; +// const std::array, 1> boxes = {{ +// {{ +// ImageFloatBox(0.482, 0.585, 0.055, 0.055), +// ImageFloatBox(0.335, 0.670, 0.050, 0.055), +// ImageFloatBox(0.412, 0.670, 0.050, 0.055), +// ImageFloatBox(0.570, 0.752, 0.045, 0.055), +// ImageFloatBox(0.645, 0.752, 0.045, 0.055), +// ImageFloatBox(0.570, 0.290, 0.080, 0.120) +// }} +// }}; +// +// MineEarlyWalkOptions options; +// options.x = 0.0; +// options.y = -1.0; +// options.pulse = 80ms; +// options.max_movements = 40; +// options.horizontal_range = 24; +// return mine_walk_early_endpoints( +// stream, context, files, boxes, options, "[Mine path 01]", force_encounter +// ); +// } +// +// static bool oreburgh_mine_move_right( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; +// const std::array, 2> boxes = {{ +// {{ +// ImageFloatBox(0.400, 0.585, 0.055, 0.055), +// ImageFloatBox(0.245, 0.670, 0.050, 0.055), +// ImageFloatBox(0.322, 0.670, 0.050, 0.055), +// ImageFloatBox(0.480, 0.752, 0.045, 0.055), +// ImageFloatBox(0.555, 0.752, 0.045, 0.055), +// ImageFloatBox(0.485, 0.290, 0.080, 0.090) +// }}, +// {{ +// ImageFloatBox(0.325, 0.585, 0.055, 0.055), +// ImageFloatBox(0.170, 0.670, 0.050, 0.055), +// ImageFloatBox(0.244, 0.670, 0.050, 0.055), +// ImageFloatBox(0.395, 0.752, 0.045, 0.055), +// ImageFloatBox(0.470, 0.752, 0.045, 0.055), +// ImageFloatBox(0.415, 0.290, 0.080, 0.090) +// }} +// }}; +// +// MineEarlyWalkOptions options; +// options.x = 1.0; +// options.y = 0.0; +// options.pulse = 40ms; +// options.max_movements = 20; +// options.horizontal_range = 12; +// return mine_walk_early_endpoints( +// stream, context, files, boxes, options, "[Mine right]" +// ); +// } +// +// static bool oreburgh_mine_down_past_central_rock( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; +// const std::array, 2> boxes = {{ +// {{ +// ImageFloatBox(0.345, 0.420, 0.040, 0.045), +// ImageFloatBox(0.195, 0.500, 0.040, 0.045), +// ImageFloatBox(0.270, 0.500, 0.040, 0.045), +// ImageFloatBox(0.410, 0.590, 0.040, 0.045), +// ImageFloatBox(0.480, 0.590, 0.040, 0.045), +// ImageFloatBox(0.445, 0.180, 0.060, 0.090) +// }}, +// {{ +// ImageFloatBox(0.420, 0.420, 0.040, 0.045), +// ImageFloatBox(0.270, 0.500, 0.040, 0.045), +// ImageFloatBox(0.345, 0.500, 0.040, 0.045), +// ImageFloatBox(0.485, 0.590, 0.040, 0.045), +// ImageFloatBox(0.555, 0.590, 0.040, 0.045), +// ImageFloatBox(0.520, 0.180, 0.060, 0.090) +// }} +// }}; +// +// MineEarlyWalkOptions options; +// options.x = 0.0; +// options.y = -1.0; +// options.pulse = 40ms; +// options.max_movements = 30; +// options.horizontal_range = 12; +// return mine_walk_early_endpoints( +// stream, context, files, boxes, options, "[Mine down 02]" +// ); +// } +// +// static bool oreburgh_mine_move_left_to_gap( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const std::array files = { "mine_path_03.png" }; +// const std::array, 1> boxes = {{ +// {{ +// ImageFloatBox(0.340, 0.495, 0.040, 0.045), +// ImageFloatBox(0.410, 0.495, 0.040, 0.045), +// ImageFloatBox(0.555, 0.580, 0.040, 0.045), +// ImageFloatBox(0.625, 0.580, 0.040, 0.045), +// ImageFloatBox(0.580, 0.180, 0.060, 0.090) +// }} +// }}; +// +// MineEarlyWalkOptions options; +// options.x = -1.0; +// options.y = 0.0; +// options.pulse = 40ms; +// options.max_movements = 30; +// options.horizontal_range = 12; +// return mine_walk_early_endpoints( +// stream, context, files, boxes, options, "[Mine left 03]" +// ); +// } + +struct MineWalkOptions { + double x = 0.0; + double y = 0.0; + std::chrono::milliseconds pulse = 40ms; + std::chrono::milliseconds settle = 500ms; + std::chrono::milliseconds initial_settle = 0ms; + size_t max_movements = 30; + int horizontal_range = 12; + int vertical_range = 6; + double max_average = 20.0; + double max_patch = 30.0; +}; + +template +static bool mine_walk_to_endpoints( + VideoStream& stream, + ProControllerContext& context, + const std::array& files, + const std::array, Endpoints>& boxes, + const MineWalkOptions& options, + const std::string& label, + bool force_encounter = false +) { + std::array, Endpoints> references; + for (size_t index = 0; index < Endpoints; index++) { + const ImageRGB32 image( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + files[index] + ); + references[index] = std::make_unique(image.scale_to(960, 540)); + } + + size_t movements = 0; + bool forced_completed = false; + + while (true) { + bool request_forced_test = false; + const bool reached = mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + if (options.initial_settle > 0ms) { + pbf_wait(ctx, options.initial_settle); + ctx.wait_for_all_requests(); + } + // Reset the match history whenever an encounter interrupts us. + size_t matching_frames = 0; + int previous_endpoint = -1; + + while (true) { + ctx.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + int accepted = -1; + double accepted_average = 1000.0; + + for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { + const MinePictureScore score = mine_score_picture( + *references[endpoint], live, boxes[endpoint], + options.horizontal_range, options.vertical_range + ); + stream.log( + label + " Endpoint " + std::to_string(endpoint + 1) + + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + + ", offset=(" + std::to_string(score.horizontal) + + "," + std::to_string(score.vertical) + ")" + ); + + if (score.average <= options.max_average && + score.worst <= options.max_patch && + score.average < accepted_average) { + accepted = static_cast(endpoint); + accepted_average = score.average; + } + } + + if (accepted >= 0) { + matching_frames = accepted == previous_endpoint + ? matching_frames + 1 : 1; + previous_endpoint = accepted; + if (matching_frames >= 3) { + stream.log( + label + " Endpoint " + std::to_string(accepted + 1) + + " confirmed.", COLOR_GREEN + ); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + previous_endpoint = -1; + + // Exit the guard before invoking the diagnostic wiggle: + // it has its own encounter watcher and must not be nested. + if (force_encounter && !forced_completed && movements >= 4) { + request_forced_test = true; + return false; + } + + if (movements >= options.max_movements) { + stream.log(label + ": movement limit reached.", COLOR_ORANGE); + return false; + } + + // Count issued commands, including interrupted commands. + movements++; + pbf_move_left_joystick( + ctx, { options.x, options.y }, options.pulse, 0ms + ); + pbf_wait(ctx, options.settle); + ctx.wait_for_all_requests(); + } + } + ); + + if (reached) return true; + if (!request_forced_test) return false; + if (!mine_force_encounter_test(stream, context)) return false; + forced_completed = true; + stream.log(label + ": forced encounter completed; resuming."); + } +} + +static bool oreburgh_mine_walk_to_first_rock( + VideoStream& stream, + ProControllerContext& context, + bool force_encounter = false +) { + const std::array files = { "mine_path_01.png" }; + const std::array, 1> boxes = {{ + {{ + ImageFloatBox(0.482, 0.585, 0.055, 0.055), + ImageFloatBox(0.335, 0.670, 0.050, 0.055), + ImageFloatBox(0.412, 0.670, 0.050, 0.055), + ImageFloatBox(0.570, 0.752, 0.045, 0.055), + ImageFloatBox(0.645, 0.752, 0.045, 0.055), + ImageFloatBox(0.570, 0.290, 0.080, 0.120) + }} + }}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 80ms; + options.max_movements = 40; + options.horizontal_range = 24; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine path 01]", force_encounter + ); +} + +static bool oreburgh_mine_move_right( + VideoStream& stream, + ProControllerContext& context +) { + const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; + const std::array, 2> boxes = {{ + {{ + ImageFloatBox(0.400, 0.585, 0.055, 0.055), + ImageFloatBox(0.245, 0.670, 0.050, 0.055), + ImageFloatBox(0.322, 0.670, 0.050, 0.055), + ImageFloatBox(0.480, 0.752, 0.045, 0.055), + ImageFloatBox(0.555, 0.752, 0.045, 0.055), + ImageFloatBox(0.485, 0.290, 0.080, 0.090) + }}, + {{ + ImageFloatBox(0.325, 0.585, 0.055, 0.055), + ImageFloatBox(0.170, 0.670, 0.050, 0.055), + ImageFloatBox(0.244, 0.670, 0.050, 0.055), + ImageFloatBox(0.395, 0.752, 0.045, 0.055), + ImageFloatBox(0.470, 0.752, 0.045, 0.055), + ImageFloatBox(0.415, 0.290, 0.080, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = 1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 20; + options.horizontal_range = 12; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine right]" + ); +} + +static bool oreburgh_mine_down_past_central_rock( + VideoStream& stream, + ProControllerContext& context +) { + const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; + const std::array, 2> boxes = {{ + {{ + ImageFloatBox(0.345, 0.420, 0.040, 0.045), + ImageFloatBox(0.195, 0.500, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.410, 0.590, 0.040, 0.045), + ImageFloatBox(0.480, 0.590, 0.040, 0.045), + ImageFloatBox(0.445, 0.180, 0.060, 0.090) + }}, + {{ + ImageFloatBox(0.420, 0.420, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.345, 0.500, 0.040, 0.045), + ImageFloatBox(0.485, 0.590, 0.040, 0.045), + ImageFloatBox(0.555, 0.590, 0.040, 0.045), + ImageFloatBox(0.520, 0.180, 0.060, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 40ms; + options.max_movements = 30; + options.horizontal_range = 12; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine down 02]" + ); +} + +static bool oreburgh_mine_move_left_to_gap( + VideoStream& stream, + ProControllerContext& context +) { + const std::array files = { "mine_path_03.png" }; + const std::array, 1> boxes = {{ + {{ + ImageFloatBox(0.340, 0.495, 0.040, 0.045), + ImageFloatBox(0.410, 0.495, 0.040, 0.045), + ImageFloatBox(0.555, 0.580, 0.040, 0.045), + ImageFloatBox(0.625, 0.580, 0.040, 0.045), + ImageFloatBox(0.580, 0.180, 0.060, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = -1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 30; + options.horizontal_range = 12; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine left 03]" + ); +} + + + + // Confirm we are far enough down before beginning the leftward section. + + +// Each picture stage owns its movement and matching settings. +struct MinePictureStep { + const char* file; + const char* label; + std::array boxes; + double x; + double y; + std::chrono::milliseconds pulse = 80ms; + size_t max_movements = 30; + double max_average = 20.0; + double max_patch = 30.0; +}; + +static bool mine_run_picture_step( + VideoStream& stream, ProControllerContext& context, + const MinePictureStep& step +) { + MineWalkOptions options; + options.x = step.x; + options.y = step.y; + options.pulse = step.pulse; + options.max_movements = step.max_movements; + options.max_average = step.max_average; + options.max_patch = step.max_patch; + options.horizontal_range = 16; + options.vertical_range = 8; + options.initial_settle = 700ms; + options.settle = 700ms; + const std::array files = {step.file}; + const std::array, 1> boxes = {{step.boxes}}; + return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); +} + +// Previous checkpoint loop retained for comparison. +// static bool oreburgh_mine_confirm_05( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// // Stationary rock surfaces in mine_path_05.png. +// // Avoid the hidden player, NPC, and Machop. +// const std::array boxes = { +// ImageFloatBox(0.240, 0.240, 0.055, 0.080), // Large left rock +// ImageFloatBox(0.390, 0.220, 0.050, 0.090), // Large middle rock +// ImageFloatBox(0.360, 0.090, 0.030, 0.030), // Upper-left small rock +// ImageFloatBox(0.550, 0.140, 0.040, 0.040) // Upper-right small rock +// }; +// +// for (size_t extra = 0; extra <= 10; extra++) { +// if (mine_check_picture( +// stream, +// context, +// "mine_path_05.png", +// boxes, +// "[Mine checkpoint 05]", +// 3 +// )) { +// stream.log( +// "[Mine checkpoint 05] Confirmed; leftward movement can begin.", +// COLOR_GREEN +// ); +// return true; +// } +// +// if (extra == 10) { +// stream.log( +// "[Mine checkpoint 05] Extra movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// stream.log("[Mine checkpoint 05] Adding 60ms downward movement."); +// +// if (!mine_timed_walk( +// stream, context, 0.0, -1.0, 60ms +// )) { +// return false; +// } +// } +// +// return false; +// } + +static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_05.png", "[Mine endpoint 05]", + {ImageFloatBox(0.240, 0.240, 0.055, 0.080), ImageFloatBox(0.390, 0.220, 0.050, 0.090), ImageFloatBox(0.360, 0.090, 0.030, 0.030), ImageFloatBox(0.550, 0.140, 0.040, 0.040)}, + 0.0, -1.0, 60ms, 10, 20.0, 30.0 + }; + return mine_run_picture_step(stream, context, step); +} + + // Temporary timed trial from the central gap through paths 6 and 7. + static bool oreburgh_mine_timed_to_07( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine 04-07] Walking down for 1500ms."); + + if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)) { + return false; + } + + if (!oreburgh_mine_confirm_05(stream, context)) { + return false; + } + + stream.log("[Mine 04-07] Walking left for 3000ms."); + + if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)) { + return false; + } + + // Path 6: crate, nearby small rock, large rock, upper-right small rock. + const std::array path6_boxes = { + ImageFloatBox(0.350, 0.250, 0.085, 0.075), + ImageFloatBox(0.405, 0.510, 0.035, 0.040), + ImageFloatBox(0.520, 0.300, 0.050, 0.080), + ImageFloatBox(0.620, 0.350, 0.030, 0.040) + }; + + if (!mine_check_picture( + stream, context, + "mine_path_06.png", path6_boxes, + "[Mine midpoint 06]" + )) { + return false; + } + + // Initial estimate: tune after the first live test. + stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); + + if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)) { + return false; + } + + // Path 7: crate and three visible rock surfaces. + const std::array path7_boxes = { + ImageFloatBox(0.365, 0.025, 0.070, 0.055), + ImageFloatBox(0.420, 0.270, 0.035, 0.040), + ImageFloatBox(0.560, 0.415, 0.025, 0.040), + ImageFloatBox(0.530, 0.650, 0.060, 0.090) + }; + + return mine_check_picture( + stream, context, + "mine_path_07.png", path7_boxes, + "[Mine endpoint 07]" + ); + } + + // Move left from path 7 to the position shown in path 8. + +// Previous checkpoint loop retained for comparison. +// static bool oreburgh_mine_left_to_08( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// // Crate, upper small rock, ledge-side rock, and lower-left small rock. +// // Avoid the character and its shadow. +// const std::array boxes = { +// ImageFloatBox(0.495, 0.035, 0.070, 0.055), +// ImageFloatBox(0.550, 0.270, 0.035, 0.040), +// ImageFloatBox(0.695, 0.420, 0.030, 0.040), +// ImageFloatBox(0.555, 0.585, 0.035, 0.040) +// }; +// +// // Check before every pulse, including after the final allowed pulse. +// for (size_t movement = 0; movement <= 20; movement++) { +// if (mine_check_picture( +// stream, +// context, +// "mine_path_08.png", +// boxes, +// "[Mine endpoint 08]", +// 3 +// )) { +// stream.log( +// "[Mine left 08] Left checkpoint reached. Stopping.", +// COLOR_GREEN +// ); +// return true; +// } +// +// if (movement == 20) { +// stream.log( +// "[Mine left 08] Movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// stream.log("[Mine left 08] Moving left for 40ms."); +// +// if (!mine_timed_walk( +// stream, context, -1.0, 0.0, 40ms +// )) { +// return false; +// } +// } +// +// return false; +// } + +static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_08.png", "[Mine endpoint 08]", + {ImageFloatBox(0.495, 0.035, 0.070, 0.055), ImageFloatBox(0.550, 0.270, 0.035, 0.040), ImageFloatBox(0.695, 0.420, 0.030, 0.040), ImageFloatBox(0.555, 0.585, 0.035, 0.040)}, + -1.0, 0.0, 40ms, 20, 20.0, 30.0 + }; + return mine_run_picture_step(stream, context, step); +} + + // Walk down along the left wall to path 9. + +// Previous checkpoint loop retained for comparison. +// static bool oreburgh_mine_down_to_09( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// // Upper small rock, rock pair, and the crate below the player. +// // Avoid the player and shadow. +// const std::array boxes = { +// ImageFloatBox(0.550, 0.115, 0.030, 0.040), +// ImageFloatBox(0.550, 0.390, 0.035, 0.040), +// ImageFloatBox(0.620, 0.390, 0.025, 0.035), +// ImageFloatBox(0.500, 0.720, 0.085, 0.080) +// }; +// +// // Check before each movement and after the last allowed movement. +// for (size_t movement = 0; movement <= 25; movement++) { +// if (mine_check_picture( +// stream, +// context, +// "mine_path_09.png", +// boxes, +// "[Mine endpoint 09]", +// 3, +// 25.0, +// 35.0 +// )) { +// stream.log( +// "[Mine down 09] Checkpoint reached. Stopping.", +// COLOR_GREEN +// ); +// return true; +// } +// +// if (movement == 25) { +// stream.log( +// "[Mine down 09] Movement limit reached. Stopping.", +// COLOR_ORANGE +// ); +// return false; +// } +// +// stream.log("[Mine down 09] Moving down for 60ms."); +// +// if (!mine_timed_walk( +// stream, context, 0.0, -1.0, 40ms +// )) { +// return false; +// } +// } +// +// return false; +// } + +static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_09.png", "[Mine endpoint 09]", + {ImageFloatBox(0.550, 0.115, 0.030, 0.040), ImageFloatBox(0.550, 0.390, 0.035, 0.040), ImageFloatBox(0.620, 0.390, 0.025, 0.035), ImageFloatBox(0.500, 0.720, 0.085, 0.080)}, + 0.0, -1.0, 40ms, 25, 25.0, 35.0 + }; + return mine_run_picture_step(stream, context, step); +} + +// Previous per-pulse picture loader retained for comparison. +// +// static bool mine_walk_to_picture( +// VideoStream& stream, +// ProControllerContext& context, +// double x, +// double y, +// const char* filename, +// const std::array& boxes, +// const std::string& label +// ) { +// for (size_t movement = 0; movement <= 30; movement++) { +// if (mine_check_picture( +// stream, context, filename, boxes, label, 3 +// )) { +// return true; +// } +// +// if (movement == 30) { +// stream.log(label + ": movement limit reached.", COLOR_ORANGE); +// return false; +// } +// +// if (!mine_timed_walk(stream, context, x, y, 80ms)) { +// return false; +// } +// } +// +// return false; +// } + + static bool mine_walk_to_picture( + VideoStream& stream, + ProControllerContext& context, + double x, + double y, + const char* filename, + const std::array& boxes, + const std::string& label + ) { + const MinePictureStep step = {filename, label.c_str(), boxes, x, y}; + return mine_run_picture_step(stream, context, step); +} + + // Require dialogue to appear first. + // Then finish only after it has been absent for one continuous second. + static bool mine_finish_item_dialogue( + VideoStream& stream, + ProControllerContext& context + ) { + ShortDialogDetector dialogue(COLOR_YELLOW); + + bool saw_dialogue = false; + auto absent_since = std::chrono::steady_clock::now(); + + const auto start = std::chrono::steady_clock::now(); + const auto deadline = start + 30s; + + stream.log("[Mine item] Starting interaction."); + + while (std::chrono::steady_clock::now() < deadline) { + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + + if (!frame) { + stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); + return false; + } + + const auto now = std::chrono::steady_clock::now(); + const bool visible = dialogue.detect(frame); + + if (visible) { + if (!saw_dialogue) { + stream.log("[Mine item] Dialogue detected."); + } + + saw_dialogue = true; + absent_since = now; + + // Short mash bursts allow frequent checks. + pbf_mash_button(context, BUTTON_A, 100ms); + } + else if (!saw_dialogue) { + if (now - start >= 10s) { + stream.log( + "[Mine item] No dialogue appeared. Stopping.", + COLOR_ORANGE + ); + return false; + } + + // Try to start the interaction. + pbf_press_button(context, BUTTON_A, 60ms, 100ms); + } + else if (now - absent_since >= 1s) { + stream.log( + "[Mine item] Dialogue absent for one second. Finished.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + + stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); + return false; + } + +// Previous route block retained for comparison. +// static bool oreburgh_mine_09_to_14( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const std::array path10_boxes = { +// ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate +// ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock +// ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock +// ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock +// }; +// +// if (!mine_walk_to_picture( +// stream, context, 1.0, 0.0, +// "mine_path_10.png", path10_boxes, "[Mine endpoint 10]" +// )) { +// return false; +// } +// +// const std::array path11_boxes = { +// ImageFloatBox(0.405, 0.560, 0.070, 0.080), +// ImageFloatBox(0.475, 0.270, 0.030, 0.040), +// ImageFloatBox(0.575, 0.290, 0.045, 0.080), +// ImageFloatBox(0.550, 0.575, 0.030, 0.040) +// }; +// +// if (!mine_walk_to_picture( +// stream, context, 0.0, -1.0, +// "mine_path_11.png", path11_boxes, "[Mine endpoint 11]" +// )) { +// return false; +// } +// +// const std::array path12_boxes = { +// ImageFloatBox(0.260, 0.560, 0.080, 0.080), +// ImageFloatBox(0.355, 0.270, 0.025, 0.035), +// ImageFloatBox(0.435, 0.290, 0.040, 0.070), +// ImageFloatBox(0.415, 0.585, 0.025, 0.035) +// }; +// +// if (!mine_walk_to_picture( +// stream, context, 1.0, 0.0, +// "mine_path_12.png", path12_boxes, "[Mine endpoint 12]" +// )) { +// return false; +// } +// +// const std::array path13_boxes = { +// ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate +// ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate +// ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock +// ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock +// }; +// +// if (!mine_walk_to_picture( +// stream, context, 0.0, -1.0, +// "mine_path_13.png", path13_boxes, "[Mine endpoint 13]" +// )) { +// return false; +// } +// +// // Turn left, then verify position before starting the interaction. +// pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); +// pbf_wait(context, 500ms); +// context.wait_for_all_requests(); +// +// if (!mine_check_picture( +// stream, context, +// "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 +// )) { +// return false; +// } +// stream.log("[Mine item] Position confirmed. Pressing A."); +// +// pbf_press_button(context, BUTTON_A, 80ms, 200ms); +// context.wait_for_all_requests(); +// +// return mine_finish_item_dialogue(stream, context); +// } + + static bool oreburgh_mine_09_to_14( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array path10_boxes = { + ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate + ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock + ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock + ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock + }; + + const MinePictureStep path10_boxes_step = { + "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path10_boxes_step)) { + return false; + } + + const std::array path11_boxes = { + ImageFloatBox(0.405, 0.560, 0.070, 0.080), + ImageFloatBox(0.475, 0.270, 0.030, 0.040), + ImageFloatBox(0.575, 0.290, 0.045, 0.080), + ImageFloatBox(0.550, 0.575, 0.030, 0.040) + }; + + const MinePictureStep path11_boxes_step = { + "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path11_boxes_step)) { + return false; + } + + const std::array path12_boxes = { + ImageFloatBox(0.260, 0.560, 0.080, 0.080), + ImageFloatBox(0.355, 0.270, 0.025, 0.035), + ImageFloatBox(0.435, 0.290, 0.040, 0.070), + ImageFloatBox(0.415, 0.585, 0.025, 0.035) + }; + + const MinePictureStep path12_boxes_step = { + "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path12_boxes_step)) { + return false; + } + + const std::array path13_boxes = { + ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate + ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate + ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock + ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock + }; + + const MinePictureStep path13_boxes_step = { + "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path13_boxes_step)) { + return false; + } + + // Turn left, then verify position before starting the interaction. + pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + if (!mine_check_picture( + stream, context, + "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 + )) { + return false; + } + stream.log("[Mine item] Position confirmed. Pressing A."); + + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + context.wait_for_all_requests(); + + return mine_finish_item_dialogue(stream, context); + } + + // Compare a reference patch with a shifted live patch using the same + // brightness adjustment as the native ImageMatchDetector. + static double mine_offset_patch_score( + const ImageRGB32& reference, + const ImageRGB32& live, + const ImageFloatBox& box, + int horizontal, + int vertical + ) { + const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); + const ImageViewRGB32 live_crop = + extract_box_reference(live, box, horizontal, vertical); + ImageRGB32 adjusted = live_crop.scale_to( + reference_crop.width(), reference_crop.height() + ); + + const auto reference_average = + ImageMatch::pixel_average(reference_crop, reference_crop); + const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); + auto scale = reference_average / live_average; + if (std::isnan(scale.r)) scale.r = 1.0; + if (std::isnan(scale.g)) scale.g = 1.0; + if (std::isnan(scale.b)) scale.b = 1.0; + scale.bound(0.8, 1.2); + ImageMatch::scale_brightness(adjusted, scale); + + return ImageMatch::pixel_RMSD(reference_crop, adjusted); + } + + struct MineRoarkOffsetScore { + double left = 1000.0; + double right = 1000.0; + double helmet = 1000.0; + int horizontal = 0; + int vertical = 0; + int helmet_horizontal = 0; + int helmet_vertical = 0; + }; + + static MineRoarkOffsetScore mine_score_roark_offsets( + const ImageRGB32& reference, + const ImageRGB32& live + ) { + const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); + const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); + const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); + + MineRoarkOffsetScore best; + double best_worst_rock = 1000.0; + double best_total = 2000.0; + + // Both rocks must use the SAME offset. They cannot match independently. + // Offsets are pixels on the 960 x 540 comparison image. + for (int y = -8; y <= 8; y++) { + for (int x = -16; x <= 16; x++) { + const double left = mine_offset_patch_score(reference, live, left_box, x, y); + const double right = mine_offset_patch_score(reference, live, right_box, x, y); + const double worst = left > right ? left : right; + const double total = left + right; + + if (worst < best_worst_rock || + (worst == best_worst_rock && total < best_total)) { + best_worst_rock = worst; + best_total = total; + best.left = left; + best.right = right; + best.horizontal = x; + best.vertical = y; + } + } + } + + // Roark's helmet may move slightly relative to the stationary rocks. + // Keep this extra search small so another red object cannot match far away. + for (int extra_y = -6; extra_y <= 6; extra_y++) { + for (int extra_x = -6; extra_x <= 6; extra_x++) { + const double score = mine_offset_patch_score( + reference, live, helmet_box, + best.horizontal + extra_x, + best.vertical + extra_y + ); + if (score < best.helmet) { + best.helmet = score; + best.helmet_horizontal = extra_x; + best.helmet_vertical = extra_y; + } + } + } + + return best; + } + + static bool oreburgh_mine_align_to_18( + VideoStream& stream, + ProControllerContext& context + ) { + const ImageRGB32 reference( + RESOURCE_PATH() + + "PokemonBDSP/Autostory/Oreburg_mine_nav/Mine_path_18.png" + ); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const double max_rmsd = 65.0; + size_t movements = 0; + + return mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + // Fresh confirmation after any encounter. + size_t matching_frames = 0; + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + + while (true) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + const MineRoarkOffsetScore score = + mine_score_roark_offsets(reference_scaled, live); + + stream.log( + "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + + ", Right rock=" + std::to_string(score.right) + + ", Helmet=" + std::to_string(score.helmet) + + ", shared offset=(" + std::to_string(score.horizontal) + + "," + std::to_string(score.vertical) + + "), helmet extra=(" + std::to_string(score.helmet_horizontal) + + "," + std::to_string(score.helmet_vertical) + ")" + ); + + if (score.left <= max_rmsd && score.right <= max_rmsd && + score.helmet <= max_rmsd) { + if (++matching_frames >= 3) { + stream.log( + "[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN + ); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + if (movements >= 80) { + stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); + return false; + } + + const bool approaching = score.left <= 120.0 && + score.right <= 120.0 && score.helmet <= 150.0; + const bool near = score.left <= 100.0 && + score.right <= 100.0 && score.helmet <= 100.0; + const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; + + // Use the outer encounter watcher; do not nest mine_timed_walk. + // Count issued pulses, including any interrupted by a battle. + movements++; + pbf_move_left_joystick(ctx, { 1.0, 0.0 }, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + } + } + ); + } + + /*static bool oreburgh_mine_align_to_18( + VideoStream& stream, + ProControllerContext& context + ) { + auto reference = std::make_shared( + RESOURCE_PATH() + + "PokemonBDSP/Autostory/Oreburg_mine_nav/Mine_path_18.png" + ); + + // Match fixed details around Roark. + // Keep the player outside these boxes. + const double max_rmsd = 65.0; + + ImageMatchDetector left_rock( + reference, + ImageFloatBox(0.415, 0.400, 0.050, 0.095), + max_rmsd, true + ); + ImageMatchDetector right_rock( + reference, + ImageFloatBox(0.555, 0.400, 0.055, 0.095), + max_rmsd, true + ); + ImageMatchDetector helmet( + reference, + ImageFloatBox(0.485, 0.335, 0.035, 0.045), + max_rmsd, true + ); + + size_t matching_frames = 0; + + // Include a final check after the last allowed movement. + for (size_t movement = 0; movement <= 80;) { + if (!mine_watch_while_checking(stream, context)) { + return false; + } + + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + + if (!frame) { + stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); + return false; + } + + const double left = left_rock.rmsd(frame); + const double right = right_rock.rmsd(frame); + const double hat = helmet.rmsd(frame); + + stream.log( + "[Mine endpoint 18] Left rock=" + std::to_string(left) + + ", Right rock=" + std::to_string(right) + + ", Helmet=" + std::to_string(hat) + ); + + if (left <= max_rmsd && + right <= max_rmsd && + hat <= max_rmsd + ) { + // Stand still while confirming the match. + if (++matching_frames >= 3) { + stream.log( + "[Mine endpoint 18] Rocks and helmet confirmed.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 150ms); + context.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + + if (movement == 80) { + stream.log( + "[Mine endpoint 18] Movement limit reached. Stopping.", + COLOR_ORANGE + ); + return false; + } + + // Small pulses reduce the chance of passing the stopping point. + // Use smaller movements when all three landmarks are getting close. + // Slow down earlier when the landmarks begin matching. + const bool approaching_target = + left <= 120.0 && + right <= 120.0 && + hat <= 150.0; + + const bool near_target = + left <= 100.0 && + right <= 100.0 && + hat <= 100.0; + + const auto pulse = near_target ? 20ms : + approaching_target ? 40ms : 60ms; + + if (!mine_timed_walk(stream, context, 1.0, 0.0, pulse)) { + return false; + } + movement++; + } + + return false; + }*/ + +// Previous route block retained for comparison. +// static bool oreburgh_mine_14_to_19( +// VideoStream& stream, +// ProControllerContext& context +// ) { +// const std::array path15_boxes = { +// ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate +// ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate +// ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock +// ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock +// }; +// +// if (!mine_walk_to_picture( +// stream, context, 1.0, 0.0, +// "mine_path_15_actual.png", path15_boxes, "[Mine endpoint 15]" +// )) { +// return false; +// } +// +// // Move below the worker before continuing right. +// const std::array path16_boxes = { +// ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate +// ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate +// ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock +// ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock +// }; +// +// if (!mine_walk_to_picture( +// stream, context, 0.0, -1.0, +// "mine_path_16.png", path16_boxes, "[Mine endpoint 16]" +// )) { +// return false; +// } +// +// // Skip 17 and find the actual stopping point at 18. +// if (!oreburgh_mine_align_to_18(stream, context)) { +// return false; +// } +// +// // Position 19 is the upward turn toward Roark. +// pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); +// pbf_wait(context, 500ms); +// context.wait_for_all_requests(); +// +// stream.log( +// "[Mine endpoint 19] Position 18 confirmed; north turn sent.", +// COLOR_GREEN +// ); +// return true; +// } + + static bool oreburgh_mine_14_to_19( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array path15_boxes = { + ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate + ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate + ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path15_boxes_step = { + "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path15_boxes_step)) { + return false; + } + + // Move below the worker before continuing right. + const std::array path16_boxes = { + ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate + ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate + ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path16_boxes_step = { + "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path16_boxes_step)) { + return false; + } + + // Skip 17 and find the actual stopping point at 18. + if (!oreburgh_mine_align_to_18(stream, context)) { + return false; + } + + // Position 19 is the upward turn toward Roark. + pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + stream.log( + "[Mine endpoint 19] Position 18 confirmed; north turn sent.", + COLOR_GREEN + ); + return true; + } + + enum class MineRouteStage { + ALIGN_ENTRANCE, + FIRST_ROCK, + RIGHT_OF_FIRST_ROCK, + BELOW_FIRST_ROCK, + CENTER_GAP, + POSITION_07, + POSITION_08, + POSITION_09, + ITEM_AT_14, + ROARK_AT_19 + }; + + struct MineRouteTestOptions { + MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; + MineRouteStage last = MineRouteStage::ROARK_AT_19; + + // False: stop the test on failure. + // True: let the checkpoint retry handler reset the game. + bool reset_on_failure = false; + }; + + static bool oreburgh_mine_first_leg_test( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options = MineRouteTestOptions() + ) { + struct Step { + const char* name; + bool (*run)(VideoStream&, ProControllerContext&); + }; + + const std::array steps = { { + { + "Align entrance", + oreburgh_mine_alignment_test + }, + { + "Walk to first rock", + [](VideoStream& video, ProControllerContext& controller) { + return oreburgh_mine_walk_to_first_rock( + video, controller, false + ); + } + }, + { + "Move right of first rock", + oreburgh_mine_move_right + }, + { + "Move below first rock", + oreburgh_mine_down_past_central_rock + }, + { + "Move left into center gap", + oreburgh_mine_move_left_to_gap + }, + { + "Reach position 07", + oreburgh_mine_timed_to_07 + }, + { + "Reach position 08", + oreburgh_mine_left_to_08 + }, + { + "Reach position 09", + oreburgh_mine_down_to_09 + }, + { + "Reach 14 and collect item", + oreburgh_mine_09_to_14 + }, + { + "Reach 18 and turn north for 19", + oreburgh_mine_14_to_19 + } + } }; + + const size_t first = static_cast(options.first); + const size_t last = static_cast(options.last); + + if (first > last || last >= steps.size()) { + stream.log("[Mine route] Invalid test range.", COLOR_ORANGE); + return false; + } + + for (size_t index = first; index <= last; index++) { + const Step& step = steps[index]; + + stream.log( + std::string("[Mine route] Starting: ") + step.name + ); + + bool succeeded = false; + + try { + succeeded = step.run(stream, context); + } + catch (OperationFailedException& error) { + stream.log( + std::string("[Mine route] Failed during ") + + step.name + ": " + error.message(), + COLOR_ORANGE + ); + + if (options.reset_on_failure) { + throw; + } + + stream.log( + "[Mine route] Test stopped without requesting a reset.", + COLOR_ORANGE + ); + return false; + } + + if (!succeeded) { + stream.log( + std::string("[Mine route] Stage failed: ") + step.name, + COLOR_ORANGE + ); + + if (options.reset_on_failure) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + std::string("Mine route failed during: ") + step.name, + stream + ); + } + + return false; + } + + stream.log( + std::string("[Mine route] Completed: ") + step.name, + COLOR_GREEN + ); + } + + stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); + return true; + } + + static void oreburgh_mine_nav( + VideoStream& stream, ProControllerContext& context) { + walk_through_grass(stream, context, 0, -1, 1, "down into mine"); + context.wait_for_all_requests(); + walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); + context.wait_for_all_requests(); + pbf_wait(context, 1824ms); + walk_through_grass(stream, context, 0, -1, 8, "down through mine"); + pbf_wait(context, 571ms); + walk_through_grass(stream, context, 1, 0, 2, "right through mine"); + pbf_wait(context, 504ms); + walk_through_grass(stream, context, 0, -1, 2, "down through mine"); + pbf_wait(context, 470ms); + walk_through_grass(stream, context, -1, 0, 2, "left through mine"); + pbf_wait(context, 574ms); + walk_through_grass(stream, context, 0, -1, 4, "down through mine"); + pbf_wait(context, 440ms); + walk_through_grass(stream, context, 0, -1, 3, "down through mine"); + pbf_wait(context, 4426ms); + + walk_through_grass(stream, context, 0, -1, 2, "down through mine"); + pbf_wait(context, 672ms); + walk_through_grass(stream, context, -1, 0, 9, "left through mine"); + pbf_wait(context, 325ms); + walk_through_grass(stream, context, 0, -1, 4, "down through mine"); + pbf_wait(context, 390ms); + walk_through_grass(stream, context, -1, 0, 3, "left through mine"); + pbf_wait(context, 205ms); + walk_through_grass(stream, context, 0, -1, 4, "down through mine"); + pbf_wait(context, 156ms); + walk_through_grass(stream, context, 1, 0, 4, "right through mine"); + pbf_wait(context, 956ms); + walk_through_grass(stream, context, 0, 1, 1, "up through mine"); + pbf_wait(context, 1206ms); + walk_through_grass(stream, context, 1, 0, 7, "right through mine"); + pbf_wait(context, 3473ms); + walk_through_grass(stream, context, 0, -1, 5, "down through mine"); + pbf_wait(context, 874ms); + walk_through_grass(stream, context, -1, 0, 1, "left through mine"); + pbf_wait(context, 2590ms); + pbf_mash_button(context, BUTTON_A, 1000ms); + walk_through_grass(stream, context, 1, 0, 12, "right through mine"); + pbf_wait(context, 539ms); + align_between_rocks_and_talk(stream, context); + pbf_press_button(context, BUTTON_X, 142ms, 0ms); + pbf_wait(context, 1707ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 162ms, 0ms); + pbf_wait(context, 457ms); + pbf_press_button(context, BUTTON_A, 144ms, 0ms); + pbf_wait(context, 1704ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 159ms, 0ms); + pbf_wait(context, 640ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 160ms, 0ms); + pbf_wait(context, 555ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 180ms, 0ms); + pbf_wait(context, 494ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 171ms, 0ms); + pbf_wait(context, 826ms); + pbf_press_button(context, BUTTON_A, 92ms, 0ms); + pbf_wait(context, 823ms); + pbf_press_button(context, BUTTON_A, 110ms, 0ms); + + } + // --------------------------------------------------------------------------- + // Segment 1 Part 3: Oreburgh Mine to Gym + // Assumes that escape rope was picked up and used to exit the mine. + // Navigates to the gym. + // ---------------------------------------------------------------------------- + static void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4443ms, 0ms); + pbf_wait(context, 1165ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 540ms, 0ms); + pbf_wait(context, 281ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 858ms, 0ms); + pbf_wait(context, 764ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2972ms, 0ms); + pbf_wait(context, 677ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 160ms, 0ms); + pbf_press_button(context, BUTTON_UP, 180ms, 0ms); + } + // --------------------------------------------------------------------------- // Segment 1 Part 4: Gym navigation // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. @@ -283,7 +3780,8 @@ namespace PokemonAutomation { checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/) { //oreburgh_gate_to_mine(env, env.console, context); - //oreburgh_mine_nav(env, env.console, context); + //oreburgh_mine_nav(env.console, context); + oreburgh_mine_first_leg_test(env.console, context); //oreburgh_mine_to_gym(env, env.console, context); //#. Oreburgh Gym navigation and battle @@ -320,7 +3818,7 @@ namespace PokemonAutomation { checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/) { //#. Oreburgh Gym navigation and battle - gym_Puzzle_01(env, env.console, context); + //gym_Puzzle_01(env, env.console, context); }, @@ -331,4 +3829,4 @@ namespace PokemonAutomation { } } -} \ No newline at end of file +} From 34383391b8f520b2622bfb8d4a8782f7c6998003 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 4 Oct 2026 17:35:54 -0500 Subject: [PATCH 19/39] Add BDSP detector tests and mine tools Registers BDSP in the unit test runner and adds screenshot-based bag and menu detector tests, including dev-program entry points and a filename-based test map hook. Also expands BDSP menu and battle detection, adds bag navigation and Escape Rope helpers for AutoStory, and refactors Segment 01 mine and gym logic into shared utility files wired into the build. --- .../PokemonBDSP_BattleMenuDetector.cpp | 405 +- .../Battles/PokemonBDSP_BattleMenuDetector.h | 160 +- .../Inference/PokemonBDSP_MenuDetector.cpp | 161 + .../Inference/PokemonBDSP_MenuDetector.h | 19 + .../Source/PokemonBDSP/PokemonBDSP_Tests.cpp | 320 +- .../Source/PokemonBDSP/PokemonBDSP_Tests.h | 53 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 729 +++- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 76 + .../PokemonBDSP_AutoStory_Segment_00.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 3714 +---------------- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 0 .../PokemonBDSP_AutoStory_Segment_00_tools.h | 2 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 2408 +++++++++++ .../PokemonBDSP_Autostory_Segment_01_tools.h | 70 + SerialPrograms/cmake/SourceFiles.cmake | 8 +- 15 files changed, 4106 insertions(+), 4021 deletions(-) rename SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/{StorySegments => Utils}/PokemonBDSP_AutoStory_Segment_00_tools.cpp (100%) rename SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/{StorySegments => Utils}/PokemonBDSP_AutoStory_Segment_00_tools.h (96%) create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp index 536df70512..6fba38e28a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp @@ -1,173 +1,232 @@ -/* Battle Menu Detector - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonTools/Images/SolidColorTest.h" -#include "PokemonBDSP_BattleMenuDetector.h" - -//#include -//using std::cout; -//using std::endl; - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - -BattleMenuDetector::BattleMenuDetector(BattleType battle_type, Color color) - : m_color(color) - , m_battle_type(battle_type) - , m_left0_status (0.140, 0.922, 0.100, 0.010) - , m_left1_status (0.140, 0.910, 0.100, 0.010) - , m_right_status (0.405, 0.925, 0.100, 0.010) -// , m_opponent_left (0.685, 0.065, 0.020, 0.030) -// , m_opponent_right (0.960, 0.065, 0.020, 0.030) - , m_opponent_left (0.708, 0.070, 0.005, 0.028) - , m_opponent_right (0.982, 0.070, 0.005, 0.028) - , m_ball_left (0.890, 0.475, 0.02, 0.03) - , m_ball_right (0.960, 0.475, 0.03, 0.03) - , m_menu_battle (0.817, 0.585 + 0 * 0.1075, 0.150, 0.070) - , m_menu_pokemon (0.817, 0.585 + 1 * 0.1075, 0.150, 0.070) - , m_menu_bag (0.817, 0.585 + 2 * 0.1075, 0.150, 0.070) - , m_menu_run (0.817, 0.585 + 3 * 0.1075, 0.150, 0.070) -{} -void BattleMenuDetector::make_overlays(VideoOverlaySet& items) const{ - items.add(m_color, m_left0_status); - items.add(m_color, m_left1_status); - items.add(m_color, m_right_status); - items.add(m_color, m_opponent_left); - items.add(m_color, m_opponent_right); - items.add(m_color, m_ball_left); - items.add(m_color, m_ball_right); - items.add(m_color, m_menu_battle); - items.add(m_color, m_menu_pokemon); - items.add(m_color, m_menu_bag); - items.add(m_color, m_menu_run); -} -bool BattleMenuDetector::is_battle_button(const ImageViewRGB32& screen) const{ - ImageStats stats = image_stats(extract_box_reference(screen, m_menu_battle)); -// cout << stats.average << stats.stddev << endl; - double stddev = stats.stddev.sum(); - if (stddev < 30 || stddev > 150){ - return false; - } - double average = stats.average.sum(); - FloatPixel actual = stats.average / average; - if (euclidean_distance(actual, {0.541961, 0.231317, 0.226722}) > 0.2){ - return false; - } - return true; -} -bool BattleMenuDetector::detect(const ImageViewRGB32& screen){ - { - bool left = is_white(extract_box_reference(screen, m_opponent_left)); - bool right = is_white(extract_box_reference(screen, m_opponent_right)); - if (!left && !right){ -// cout << "opp left or right" << endl; - return false; - } - } - { - bool left0 = is_white(extract_box_reference(screen, m_left0_status)); - bool left1 = is_white(extract_box_reference(screen, m_left1_status)); - bool right = is_white(extract_box_reference(screen, m_right_status)); - if (!left0 && !left1 && !right){ - return false; - } - } - - if (m_battle_type == BattleType::STARTER){ - return is_battle_button(screen); - } - - - if (m_battle_type == BattleType::STANDARD){ - if (!is_white(extract_box_reference(screen, m_ball_left))){ -// cout << "Not white" << endl; - return false; - } - if (!is_white(extract_box_reference(screen, m_ball_right))){ -// cout << "Not white" << endl; - return false; - } - } - - if (!is_battle_button(screen)){ - return false; - } -// cout << "Start Checking Pokemon" << endl; - { - ImageStats stats = image_stats(extract_box_reference(screen, m_menu_pokemon)); -// cout << stats.average << stats.stddev << endl; - double stddev = stats.stddev.sum(); - if (stddev < 30 || stddev > 150){ - return false; - } - double average = stats.average.sum(); - FloatPixel actual = stats.average / average; - if (euclidean_distance(actual, {0.255944, 0.582771, 0.161285}) > 0.2){ - return false; - } - } - // cout << "Start Checking bag" << endl; - { - ImageStats stats = image_stats(extract_box_reference(screen, m_menu_bag)); -// cout << stats.average << stats.stddev << endl; - double stddev = stats.stddev.sum(); - if (stddev < 30 || stddev > 100){ - return false; - } - double average = stats.average.sum(); - FloatPixel actual = stats.average / average; - if (euclidean_distance(actual, {0.485857, 0.414946, 0.099197}) > 0.2){ - return false; - } - } - { - ImageStats stats = image_stats(extract_box_reference(screen, m_menu_run)); -// cout << stats.average << stats.stddev << endl; - double stddev = stats.stddev.sum(); - if (stddev < 30 || stddev > 100){ - return false; - } - double average = stats.average.sum(); - FloatPixel actual = stats.average / average; - if (euclidean_distance(actual, {0.250977, 0.269868, 0.479155}) > 0.2){ - return false; - } - } - - return true; -} - - - - -BattleMenuWatcher::BattleMenuWatcher(BattleType battle_type, Color color) - : BattleMenuDetector(battle_type, color) - , VisualInferenceCallback("BattleMenuWatcher") -{} -void BattleMenuWatcher::make_overlays(VideoOverlaySet& items) const{ - BattleMenuDetector::make_overlays(items); -} -bool BattleMenuWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ - // Need 5 consecutive successful detections. - if (!detect(frame)){ -// cout << "no detect" << endl; - m_trigger_count = 0; - return false; - } -// cout << "detected" << endl; - m_trigger_count++; - return m_trigger_count >= 5; -} - - - - -} -} -} +/* Battle Menu Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "PokemonBDSP_BattleMenuDetector.h" + +//#include +//using std::cout; +//using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +BattleMenuDetector::BattleMenuDetector(BattleType battle_type, Color color) + : m_color(color) + , m_battle_type(battle_type) + , m_left0_status (0.140, 0.922, 0.100, 0.010) + , m_left1_status (0.140, 0.910, 0.100, 0.010) + , m_right_status (0.405, 0.925, 0.100, 0.010) +// , m_opponent_left (0.685, 0.065, 0.020, 0.030) +// , m_opponent_right (0.960, 0.065, 0.020, 0.030) + , m_opponent_left (0.708, 0.070, 0.005, 0.028) + , m_opponent_right (0.982, 0.070, 0.005, 0.028) + , m_ball_left (0.890, 0.475, 0.02, 0.03) + , m_ball_right (0.960, 0.475, 0.03, 0.03) + , m_menu_battle (0.817, 0.585 + 0 * 0.1075, 0.150, 0.070) + , m_menu_pokemon (0.817, 0.585 + 1 * 0.1075, 0.150, 0.070) + , m_menu_bag (0.817, 0.585 + 2 * 0.1075, 0.150, 0.070) + , m_menu_run (0.817, 0.585 + 3 * 0.1075, 0.150, 0.070) +{} +void BattleMenuDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_left0_status); + items.add(m_color, m_left1_status); + items.add(m_color, m_right_status); + items.add(m_color, m_opponent_left); + items.add(m_color, m_opponent_right); + items.add(m_color, m_ball_left); + items.add(m_color, m_ball_right); + items.add(m_color, m_menu_battle); + items.add(m_color, m_menu_pokemon); + items.add(m_color, m_menu_bag); + items.add(m_color, m_menu_run); +} +bool BattleMenuDetector::is_battle_button(const ImageViewRGB32& screen) const{ + ImageStats stats = image_stats(extract_box_reference(screen, m_menu_battle)); +// cout << stats.average << stats.stddev << endl; + double stddev = stats.stddev.sum(); + if (stddev < 30 || stddev > 150){ + return false; + } + double average = stats.average.sum(); + FloatPixel actual = stats.average / average; + if (euclidean_distance(actual, {0.541961, 0.231317, 0.226722}) > 0.2){ + return false; + } + return true; +} +bool BattleMenuDetector::detect(const ImageViewRGB32& screen){ + { + bool left = is_white(extract_box_reference(screen, m_opponent_left)); + bool right = is_white(extract_box_reference(screen, m_opponent_right)); + if (!left && !right){ +// cout << "opp left or right" << endl; + return false; + } + } + { + bool left0 = is_white(extract_box_reference(screen, m_left0_status)); + bool left1 = is_white(extract_box_reference(screen, m_left1_status)); + bool right = is_white(extract_box_reference(screen, m_right_status)); + if (!left0 && !left1 && !right){ + return false; + } + } + + if (m_battle_type == BattleType::STARTER){ + return is_battle_button(screen); + } + + + if (m_battle_type == BattleType::STANDARD){ + if (!is_white(extract_box_reference(screen, m_ball_left))){ +// cout << "Not white" << endl; + return false; + } + if (!is_white(extract_box_reference(screen, m_ball_right))){ +// cout << "Not white" << endl; + return false; + } + } + + if (!is_battle_button(screen)){ + return false; + } +// cout << "Start Checking Pokemon" << endl; + { + ImageStats stats = image_stats(extract_box_reference(screen, m_menu_pokemon)); +// cout << stats.average << stats.stddev << endl; + double stddev = stats.stddev.sum(); + if (stddev < 30 || stddev > 150){ + return false; + } + double average = stats.average.sum(); + FloatPixel actual = stats.average / average; + if (euclidean_distance(actual, {0.255944, 0.582771, 0.161285}) > 0.2){ + return false; + } + } + // cout << "Start Checking bag" << endl; + { + ImageStats stats = image_stats(extract_box_reference(screen, m_menu_bag)); +// cout << stats.average << stats.stddev << endl; + double stddev = stats.stddev.sum(); + if (stddev < 30 || stddev > 100){ + return false; + } + double average = stats.average.sum(); + FloatPixel actual = stats.average / average; + if (euclidean_distance(actual, {0.485857, 0.414946, 0.099197}) > 0.2){ + return false; + } + } + { + ImageStats stats = image_stats(extract_box_reference(screen, m_menu_run)); +// cout << stats.average << stats.stddev << endl; + double stddev = stats.stddev.sum(); + if (stddev < 30 || stddev > 100){ + return false; + } + double average = stats.average.sum(); + FloatPixel actual = stats.average / average; + if (euclidean_distance(actual, {0.250977, 0.269868, 0.479155}) > 0.2){ + return false; + } + } + + return true; +} + + + + +BattleMenuOption BattleMenuDetector::selected_option(const ImageViewRGB32& screen){ + if (!screen || !detect(screen)){ + return BattleMenuOption::UNKNOWN; + } + int selected = -1; + for (int row = 0; row < 4; row++){ + const double offset = row * 0.1075; + bool brackets = false; + // The corner brackets wiggle; all four must agree on one small offset. + const double offsets[] = {0.0, -0.003, 0.003, -0.006, 0.006}; + for (double dy : offsets){ + for (double dx : offsets){ + const ImageFloatBox corners[] = { + {0.793 + dx, 0.562 + offset + dy, 0.018, 0.023}, + {0.976 + dx, 0.562 + offset + dy, 0.018, 0.023}, + {0.793 + dx, 0.652 + offset + dy, 0.018, 0.023}, + {0.976 + dx, 0.652 + offset + dy, 0.018, 0.023}, + }; + bool candidate = true; + for (const ImageFloatBox& box : corners){ + const ImageViewRGB32 patch = extract_box_reference(screen, box); + size_t white = 0; + const size_t total = patch.width() * patch.height(); + for (size_t y = 0; y < patch.height(); y++){ + for (size_t x = 0; x < patch.width(); x++){ + const uint32_t pixel = patch.pixel(x, y); + const unsigned r = (pixel >> 16) & 255; + const unsigned g = (pixel >> 8) & 255; + const unsigned b = pixel & 255; + const unsigned low = std::min(r, std::min(g, b)); + const unsigned high = std::max(r, std::max(g, b)); + white += low > 210 && high - low < 35; + } + } + if (total == 0 || static_cast(white) / total < 0.15){ + candidate = false; + break; + } + } + if (candidate){ + brackets = true; + break; + } + } + if (brackets){ + break; + } + } + if (brackets){ + if (selected >= 0){ + return BattleMenuOption::UNKNOWN; + } + selected = row; + } + } + return static_cast(selected); +} + +BattleMenuWatcher::BattleMenuWatcher(BattleType battle_type, Color color) + : BattleMenuDetector(battle_type, color) + , VisualInferenceCallback("BattleMenuWatcher") +{} +void BattleMenuWatcher::make_overlays(VideoOverlaySet& items) const{ + BattleMenuDetector::make_overlays(items); +} +bool BattleMenuWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ + // Need 5 consecutive successful detections. + if (!detect(frame)){ +// cout << "no detect" << endl; + m_trigger_count = 0; + return false; + } +// cout << "detected" << endl; + m_trigger_count++; + return m_trigger_count >= 5; +} + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h index 30a7d2249b..5fa9450dbc 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h @@ -1,73 +1,87 @@ -/* Battle Menu Detector - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonBDSP_BattleMenuDetector_H -#define PokemonAutomation_PokemonBDSP_BattleMenuDetector_H - -#include "Common/Cpp/Color.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" -#include "CommonTools/VisualDetector.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - -enum class BattleType{ - STARTER, - STANDARD, - GREAT_MARSH, - TRAINER, -}; - - -class BattleMenuDetector : public StaticScreenDetector{ -public: - BattleMenuDetector(BattleType battle_type, Color color = COLOR_RED); - - virtual void make_overlays(VideoOverlaySet& items) const override; - virtual bool detect(const ImageViewRGB32& screen) override; - -private: - bool is_battle_button(const ImageViewRGB32& screen) const; - -private: - Color m_color; - BattleType m_battle_type; - ImageFloatBox m_left0_status; - ImageFloatBox m_left1_status; - ImageFloatBox m_right_status; - ImageFloatBox m_opponent_left; - ImageFloatBox m_opponent_right; - ImageFloatBox m_ball_left; - ImageFloatBox m_ball_right; - ImageFloatBox m_menu_battle; - ImageFloatBox m_menu_pokemon; - ImageFloatBox m_menu_bag; - ImageFloatBox m_menu_run; -}; - - -class BattleMenuWatcher : public BattleMenuDetector, public VisualInferenceCallback{ -public: - BattleMenuWatcher(BattleType battle_type, Color color = COLOR_RED); - - virtual void make_overlays(VideoOverlaySet& items) const override; - virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override final; - using VisualInferenceCallback::process_frame; - - -private: - size_t m_trigger_count = 0; -}; - - - -} -} -} -#endif +/* Battle Menu Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_BattleMenuDetector_H +#define PokemonAutomation_PokemonBDSP_BattleMenuDetector_H + +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +enum class BattleType{ + STARTER, + STANDARD, + GREAT_MARSH, + TRAINER, +}; + + +// Vertical command order in the BDSP battle menu. +enum class BattleMenuOption{ + UNKNOWN = -1, + BATTLE = 0, + POKEMON = 1, + BAG = 2, + RUN = 3, +}; + +class BattleMenuDetector : public StaticScreenDetector{ +public: + BattleMenuDetector(BattleType battle_type, Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + + // Requires the battle menu and white selection brackets at all four corners. + // Missing or ambiguous brackets return UNKNOWN; never infer the cursor. + BattleMenuOption selected_option(const ImageViewRGB32& screen); + + +private: + bool is_battle_button(const ImageViewRGB32& screen) const; + +private: + Color m_color; + BattleType m_battle_type; + ImageFloatBox m_left0_status; + ImageFloatBox m_left1_status; + ImageFloatBox m_right_status; + ImageFloatBox m_opponent_left; + ImageFloatBox m_opponent_right; + ImageFloatBox m_ball_left; + ImageFloatBox m_ball_right; + ImageFloatBox m_menu_battle; + ImageFloatBox m_menu_pokemon; + ImageFloatBox m_menu_bag; + ImageFloatBox m_menu_run; +}; + + +class BattleMenuWatcher : public BattleMenuDetector, public VisualInferenceCallback{ +public: + BattleMenuWatcher(BattleType battle_type, Color color = COLOR_RED); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override final; + using VisualInferenceCallback::process_frame; + + +private: + size_t m_trigger_count = 0; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp index 12da43966e..b84334cdb2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp @@ -4,6 +4,13 @@ * */ +#include +#include +#include +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" @@ -31,6 +38,16 @@ MenuDetector::MenuDetector(Color color, bool skip_new_banner) , m_line3(menu_stripe(3, skip_new_banner)) , m_line4(menu_stripe(4, skip_new_banner)) , m_cross(0.20, 0.15, 0.60, 0.37) + , m_panel_top(0.150, 0.100, 0.700, 0.015) + , m_panel_left(0.150, 0.125, 0.020, 0.460) + , m_panel_right(0.835, 0.125, 0.015, 0.460) + , m_footer(0.050, 0.963, 0.600, 0.025) + , m_confirm_button(0.736, 0.960, 0.022, 0.038) + , m_save_button(0.835, 0.960, 0.024, 0.038) + , m_close_button(0.915, 0.960, 0.024, 0.038) + , m_save_heading(0.920, 0.755, 0.070, 0.040) + , m_save_background(0.973, 0.820, 0.020, 0.065) + , m_save_shortcut(0.936, 0.826, 0.029, 0.053) {} void MenuDetector::make_overlays(VideoOverlaySet& items) const{ @@ -40,8 +57,71 @@ void MenuDetector::make_overlays(VideoOverlaySet& items) const{ items.add(m_color, m_line3); items.add(m_color, m_line4); items.add(m_color, m_cross); + items.add(m_color, m_panel_top); + items.add(m_color, m_panel_left); + items.add(m_color, m_panel_right); + items.add(m_color, m_footer); + items.add(m_color, m_confirm_button); + items.add(m_color, m_save_button); + items.add(m_color, m_close_button); + items.add(m_color, m_save_heading); + items.add(m_color, m_save_background); + items.add(m_color, m_save_shortcut); } +// A/R/B prompts and the Save shortcut have dark neutral button backgrounds +// and light lettering. Check their contrast without OCR or image resources. +static bool has_menu_button(const ImageViewRGB32& patch){ + size_t dark = 0; + size_t light = 0; + for (size_t y = 0; y < patch.height(); y++){ + for (size_t x = 0; x < patch.width(); x++){ + const uint32_t pixel = patch.pixel(x, y); + const int r = (pixel >> 16) & 0xff; + const int g = (pixel >> 8) & 0xff; + const int b = pixel & 0xff; + const int low = std::min(r, std::min(g, b)); + const int high = std::max(r, std::max(g, b)); + if (high - low < 35){ + dark += high < 160; + light += low > 200; + } + } + } + const double pixels = (double)patch.width() * patch.height(); + return pixels > 0 && dark / pixels >= 0.15 && light / pixels >= 0.08; +} + +bool MenuDetector::detect_menu_chrome(const ImageViewRGB32& screen) const{ + // The surrounding panel remains the same with six or eight unlocked icons. + for (const ImageFloatBox& box : {m_panel_top, m_panel_left, m_panel_right}){ + if (!is_white(image_stats(extract_box_reference(screen, box)))){ + return false; + } + } + const ImageStats footer = image_stats(extract_box_reference(screen, m_footer)); + if (footer.average.b < footer.average.r + 35 || + footer.average.g < footer.average.r + 20 || footer.stddev.sum() > 60){ + return false; + } + for (const ImageFloatBox& box : {m_confirm_button, m_save_button, m_close_button, m_save_shortcut}){ + if (!has_menu_button(extract_box_reference(screen, box))){ + return false; + } + } + // Require both the light Save heading and its cyan background as well. + const ImageStats heading = image_stats(extract_box_reference(screen, m_save_heading)); + const ImageStats background = image_stats(extract_box_reference(screen, m_save_background)); + return heading.average.r > 180 && heading.average.g > 180 && heading.average.b > 180 && + heading.stddev.sum() > 30 && + background.average.g > background.average.r + 40 && + background.average.b > background.average.r + 40 && background.stddev.sum() < 90; +} + bool MenuDetector::detect(const ImageViewRGB32& screen){ + if (detect_menu_chrome(screen)){ + return true; + } + // Retain the existing layout check for captures supported by the old detector. ImageStats stats0 = image_stats(extract_box_reference(screen, m_line0)); if (!is_white(stats0)){ return false; @@ -84,6 +164,87 @@ bool MenuWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp + +std::string menu_detector_test_folder(){ + QDir directory(QCoreApplication::applicationDirPath()); + for (size_t level = 0; level < 6; level++){ + const QString folder = directory.filePath("Packages/Resources/PokemonBDSP/menu_detector"); + if (QDir(folder).exists()){ + return QDir(folder).absolutePath().toStdString(); + } + if (!directory.cdUp()) break; + } + return RESOURCE_PATH() + "PokemonBDSP/menu_detector"; +} + +bool run_menu_detector_tests(Logger& logger, const std::string& folder){ + const QDir directory(QString::fromStdString(folder)); + const auto files = directory.entryList({"*.png"}, QDir::Files, QDir::Name); + if (files.isEmpty()){ + logger.log("[Menu tests] No PNG screenshots in " + folder, COLOR_RED); + return false; + } + size_t passed = 0; + size_t failed = 0; + auto check = [&](bool actual, bool expected, const std::string& label){ + const bool success = actual == expected; + (success ? passed : failed)++; + logger.log(std::string(success ? "[PASS] " : "[FAIL] ") + label + + ": detected=" + (actual ? "true" : "false") + + ", expected=" + (expected ? "true" : "false"), success ? COLOR_GREEN : COLOR_RED); + }; + for (const QString& filename : files){ + const bool expected = !filename.startsWith("non_menu_"); + try{ + const ImageRGB32 image(directory.filePath(filename).toStdString()); + for (size_t width : {size_t(1920), size_t(1280), size_t(960)}){ + const ImageRGB32 scaled = image.scale_to(width, width * 9 / 16); + MenuDetector detector; + check(detector.detect(scaled), expected, + filename.toStdString() + " at " + std::to_string(width) + "px"); + } + }catch (const std::exception& error){ + failed++; + logger.log("[FAIL] " + filename.toStdString() + ": " + error.what(), COLOR_RED); + } + } + // Uniform bright/dark frames must not count as an open menu. + for (uint32_t color : {uint32_t(0xff000000), uint32_t(0xffffffff)}){ + ImageRGB32 blank(1280, 720); + for (size_t y = 0; y < blank.height(); y++){ + for (size_t x = 0; x < blank.width(); x++) blank.pixel(x, y) = color; + } + MenuDetector detector; + check(detector.detect(blank), false, color == 0xff000000 ? "Black frame" : "White frame"); + } + logger.log("[Menu tests] " + std::to_string(passed) + " passed, " + + std::to_string(failed) + " failed.", failed == 0 ? COLOR_GREEN : COLOR_RED); + return failed == 0; +} + +// Adapter for the existing filename-based command-line test map. +int test_MenuDetector(const ImageViewRGB32& image, bool expected){ + for (size_t width : {size_t(1920), size_t(1280), size_t(960)}){ + const ImageRGB32 scaled = image.scale_to(width, width * 9 / 16); + MenuDetector detector; + if (detector.detect(scaled) != expected) return 1; + } + return 0; +} + +class Test_MenuDetector : public UnitTest{ +public: + Test_MenuDetector() : UnitTest("PokemonBDSP::MenuDetector screenshot tests"){} + UnitTestResult run(Logger& logger, CancellableScope&) const override{ + return run_menu_detector_tests(logger, menu_detector_test_folder()); + } +}; + +void add_tests_MenuDetector(UnitTestDatabase& database){ + database.add(); +} + + } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h index f7e5a0517f..e7ef54f78a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonBDSP_MenuDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTest.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" @@ -17,6 +18,12 @@ namespace NintendoSwitch{ namespace PokemonBDSP{ +// Screenshot tests accept no controller context. non_menu_*.png are negative cases. +std::string menu_detector_test_folder(); +bool run_menu_detector_tests(Logger& logger, const std::string& folder); +void add_tests_MenuDetector(UnitTestDatabase& database); +int test_MenuDetector(const ImageViewRGB32& image, bool expected); + class MenuDetector : public StaticScreenDetector{ public: MenuDetector(Color color = COLOR_RED, bool skip_new_banner = false); @@ -25,6 +32,8 @@ class MenuDetector : public StaticScreenDetector{ virtual bool detect(const ImageViewRGB32& screen) override; private: + bool detect_menu_chrome(const ImageViewRGB32& screen) const; + Color m_color; ImageFloatBox m_line0; ImageFloatBox m_line1; @@ -32,6 +41,16 @@ class MenuDetector : public StaticScreenDetector{ ImageFloatBox m_line3; ImageFloatBox m_line4; ImageFloatBox m_cross; + ImageFloatBox m_panel_top; + ImageFloatBox m_panel_left; + ImageFloatBox m_panel_right; + ImageFloatBox m_footer; + ImageFloatBox m_confirm_button; + ImageFloatBox m_save_button; + ImageFloatBox m_close_button; + ImageFloatBox m_save_heading; + ImageFloatBox m_save_background; + ImageFloatBox m_save_shortcut; }; diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp index b8ff5e26a5..faf2f6a044 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp @@ -1,23 +1,297 @@ -/* Pokemon BDSP Tests - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "Common/Cpp/TestRunners/UnitTestDatabase.h" -#include "PokemonBDSP_Tests.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - - -void add_tests(UnitTestDatabase& database){ -} - - - -} -} -} +/* Pokemon BDSP Tests + * + * From: https://github.com/PokemonAutomation/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_Tests.h" +#include "Inference/PokemonBDSP_MenuDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ +namespace{ + +std::string action_name(BagMoveAction action){ + switch (action){ + case BagMoveAction::CONFIRM: return "Confirm"; + case BagMoveAction::UP: return "Up"; + case BagMoveAction::DOWN: return "Down"; + case BagMoveAction::PAGE_DOWN: return "Page down"; + case BagMoveAction::REWIND: return "Rewind"; + default: return "Unknown"; + } +} + +// A name ROI with no dark text is an empty row, not an unknown item. +bool empty_bag_row(const ImageViewRGB32& image, size_t row){ + const auto text = extract_box_reference(image, ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.052)); + size_t dark = 0; + for (size_t y = 0; y < text.height(); y++){ + for (size_t x = 0; x < text.width(); x++){ + uint32_t pixel = text.pixel(x, y); + dark += ((pixel >> 16) & 255) < 100 && ((pixel >> 8) & 255) < 100 && (pixel & 255) < 100; + } + } + return dark < text.width() * text.height() * 0.001; +} + +struct TestItemTemplate{ + BagItemTemplate item; + std::shared_ptr reference; +}; + +class Test_BagScreenshots : public UnitTest{ +public: + Test_BagScreenshots() : UnitTest("PokemonBDSP::Bag screenshot tests"){} + UnitTestResult run(Logger& logger, CancellableScope&) const override{ + return run_bag_screenshot_tests(logger, RESOURCE_PATH() + "PokemonBDSP/itembagtests"); + } +}; + +} + +bool run_bag_screenshot_tests(Logger& logger, const std::string& folder){ + const std::string prefix = folder + "/"; + QFile file(QString::fromStdString(prefix + "bag_tests.json")); + if (!file.open(QIODevice::ReadOnly)){ + logger.log("[Bag tests] Cannot open " + prefix + "bag_tests.json", COLOR_RED); + return false; + } + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(file.readAll(), &error); + const auto manifest = doc.object(); + const auto template_json = manifest["templates"].toArray(); + const auto cases = manifest["cases"].toArray(); + if (error.error != QJsonParseError::NoError || template_json.isEmpty() || cases.isEmpty()){ + logger.log("[Bag tests] Invalid or empty test manifest.", COLOR_RED); + return false; + } + std::map> images; + auto load_image = [&](const std::string& name){ + auto it = images.find(name); + if (it == images.end()){ + it = images.emplace(name, std::make_shared(prefix + name)).first; + } + return it->second; + }; + std::vector templates; + for (const auto& value : template_json){ + const auto entry = value.toObject(); + TestItemTemplate t; + t.item.label = entry["label"].toString().toStdString(); + t.item.reference_row = entry["row"].toInt(); + const auto name = entry["image"].toString().toStdString(); + if (t.item.label.empty() || t.item.reference_row < 1 || t.item.reference_row > 7 || + !QFileInfo::exists(QString::fromStdString(prefix + name))){ + logger.log("[Bag tests] Invalid or missing item reference: " + name, COLOR_RED); + return false; + } + t.reference = load_image(name); + templates.emplace_back(std::move(t)); + } + + size_t passed = 0; + size_t failed = 0; + auto check = [&](bool result, const std::string& detail){ + (result ? passed : failed)++; + logger.log(std::string(result ? "[PASS] " : "[FAIL] ") + detail, result ? COLOR_GREEN : COLOR_RED); + }; + // All 64 routes, including wraparound and Right for opposite pockets. + for (int current = 1; current <= 8; current++){ + for (int target = 1; target <= 8; target++){ + const auto plan = plan_bag_pocket_move(current, target); + const int delta = target - current; + const int distance = std::abs(delta); + const size_t expected = static_cast(std::min(distance, 8 - distance)); + const auto direction = distance == 0 ? BagPocketAction::CONFIRM : + distance == 4 || (delta > 0 && distance < 4) || (delta < 0 && distance > 4) + ? BagPocketAction::RIGHT : BagPocketAction::LEFT; + check(plan.presses == expected && plan.action == direction, + "Pocket route " + std::to_string(current) + " to " + std::to_string(target)); + } + } + check(plan_bag_pocket_move(4, 5).action == BagPocketAction::RIGHT && + plan_bag_pocket_move(4, 5).presses == 1, "Dropped press: recalculate from pocket 4 to 5"); + check(plan_bag_pocket_move(0, 5).action == BagPocketAction::UNKNOWN && + plan_bag_pocket_move(1, 9).action == BagPocketAction::UNKNOWN, + "Unknown or invalid pocket gives no movement"); + // Independent expected values, including paging and unknown-cursor handling. + const std::array pages{13, 11, 10, 9, 8, 7, 1}; + for (int cursor = 1; cursor <= 7; cursor++){ + const auto plan = plan_bag_item_move(cursor, 0); + check(plan.presses == pages[cursor - 1] && plan.action == + (cursor == 7 ? BagMoveAction::REWIND : BagMoveAction::PAGE_DOWN), + "Absent item, cursor " + std::to_string(cursor) + ": " + action_name(plan.action) + + " " + std::to_string(plan.presses)); + } + check(plan_bag_item_move(0, 3).action == BagMoveAction::UNKNOWN, "Unknown cursor gives no movement"); + check(plan_bag_item_move(2, 8).action == BagMoveAction::UNKNOWN, "Invalid item row gives no movement"); + check(bag_cursor_position(ImageRGB32()) == 0, "Missing image gives unknown cursor"); + + // Exact expected displacements for every cursor/target combination. + const int displacement[7][7] = { + { 0, 1, 2, 3, 4, 5, 6}, + {-1, 0, 1, 2, 3, 4, 5}, + {-2, -1, 0, 1, 2, 3, 4}, + {-3, -2, -1, 0, 1, 2, 3}, + {-4, -3, -2, -1, 0, 1, 2}, + {-5, -4, -3, -2, -1, 0, 1}, + {-6, -5, -4, -3, -2, -1, 0} + }; + for (int cursor = 1; cursor <= 7; cursor++){ + for (int target = 1; target <= 7; target++){ + const int delta = displacement[cursor - 1][target - 1]; + const auto plan = plan_bag_item_move(cursor, target); + check(plan.action == (delta == 0 ? BagMoveAction::CONFIRM : + delta < 0 ? BagMoveAction::UP : BagMoveAction::DOWN) && + plan.presses == static_cast(delta < 0 ? -delta : delta), + "Cursor " + std::to_string(cursor) + " to item " + std::to_string(target) + + ": " + action_name(plan.action) + " " + std::to_string(plan.presses)); + } + } + ImageRGB32 blank(1920, 1080); + for (size_t y = 0; y < blank.height(); y++){ + for (size_t x = 0; x < blank.width(); x++){ + blank.pixel(x, y) = 0xffffffff; + } + } + check(bag_cursor_position(blank) == 0, "Blank screenshot has no cursor"); + for (const auto& t : templates){ + const auto matches = match_bag_item_rows(blank, *t.reference, t.item); + bool found = false; + for (const auto& m : matches){ + found |= m.matched; + } + check(!found, "Blank screenshot does not match " + t.item.label); + } + + for (const auto& case_value : cases){ + const auto test = case_value.toObject(); + const auto filename = test["image"].toString().toStdString(); + const auto expected = test["items"].toArray(); + const int expected_cursor = test["cursor"].toInt(-1); + if (expected.size() != 7 || expected_cursor < 0 || expected_cursor > 7 || + !QFileInfo::exists(QString::fromStdString(prefix + filename))){ + check(false, "Invalid or missing screenshot case: " + filename); + continue; + } + const auto image = load_image(filename); + const int cursor = bag_cursor_position(*image); + check(cursor == expected_cursor, filename + ": cursor " + std::to_string(cursor) + + ", expected " + std::to_string(expected_cursor)); + // Scale the same screen to the minimum supported capture resolution. + const auto small = image->scale_to(1280, 720); + check(bag_cursor_position(small) == expected_cursor, filename + ": cursor at 720p"); + + std::array labels; + std::array best; + std::array second; + best.fill(std::numeric_limits::infinity()); + second = best; + for (const auto& t : templates){ + const auto matches = match_bag_item_rows(*image, *t.reference, t.item); + for (size_t row = 0; row < 7; row++){ + const auto& m = matches[row]; + if (!m.matched){ + continue; + } + const double score = m.icon_rmsd + m.name_rmsd; + if (score < best[row]){ + second[row] = best[row]; + best[row] = score; + labels[row] = t.item.label; + }else if (score < second[row]){ + second[row] = score; + } + } + } + for (size_t row = 0; row < 7; row++){ + if (labels[row].empty()){ + labels[row] = empty_bag_row(*image, row + 1) ? "" : "Unknown"; + }else if (second[row] - best[row] < 2.0){ + labels[row] = "Unknown"; + } + check(labels[row] == expected[row].toString().toStdString(), + filename + ": row " + std::to_string(row + 1) + " = " + + (labels[row].empty() ? "Empty" : labels[row]) + ", expected " + + (expected[row].toString().isEmpty() ? "Empty" : expected[row].toString().toStdString())); + } + for (const auto& query_value : test["queries"].toArray()){ + const auto query = query_value.toObject(); + const auto wanted = query["item"].toString().toStdString(); + int item_row = 0; + for (size_t row = 0; row < 7; row++){ + if (labels[row] == wanted){ + item_row = static_cast(row + 1); + } + } + const auto plan = plan_bag_item_move(cursor, item_row); + check(action_name(plan.action) == query["action"].toString().toStdString() && + plan.presses == static_cast(query["presses"].toInt(-1)), + filename + ": looking for " + wanted + ", item row " + std::to_string(item_row) + + ", cursor row " + std::to_string(cursor) + ": " + action_name(plan.action) + + " " + std::to_string(plan.presses) + " (no input sent)"); + } + } + // Optional pocket fixtures extend the same offline/dev-mode suite. + QFile pocket_file(QString::fromStdString(prefix + "pocket_tests.json")); + if (pocket_file.exists()){ + if (!pocket_file.open(QIODevice::ReadOnly)){ + check(false, "Cannot open pocket test manifest"); + }else{ + QJsonParseError pocket_error; + const auto pocket_doc = QJsonDocument::fromJson(pocket_file.readAll(), &pocket_error); + const auto pocket_cases = pocket_doc.object()["cases"].toArray(); + check(pocket_error.error == QJsonParseError::NoError && !pocket_cases.isEmpty(), + "Pocket test manifest is valid"); + for (const auto& value : pocket_cases){ + const auto entry = value.toObject(); + const auto name = entry["image"].toString().toStdString(); + const int expected_pocket = entry["pocket"].toInt(-1); + if (expected_pocket < 0 || expected_pocket > 8 || !entry["empty"].isBool() || + !QFileInfo::exists(QString::fromStdString(prefix + name))){ + check(false, "Invalid or missing pocket case: " + name); + continue; + } + const auto image = load_image(name); + for (size_t width : {1920, 1280, 960}){ + const auto scaled = image->scale_to(width, width * 9 / 16); + check(bag_pocket_position(scaled) == expected_pocket && + bag_list_is_empty(scaled) == entry["empty"].toBool(), + name + ": pocket " + std::to_string(expected_pocket) + + ", empty=" + (entry["empty"].toBool() ? "true" : "false") + + " at width " + std::to_string(width)); + } + } + } + } + check(bag_pocket_position(blank) == 0 && !bag_list_is_empty(blank), + "Blank screenshot has no pocket and is not an empty Bag"); + check(bag_pocket_position(ImageRGB32()) == 0 && !bag_list_is_empty(ImageRGB32()), + "Missing screenshot has no pocket and is not an empty Bag"); + logger.log("[Bag tests] " + std::to_string(passed) + " passed, " + std::to_string(failed) + " failed.", + failed == 0 ? COLOR_GREEN : COLOR_RED); + return failed == 0; +} + +void add_tests(UnitTestDatabase& database){ + database.add(); + add_tests_MenuDetector(database); +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.h b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.h index 009a365035..272fc9f29d 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.h +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.h @@ -1,25 +1,28 @@ -/* Pokemon BDSP Tests - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonBDSP_Tests_H -#define PokemonAutomation_PokemonBDSP_Tests_H - -#include "Common/Cpp/TestRunners/UnitTest.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - - -void add_tests(UnitTestDatabase& database); - - - -} -} -} -#endif +/* Pokemon BDSP Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_Tests_H +#define PokemonAutomation_PokemonBDSP_Tests_H + +#include "Common/Cpp/TestRunners/UnitTest.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + + +// No controller context is accepted; this function only reads images and logs. +bool run_bag_screenshot_tests(Logger& logger, const std::string& folder); + +void add_tests(UnitTestDatabase& database); + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index dd03c16e90..2609f0f1a9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1,9 +1,11 @@ +#include /* BDSP AutoStory Tools * * From: https://github.com/PokemonAutomation/ * */ #include +#include "CommonFramework/ImageTools/ImageDiff.h" #include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Globals.h" @@ -361,7 +363,7 @@ static const char* shop_item_slug(ShopItem item){ } } -// {MaxRepel, 2, 4}, {FullRestore, 0, 3} → "4 Max repel, 3 Full restore" +// {MaxRepel, 2, 4}, {FullRestore, 0, 3} → "4 Max repel, 3 Full restore" static std::string make_buy_label(const std::vector& items){ std::string label; for (size_t i = 0; i < items.size(); i++){ @@ -394,7 +396,7 @@ bool buy_items( context.wait_for_all_requests(); - // ── Coordinate constants ───────────────────────────────────────────────── + // ── Coordinate constants ───────────────────────────────────────────────── // Area where the selection arrow appears at the vendor greeting menu (Buy/Sell/Leave). static const ImageFloatBox VENDOR_ARROW_BOX{0.630000, 0.550000, 0.070000, 0.120000}; @@ -409,13 +411,13 @@ bool buy_items( static constexpr size_t MAX_OUTER = 50; static constexpr size_t MAX_ITEM_SEARCH = 50; // max iterations per item before giving up - // ── Outer state machine ────────────────────────────────────────────────── + // ── Outer state machine ────────────────────────────────────────────────── for (size_t outer = 0; outer < MAX_OUTER; outer++){ SelectionArrowFinder arrow_vendor(stream.overlay(), VENDOR_ARROW_BOX, COLOR_GREEN); ShopBuyingWindowWatcher buying_window; ShortDialogWatcher dialog(COLOR_RED); - // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. + // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. stream.log("Starting to loop over items"); int state = run_until( stream, context, @@ -432,12 +434,12 @@ bool buy_items( switch (state){ - case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" + case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" stream.log("Found the selection arrow, pressing A to enter the buying window...", COLOR_GREEN); pbf_press_button(context, BUTTON_A, 80ms, 500ms); break; - case 1: // case 2: buying window open → buy each requested item + case 1: // case 2: buying window open → buy each requested item stream.log("Found the buying window, searching for the right item...", COLOR_GREEN); { bool buying_failed = false; @@ -472,7 +474,7 @@ bool buy_items( switch (sub){ - case 0: // YES/NO confirmation arrow → confirm purchase + case 0: // YES/NO confirmation arrow → confirm purchase stream.log("Found the confirmation to buy arrow, pressing A to buy", COLOR_GREEN); { context.wait_for_all_requests(); @@ -492,7 +494,7 @@ bool buy_items( case 1: // quantity window if (!hover_confirmed){ - // Quantity window appeared before we confirmed the right item — + // Quantity window appeared before we confirmed the right item — // an earlier A press accidentally opened the wrong item's selector. // Press B to go back to the list and continue searching. stream.log("Unexpected quantity window (wrong item), pressing B...", COLOR_YELLOW); @@ -513,14 +515,14 @@ bool buy_items( pbf_wait(context, 1000ms); break; - case 2: // correct item highlighted → press A to open quantity selector + case 2: // correct item highlighted → press A to open quantity selector stream.log("Found the right item, pressing A to buy a certain quantity...", COLOR_GREEN); hover_confirmed = true; pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 1000ms); break; - default: // outline not found or wrong item → advance to next row + default: // outline not found or wrong item → advance to next row stream.log("Not on the right item, moving...", COLOR_GREEN); pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); pbf_wait(context, 300ms); @@ -543,7 +545,7 @@ bool buy_items( break; } - case 2: // case 3: unexpected ShortDialog → press B until back in buying window + case 2: // case 3: unexpected ShortDialog → press B until back in buying window { ShopBuyingWindowWatcher back_in_shop; run_until( @@ -556,7 +558,7 @@ bool buy_items( break; } - default: // outer default: unrecognised state → press B to recover + default: // outer default: unrecognised state → press B to recover pbf_press_button(context, BUTTON_B, 80ms, 3000ms); break; } @@ -1029,9 +1031,11 @@ void open_menu( ){ using namespace std::chrono_literals; - context.wait_for_all_requests(); - overworld_to_menu(stream, context); - pbf_wait(context, 1000ms); + // Previous single opening attempt: + // context.wait_for_all_requests(); + // overworld_to_menu(stream, context); + // pbf_wait(context, 1000ms); + // context.wait_for_all_requests(); context.wait_for_all_requests(); MenuCursorDetector detector; @@ -1040,9 +1044,17 @@ void open_menu( "POKEDEX", "POKEMON", "BAG", "CARD", "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", }; - std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/AutoStory"; + // Previous optional template directory: PokemonBDSP/AutoStory. + std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/Autostory"; for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ std::string path = ref_dir + "/" + ICON_NAMES[i] + ".png"; + // These templates are optional. The dark-pixel cursor detector + // remains available when a template is not packaged. + std::error_code file_error; + if (!std::filesystem::is_regular_file(path, file_error)){ + continue; + } + // Previously constructed the image before checking its availability. ImageRGB32 img(path); if (img){ detector.set_icon_reference( @@ -1052,16 +1064,54 @@ void open_menu( } } } + // Previous single detection attempt retained for comparison. + // MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; + // for (int i = 0; i < 10 && cur == MenuCursorPosition::UNKNOWN; i++){ + // context.wait_for(50ms); + // VideoSnapshot snap = stream.video().snapshot(); + // if (snap) cur = detector.detect(snap); + // } + // if (cur == MenuCursorPosition::UNKNOWN){ + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // "Menu cursor not detected after opening menu.", + // stream + // ); + // } + MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; - for (int i = 0; i < 10 && cur == MenuCursorPosition::UNKNOWN; i++){ - context.wait_for(50ms); - VideoSnapshot snap = stream.video().snapshot(); - if (snap) cur = detector.detect(snap); + for (size_t attempt = 1; attempt <= 3; attempt++){ + stream.log("[Open menu] Open and detect attempt " + std::to_string(attempt) + "/3."); + try { + // A late-opening menu may already be visible after a timeout. + // Check before pressing X again, since X would close that menu. + MenuWatcher already_open; + if (wait_until(stream, context, 100ms, {{already_open}}) < 0){ + overworld_to_menu(stream, context); + } + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + for (int frame = 0; frame < 10 && cur == MenuCursorPosition::UNKNOWN; frame++){ + context.wait_for(50ms); + const VideoSnapshot snapshot = stream.video().snapshot(); + if (snapshot){ + cur = detector.detect(snapshot); + } + } + if (cur != MenuCursorPosition::UNKNOWN){ + stream.log("[Open menu] Menu and cursor detected on attempt " + std::to_string(attempt) + ".", COLOR_GREEN); + break; + } + stream.log("[Open menu] Menu cursor not detected on attempt " + std::to_string(attempt) + ".", COLOR_ORANGE); + } + catch (OperationFailedException& error){ + stream.log("[Open menu] Attempt " + std::to_string(attempt) + " failed: " + error.message(), COLOR_ORANGE); + } } if (cur == MenuCursorPosition::UNKNOWN){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "Menu cursor not detected after opening menu.", + "Menu or cursor detection failed after 3 opening attempts.", stream ); } @@ -1095,6 +1145,643 @@ void open_menu( } +// Search the selected item's fixed description header rather than guessing +// which list row is selected. This remains in place while the list scrolls. +// A cursor parked at row 2 or 6 does not indicate a list end: only repeated +// unchanged item headers after movement are used to detect an end. +// Previous selected-item-only search, retained for reference. +// bool find_bag_item( +// VideoStream& stream, +// ProControllerContext& context, +// const BagItemTemplate& item, +// int num_icons, +// size_t max_scrolls_per_pocket, +// std::chrono::milliseconds search_timeout +// ){ +// auto reference = std::make_shared( +// RESOURCE_PATH() + item.reference_path +// ); +// ImageMatchDetector icon(reference, item.icon_box, item.max_icon_rmsd, true); +// ImageMatchDetector name(reference, item.name_box, item.max_name_rmsd, true); +// ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); +// const ImageFloatBox selected_header(0.428, 0.754, 0.382, 0.046); +// const auto deadline = std::chrono::steady_clock::now() + search_timeout; +// +// stream.log("[Bag search] Looking for " + item.label + "."); +// open_menu(stream, context, MenuCursorPosition::BAG, num_icons); +// pbf_wait(context, 1000ms); +// context.wait_for_all_requests(); +// +// for (size_t pocket = 0; pocket < 8; pocket++){ +// ImageRGB32 initial_header; +// ImageRGB32 previous_header; +// size_t unchanged = 0; +// size_t matching_frames = 0; +// size_t scrolls = 0; +// bool scanning_up = false; +// +// while (std::chrono::steady_clock::now() < deadline){ +// context.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// if (!frame || !bag.detect(frame)){ +// stream.log("[Bag search] Bag screen not detected.", COLOR_ORANGE); +// return false; +// } +// const double icon_score = icon.rmsd(frame); +// const double name_score = item.check_name ? name.rmsd(frame) : 0.0; +// stream.log( +// "[Bag search] Pocket " + std::to_string(pocket + 1) + +// ", scroll " + std::to_string(scrolls) + +// ": icon=" + std::to_string(icon_score) + +// ", name=" + std::to_string(name_score) +// ); +// if (icon_score <= item.max_icon_rmsd && +// name_score <= item.max_name_rmsd){ +// if (++matching_frames >= 3){ +// stream.log("[Bag search] " + item.label + " confirmed.", COLOR_GREEN); +// return true; +// } +// pbf_wait(context, 150ms); +// continue; +// } +// matching_frames = 0; +// +// const ImageRGB32 header = extract_box_reference(frame, selected_header).scale_to(382, 46); +// if (!initial_header){ +// initial_header = header.copy(); +// } +// else if (scrolls > 0){ +// const bool same_as_previous = +// ImageMatch::pixel_RMSD(previous_header, header) <= 3.0; +// const bool returned_to_start = +// ImageMatch::pixel_RMSD(initial_header, header) <= 3.0; +// if (returned_to_start && !same_as_previous){ +// break; +// } +// // Repeated unchanged rows mean an empty pocket or list end. +// // Returning to the initial row means the list wrapped. +// unchanged = same_as_previous || returned_to_start ? unchanged + 1 : 0; +// if (unchanged >= 3){ +// if (scanning_up){ +// break; +// } +// // A non-wrapping list may have opened in the middle. +// // Search upward as well before moving to another pocket. +// scanning_up = true; +// unchanged = 0; +// initial_header = header.copy(); +// } +// } +// previous_header = header.copy(); +// if (scrolls >= max_scrolls_per_pocket){ +// stream.log("[Bag search] Scroll limit reached; search incomplete.", COLOR_ORANGE); +// return false; +// } +// scrolls++; +// pbf_press_dpad(context, scanning_up ? DPAD_UP : DPAD_DOWN, 80ms, 350ms); +// } +// if (std::chrono::steady_clock::now() >= deadline){ +// stream.log("[Bag search] Search timed out; search incomplete.", COLOR_ORANGE); +// return false; +// } +// if (pocket < 7){ +// pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); +// context.wait_for_all_requests(); +// } +// } +// stream.log("[Bag search] Item not found in any pocket.", COLOR_ORANGE); +// return false; +// } +// + +// One narrow strip on the left side of the red selection border. Divide its +// height into the seven row bands; the item's art and NEW badge are outside it. +int bag_cursor_position(const ImageViewRGB32& frame){ + if (!frame){ + return 0; + } + const ImageViewRGB32 strip = extract_box_reference( + frame, ImageFloatBox(0.431, 0.229, 0.005, 0.470) + ); + double best = 0.0; + double second = 0.0; + int position = 0; + for (int row = 0; row < 7; row++){ + const size_t top = static_cast(strip.height() * (row * 0.075 / 0.470)); + const size_t bottom = static_cast(strip.height() * ((row * 0.075 + 0.020) / 0.470)); + size_t red = 0; + size_t total = 0; + for (size_t y = top; y < bottom && y < strip.height(); y++){ + for (size_t x = 0; x < strip.width(); x++){ + const uint32_t pixel = strip.pixel(x, y); + const unsigned r = (pixel >> 16) & 255; + const unsigned g = (pixel >> 8) & 255; + const unsigned b = pixel & 255; + red += r > 180 && g < 120 && b < 120; + total++; + } + } + const double score = total == 0 ? 0.0 : static_cast(red) / total; + if (score > best){ + second = best; + best = score; + position = row + 1; + }else if (score > second){ + second = score; + } + } + return best >= 0.15 && best - second >= 0.10 ? position : 0; +} + +static ImageFloatBox bag_list_icon_box(size_t row){ + return ImageFloatBox(0.449, 0.212 + (row - 1) * 0.075, 0.033, 0.050); +} + +static ImageFloatBox bag_list_name_box(size_t row){ + // Previous height included the faint lower selection border in rows 2/6. + // return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.052); + return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.050); +} + +static double bag_row_patch_score( + const ImageRGB32& reference, + const ImageViewRGB32& frame, + const ImageFloatBox& box +){ + const ImageRGB32 live = extract_box_reference(frame, box).scale_to( + reference.width(), reference.height() + ); + return ImageMatch::pixel_RMSD(reference, live); +} + +// Previous name-area empty-list check retained for reference. +// static bool bag_list_is_empty(const ImageViewRGB32& frame){ +// const ImageViewRGB32 names = extract_box_reference( +// frame, ImageFloatBox(0.492, 0.213, 0.335, 0.500) +// ); +// size_t dark = 0; +// for (size_t y = 0; y < names.height(); y++){ +// for (size_t x = 0; x < names.width(); x++){ +// const uint32_t pixel = names.pixel(x, y); +// dark += ((pixel >> 16) & 255) < 100 && +// ((pixel >> 8) & 255) < 100 && (pixel & 255) < 100; +// } +// } +// return dark < names.width() * names.height() * 0.001; +// } + +int bag_pocket_position(const ImageViewRGB32& frame){ + if (!frame) return 0; + int selected = 0; + for (int pocket = 1; pocket <= 8; pocket++){ + // The lower left edge of each tab is below the red NEW badge. + // This strip includes both the normal outline and enlarged selection. + const auto edge = extract_box_reference(frame, + ImageFloatBox(0.570 + (pocket - 1) * 0.0421, 0.098, 0.0045, 0.021)); + size_t colored = 0; + for (size_t y = 0; y < edge.height(); y++){ + for (size_t x = 0; x < edge.width(); x++){ + const uint32_t pixel = edge.pixel(x, y); + const int r = (pixel >> 16) & 255; + const int g = (pixel >> 8) & 255; + const int b = pixel & 255; + // Red when focused, blue when the item list has focus. + colored += (r > 160 && r > g + 60 && r > b + 60) || + (b > 160 && b > r + 40 && b > g + 20); + } + } + const double pixels = (double)edge.width() * edge.height(); + if (pixels > 0 && colored / pixels >= 0.18){ + if (selected != 0) return 0; + selected = pocket; + } + } + return selected; +} + +bool bag_list_is_empty(const ImageViewRGB32& frame){ + if (!frame || bag_pocket_position(frame) == 0 || bag_cursor_position(frame) != 0) return false; + // Empty pockets have no item icons. The central Empty label is outside + // this column; counting dark pixels in the name area included that label. + const auto icons = extract_box_reference(frame, ImageFloatBox(0.449, 0.213, 0.032, 0.500)); + const auto stats = image_stats(icons); + return stats.average.r > 220 && stats.average.g > 220 && stats.average.b > 220 && + stats.stddev.sum() < 20; +} + + +std::array match_bag_item_rows( + const ImageViewRGB32& frame, + const ImageViewRGB32& reference, + const BagItemTemplate& item +){ + std::array result; + if (!frame || !reference || item.reference_row < 1 || item.reference_row > 7){ + return result; + } + const ImageRGB32 icon = extract_box_reference(reference, bag_list_icon_box(item.reference_row)).copy(); + const ImageRGB32 name = extract_box_reference(reference, bag_list_name_box(item.reference_row)).copy(); + for (size_t row = 1; row <= 7; row++){ + auto& match = result[row - 1]; + match.icon_rmsd = bag_row_patch_score(icon, frame, bag_list_icon_box(row)); + match.name_rmsd = item.check_name + ? bag_row_patch_score(name, frame, bag_list_name_box(row)) : 0.0; + match.matched = match.icon_rmsd <= item.max_icon_rmsd && match.name_rmsd <= item.max_name_rmsd; + } + return result; +} + +BagMovePlan plan_bag_item_move(int cursor_row, int item_row){ + if (cursor_row < 1 || cursor_row > 7 || item_row < 0 || item_row > 7){ + return {}; + } + if (item_row != 0){ + if (item_row == cursor_row){ + return {BagMoveAction::CONFIRM, 0}; + } + return { + item_row > cursor_row ? BagMoveAction::DOWN : BagMoveAction::UP, + static_cast(std::abs(item_row - cursor_row)) + }; + } + if (cursor_row == 7){ + return {BagMoveAction::REWIND, 1}; + } + return {BagMoveAction::PAGE_DOWN, cursor_row == 1 ? 13 : static_cast(13 - cursor_row)}; +} + + +static int bag_confirm_pocket(VideoStream& stream, ProControllerContext& context){ + int previous = 0; + size_t matches = 0; + for (size_t check = 0; check < 10; check++){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + const int pocket = frame ? bag_pocket_position(frame) : 0; + matches = pocket != 0 && pocket == previous ? matches + 1 : pocket != 0 ? 1 : 0; + previous = pocket; + if (matches >= 3) return pocket; + pbf_wait(context, 100ms); + } + return 0; +} + +// static bool bag_advance_pocket(VideoStream& stream, ProControllerContext& context, int current){ +// const int expected = current % 8 + 1; +// for (size_t attempt = 0; attempt < 3; attempt++){ +// // Recheck before retrying: a delayed change must not cause a second step. +// const int observed = bag_confirm_pocket(stream, context); +// if (observed == expected) return true; +// if (observed != current){ +// stream.log("[Bag search] Pocket selection uncertain; stopping.", COLOR_ORANGE); +// return false; +// } +// pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); +// const int result = bag_confirm_pocket(stream, context); +// stream.log("[Bag search] Pocket change: " + std::to_string(current) + " -> " + +// std::to_string(result) + ", expected " + std::to_string(expected) + "."); +// if (result == expected) return true; +// } +// stream.log("[Bag search] Could not confirm pocket change after 3 attempts.", COLOR_ORANGE); +// return false; +// } + +BagPocketMovePlan plan_bag_pocket_move(int current, int target){ + if (current < 1 || current > 8 || target < 1 || target > 8) return {}; + const int right = (target - current + 8) % 8; + const int left = (current - target + 8) % 8; + if (right == 0) return {BagPocketAction::CONFIRM, 0}; + return right <= left ? BagPocketMovePlan{BagPocketAction::RIGHT, static_cast(right)} + : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; +} + +static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& context, int target){ + int current = bag_confirm_pocket(stream, context); + for (size_t attempt = 0; attempt < 3; attempt++){ + const auto plan = plan_bag_pocket_move(current, target); + if (plan.action == BagPocketAction::CONFIRM) return true; + if (plan.action == BagPocketAction::UNKNOWN){ + stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); + return false; + } + stream.log("[Bag search] Pocket " + std::to_string(current) + " to " + + std::to_string(target) + ": " + + (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + + std::to_string(plan.presses) + " presses, attempt " + std::to_string(attempt + 1) + "/3."); + for (size_t press = 0; press < plan.presses; press++){ + pbf_press_dpad(context, plan.action == BagPocketAction::RIGHT ? DPAD_RIGHT : DPAD_LEFT, + 80ms, 150ms); + } + pbf_wait(context, 350ms); + current = bag_confirm_pocket(stream, context); + stream.log("[Bag search] Final pocket=" + std::to_string(current) + + ", expected=" + std::to_string(target) + "."); + if (current == target) return true; + } + stream.log("[Bag search] Target pocket not reached after 3 movement batches.", COLOR_ORANGE); + return false; +} + + +bool find_bag_item( + VideoStream& stream, + ProControllerContext& context, + const BagItemTemplate& item, + int target_pocket, + int num_icons, + size_t max_scrolls_per_pocket, + std::chrono::milliseconds search_timeout +){ + if (target_pocket < 1 || target_pocket > 8 || item.reference_row < 1 || item.reference_row > 7){ + stream.log("[Bag search] Invalid template reference row.", COLOR_ORANGE); + return false; + } + auto reference = std::make_shared(RESOURCE_PATH() + item.reference_path); +// const ImageRGB32 reference_icon = extract_box_reference( +// *reference, bag_list_icon_box(item.reference_row) +// ).copy(); +// const ImageRGB32 reference_name = extract_box_reference( +// *reference, bag_list_name_box(item.reference_row) +// ).copy(); + ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); + ImageMatchDetector selected_icon(reference, item.icon_box, item.max_icon_rmsd, true); + ImageMatchDetector selected_name(reference, item.name_box, item.max_name_rmsd, true); + const auto deadline = std::chrono::steady_clock::now() + search_timeout; + + open_menu(stream, context, MenuCursorPosition::BAG, num_icons); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + + if (!bag_navigate_to_pocket(stream, context, target_pocket)) return false; + // Previously searched all eight pockets. Only inspect the requested pocket now. + // for (size_t pocket = 0; pocket < 8; pocket++){ + { + const int selected_pocket = bag_confirm_pocket(stream, context); + if (selected_pocket == 0){ + stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); + return false; + } + size_t commands = 0; + bool empty = false; + auto move = [&](DpadPosition direction, size_t count, bool check_rows = false){ + for (size_t press = 0; press < count; press++){ + if (commands >= max_scrolls_per_pocket || + std::chrono::steady_clock::now() >= deadline){ + stream.log("[Bag search] Limit reached; search incomplete.", COLOR_ORANGE); + return false; + } + commands++; + pbf_press_dpad(context, direction, 80ms, 150ms); + if (check_rows){ + context.wait_for_all_requests(); + const VideoSnapshot page_frame = stream.video().snapshot(); + if (!page_frame || !bag.detect(page_frame)){ + return false; + } + const auto matches = match_bag_item_rows(page_frame, *reference, item); + for (const auto& match : matches){ + if (match.matched){ + // Stop paging; the main loop verifies and selects the row. + return true; + } + } + } + } + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + return true; + }; + for (size_t pass = 0; pass < 3 && !empty; pass++){ + size_t matching_frames = 0; + size_t missing_cursor = 0; + size_t failed_confirmation = 0; + size_t unchanged_pages = 0; + ImageRGB32 previous_page; + stream.log( + "[Bag search] Looking for " + item.label + ": pocket " + + std::to_string(selected_pocket) + ", pass " + std::to_string(pass + 1) + "/3." + ); + while (std::chrono::steady_clock::now() < deadline){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame || !bag.detect(frame)){ + stream.log("[Bag search] Bag screen not detected.", COLOR_ORANGE); + return false; + } + const int cursor = bag_cursor_position(frame); + int target = 0; + double best = 1000.0; + bool ambiguous = false; + // The seven list ROIs move neither with the cursor nor with + // scrolling. Each contains an icon patch and a name patch. + // Previous per-row loop retained for reference. + // for (size_t row = 1; row <= 7; row++){ + // const double icon = bag_row_patch_score(reference_icon, frame, bag_list_icon_box(row)); + // const double name = item.check_name + // ? bag_row_patch_score(reference_name, frame, bag_list_name_box(row)) : 0.0; + // if (icon <= item.max_icon_rmsd && name <= item.max_name_rmsd){ + // if (target != 0){ + // ambiguous = true; + // } + // if (icon + name < best){ + // target = static_cast(row); + // best = icon + name; + // } + // } + // } + const auto matches = match_bag_item_rows(frame, *reference, item); + for (size_t row = 1; row <= 7; row++){ + const auto& match = matches[row - 1]; + if (match.matched){ + if (target != 0){ + ambiguous = true; + } + if (match.icon_rmsd + match.name_rmsd < best){ + target = static_cast(row); + best = match.icon_rmsd + match.name_rmsd; + } + } + } + stream.log( + "[Bag search] " + item.label + (target == 0 ? " is not detected" : " is detected") + + " on screen; item row=" + std::to_string(target) + + ", cursor row=" + std::to_string(cursor) + "." + ); + if (ambiguous){ + stream.log("[Bag search] Multiple rows match; selection is uncertain.", COLOR_ORANGE); + return false; + } + if (cursor == 0){ + if (bag_list_is_empty(frame)){ + empty = true; + break; + } + if (++missing_cursor >= 3){ + stream.log("[Bag search] Cursor location uncertain.", COLOR_ORANGE); + return false; + } + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + continue; + } + missing_cursor = 0; + if (target != 0){ + previous_page = ImageRGB32(); + unchanged_pages = 0; + if (target == cursor && selected_icon.detect(frame) && + (!item.check_name || selected_name.detect(frame))){ + if (++matching_frames >= 3){ + stream.log("[Bag search] Item and cursor confirmed in row " + + std::to_string(cursor) + ".", COLOR_GREEN); + return true; + } + pbf_wait(context, 150ms); + context.wait_for_all_requests(); + continue; + } + matching_frames = 0; + if (target == cursor){ + if (++failed_confirmation < 3){ + pbf_wait(context, 150ms); + context.wait_for_all_requests(); + continue; + } + stream.log("[Bag search] Selected-item confirmation failed.", COLOR_ORANGE); + return false; + } + failed_confirmation = 0; + const size_t count = plan_bag_item_move(cursor, target).presses; + stream.log("[Bag search] Moving " + std::string(target > cursor ? "down " : "up ") + + std::to_string(count) + " time(s), then checking again."); + if (!move(target > cursor ? DPAD_DOWN : DPAD_UP, count)){ + return false; + } + continue; + } + matching_frames = 0; + const ImageRGB32 page = extract_box_reference( + frame, ImageFloatBox(0.449, 0.212, 0.378, 0.500) + ).scale_to(378, 500); + if (previous_page && ImageMatch::pixel_RMSD(previous_page, page) <= 3.0){ + unchanged_pages++; + }else{ + unchanged_pages = 0; + } + previous_page = page.copy(); + // Row 7 signals the bottom of a long list. Repeated pages + // also cover shorter lists whose final row is less than 7. + if (cursor == 7 || unchanged_pages >= 2){ + stream.log("[Bag search] List end; returning to row 1."); + while (true){ + const VideoSnapshot rewind_frame = stream.video().snapshot(); + if (!rewind_frame || !bag.detect(rewind_frame)){ + return false; + } + const int rewind_cursor = bag_cursor_position(rewind_frame); + if (rewind_cursor == 1){ + break; + } + if (rewind_cursor == 0 || !move(DPAD_UP, 1)){ + return false; + } + } + break; + } + const size_t count = plan_bag_item_move(cursor, 0).presses; + stream.log("[Bag search] Advancing " + std::to_string(count) + " presses to another page."); + if (!move(DPAD_DOWN, count, true)){ + return false; + } + } + if (std::chrono::steady_clock::now() >= deadline){ + stream.log("[Bag search] Timeout; search incomplete.", COLOR_ORANGE); + return false; + } + } +// if (pocket < 7){ +// // pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); +// // context.wait_for_all_requests(); +// if (!bag_advance_pocket(stream, context, selected_pocket)) return false; +// } + + } + stream.log("[Bag search] Item not found after three passes per nonempty pocket.", COLOR_ORANGE); + return false; +} + +bool use_escape_rope( + VideoStream& stream, + ProControllerContext& context, + int num_icons +){ + const BagItemTemplate rope{ + // "PokemonBDSP/AutoStory/Bag_EscapeRope.png", "escape-rope" + "PokemonBDSP/Autostory/Bag_EscapeRope.png", "escape-rope" + }; + if (!find_bag_item(stream, context, rope, 5, num_icons)){ + return false; + } + + pbf_press_button(context, BUTTON_A, 80ms, 350ms); + context.wait_for_all_requests(); + SelectionArrowFinder use_menu( + stream.overlay(), ImageFloatBox(0.660, 0.100, 0.310, 0.430), COLOR_GREEN + ); + if (wait_until(stream, context, 3s, {{use_menu}}) != 0){ + stream.log("[Escape Rope] Item action menu not detected.", COLOR_ORANGE); + return false; + } + + // Watch while selecting Use so the transition cannot happen before + // the watcher starts. Extra A presses handle a confirmation prompt. + BlackScreenWatcher transition; + // Escape Rope fades white in the supplied clips; support black transitions too. + WhiteScreenWatcher white_transition; + const int result = run_until( + stream, context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 10; press++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); + } + ctx.wait_for_all_requests(); + pbf_wait(ctx, 10s); + ctx.wait_for_all_requests(); + }, + {transition, white_transition} + ); + if (result < 0){ + stream.log("[Escape Rope] No exit transition detected.", COLOR_ORANGE); + return false; + } + BlackScreenWatcher finished( + COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms + ); + WhiteScreenWatcher white_finished( + COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, + WhiteScreenWatcher::FinderType::GONE, 500ms + ); + stream.log(std::string("[Escape Rope] ") + + (result == 1 ? "White" : "Black") + " exit transition detected."); + // if (wait_until(stream, context, 15s, {{finished}}) != 0){ return false; } + const int finish_result = result == 1 + ? wait_until(stream, context, 15s, {{white_finished}}) + : wait_until(stream, context, 15s, {{finished}}); + if (finish_result != 0){ + stream.log("[Escape Rope] Exit transition did not finish.", COLOR_ORANGE); + return false; + } + OverworldWatcher overworld; + if (wait_until(stream, context, 10s, {{overworld}}) != 0){ + stream.log("[Escape Rope] Overworld not detected after transition.", COLOR_ORANGE); + return false; + } + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + stream.log("[Escape Rope] Exit transition and overworld confirmed.", COLOR_GREEN); + return true; +} + + bool activate_repel( VideoStream& stream, ProControllerContext& context, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 01781a11f2..8d6cb72937 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStoryTools_H #define PokemonAutomation_PokemonBDSP_AutoStoryTools_H +#include #include #include #include @@ -231,6 +232,81 @@ void mash_until_dialogue_ends( Milliseconds timeout = std::chrono::seconds(60) ); +// A full Bag screenshot with the desired item highlighted. Paths are relative +// to RESOURCE_PATH(); quantities and selection borders are excluded. +struct BagItemTemplate { + std::string reference_path; + std::string label; + ImageFloatBox icon_box{0.428, 0.754, 0.029, 0.044}; + ImageFloatBox name_box{0.460, 0.755, 0.350, 0.045}; + double max_icon_rmsd = 25.0; + double max_name_rmsd = 20.0; + bool check_name = true; + // Selected item row in the reference screenshot (1-7). + size_t reference_row = 1; +}; + +// Screenshot-only helpers shared by live search and developer tests. +// Cursor 0 means unknown. Array index 0 corresponds to visible row 1. +struct BagItemRowMatch { + double icon_rmsd = 1000.0; + double name_rmsd = 1000.0; + bool matched = false; +}; + +// Pocket positions 1-8: Medicine, Balls, Battle Items, Berries, Other Items, +// TMs, Treasures, Key Items. Zero means the selected tab is uncertain. +int bag_pocket_position(const ImageViewRGB32& frame); +bool bag_list_is_empty(const ImageViewRGB32& frame); +int bag_cursor_position(const ImageViewRGB32& frame); +std::array match_bag_item_rows( + const ImageViewRGB32& frame, + const ImageViewRGB32& reference, + const BagItemTemplate& item +); + +enum class BagMoveAction { UNKNOWN, CONFIRM, UP, DOWN, PAGE_DOWN, REWIND }; +struct BagMovePlan { + BagMoveAction action = BagMoveAction::UNKNOWN; + size_t presses = 0; +}; + +// Describes the next input without sending it. REWIND means repeat Up and +// recheck until row 1; a screenshot cannot tell the total distance to the top. +BagMovePlan plan_bag_item_move(int cursor_row, int item_row); + +enum class BagPocketAction { UNKNOWN, CONFIRM, LEFT, RIGHT }; +struct BagPocketMovePlan { + BagPocketAction action = BagPocketAction::UNKNOWN; + size_t presses = 0; +}; +// Circular eight-pocket route; ties prefer Right. Invalid positions send no input. +BagPocketMovePlan plan_bag_pocket_move(int current, int target); + + +// Starts in the overworld, opens the Bag, and leaves the requested item +// highlighted. Navigates to the requested pocket, then searches its item rows. +// Checks all seven visible rows and the cursor, then confirms the selected +// item in the green header. Makes up to three passes per pocket. +// Limits report an incomplete search, not absence. +// Returns false on missing frames, timeout, or no match; does not use an item. +bool find_bag_item( + VideoStream& stream, + ProControllerContext& context, + const BagItemTemplate& item, + int target_pocket, + int num_icons = 8, + size_t max_scrolls_per_pocket = 512, + std::chrono::milliseconds search_timeout = std::chrono::minutes(10) +); + +// Find Escape Rope, choose Use, and verify a transition back to the overworld. +bool use_escape_rope( + VideoStream& stream, + ProControllerContext& context, + int num_icons = 8 +); + // Open the bag, navigate to the repel compartment tab, and use a repel. // Returns false if the repel compartment tab could not be found. // num_icons: 5 (early-game layout) or 8 (late-game layout). diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 56a04ff3a5..41baf16203 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -6,7 +6,7 @@ */ #include "PokemonBDSP_AutoStory_Segment_00.h" -#include "PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" #include "CommonFramework/Exceptions/OperationFailedException.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 7dc8790bfd..b41ff215cb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -1,3706 +1,16 @@ /* BDSP AutoStory - Segment 01 * From after choosing the starter Pokemon until after acquiring the first badge. - * * From: https://github.com/PokemonAutomation/ - * */ -#include -#include -#include -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/ImageTools/ImageDiff.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" -#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP_AutoStory_Segment_01.h" -#include "PokemonBDSP_AutoStory_Segment_00_tools.h" - -using namespace std::chrono_literals; +#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - enum class MoveDirection { UP, DOWN, LEFT, RIGHT }; - - // Left joystick vector for a direction (matches the game's joystick convention used in this file). - static std::pair joystick_for(MoveDirection direction) { - switch (direction) { - case MoveDirection::UP: return { 0.0, 1.0 }; - case MoveDirection::DOWN: return { 0.0, -1.0 }; - case MoveDirection::LEFT: return { -1.0, 0.0 }; - case MoveDirection::RIGHT: return { 1.0, 0.0 }; - } - return { 0.0, 0.0 }; - } - - // Runs from a wild battle that is already in progress (menu visible or about to be). - // Throws OperationFailedException if it cannot escape within `timeout`. - static void escape_wild_battle( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD, - std::chrono::seconds timeout = 30s - ) { - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + timeout; - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}) == 0) { - escaped = true; - break; - } - } - if (!escaped) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "escape_wild_battle: Failed to run from battle.", - stream - ); - } - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - } - - // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. - // Returns false (without throwing) only if every attempt times out, so callers can skip - // an optional interaction. Multiple attempts guard against a single missed detection. - static bool try_detect_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label, - size_t attempts = 3, - std::chrono::milliseconds window = 5000ms - ) { - for (size_t attempt = 0; attempt < attempts; attempt++) { - ShortDialogWatcher watcher_cyan(COLOR_CYAN); - ShortDialogWatcher watcher_red(COLOR_RED); - if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0) { - stream.log(label + " dialogue detected."); - return true; - } - stream.log( - label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + std::to_string(attempts) + ").", - COLOR_ORANGE - ); - } - return false; - } - - struct DoorwayWalkOptions { - BattleType battle_type = BattleType::STANDARD; - // Active movement per step. Time spent in battles or waiting is never counted. - std::chrono::milliseconds step = 200ms; - // Extra walking allowed beyond `travel_time` before giving up. - std::chrono::milliseconds grace = 1600ms; - // How long to watch for a black screen after each step. - std::chrono::milliseconds black_screen_window = 1500ms; - // After a black screen, how long to wait for a battle menu before deciding it was a doorway. - std::chrono::milliseconds battle_menu_window = 8000ms; - size_t max_battles = 10; - // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). - bool detect_white_screen = false; - }; - - // Reusable: walk in `direction` for a cumulative `travel_time` of active movement until - // a black screen NOT followed by a battle menu (a doorway/area transition) is seen. - // Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are - // escaped and do not consume the time budget. - // Throws OperationFailedException if the doorway is not found within - // travel_time + options.grace, if more than options.max_battles occur, or if a battle - // cannot be escaped. - // Returns the total active walking time used. - static std::chrono::milliseconds walk_until_doorway( - VideoStream& stream, - ProControllerContext& context, - MoveDirection direction, - std::chrono::milliseconds travel_time, - const DoorwayWalkOptions& options = DoorwayWalkOptions() - ) { - enum class NavState { WALKING, BLACK_SCREEN, IN_BATTLE, DONE }; - - const auto [x, y] = joystick_for(direction); - const std::chrono::milliseconds max_travel = travel_time + options.grace; - - std::chrono::milliseconds walked = 0ms; - size_t battles = 0; - NavState state = NavState::WALKING; - - while (state != NavState::DONE) { - switch (state) { - case NavState::WALKING: { - if (walked >= max_travel) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "walk_until_doorway: Doorway not found within time budget.", - stream - ); - } - context.wait_for_all_requests(); - pbf_move_left_joystick(context, {x, y}, options.step, 0ms); - context.wait_for_all_requests(); - walked += options.step; - - BlackScreenWatcher black_screen; - WhiteScreenWatcher white_screen; - if (options.detect_white_screen) { - int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); - if (ret == 0) { - state = NavState::BLACK_SCREEN; - } else if (ret == 1) { - stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); - state = NavState::DONE; - } - } else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0) { - state = NavState::BLACK_SCREEN; - } - break; - } - case NavState::BLACK_SCREEN: { - stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); - BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); - if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0) { - state = NavState::IN_BATTLE; - } else { - state = NavState::DONE; - } - break; - } - case NavState::IN_BATTLE: { - if (++battles > options.max_battles) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "walk_until_doorway: Too many battles while walking.", - stream - ); - } - stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + std::to_string(travel_time.count()) + "ms.", COLOR_ORANGE); - escape_wild_battle(stream, context, options.battle_type); - state = NavState::WALKING; - break; - } - case NavState::DONE: - break; - } - } - stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", COLOR_GREEN); - return walked; - } - - static void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - stream.log("Starting Barry 1 battle..."); - - // State machine: loop until EndBattleWatcher fires - for (size_t c = 0; c < 20; c++) { - context.wait_for_all_requests(); - - BattleMenuWatcher battle_menu(BattleType::STANDARD); - EndBattleWatcher end_battle; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_mash_button(context, BUTTON_ZL, 120000ms); - }, - { - battle_menu, - end_battle, - } - ); - switch (ret) { - case 0: - stream.log("Battle menu detected - using first move."); - pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); - break; - case 1: - stream.log("Barry 1 battle complete."); - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - return; - default: - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Barry_1_fight: Timed out waiting for battle menu or end.", - stream - ); - } - } - - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Barry_1_fight: Battle did not end after 20 turns.", - stream - ); - } - - static void handle_unexpected_trainer_battle( - SingleSwitchProgramEnvironment& env, - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("Unexpected trainer dialogue detected.", COLOR_ORANGE); - - // Clear the white dialogue boxes before entering battle logic. - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - - Barry_1_fight(env, stream, context); - - ShortDialogWatcher post_battle_dialog_cyan(COLOR_CYAN); - ShortDialogWatcher post_battle_dialog_red(COLOR_RED); - while (true) { - int dialog_ret = wait_until( - stream, - context, - 3s, - { - {post_battle_dialog_cyan}, - {post_battle_dialog_red}, - } - ); - if (dialog_ret < 0) { - break; - } - pbf_mash_button(context, BUTTON_B, 1200ms); - context.wait_for_all_requests(); - } - - pbf_move_left_joystick(context, {0, -1}, 1000ms, 0ms); - walk_through_grass(stream, context, 1, 0, 10, "right through grass"); - } - - static void walk_through_grass_with_trainer_watch( - SingleSwitchProgramEnvironment& env, - VideoStream& stream, - ProControllerContext& context, - int8_t x, - int8_t y, - size_t steps, - const std::string& direction_name, - BattleType battle_type = BattleType::STANDARD - ) { - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < steps; step++) { - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, {(double)x, (double)y}, 200ms, 0ms); - - context.wait_for_all_requests(); - - ShortDialogWatcher trainer_dialog_cyan(COLOR_CYAN); - ShortDialogWatcher trainer_dialog_red(COLOR_RED); - int battle_ret = wait_until( - stream, - context, - 4500ms, - { - {trainer_dialog_cyan}, - {trainer_dialog_red}, - {battle_start}, - {battle_menu}, - {battle_dialog}, - } - ); - - if (battle_ret == 0 || battle_ret == 1) { - handle_unexpected_trainer_battle(env, stream, context); - return; - } - - if (battle_ret >= 2) { - stream.log("[AutoStory] Wild battle detected during movement.", COLOR_ORANGE); - - if (battle_ret == 2) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - size_t overworld_streak = 0; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - // No battle menu for 3s. If the overworld is showing on - // two consecutive checks, the battle is already over. - OverworldWatcher overworld_check; - if (wait_until(stream, context, 1s, {{overworld_check}}) == 0) { - overworld_streak++; - } else { - overworld_streak = 0; - } - if (overworld_streak >= 2) { - stream.log("[AutoStory] Overworld detected without battle menu. Battle already over.", COLOR_ORANGE); - escaped = true; - break; - } - continue; - } - overworld_streak = 0; - - // Cursor direction can drift; try both vertical directions. - pbf_move_left_joystick(context, {0, 1}, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - - if (run_result == 1) { - pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end_alt; - BattleMenuWatcher back_to_menu_alt(battle_type, COLOR_YELLOW); - int run_result_alt = wait_until(stream, context, 4s, {{battle_end_alt}, {back_to_menu_alt}}); - if (run_result_alt == 0) { - escaped = true; - break; - } - } - } - - if (!escaped) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from wild battle while moving " + direction_name + ".", - stream - ); - } - - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - } - } - } - - // ---------------------------------------------------------------- - // Segment 1 Part 1: Jubilife City to Oreburgh Gate - // Navigates the player from Jubilife City to the Oreburgh Gate, including the - // first Barry fight. - // --------------------------------------------------------------------------- - static void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - // Wait for game to fully accept input after save/overworld return - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1465ms, 0ms); - pbf_wait(context, 127ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); - pbf_wait(context, 698ms); - leave_pokemon_center_before_barry_1(stream, context); - pbf_wait(context, 2340ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); - pbf_wait(context, 348ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2694ms, 0ms); - pbf_wait(context, 482ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3810ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); - pbf_wait(context, 1647ms); - walk_through_grass(stream, context, 1, 0, 5, "right through grass"); - wait_for_dialogue(stream, context, "Dallas fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Dallas - context.wait_for_all_requests(); - walk_through_grass(stream, context, 1, 0, 5, "right through grass"); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); - walk_through_grass(stream, context, 1, 0, 10, "right through grass"); - //there is a small chance that the player dodges sSebastian, so this - //function addresses that edge case - if (try_detect_dialogue(stream, context, "Sebastian fight")) { - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Sebastian - context.wait_for_all_requests(); - } else { - stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); - } - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2000ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); - pbf_wait(context, 200ms); - context.wait_for_all_requests(); - bool black_screen_found = false; - for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { - BlackScreenWatcher black_screen; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - for (size_t i = 0; i < 10; i++) { - pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - } - pbf_move_left_joystick(context, { 0, 1 }, 10000ms, 0ms); - }, - { black_screen } - ); - if (ret == 0) { - stream.log("Black screen detected."); - black_screen_found = true; - } - } - context.wait_for_all_requests(); - - if (!black_screen_found) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", - stream - ); - } - } - static void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {1.000000, -0.000000 }, 879ms, 0ms); - wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); - pbf_mash_button(context, BUTTON_B, 15000ms); - walk_through_grass(stream, context, 1, 0, 10, "right through gate"); - //Similar to sebastian, an edge case exists where we run into too many - //wild battles and don't reach curtis or diana. This detect dialog function solves that. - while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)) { - walk_through_grass(stream, context, 1, 0, 1, "right through gate"); - } - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Curtis - walk_through_grass(stream, context, 1, 0, 10, "right through gate"); - while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)) { - walk_through_grass(stream, context, 1, 0, 1, "right through gate"); - } - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Diana - // Walk right until the gate exit transition (white screen, or black screen without a battle menu). - // Wild battles are escaped without using up the budget. Throws on failure so the - // checkpoint is retried. - DoorwayWalkOptions gate_exit_options; - gate_exit_options.detect_white_screen = true; - walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); - context.wait_for_all_requests(); - } - // --------------------------------------------------------------------------- - // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine - // Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. - // Navigates to the mine. - // ---------------------------------------------------------------------------- - static void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)) { - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - } - pbf_mash_button(context, BUTTON_B, 12000ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); - pbf_wait(context, 23ms); - bool black_screen_found = false; - for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { - BlackScreenWatcher black_screen; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); - }, - { black_screen } - ); - if (ret == 0) { - stream.log("Black screen detected."); - black_screen_found = true; - } - } - context.wait_for_all_requests(); - - if (!black_screen_found) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "oreburgh_gate_to_mine: No black screen after 3 attempts.", - stream - ); - } - - // Let the mine entrance transition fully finish so the next step's black screen - // watcher can't pick up the tail of this one. - BlackScreenOverWatcher black_over; - wait_until(stream, context, 6000ms, {{black_over}}); - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - } - // Aligns the player between the two rocks using template matching in fixed ROIs, - // then faces up and presses A to talk to the NPC. - static void align_between_rocks_and_talk( - VideoStream& stream, ProControllerContext& context) { - auto rock1_image = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/rock_smash_rock1.png"); - auto rock2_image = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/rock_smash_rock2.png"); - auto helmet_image = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/roark_helmet.png"); - const ImageFloatBox rock1_box(0.390, 0.370, 0.075, 0.130); - const ImageFloatBox rock2_box(0.529, 0.370, 0.075, 0.130); - const ImageFloatBox helmet_box(0.474, 0.32, 0.035, 0.065); - const double max_rmsd = 60; - ImageMatchDetector rock1(rock1_image, rock1_box, max_rmsd, true); - ImageMatchDetector rock2(rock2_image, rock2_box, max_rmsd, true); - ImageMatchDetector helmet(helmet_image, helmet_box, max_rmsd, true); - - auto score = [&]() { - context.wait_for_all_requests(); - VideoSnapshot frame = stream.video().snapshot(); - return std::array{rock1.rmsd(frame), rock2.rmsd(frame), helmet.rmsd(frame)}; - }; - auto total = [](const std::array& s) { return s[0] + s[1] + s[2]; }; - - const size_t MAX_ATTEMPTS = 12; - int8_t dir_x = 1; - bool aligned = false; - auto prev = score(); - for (size_t attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { - if (prev[0] <= max_rmsd && prev[1] <= max_rmsd && prev[2] <= max_rmsd) { - aligned = true; - break; - } - walk_through_grass( - stream, context, dir_x, 0, 1, - dir_x > 0 ? "right (rock align)" : "left (rock align)" - ); - pbf_wait(context, 300ms); - auto cur = score(); - if (total(cur) > total(prev)) { - dir_x = (int8_t)-dir_x; - } - prev = cur; - } - if (!aligned) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "Failed to align between the two rocks and Roark's helmet.", - stream - ); - } - - // Face north (up) without moving off the spot: a tap turns the character. - pbf_move_left_joystick(context, { 0.0, 1.0 }, 100ms, 300ms); - pbf_press_button(context, BUTTON_A, 200ms, 800ms); - pbf_mash_button(context, BUTTON_A, 4000ms); - pbf_wait(context, 4873ms); - } - - // Diagnostic only: identifies the entrance start without moving the player. - // Thresholds are initial values; use the logged scores to calibrate on live video. - enum class MineStartPosition { - UNKNOWN, - LEFT, - CENTER, - RIGHT - }; - - static MineStartPosition detect_mine_start( - VideoStream& stream, ProControllerContext& context - ) { - const std::array names = {"Left", "Center", "Right"}; - const std::array files = { - "mine_start_left.png", "mine_start_center.png", "mine_start_right.png" - }; - // Wooden beam and side walls: exclude the animated player and shadow. - const std::array boxes = { - ImageFloatBox(0.24, 0.24, 0.52, 0.12), - ImageFloatBox(0.18, 0.37, 0.20, 0.23), - ImageFloatBox(0.64, 0.37, 0.18, 0.23) - }; - std::array, 3>, 3> detectors; - for (size_t candidate = 0; candidate < names.size(); candidate++) { - auto reference = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + files[candidate] - ); - for (size_t region = 0; region < boxes.size(); region++) { - detectors[candidate][region] = std::make_unique( - reference, boxes[region], 60.0, true - ); - } - } - - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - int previous = -1; - size_t streak = 0; - for (size_t attempt = 0; attempt < 12; attempt++) { - const VideoSnapshot frame = stream.video().snapshot(); - std::array scores = {}; - std::array regions_ok = {true, true, true}; - for (size_t candidate = 0; candidate < names.size(); candidate++) { - for (size_t region = 0; region < boxes.size(); region++) { - const double score = detectors[candidate][region]->rmsd(frame); - scores[candidate] += score / boxes.size(); - regions_ok[candidate] = regions_ok[candidate] && score <= 60.0; - } - } - size_t best = 0; - for (size_t candidate = 1; candidate < names.size(); candidate++) { - if (scores[candidate] < scores[best]) best = candidate; - } - double second = 10000.0; - for (size_t candidate = 0; candidate < names.size(); candidate++) { - if (candidate != best && scores[candidate] < second) second = scores[candidate]; - } - stream.log("[Mine start] Scores (lower is better): Left=" + - std::to_string(scores[0]) + ", Center=" + std::to_string(scores[1]) + - ", Right=" + std::to_string(scores[2])); - - const int detected = regions_ok[best] && scores[best] <= 40.0 && - second - scores[best] >= 8.0 ? static_cast(best) : -1; - if (detected >= 0) { - streak = detected == previous ? streak + 1 : 1; - if (streak >= 3) { - stream.log( - std::string("[Mine start] Detected: ") + names[best], - COLOR_GREEN - ); - - switch (best) { - case 0: return MineStartPosition::LEFT; - case 1: return MineStartPosition::CENTER; - case 2: return MineStartPosition::RIGHT; - } - - return MineStartPosition::UNKNOWN; - } - } else { - streak = 0; - } - previous = detected; - pbf_wait(context, 250ms); - context.wait_for_all_requests(); - } - stream.log("[Mine start] Detected: Unsure.", COLOR_ORANGE); - return MineStartPosition::UNKNOWN; - } - // Keeps the original picture-only test available. - static void oreburgh_mine_localization_test( - VideoStream& stream, - ProControllerContext& context - ) { - detect_mine_start(stream, context); - } - struct MineCenterOffset { - bool valid; - double offset; - double score; - }; - - // Finds the sideways shift of stationary scenery relative to the center picture. - // Positive offset: scenery shifted right, so the player is left of center. - static MineCenterOffset measure_mine_center_offset( - const ImageViewRGB32& reference, - const ImageViewRGB32& live - ) { - if (!reference || !live) { - return { false, 0.0, 1000.0 }; - } - - // Use a fixed working size so the search works at different capture resolutions. - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - const ImageRGB32 live_scaled = live.scale_to(960, 540); - - // Entrance beam and both posts. Avoid the player and shadow. - const std::array boxes = { - ImageFloatBox(0.35, 0.285, 0.30, 0.07), - ImageFloatBox(0.355, 0.37, 0.035, 0.23), - ImageFloatBox(0.615, 0.37, 0.035, 0.23) - }; - - std::array scores = {}; - std::array worst_regions = {}; - - int best_shift = 0; - double best_score = 1000.0; - - // Search up to 10% of the screen width in either direction. - for (int shift = -96; shift <= 96; shift++) { - double total = 0.0; - double worst = 0.0; - - for (const ImageFloatBox& box : boxes) { - const ImageViewRGB32 reference_crop = - extract_box_reference(reference_scaled, box); - - // Translate the same crop horizontally in the live image. - const ImageViewRGB32 live_crop = - extract_box_reference(live_scaled, box, shift, 0); - - const double score = - ImageMatch::pixel_RMSD(reference_crop, live_crop); - - total += score; - if (score > worst) { - worst = score; - } - } - - const double average = total / boxes.size(); - const size_t index = static_cast(shift + 96); - - scores[index] = average; - worst_regions[index] = worst; - - if (average < best_score) { - best_score = average; - best_shift = shift; - } - } - - // Reject another similarly good match well away from the chosen position. - double alternative_score = 1000.0; - - for (int shift = -96; shift <= 96; shift++) { - if (std::abs(shift - best_shift) <= 12) { - continue; - } - - const double score = scores[static_cast(shift + 96)]; - if (score < alternative_score) { - alternative_score = score; - } - } - - const size_t best_index = static_cast(best_shift + 96); - - // Initial limits: live testing will tell us whether these need adjustment. - const bool valid = - best_score <= 20.0 && - worst_regions[best_index] <= 30.0 && - alternative_score - best_score >= 3.0 && - std::abs(best_shift) < 96; - - return { - valid, - static_cast(best_shift) / 960.0, - best_score - }; - } - // Test only: align with the entrance center, then stop. - static bool oreburgh_mine_alignment_test( - VideoStream& stream, - ProControllerContext& context - ) { - const ImageRGB32 reference( - RESOURCE_PATH() + - "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_start_center.png" - ); - - constexpr size_t MAX_MOVEMENTS = 12; - constexpr double CENTER_TOLERANCE = 0.008; - - size_t movements = 0; - size_t center_streak = 0; - - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - while (true) { - const VideoSnapshot frame = stream.video().snapshot(); - - const MineCenterOffset measurement = - measure_mine_center_offset(reference, frame); - - stream.log( - "[Mine align] Offset=" + - std::to_string(measurement.offset) + - ", score=" + std::to_string(measurement.score) + - (measurement.valid ? ", trusted" : ", unsure") - ); - - if (!measurement.valid) { - stream.log( - "[Mine align] Entrance position unsure. Stopping.", - COLOR_ORANGE - ); - return false; - } - - if (std::abs(measurement.offset) <= CENTER_TOLERANCE) { - center_streak++; - - if (center_streak >= 3) { - stream.log( - "[Mine align] Center reached. Test complete.", - COLOR_GREEN - ); - return true; - } - - pbf_wait(context, 250ms); - context.wait_for_all_requests(); - continue; - } - - center_streak = 0; - - // Check the latest position before enforcing the movement limit. - if (movements >= MAX_MOVEMENTS) { - stream.log( - "[Mine align] Movement limit reached. Stopping.", - COLOR_ORANGE - ); - return false; - } - - const double x = measurement.offset > 0.0 ? 1.0 : -1.0; - - // Smaller movements when close to the center. - const auto duration = - std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; - - stream.log( - x > 0.0 - ? "[Mine align] Moving a little right." - : "[Mine align] Moving a little left." - ); - - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - - const int result = run_until( - stream, - context, - [x, duration](ProControllerContext& ctx) { - pbf_move_left_joystick( - ctx, { x, 0.0 }, duration, 0ms - ); - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - }, - { black, menu, dialog } - ); - - if (result >= 0) { - stream.log( - "[Mine align] Battle or transition detected. " - "Stopping the alignment test.", - COLOR_ORANGE - ); - return false; - } - - movements++; - } - } - // Confirm a battle before using the existing escape helper. - static bool mine_escape_and_settle( - VideoStream& stream, - ProControllerContext& context - ) { - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - - if (wait_until(stream, context, 12s, { {menu} }) != 0) { - stream.log( - "[Mine encounter] Battle menu not confirmed. Stopping.", - COLOR_ORANGE - ); - return false; - } - - stream.log("[Mine encounter] Battle confirmed. Escaping."); - escape_wild_battle(stream, context); - context.wait_for_all_requests(); - - // Check the return waits instead of ignoring their results. - BlackScreenWatcher visible( - COLOR_YELLOW, - ImageFloatBox(0.1, 0.1, 0.8, 0.8), - 100, - 10, - BlackScreenWatcher::FinderType::GONE, - 500ms - ); - - if (wait_until(stream, context, 8s, { {visible} }) != 0) { - stream.log( - "[Mine encounter] Screen did not return. Stopping.", - COLOR_ORANGE - ); - return false; - } - - OverworldWatcher overworld; - - if (wait_until(stream, context, 5s, { {overworld} }) != 0) { - stream.log( - "[Mine encounter] Overworld not detected. Stopping.", - COLOR_ORANGE - ); - return false; - } - - pbf_wait(context, 800ms); - context.wait_for_all_requests(); - - stream.log( - "[Mine encounter] Escaped. Returning to the rock-walking loop.", - COLOR_GREEN - ); - return true; - } - - // Temporary test: wiggle along the corridor until a battle is detected. - static bool mine_force_encounter_test( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine encounter] Starting forced encounter wiggle."); - - const auto deadline = - std::chrono::steady_clock::now() + 60s; - - size_t pulses = 0; - - while (std::chrono::steady_clock::now() < deadline) { - // Alternate down and up. Do not move sideways. - const double y = pulses % 2 == 0 ? -1.0 : 1.0; - - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - - const int result = run_until( - stream, - context, - [y](ProControllerContext& ctx) { - pbf_move_left_joystick( - ctx, { 0.0, y }, 120ms, 0ms - ); - pbf_wait(ctx, 400ms); - ctx.wait_for_all_requests(); - }, - { black, menu, dialog } - ); - - if (result >= 0) { - stream.log( - "[Mine encounter] Wiggle interrupted. Checking for battle." - ); - return mine_escape_and_settle(stream, context); - } - - pulses++; - } - - stream.log( - "[Mine encounter] No encounter within 60 seconds. " - "Test incomplete; stopping.", - COLOR_ORANGE - ); - return false; - } - // Walk toward the first rock checkpoint, then stop. - // This test stops on battles; escaping and resuming come next. -// Previous implementation retained during consolidation. -// static bool oreburgh_mine_walk_to_first_rock( -// VideoStream& stream, -// ProControllerContext& context, -// bool force_encounter = false -// ) { -// const ImageRGB32 reference( -// RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_01.png" -// ); -// const ImageRGB32 reference_scaled = reference.scale_to(960, 540); -// -// // Five small rocks and the large upper-right rock. -// // Boxes cover rock surfaces, avoiding the character and shadows. -// const std::array rock_boxes = { -// ImageFloatBox(0.482, 0.585, 0.055, 0.055), // Central rock -// ImageFloatBox(0.335, 0.670, 0.050, 0.055), // Left pair: left -// ImageFloatBox(0.412, 0.670, 0.050, 0.055), // Left pair: right -// ImageFloatBox(0.570, 0.752, 0.045, 0.055), // Right pair: left -// ImageFloatBox(0.645, 0.752, 0.045, 0.055), // Right pair: right -// ImageFloatBox(0.570, 0.290, 0.080, 0.120) // Large upper-right rock -// }; -// bool forced_encounter_completed = false; -// size_t encounters = 0; -// -// constexpr size_t MAX_MOVEMENTS = 40; -// size_t movements = 0; -// size_t matching_frames = 0; -// -// while (true) { -// context.wait_for_all_requests(); -// const VideoSnapshot frame = stream.video().snapshot(); -// -// if (!frame) { -// stream.log( -// "[Mine path 01] No video frame. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// const ImageRGB32 live_scaled = frame->scale_to(960, 540); -// -// std::array best_scores = {}; -// double best_average = 1000.0; -// int best_shift = 0; -// int best_vertical_shift = 0; -// -// // Small horizontal and vertical search. -// // All six rocks must match at the SAME pair of offsets. -// for (int vertical = -6; vertical <= 6; vertical++) { -// for (int horizontal = -24; horizontal <= 24; horizontal++) { -// std::array scores = {}; -// double total = 0.0; -// -// for (size_t rock = 0; rock < rock_boxes.size(); rock++) { -// const ImageViewRGB32 reference_crop = -// extract_box_reference( -// reference_scaled, rock_boxes[rock] -// ); -// -// const ImageViewRGB32 live_crop = -// extract_box_reference( -// live_scaled, -// rock_boxes[rock], -// horizontal, -// vertical -// ); -// -// scores[rock] = -// ImageMatch::pixel_RMSD(reference_crop, live_crop); -// -// total += scores[rock]; -// } -// -// const double average = total / rock_boxes.size(); -// -// if (average < best_average) { -// best_average = average; -// best_scores = scores; -// best_shift = horizontal; -// best_vertical_shift = vertical; -// } -// } -// } -// -// stream.log( -// "[Mine path 01] Horizontal=" + std::to_string(best_shift) + -// ", vertical=" + std::to_string(best_vertical_shift) + -// ", central=" + std::to_string(best_scores[0]) + -// ", left1=" + std::to_string(best_scores[1]) + -// ", left2=" + std::to_string(best_scores[2]) + -// ", right1=" + std::to_string(best_scores[3]) + -// ", right2=" + std::to_string(best_scores[4]) + -// ", large=" + std::to_string(best_scores[5]) -// ); -// -// // Initial limits: each rock must match, plus a good overall score. -// bool at_target = best_average <= 20.0; -// -// for (double score : best_scores) { -// if (score > 30.0) { -// at_target = false; -// } -// } -// -// if (at_target) { -// matching_frames++; -// -// if (matching_frames >= 3) { -// stream.log( -// "[Mine path 01] First rock reached. Stopping.", -// COLOR_GREEN -// ); -// return true; -// } -// -// pbf_wait(context, 250ms); -// context.wait_for_all_requests(); -// continue; -// } -// -// matching_frames = 0; -// -// // Trigger once, partway down the corridor. -// if (force_encounter && -// !forced_encounter_completed && -// movements >= 4) { -// -// if (!mine_force_encounter_test(stream, context)) { -// return false; -// } -// -// forced_encounter_completed = true; -// -// stream.log( -// "[Mine path 01] Forced encounter completed. Resuming navigation.", -// COLOR_GREEN -// ); -// -// // Take a fresh picture before sending another movement. -// continue; -// } -// -// if (movements >= MAX_MOVEMENTS) { -// stream.log( -// "[Mine path 01] Movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// BlackScreenWatcher black; -// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); -// BattleDialogWatcher dialog(COLOR_YELLOW); -// -// const int result = run_until( -// stream, -// context, -// [](ProControllerContext& ctx) { -// pbf_move_left_joystick( -// ctx, { 0.0, -1.0 }, 80ms, 0ms -// ); -// pbf_wait(ctx, 500ms); -// ctx.wait_for_all_requests(); -// }, -// { black, menu, dialog } -// ); -// -// // Count commands as a safety budget, including interrupted commands. -// // This counter does not represent the player's position. -// movements++; -// -// if (result >= 0) { -// matching_frames = 0; -// -// if (++encounters > 10) { -// stream.log( -// "[Mine path 01] Encounter limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// if (!mine_escape_and_settle(stream, context)) { -// return false; -// } -// -// stream.log("[Mine path 01] Resuming after encounter."); -// continue; -// } -// -// } -// } - // Move right until either accepted endpoint is recognized. -// Previous implementation retained during consolidation. -// static bool oreburgh_mine_move_right( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const ImageRGB32 reference1( -// RESOURCE_PATH() + -// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_01_1.png" -// ); -// const ImageRGB32 reference2( -// RESOURCE_PATH() + -// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_01_2.png" -// ); -// -// const std::array references = { -// reference1.scale_to(960, 540), -// reference2.scale_to(960, 540) -// }; -// -// // Six rock regions for each endpoint. -// // The upper-right rock uses a higher patch to avoid the character. -// const std::array, 2> boxes = { { -// {{ -// ImageFloatBox(0.400, 0.585, 0.055, 0.055), -// ImageFloatBox(0.245, 0.670, 0.050, 0.055), -// ImageFloatBox(0.322, 0.670, 0.050, 0.055), -// ImageFloatBox(0.480, 0.752, 0.045, 0.055), -// ImageFloatBox(0.555, 0.752, 0.045, 0.055), -// ImageFloatBox(0.485, 0.290, 0.080, 0.090) -// }}, -// {{ -// ImageFloatBox(0.325, 0.585, 0.055, 0.055), -// ImageFloatBox(0.170, 0.670, 0.050, 0.055), -// ImageFloatBox(0.244, 0.670, 0.050, 0.055), -// ImageFloatBox(0.395, 0.752, 0.045, 0.055), -// ImageFloatBox(0.470, 0.752, 0.045, 0.055), -// ImageFloatBox(0.415, 0.290, 0.080, 0.090) -// }} -// } }; -// -// size_t movements = 0; -// size_t encounters = 0; -// size_t matching_frames = 0; -// int previous_endpoint = -1; -// -// while (true) { -// context.wait_for_all_requests(); -// const VideoSnapshot frame = stream.video().snapshot(); -// -// if (!frame) { -// stream.log("[Mine right] No video frame. Stopping.", COLOR_ORANGE); -// return false; -// } -// -// const ImageRGB32 live = frame->scale_to(960, 540); -// int accepted_endpoint = -1; -// double accepted_score = 1000.0; -// -// for (size_t endpoint = 0; endpoint < references.size(); endpoint++) { -// double best_average = 1000.0; -// double best_worst = 1000.0; -// -// // Small tolerance around each accepted endpoint. -// for (int vertical = -6; vertical <= 6; vertical++) { -// for (int horizontal = -12; horizontal <= 12; horizontal++) { -// double total = 0.0; -// double worst = 0.0; -// -// for (const ImageFloatBox& box : boxes[endpoint]) { -// const ImageViewRGB32 reference_crop = -// extract_box_reference(references[endpoint], box); -// -// const ImageViewRGB32 live_crop = -// extract_box_reference( -// live, box, horizontal, vertical -// ); -// -// const double score = -// ImageMatch::pixel_RMSD(reference_crop, live_crop); -// -// total += score; -// if (score > worst) { -// worst = score; -// } -// } -// -// const double average = total / boxes[endpoint].size(); -// -// if (average < best_average) { -// best_average = average; -// best_worst = worst; -// } -// } -// } -// -// stream.log( -// "[Mine right] Endpoint " + std::to_string(endpoint + 1) + -// ": average=" + std::to_string(best_average) + -// ", worst rock=" + std::to_string(best_worst) -// ); -// -// if (best_average <= 20.0 && -// best_worst <= 30.0 && -// best_average < accepted_score) { -// -// accepted_endpoint = static_cast(endpoint); -// accepted_score = best_average; -// } -// } -// -// if (accepted_endpoint >= 0) { -// matching_frames = -// accepted_endpoint == previous_endpoint -// ? matching_frames + 1 -// : 1; -// -// previous_endpoint = accepted_endpoint; -// -// if (matching_frames >= 3) { -// stream.log( -// "[Mine right] Endpoint " + -// std::to_string(accepted_endpoint + 1) + -// " reached. Stopping.", -// COLOR_GREEN -// ); -// return true; -// } -// -// pbf_wait(context, 250ms); -// context.wait_for_all_requests(); -// continue; -// } -// -// matching_frames = 0; -// previous_endpoint = -1; -// -// if (movements >= 20) { -// stream.log( -// "[Mine right] Movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// BlackScreenWatcher black; -// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); -// BattleDialogWatcher dialog(COLOR_YELLOW); -// -// const int result = run_until( -// stream, -// context, -// [](ProControllerContext& ctx) { -// pbf_move_left_joystick( -// ctx, { 1.0, 0.0 }, 40ms, 0ms -// ); -// pbf_wait(ctx, 500ms); -// ctx.wait_for_all_requests(); -// }, -// { black, menu, dialog } -// ); -// -// movements++; -// -// if (result >= 0) { -// if (++encounters > 10) { -// stream.log( -// "[Mine right] Encounter limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// if (!mine_escape_and_settle(stream, context)) { -// return false; -// } -// -// stream.log("[Mine right] Resuming after encounter."); -// } -// } -// } - // Walk down past the central rock to either accepted stopping point. -// Previous implementation retained during consolidation. -// static bool oreburgh_mine_down_past_central_rock( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const ImageRGB32 reference1( -// RESOURCE_PATH() + -// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_02_1.png" -// ); -// const ImageRGB32 reference2( -// RESOURCE_PATH() + -// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_02_2.png" -// ); -// -// const std::array references = { -// reference1.scale_to(960, 540), -// reference2.scale_to(960, 540) -// }; -// -// // Rock surfaces in each reference picture. -// // Avoid the player standing above the lower-right pair. -// const std::array, 2> boxes = { { -// {{ -// ImageFloatBox(0.345, 0.420, 0.040, 0.045), // Central rock -// ImageFloatBox(0.195, 0.500, 0.040, 0.045), // Left pair: left -// ImageFloatBox(0.270, 0.500, 0.040, 0.045), // Left pair: right -// ImageFloatBox(0.410, 0.590, 0.040, 0.045), // Lower pair: left -// ImageFloatBox(0.480, 0.590, 0.040, 0.045), // Lower pair: right -// ImageFloatBox(0.445, 0.180, 0.060, 0.090) // Large upper rock -// }}, -// {{ -// ImageFloatBox(0.420, 0.420, 0.040, 0.045), -// ImageFloatBox(0.270, 0.500, 0.040, 0.045), -// ImageFloatBox(0.345, 0.500, 0.040, 0.045), -// ImageFloatBox(0.485, 0.590, 0.040, 0.045), -// ImageFloatBox(0.555, 0.590, 0.040, 0.045), -// ImageFloatBox(0.520, 0.180, 0.060, 0.090) -// }} -// } }; -// -// size_t movements = 0; -// size_t encounters = 0; -// size_t matching_frames = 0; -// int previous_endpoint = -1; -// -// while (true) { -// context.wait_for_all_requests(); -// const VideoSnapshot frame = stream.video().snapshot(); -// -// if (!frame) { -// stream.log("[Mine down 02] No video frame. Stopping.", COLOR_ORANGE); -// return false; -// } -// -// const ImageRGB32 live = frame->scale_to(960, 540); -// int accepted_endpoint = -1; -// double accepted_score = 1000.0; -// -// for (size_t endpoint = 0; endpoint < references.size(); endpoint++) { -// double best_average = 1000.0; -// double best_worst = 1000.0; -// -// // Allow small differences in the stopping position. -// for (int vertical = -6; vertical <= 6; vertical++) { -// for (int horizontal = -12; horizontal <= 12; horizontal++) { -// double total = 0.0; -// double worst = 0.0; -// -// for (const ImageFloatBox& box : boxes[endpoint]) { -// const ImageViewRGB32 reference_crop = -// extract_box_reference(references[endpoint], box); -// -// const ImageViewRGB32 live_crop = -// extract_box_reference( -// live, box, horizontal, vertical -// ); -// -// const double score = -// ImageMatch::pixel_RMSD(reference_crop, live_crop); -// -// total += score; -// -// if (score > worst) { -// worst = score; -// } -// } -// -// const double average = total / boxes[endpoint].size(); -// -// if (average < best_average) { -// best_average = average; -// best_worst = worst; -// } -// } -// } -// -// stream.log( -// "[Mine down 02] Endpoint " + std::to_string(endpoint + 1) + -// ": average=" + std::to_string(best_average) + -// ", worst rock=" + std::to_string(best_worst) -// ); -// -// if (best_average <= 20.0 && -// best_worst <= 30.0 && -// best_average < accepted_score) { -// -// accepted_endpoint = static_cast(endpoint); -// accepted_score = best_average; -// } -// } -// -// if (accepted_endpoint >= 0) { -// matching_frames = -// accepted_endpoint == previous_endpoint -// ? matching_frames + 1 -// : 1; -// -// previous_endpoint = accepted_endpoint; -// -// if (matching_frames >= 3) { -// stream.log( -// "[Mine down 02] Endpoint " + -// std::to_string(accepted_endpoint + 1) + -// " reached. Stopping.", -// COLOR_GREEN -// ); -// return true; -// } -// -// pbf_wait(context, 250ms); -// context.wait_for_all_requests(); -// continue; -// } -// -// matching_frames = 0; -// previous_endpoint = -1; -// -// if (movements >= 30) { -// stream.log( -// "[Mine down 02] Movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// BlackScreenWatcher black; -// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); -// BattleDialogWatcher dialog(COLOR_YELLOW); -// -// const int result = run_until( -// stream, -// context, -// [](ProControllerContext& ctx) { -// pbf_move_left_joystick( -// ctx, { 0.0, -1.0 }, 40ms, 0ms -// ); -// pbf_wait(ctx, 500ms); -// ctx.wait_for_all_requests(); -// }, -// { black, menu, dialog } -// ); -// -// movements++; -// -// if (result >= 0) { -// if (++encounters > 10) { -// stream.log( -// "[Mine down 02] Encounter limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// if (!mine_escape_and_settle(stream, context)) { -// return false; -// } -// -// stream.log("[Mine down 02] Resuming after encounter."); -// } -// } -// } - - // Move left into the central gap, then stop. -// Previous implementation retained during consolidation. -// static bool oreburgh_mine_move_left_to_gap( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const ImageRGB32 reference( -// RESOURCE_PATH() + -// "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_path_03.png" -// ); -// const ImageRGB32 reference_scaled = reference.scale_to(960, 540); -// -// // Four visible small rocks and the large upper-right rock. -// // Exclude the central rock hidden behind the character. -// const std::array boxes = { -// ImageFloatBox(0.340, 0.495, 0.040, 0.045), // Left pair: left -// ImageFloatBox(0.410, 0.495, 0.040, 0.045), // Left pair: right -// ImageFloatBox(0.555, 0.580, 0.040, 0.045), // Right pair: left -// ImageFloatBox(0.625, 0.580, 0.040, 0.045), // Right pair: right -// ImageFloatBox(0.580, 0.180, 0.060, 0.090) // Large upper-right rock -// }; -// -// size_t movements = 0; -// size_t encounters = 0; -// size_t matching_frames = 0; -// -// while (true) { -// context.wait_for_all_requests(); -// const VideoSnapshot frame = stream.video().snapshot(); -// -// if (!frame) { -// stream.log("[Mine left 03] No video frame. Stopping.", COLOR_ORANGE); -// return false; -// } -// -// const ImageRGB32 live = frame->scale_to(960, 540); -// -// double best_average = 1000.0; -// double best_worst = 1000.0; -// int best_horizontal = 0; -// int best_vertical = 0; -// -// for (int vertical = -6; vertical <= 6; vertical++) { -// for (int horizontal = -12; horizontal <= 12; horizontal++) { -// double total = 0.0; -// double worst = 0.0; -// -// for (const ImageFloatBox& box : boxes) { -// const ImageViewRGB32 reference_crop = -// extract_box_reference(reference_scaled, box); -// -// const ImageViewRGB32 live_crop = -// extract_box_reference( -// live, box, horizontal, vertical -// ); -// -// const double score = -// ImageMatch::pixel_RMSD(reference_crop, live_crop); -// -// total += score; -// -// if (score > worst) { -// worst = score; -// } -// } -// -// const double average = total / boxes.size(); -// -// if (average < best_average) { -// best_average = average; -// best_worst = worst; -// best_horizontal = horizontal; -// best_vertical = vertical; -// } -// } -// } -// -// stream.log( -// "[Mine left 03] Horizontal=" + -// std::to_string(best_horizontal) + -// ", vertical=" + std::to_string(best_vertical) + -// ", average=" + std::to_string(best_average) + -// ", worst rock=" + std::to_string(best_worst) -// ); -// -// const bool at_target = -// best_average <= 20.0 && -// best_worst <= 30.0; -// -// if (at_target) { -// matching_frames++; -// -// if (matching_frames >= 3) { -// stream.log( -// "[Mine left 03] Central gap reached. Stopping.", -// COLOR_GREEN -// ); -// return true; -// } -// -// pbf_wait(context, 250ms); -// context.wait_for_all_requests(); -// continue; -// } -// -// matching_frames = 0; -// -// if (movements >= 30) { -// stream.log( -// "[Mine left 03] Movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// BlackScreenWatcher black; -// BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); -// BattleDialogWatcher dialog(COLOR_YELLOW); -// -// const int result = run_until( -// stream, -// context, -// [](ProControllerContext& ctx) { -// pbf_move_left_joystick( -// ctx, { -1.0, 0.0 }, 40ms, 0ms -// ); -// pbf_wait(ctx, 500ms); -// ctx.wait_for_all_requests(); -// }, -// { black, menu, dialog } -// ); -// -// movements++; -// -// if (result >= 0) { -// if (++encounters > 10) { -// stream.log( -// "[Mine left 03] Encounter limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// if (!mine_escape_and_settle(stream, context)) { -// return false; -// } -// -// stream.log("[Mine left 03] Resuming after encounter."); -// } -// } -// } - - // Watch throughout an action, including its movement and picture checks. - // After an encounter, restart the action using fresh watcher state. - template - static bool mine_run_with_encounters( - VideoStream& stream, - ProControllerContext& context, - Action action - ) { - for (size_t encounters = 0;;) { - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - bool completed = false; - - const int result = run_until( - stream, context, - [&](ProControllerContext& ctx) { - completed = action(ctx); - }, - { black, menu, dialog } - ); - - if (result < 0) { - return completed; - } - - if (++encounters > 10) { - stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); - return false; - } - - if (!mine_escape_and_settle(stream, context)) { - return false; - } - - stream.log("[Mine encounter] Escaped; resuming interrupted action."); - } - } - - static bool mine_timed_walk( - VideoStream& stream, - ProControllerContext& context, - double x, - double y, - std::chrono::milliseconds duration - ) { - auto remaining = duration; - - return mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - while (remaining > 0ms) { - const auto pulse = remaining < 100ms ? remaining : 100ms; - pbf_move_left_joystick(ctx, { x, y }, pulse, 0ms); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - - // An interrupted pulse is retried after escaping. - // This is a timing budget, not exact distance tracking. - remaining -= pulse; - } - return true; - } - ); - } - - // Compatibility helper for callers such as alignment at position 18. - static bool mine_watch_while_checking( - VideoStream& stream, - ProControllerContext& context - ) { - return mine_run_with_encounters( - stream, context, - [](ProControllerContext& ctx) { - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - return true; - } - ); - } - -// Previous four-region scorer retained for comparison. -// struct MinePictureScore { -// double average = 1000.0; -// double worst = 1000.0; -// }; -// -// // Pure picture comparison: no movement, waits, or battle handling here. -// static MinePictureScore mine_score_picture( -// const ImageRGB32& reference, -// const ImageRGB32& live, -// const std::array& boxes -// ) { -// MinePictureScore best; -// -// for (int vertical = -8; vertical <= 8; vertical++) { -// for (int horizontal = -16; horizontal <= 16; horizontal++) { -// double total = 0.0; -// double worst = 0.0; -// -// for (const ImageFloatBox& box : boxes) { -// const ImageViewRGB32 reference_crop = -// extract_box_reference(reference, box); -// const ImageViewRGB32 live_crop = -// extract_box_reference(live, box, horizontal, vertical); -// const double score = -// ImageMatch::pixel_RMSD(reference_crop, live_crop); -// -// total += score; -// if (score > worst) { -// worst = score; -// } -// } -// -// const double average = total / boxes.size(); -// if (average < best.average) { -// best.average = average; -// best.worst = worst; -// } -// } -// } -// -// return best; -// } - -// Replace the existing MinePictureScore and mine_score_picture with this block. -struct MinePictureScore { - double average = 1000.0; - double worst = 1000.0; - int horizontal = 0; - int vertical = 0; -}; - -// Supports four, five, or six regions. Existing four-region callers keep -// their original search ranges through the default arguments. -template -static MinePictureScore mine_score_picture( - const ImageRGB32& reference, - const ImageRGB32& live, - const std::array& boxes, - int horizontal_range = 16, - int vertical_range = 8 -) { - MinePictureScore best; - for (int y = -vertical_range; y <= vertical_range; y++) { - for (int x = -horizontal_range; x <= horizontal_range; x++) { - double total = 0.0; - double worst = 0.0; - for (const ImageFloatBox& box : boxes) { - const ImageViewRGB32 ref = extract_box_reference(reference, box); - const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); - const double score = ImageMatch::pixel_RMSD(ref, patch); - total += score; - if (score > worst) worst = score; - } - const double average = total / boxes.size(); - if (average < best.average) { - best.average = average; - best.worst = worst; - best.horizontal = x; - best.vertical = y; - } - } - } - return best; -} - - - static bool mine_check_picture( - VideoStream& stream, - ProControllerContext& context, - const char* filename, - const std::array& boxes, - const std::string& label, - size_t max_attempts = 8, - double max_average = 20.0, - double max_patch = 30.0 - ) { - const ImageRGB32 reference( - RESOURCE_PATH() + - "PokemonBDSP/Autostory/Oreburg_mine_nav/" + filename - ); - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - - const bool confirmed = mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - // The watcher stays active throughout settling and sampling. - // There is no separate 500ms watcher wait before each sample. - ctx.wait_for_all_requests(); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - - // Restart confirmation after a battle; do not reuse old matches. - size_t matching_frames = 0; - - for (size_t attempt = 0; attempt < max_attempts; attempt++) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log(label + ": no video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - const MinePictureScore score = - mine_score_picture(reference_scaled, live, boxes); - - stream.log( - label + ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) - ); - - if (score.average <= max_average && score.worst <= max_patch) { - if (++matching_frames >= 3) { - return true; - } - } - else { - matching_frames = 0; - } - - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - } - - return false; - } - ); - - stream.log( - label + (confirmed ? ": confirmed." : ": not confirmed."), - confirmed ? COLOR_GREEN : COLOR_ORANGE - ); - return confirmed; - } - -// Paste this block AFTER mine_check_picture and BEFORE oreburgh_mine_confirm_05. -// Keep the four old early-route functions commented out above this block. - -// Previous early-stage engine retained for comparison. -// struct MineEarlyWalkOptions { -// double x = 0.0; -// double y = 0.0; -// std::chrono::milliseconds pulse = 40ms; -// std::chrono::milliseconds settle = 500ms; -// size_t max_movements = 30; -// int horizontal_range = 12; -// int vertical_range = 6; -// double max_average = 20.0; -// double max_patch = 30.0; -// }; -// -// template -// static bool mine_walk_early_endpoints( -// VideoStream& stream, -// ProControllerContext& context, -// const std::array& files, -// const std::array, Endpoints>& boxes, -// const MineEarlyWalkOptions& options, -// const std::string& label, -// bool force_encounter = false -// ) { -// std::array, Endpoints> references; -// for (size_t index = 0; index < Endpoints; index++) { -// const ImageRGB32 image( -// RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + files[index] -// ); -// references[index] = std::make_unique(image.scale_to(960, 540)); -// } -// -// size_t movements = 0; -// bool forced_completed = false; -// -// while (true) { -// bool request_forced_test = false; -// const bool reached = mine_run_with_encounters( -// stream, context, -// [&](ProControllerContext& ctx) { -// // Reset the match history whenever an encounter interrupts us. -// size_t matching_frames = 0; -// int previous_endpoint = -1; -// -// while (true) { -// ctx.wait_for_all_requests(); -// const VideoSnapshot frame = stream.video().snapshot(); -// if (!frame) { -// stream.log(label + ": no video frame.", COLOR_ORANGE); -// return false; -// } -// -// const ImageRGB32 live = frame->scale_to(960, 540); -// int accepted = -1; -// double accepted_average = 1000.0; -// -// for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { -// const MinePictureScore score = mine_score_picture( -// *references[endpoint], live, boxes[endpoint], -// options.horizontal_range, options.vertical_range -// ); -// stream.log( -// label + " Endpoint " + std::to_string(endpoint + 1) + -// ": average=" + std::to_string(score.average) + -// ", worst patch=" + std::to_string(score.worst) + -// ", offset=(" + std::to_string(score.horizontal) + -// "," + std::to_string(score.vertical) + ")" -// ); -// -// if (score.average <= options.max_average && -// score.worst <= options.max_patch && -// score.average < accepted_average) { -// accepted = static_cast(endpoint); -// accepted_average = score.average; -// } -// } -// -// if (accepted >= 0) { -// matching_frames = accepted == previous_endpoint -// ? matching_frames + 1 : 1; -// previous_endpoint = accepted; -// if (matching_frames >= 3) { -// stream.log( -// label + " Endpoint " + std::to_string(accepted + 1) + -// " confirmed.", COLOR_GREEN -// ); -// return true; -// } -// pbf_wait(ctx, 250ms); -// ctx.wait_for_all_requests(); -// continue; -// } -// -// matching_frames = 0; -// previous_endpoint = -1; -// -// // Exit the guard before invoking the diagnostic wiggle: -// // it has its own encounter watcher and must not be nested. -// if (force_encounter && !forced_completed && movements >= 4) { -// request_forced_test = true; -// return false; -// } -// -// if (movements >= options.max_movements) { -// stream.log(label + ": movement limit reached.", COLOR_ORANGE); -// return false; -// } -// -// // Count issued commands, including interrupted commands. -// movements++; -// pbf_move_left_joystick( -// ctx, { options.x, options.y }, options.pulse, 0ms -// ); -// pbf_wait(ctx, options.settle); -// ctx.wait_for_all_requests(); -// } -// } -// ); -// -// if (reached) return true; -// if (!request_forced_test) return false; -// if (!mine_force_encounter_test(stream, context)) return false; -// forced_completed = true; -// stream.log(label + ": forced encounter completed; resuming."); -// } -// } -// -// static bool oreburgh_mine_walk_to_first_rock( -// VideoStream& stream, -// ProControllerContext& context, -// bool force_encounter = false -// ) { -// const std::array files = { "mine_path_01.png" }; -// const std::array, 1> boxes = {{ -// {{ -// ImageFloatBox(0.482, 0.585, 0.055, 0.055), -// ImageFloatBox(0.335, 0.670, 0.050, 0.055), -// ImageFloatBox(0.412, 0.670, 0.050, 0.055), -// ImageFloatBox(0.570, 0.752, 0.045, 0.055), -// ImageFloatBox(0.645, 0.752, 0.045, 0.055), -// ImageFloatBox(0.570, 0.290, 0.080, 0.120) -// }} -// }}; -// -// MineEarlyWalkOptions options; -// options.x = 0.0; -// options.y = -1.0; -// options.pulse = 80ms; -// options.max_movements = 40; -// options.horizontal_range = 24; -// return mine_walk_early_endpoints( -// stream, context, files, boxes, options, "[Mine path 01]", force_encounter -// ); -// } -// -// static bool oreburgh_mine_move_right( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; -// const std::array, 2> boxes = {{ -// {{ -// ImageFloatBox(0.400, 0.585, 0.055, 0.055), -// ImageFloatBox(0.245, 0.670, 0.050, 0.055), -// ImageFloatBox(0.322, 0.670, 0.050, 0.055), -// ImageFloatBox(0.480, 0.752, 0.045, 0.055), -// ImageFloatBox(0.555, 0.752, 0.045, 0.055), -// ImageFloatBox(0.485, 0.290, 0.080, 0.090) -// }}, -// {{ -// ImageFloatBox(0.325, 0.585, 0.055, 0.055), -// ImageFloatBox(0.170, 0.670, 0.050, 0.055), -// ImageFloatBox(0.244, 0.670, 0.050, 0.055), -// ImageFloatBox(0.395, 0.752, 0.045, 0.055), -// ImageFloatBox(0.470, 0.752, 0.045, 0.055), -// ImageFloatBox(0.415, 0.290, 0.080, 0.090) -// }} -// }}; -// -// MineEarlyWalkOptions options; -// options.x = 1.0; -// options.y = 0.0; -// options.pulse = 40ms; -// options.max_movements = 20; -// options.horizontal_range = 12; -// return mine_walk_early_endpoints( -// stream, context, files, boxes, options, "[Mine right]" -// ); -// } -// -// static bool oreburgh_mine_down_past_central_rock( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; -// const std::array, 2> boxes = {{ -// {{ -// ImageFloatBox(0.345, 0.420, 0.040, 0.045), -// ImageFloatBox(0.195, 0.500, 0.040, 0.045), -// ImageFloatBox(0.270, 0.500, 0.040, 0.045), -// ImageFloatBox(0.410, 0.590, 0.040, 0.045), -// ImageFloatBox(0.480, 0.590, 0.040, 0.045), -// ImageFloatBox(0.445, 0.180, 0.060, 0.090) -// }}, -// {{ -// ImageFloatBox(0.420, 0.420, 0.040, 0.045), -// ImageFloatBox(0.270, 0.500, 0.040, 0.045), -// ImageFloatBox(0.345, 0.500, 0.040, 0.045), -// ImageFloatBox(0.485, 0.590, 0.040, 0.045), -// ImageFloatBox(0.555, 0.590, 0.040, 0.045), -// ImageFloatBox(0.520, 0.180, 0.060, 0.090) -// }} -// }}; -// -// MineEarlyWalkOptions options; -// options.x = 0.0; -// options.y = -1.0; -// options.pulse = 40ms; -// options.max_movements = 30; -// options.horizontal_range = 12; -// return mine_walk_early_endpoints( -// stream, context, files, boxes, options, "[Mine down 02]" -// ); -// } -// -// static bool oreburgh_mine_move_left_to_gap( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const std::array files = { "mine_path_03.png" }; -// const std::array, 1> boxes = {{ -// {{ -// ImageFloatBox(0.340, 0.495, 0.040, 0.045), -// ImageFloatBox(0.410, 0.495, 0.040, 0.045), -// ImageFloatBox(0.555, 0.580, 0.040, 0.045), -// ImageFloatBox(0.625, 0.580, 0.040, 0.045), -// ImageFloatBox(0.580, 0.180, 0.060, 0.090) -// }} -// }}; -// -// MineEarlyWalkOptions options; -// options.x = -1.0; -// options.y = 0.0; -// options.pulse = 40ms; -// options.max_movements = 30; -// options.horizontal_range = 12; -// return mine_walk_early_endpoints( -// stream, context, files, boxes, options, "[Mine left 03]" -// ); -// } - -struct MineWalkOptions { - double x = 0.0; - double y = 0.0; - std::chrono::milliseconds pulse = 40ms; - std::chrono::milliseconds settle = 500ms; - std::chrono::milliseconds initial_settle = 0ms; - size_t max_movements = 30; - int horizontal_range = 12; - int vertical_range = 6; - double max_average = 20.0; - double max_patch = 30.0; -}; - -template -static bool mine_walk_to_endpoints( - VideoStream& stream, - ProControllerContext& context, - const std::array& files, - const std::array, Endpoints>& boxes, - const MineWalkOptions& options, - const std::string& label, - bool force_encounter = false -) { - std::array, Endpoints> references; - for (size_t index = 0; index < Endpoints; index++) { - const ImageRGB32 image( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + files[index] - ); - references[index] = std::make_unique(image.scale_to(960, 540)); - } - - size_t movements = 0; - bool forced_completed = false; - - while (true) { - bool request_forced_test = false; - const bool reached = mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - if (options.initial_settle > 0ms) { - pbf_wait(ctx, options.initial_settle); - ctx.wait_for_all_requests(); - } - // Reset the match history whenever an encounter interrupts us. - size_t matching_frames = 0; - int previous_endpoint = -1; - - while (true) { - ctx.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log(label + ": no video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - int accepted = -1; - double accepted_average = 1000.0; - - for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { - const MinePictureScore score = mine_score_picture( - *references[endpoint], live, boxes[endpoint], - options.horizontal_range, options.vertical_range - ); - stream.log( - label + " Endpoint " + std::to_string(endpoint + 1) + - ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) + - ", offset=(" + std::to_string(score.horizontal) + - "," + std::to_string(score.vertical) + ")" - ); - - if (score.average <= options.max_average && - score.worst <= options.max_patch && - score.average < accepted_average) { - accepted = static_cast(endpoint); - accepted_average = score.average; - } - } - - if (accepted >= 0) { - matching_frames = accepted == previous_endpoint - ? matching_frames + 1 : 1; - previous_endpoint = accepted; - if (matching_frames >= 3) { - stream.log( - label + " Endpoint " + std::to_string(accepted + 1) + - " confirmed.", COLOR_GREEN - ); - return true; - } - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - previous_endpoint = -1; - - // Exit the guard before invoking the diagnostic wiggle: - // it has its own encounter watcher and must not be nested. - if (force_encounter && !forced_completed && movements >= 4) { - request_forced_test = true; - return false; - } - - if (movements >= options.max_movements) { - stream.log(label + ": movement limit reached.", COLOR_ORANGE); - return false; - } - - // Count issued commands, including interrupted commands. - movements++; - pbf_move_left_joystick( - ctx, { options.x, options.y }, options.pulse, 0ms - ); - pbf_wait(ctx, options.settle); - ctx.wait_for_all_requests(); - } - } - ); - - if (reached) return true; - if (!request_forced_test) return false; - if (!mine_force_encounter_test(stream, context)) return false; - forced_completed = true; - stream.log(label + ": forced encounter completed; resuming."); - } -} - -static bool oreburgh_mine_walk_to_first_rock( - VideoStream& stream, - ProControllerContext& context, - bool force_encounter = false -) { - const std::array files = { "mine_path_01.png" }; - const std::array, 1> boxes = {{ - {{ - ImageFloatBox(0.482, 0.585, 0.055, 0.055), - ImageFloatBox(0.335, 0.670, 0.050, 0.055), - ImageFloatBox(0.412, 0.670, 0.050, 0.055), - ImageFloatBox(0.570, 0.752, 0.045, 0.055), - ImageFloatBox(0.645, 0.752, 0.045, 0.055), - ImageFloatBox(0.570, 0.290, 0.080, 0.120) - }} - }}; - - MineWalkOptions options; - options.x = 0.0; - options.y = -1.0; - options.pulse = 80ms; - options.max_movements = 40; - options.horizontal_range = 24; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine path 01]", force_encounter - ); -} - -static bool oreburgh_mine_move_right( - VideoStream& stream, - ProControllerContext& context -) { - const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; - const std::array, 2> boxes = {{ - {{ - ImageFloatBox(0.400, 0.585, 0.055, 0.055), - ImageFloatBox(0.245, 0.670, 0.050, 0.055), - ImageFloatBox(0.322, 0.670, 0.050, 0.055), - ImageFloatBox(0.480, 0.752, 0.045, 0.055), - ImageFloatBox(0.555, 0.752, 0.045, 0.055), - ImageFloatBox(0.485, 0.290, 0.080, 0.090) - }}, - {{ - ImageFloatBox(0.325, 0.585, 0.055, 0.055), - ImageFloatBox(0.170, 0.670, 0.050, 0.055), - ImageFloatBox(0.244, 0.670, 0.050, 0.055), - ImageFloatBox(0.395, 0.752, 0.045, 0.055), - ImageFloatBox(0.470, 0.752, 0.045, 0.055), - ImageFloatBox(0.415, 0.290, 0.080, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = 1.0; - options.y = 0.0; - options.pulse = 40ms; - options.max_movements = 20; - options.horizontal_range = 12; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine right]" - ); -} - -static bool oreburgh_mine_down_past_central_rock( - VideoStream& stream, - ProControllerContext& context -) { - const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; - const std::array, 2> boxes = {{ - {{ - ImageFloatBox(0.345, 0.420, 0.040, 0.045), - ImageFloatBox(0.195, 0.500, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.410, 0.590, 0.040, 0.045), - ImageFloatBox(0.480, 0.590, 0.040, 0.045), - ImageFloatBox(0.445, 0.180, 0.060, 0.090) - }}, - {{ - ImageFloatBox(0.420, 0.420, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.345, 0.500, 0.040, 0.045), - ImageFloatBox(0.485, 0.590, 0.040, 0.045), - ImageFloatBox(0.555, 0.590, 0.040, 0.045), - ImageFloatBox(0.520, 0.180, 0.060, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = 0.0; - options.y = -1.0; - options.pulse = 40ms; - options.max_movements = 30; - options.horizontal_range = 12; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine down 02]" - ); -} - -static bool oreburgh_mine_move_left_to_gap( - VideoStream& stream, - ProControllerContext& context -) { - const std::array files = { "mine_path_03.png" }; - const std::array, 1> boxes = {{ - {{ - ImageFloatBox(0.340, 0.495, 0.040, 0.045), - ImageFloatBox(0.410, 0.495, 0.040, 0.045), - ImageFloatBox(0.555, 0.580, 0.040, 0.045), - ImageFloatBox(0.625, 0.580, 0.040, 0.045), - ImageFloatBox(0.580, 0.180, 0.060, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = -1.0; - options.y = 0.0; - options.pulse = 40ms; - options.max_movements = 30; - options.horizontal_range = 12; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine left 03]" - ); -} - - - - // Confirm we are far enough down before beginning the leftward section. - - -// Each picture stage owns its movement and matching settings. -struct MinePictureStep { - const char* file; - const char* label; - std::array boxes; - double x; - double y; - std::chrono::milliseconds pulse = 80ms; - size_t max_movements = 30; - double max_average = 20.0; - double max_patch = 30.0; -}; - -static bool mine_run_picture_step( - VideoStream& stream, ProControllerContext& context, - const MinePictureStep& step -) { - MineWalkOptions options; - options.x = step.x; - options.y = step.y; - options.pulse = step.pulse; - options.max_movements = step.max_movements; - options.max_average = step.max_average; - options.max_patch = step.max_patch; - options.horizontal_range = 16; - options.vertical_range = 8; - options.initial_settle = 700ms; - options.settle = 700ms; - const std::array files = {step.file}; - const std::array, 1> boxes = {{step.boxes}}; - return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); -} - -// Previous checkpoint loop retained for comparison. -// static bool oreburgh_mine_confirm_05( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// // Stationary rock surfaces in mine_path_05.png. -// // Avoid the hidden player, NPC, and Machop. -// const std::array boxes = { -// ImageFloatBox(0.240, 0.240, 0.055, 0.080), // Large left rock -// ImageFloatBox(0.390, 0.220, 0.050, 0.090), // Large middle rock -// ImageFloatBox(0.360, 0.090, 0.030, 0.030), // Upper-left small rock -// ImageFloatBox(0.550, 0.140, 0.040, 0.040) // Upper-right small rock -// }; -// -// for (size_t extra = 0; extra <= 10; extra++) { -// if (mine_check_picture( -// stream, -// context, -// "mine_path_05.png", -// boxes, -// "[Mine checkpoint 05]", -// 3 -// )) { -// stream.log( -// "[Mine checkpoint 05] Confirmed; leftward movement can begin.", -// COLOR_GREEN -// ); -// return true; -// } -// -// if (extra == 10) { -// stream.log( -// "[Mine checkpoint 05] Extra movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// stream.log("[Mine checkpoint 05] Adding 60ms downward movement."); -// -// if (!mine_timed_walk( -// stream, context, 0.0, -1.0, 60ms -// )) { -// return false; -// } -// } -// -// return false; -// } - -static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_05.png", "[Mine endpoint 05]", - {ImageFloatBox(0.240, 0.240, 0.055, 0.080), ImageFloatBox(0.390, 0.220, 0.050, 0.090), ImageFloatBox(0.360, 0.090, 0.030, 0.030), ImageFloatBox(0.550, 0.140, 0.040, 0.040)}, - 0.0, -1.0, 60ms, 10, 20.0, 30.0 - }; - return mine_run_picture_step(stream, context, step); -} - - // Temporary timed trial from the central gap through paths 6 and 7. - static bool oreburgh_mine_timed_to_07( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine 04-07] Walking down for 1500ms."); - - if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)) { - return false; - } - - if (!oreburgh_mine_confirm_05(stream, context)) { - return false; - } - - stream.log("[Mine 04-07] Walking left for 3000ms."); - - if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)) { - return false; - } - - // Path 6: crate, nearby small rock, large rock, upper-right small rock. - const std::array path6_boxes = { - ImageFloatBox(0.350, 0.250, 0.085, 0.075), - ImageFloatBox(0.405, 0.510, 0.035, 0.040), - ImageFloatBox(0.520, 0.300, 0.050, 0.080), - ImageFloatBox(0.620, 0.350, 0.030, 0.040) - }; - - if (!mine_check_picture( - stream, context, - "mine_path_06.png", path6_boxes, - "[Mine midpoint 06]" - )) { - return false; - } - - // Initial estimate: tune after the first live test. - stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); - - if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)) { - return false; - } - - // Path 7: crate and three visible rock surfaces. - const std::array path7_boxes = { - ImageFloatBox(0.365, 0.025, 0.070, 0.055), - ImageFloatBox(0.420, 0.270, 0.035, 0.040), - ImageFloatBox(0.560, 0.415, 0.025, 0.040), - ImageFloatBox(0.530, 0.650, 0.060, 0.090) - }; - - return mine_check_picture( - stream, context, - "mine_path_07.png", path7_boxes, - "[Mine endpoint 07]" - ); - } - - // Move left from path 7 to the position shown in path 8. - -// Previous checkpoint loop retained for comparison. -// static bool oreburgh_mine_left_to_08( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// // Crate, upper small rock, ledge-side rock, and lower-left small rock. -// // Avoid the character and its shadow. -// const std::array boxes = { -// ImageFloatBox(0.495, 0.035, 0.070, 0.055), -// ImageFloatBox(0.550, 0.270, 0.035, 0.040), -// ImageFloatBox(0.695, 0.420, 0.030, 0.040), -// ImageFloatBox(0.555, 0.585, 0.035, 0.040) -// }; -// -// // Check before every pulse, including after the final allowed pulse. -// for (size_t movement = 0; movement <= 20; movement++) { -// if (mine_check_picture( -// stream, -// context, -// "mine_path_08.png", -// boxes, -// "[Mine endpoint 08]", -// 3 -// )) { -// stream.log( -// "[Mine left 08] Left checkpoint reached. Stopping.", -// COLOR_GREEN -// ); -// return true; -// } -// -// if (movement == 20) { -// stream.log( -// "[Mine left 08] Movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// stream.log("[Mine left 08] Moving left for 40ms."); -// -// if (!mine_timed_walk( -// stream, context, -1.0, 0.0, 40ms -// )) { -// return false; -// } -// } -// -// return false; -// } - -static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_08.png", "[Mine endpoint 08]", - {ImageFloatBox(0.495, 0.035, 0.070, 0.055), ImageFloatBox(0.550, 0.270, 0.035, 0.040), ImageFloatBox(0.695, 0.420, 0.030, 0.040), ImageFloatBox(0.555, 0.585, 0.035, 0.040)}, - -1.0, 0.0, 40ms, 20, 20.0, 30.0 - }; - return mine_run_picture_step(stream, context, step); -} - - // Walk down along the left wall to path 9. - -// Previous checkpoint loop retained for comparison. -// static bool oreburgh_mine_down_to_09( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// // Upper small rock, rock pair, and the crate below the player. -// // Avoid the player and shadow. -// const std::array boxes = { -// ImageFloatBox(0.550, 0.115, 0.030, 0.040), -// ImageFloatBox(0.550, 0.390, 0.035, 0.040), -// ImageFloatBox(0.620, 0.390, 0.025, 0.035), -// ImageFloatBox(0.500, 0.720, 0.085, 0.080) -// }; -// -// // Check before each movement and after the last allowed movement. -// for (size_t movement = 0; movement <= 25; movement++) { -// if (mine_check_picture( -// stream, -// context, -// "mine_path_09.png", -// boxes, -// "[Mine endpoint 09]", -// 3, -// 25.0, -// 35.0 -// )) { -// stream.log( -// "[Mine down 09] Checkpoint reached. Stopping.", -// COLOR_GREEN -// ); -// return true; -// } -// -// if (movement == 25) { -// stream.log( -// "[Mine down 09] Movement limit reached. Stopping.", -// COLOR_ORANGE -// ); -// return false; -// } -// -// stream.log("[Mine down 09] Moving down for 60ms."); -// -// if (!mine_timed_walk( -// stream, context, 0.0, -1.0, 40ms -// )) { -// return false; -// } -// } -// -// return false; -// } - -static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_09.png", "[Mine endpoint 09]", - {ImageFloatBox(0.550, 0.115, 0.030, 0.040), ImageFloatBox(0.550, 0.390, 0.035, 0.040), ImageFloatBox(0.620, 0.390, 0.025, 0.035), ImageFloatBox(0.500, 0.720, 0.085, 0.080)}, - 0.0, -1.0, 40ms, 25, 25.0, 35.0 - }; - return mine_run_picture_step(stream, context, step); -} - -// Previous per-pulse picture loader retained for comparison. -// -// static bool mine_walk_to_picture( -// VideoStream& stream, -// ProControllerContext& context, -// double x, -// double y, -// const char* filename, -// const std::array& boxes, -// const std::string& label -// ) { -// for (size_t movement = 0; movement <= 30; movement++) { -// if (mine_check_picture( -// stream, context, filename, boxes, label, 3 -// )) { -// return true; -// } -// -// if (movement == 30) { -// stream.log(label + ": movement limit reached.", COLOR_ORANGE); -// return false; -// } -// -// if (!mine_timed_walk(stream, context, x, y, 80ms)) { -// return false; -// } -// } -// -// return false; -// } - - static bool mine_walk_to_picture( - VideoStream& stream, - ProControllerContext& context, - double x, - double y, - const char* filename, - const std::array& boxes, - const std::string& label - ) { - const MinePictureStep step = {filename, label.c_str(), boxes, x, y}; - return mine_run_picture_step(stream, context, step); -} - - // Require dialogue to appear first. - // Then finish only after it has been absent for one continuous second. - static bool mine_finish_item_dialogue( - VideoStream& stream, - ProControllerContext& context - ) { - ShortDialogDetector dialogue(COLOR_YELLOW); - - bool saw_dialogue = false; - auto absent_since = std::chrono::steady_clock::now(); - - const auto start = std::chrono::steady_clock::now(); - const auto deadline = start + 30s; - - stream.log("[Mine item] Starting interaction."); - - while (std::chrono::steady_clock::now() < deadline) { - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - - if (!frame) { - stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); - return false; - } - - const auto now = std::chrono::steady_clock::now(); - const bool visible = dialogue.detect(frame); - - if (visible) { - if (!saw_dialogue) { - stream.log("[Mine item] Dialogue detected."); - } - - saw_dialogue = true; - absent_since = now; - - // Short mash bursts allow frequent checks. - pbf_mash_button(context, BUTTON_A, 100ms); - } - else if (!saw_dialogue) { - if (now - start >= 10s) { - stream.log( - "[Mine item] No dialogue appeared. Stopping.", - COLOR_ORANGE - ); - return false; - } - - // Try to start the interaction. - pbf_press_button(context, BUTTON_A, 60ms, 100ms); - } - else if (now - absent_since >= 1s) { - stream.log( - "[Mine item] Dialogue absent for one second. Finished.", - COLOR_GREEN - ); - return true; - } - - pbf_wait(context, 100ms); - context.wait_for_all_requests(); - } - - stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); - return false; - } - -// Previous route block retained for comparison. -// static bool oreburgh_mine_09_to_14( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const std::array path10_boxes = { -// ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate -// ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock -// ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock -// ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock -// }; -// -// if (!mine_walk_to_picture( -// stream, context, 1.0, 0.0, -// "mine_path_10.png", path10_boxes, "[Mine endpoint 10]" -// )) { -// return false; -// } -// -// const std::array path11_boxes = { -// ImageFloatBox(0.405, 0.560, 0.070, 0.080), -// ImageFloatBox(0.475, 0.270, 0.030, 0.040), -// ImageFloatBox(0.575, 0.290, 0.045, 0.080), -// ImageFloatBox(0.550, 0.575, 0.030, 0.040) -// }; -// -// if (!mine_walk_to_picture( -// stream, context, 0.0, -1.0, -// "mine_path_11.png", path11_boxes, "[Mine endpoint 11]" -// )) { -// return false; -// } -// -// const std::array path12_boxes = { -// ImageFloatBox(0.260, 0.560, 0.080, 0.080), -// ImageFloatBox(0.355, 0.270, 0.025, 0.035), -// ImageFloatBox(0.435, 0.290, 0.040, 0.070), -// ImageFloatBox(0.415, 0.585, 0.025, 0.035) -// }; -// -// if (!mine_walk_to_picture( -// stream, context, 1.0, 0.0, -// "mine_path_12.png", path12_boxes, "[Mine endpoint 12]" -// )) { -// return false; -// } -// -// const std::array path13_boxes = { -// ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate -// ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate -// ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock -// ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock -// }; -// -// if (!mine_walk_to_picture( -// stream, context, 0.0, -1.0, -// "mine_path_13.png", path13_boxes, "[Mine endpoint 13]" -// )) { -// return false; -// } -// -// // Turn left, then verify position before starting the interaction. -// pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); -// pbf_wait(context, 500ms); -// context.wait_for_all_requests(); -// -// if (!mine_check_picture( -// stream, context, -// "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 -// )) { -// return false; -// } -// stream.log("[Mine item] Position confirmed. Pressing A."); -// -// pbf_press_button(context, BUTTON_A, 80ms, 200ms); -// context.wait_for_all_requests(); -// -// return mine_finish_item_dialogue(stream, context); -// } - - static bool oreburgh_mine_09_to_14( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array path10_boxes = { - ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate - ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock - ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock - ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock - }; - - const MinePictureStep path10_boxes_step = { - "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path10_boxes_step)) { - return false; - } - - const std::array path11_boxes = { - ImageFloatBox(0.405, 0.560, 0.070, 0.080), - ImageFloatBox(0.475, 0.270, 0.030, 0.040), - ImageFloatBox(0.575, 0.290, 0.045, 0.080), - ImageFloatBox(0.550, 0.575, 0.030, 0.040) - }; - - const MinePictureStep path11_boxes_step = { - "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path11_boxes_step)) { - return false; - } - - const std::array path12_boxes = { - ImageFloatBox(0.260, 0.560, 0.080, 0.080), - ImageFloatBox(0.355, 0.270, 0.025, 0.035), - ImageFloatBox(0.435, 0.290, 0.040, 0.070), - ImageFloatBox(0.415, 0.585, 0.025, 0.035) - }; - - const MinePictureStep path12_boxes_step = { - "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path12_boxes_step)) { - return false; - } - - const std::array path13_boxes = { - ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate - ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate - ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock - ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock - }; - - const MinePictureStep path13_boxes_step = { - "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path13_boxes_step)) { - return false; - } - - // Turn left, then verify position before starting the interaction. - pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - if (!mine_check_picture( - stream, context, - "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 - )) { - return false; - } - stream.log("[Mine item] Position confirmed. Pressing A."); - - pbf_press_button(context, BUTTON_A, 80ms, 200ms); - context.wait_for_all_requests(); - - return mine_finish_item_dialogue(stream, context); - } - - // Compare a reference patch with a shifted live patch using the same - // brightness adjustment as the native ImageMatchDetector. - static double mine_offset_patch_score( - const ImageRGB32& reference, - const ImageRGB32& live, - const ImageFloatBox& box, - int horizontal, - int vertical - ) { - const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); - const ImageViewRGB32 live_crop = - extract_box_reference(live, box, horizontal, vertical); - ImageRGB32 adjusted = live_crop.scale_to( - reference_crop.width(), reference_crop.height() - ); - - const auto reference_average = - ImageMatch::pixel_average(reference_crop, reference_crop); - const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); - auto scale = reference_average / live_average; - if (std::isnan(scale.r)) scale.r = 1.0; - if (std::isnan(scale.g)) scale.g = 1.0; - if (std::isnan(scale.b)) scale.b = 1.0; - scale.bound(0.8, 1.2); - ImageMatch::scale_brightness(adjusted, scale); - - return ImageMatch::pixel_RMSD(reference_crop, adjusted); - } - - struct MineRoarkOffsetScore { - double left = 1000.0; - double right = 1000.0; - double helmet = 1000.0; - int horizontal = 0; - int vertical = 0; - int helmet_horizontal = 0; - int helmet_vertical = 0; - }; - - static MineRoarkOffsetScore mine_score_roark_offsets( - const ImageRGB32& reference, - const ImageRGB32& live - ) { - const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); - const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); - const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); - - MineRoarkOffsetScore best; - double best_worst_rock = 1000.0; - double best_total = 2000.0; - - // Both rocks must use the SAME offset. They cannot match independently. - // Offsets are pixels on the 960 x 540 comparison image. - for (int y = -8; y <= 8; y++) { - for (int x = -16; x <= 16; x++) { - const double left = mine_offset_patch_score(reference, live, left_box, x, y); - const double right = mine_offset_patch_score(reference, live, right_box, x, y); - const double worst = left > right ? left : right; - const double total = left + right; - - if (worst < best_worst_rock || - (worst == best_worst_rock && total < best_total)) { - best_worst_rock = worst; - best_total = total; - best.left = left; - best.right = right; - best.horizontal = x; - best.vertical = y; - } - } - } - - // Roark's helmet may move slightly relative to the stationary rocks. - // Keep this extra search small so another red object cannot match far away. - for (int extra_y = -6; extra_y <= 6; extra_y++) { - for (int extra_x = -6; extra_x <= 6; extra_x++) { - const double score = mine_offset_patch_score( - reference, live, helmet_box, - best.horizontal + extra_x, - best.vertical + extra_y - ); - if (score < best.helmet) { - best.helmet = score; - best.helmet_horizontal = extra_x; - best.helmet_vertical = extra_y; - } - } - } - - return best; - } - - static bool oreburgh_mine_align_to_18( - VideoStream& stream, - ProControllerContext& context - ) { - const ImageRGB32 reference( - RESOURCE_PATH() + - "PokemonBDSP/Autostory/Oreburg_mine_nav/Mine_path_18.png" - ); - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - const double max_rmsd = 65.0; - size_t movements = 0; - - return mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - // Fresh confirmation after any encounter. - size_t matching_frames = 0; - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - - while (true) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - const MineRoarkOffsetScore score = - mine_score_roark_offsets(reference_scaled, live); - - stream.log( - "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + - ", Right rock=" + std::to_string(score.right) + - ", Helmet=" + std::to_string(score.helmet) + - ", shared offset=(" + std::to_string(score.horizontal) + - "," + std::to_string(score.vertical) + - "), helmet extra=(" + std::to_string(score.helmet_horizontal) + - "," + std::to_string(score.helmet_vertical) + ")" - ); - - if (score.left <= max_rmsd && score.right <= max_rmsd && - score.helmet <= max_rmsd) { - if (++matching_frames >= 3) { - stream.log( - "[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN - ); - return true; - } - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - if (movements >= 80) { - stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); - return false; - } - - const bool approaching = score.left <= 120.0 && - score.right <= 120.0 && score.helmet <= 150.0; - const bool near = score.left <= 100.0 && - score.right <= 100.0 && score.helmet <= 100.0; - const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; - - // Use the outer encounter watcher; do not nest mine_timed_walk. - // Count issued pulses, including any interrupted by a battle. - movements++; - pbf_move_left_joystick(ctx, { 1.0, 0.0 }, pulse, 0ms); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - } - } - ); - } - - /*static bool oreburgh_mine_align_to_18( - VideoStream& stream, - ProControllerContext& context - ) { - auto reference = std::make_shared( - RESOURCE_PATH() + - "PokemonBDSP/Autostory/Oreburg_mine_nav/Mine_path_18.png" - ); - - // Match fixed details around Roark. - // Keep the player outside these boxes. - const double max_rmsd = 65.0; - - ImageMatchDetector left_rock( - reference, - ImageFloatBox(0.415, 0.400, 0.050, 0.095), - max_rmsd, true - ); - ImageMatchDetector right_rock( - reference, - ImageFloatBox(0.555, 0.400, 0.055, 0.095), - max_rmsd, true - ); - ImageMatchDetector helmet( - reference, - ImageFloatBox(0.485, 0.335, 0.035, 0.045), - max_rmsd, true - ); - - size_t matching_frames = 0; - - // Include a final check after the last allowed movement. - for (size_t movement = 0; movement <= 80;) { - if (!mine_watch_while_checking(stream, context)) { - return false; - } - - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - - if (!frame) { - stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); - return false; - } - - const double left = left_rock.rmsd(frame); - const double right = right_rock.rmsd(frame); - const double hat = helmet.rmsd(frame); - - stream.log( - "[Mine endpoint 18] Left rock=" + std::to_string(left) + - ", Right rock=" + std::to_string(right) + - ", Helmet=" + std::to_string(hat) - ); - - if (left <= max_rmsd && - right <= max_rmsd && - hat <= max_rmsd - ) { - // Stand still while confirming the match. - if (++matching_frames >= 3) { - stream.log( - "[Mine endpoint 18] Rocks and helmet confirmed.", - COLOR_GREEN - ); - return true; - } - - pbf_wait(context, 150ms); - context.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - - if (movement == 80) { - stream.log( - "[Mine endpoint 18] Movement limit reached. Stopping.", - COLOR_ORANGE - ); - return false; - } - - // Small pulses reduce the chance of passing the stopping point. - // Use smaller movements when all three landmarks are getting close. - // Slow down earlier when the landmarks begin matching. - const bool approaching_target = - left <= 120.0 && - right <= 120.0 && - hat <= 150.0; - - const bool near_target = - left <= 100.0 && - right <= 100.0 && - hat <= 100.0; - - const auto pulse = near_target ? 20ms : - approaching_target ? 40ms : 60ms; - - if (!mine_timed_walk(stream, context, 1.0, 0.0, pulse)) { - return false; - } - movement++; - } - - return false; - }*/ - -// Previous route block retained for comparison. -// static bool oreburgh_mine_14_to_19( -// VideoStream& stream, -// ProControllerContext& context -// ) { -// const std::array path15_boxes = { -// ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate -// ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate -// ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock -// ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock -// }; -// -// if (!mine_walk_to_picture( -// stream, context, 1.0, 0.0, -// "mine_path_15_actual.png", path15_boxes, "[Mine endpoint 15]" -// )) { -// return false; -// } -// -// // Move below the worker before continuing right. -// const std::array path16_boxes = { -// ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate -// ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate -// ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock -// ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock -// }; -// -// if (!mine_walk_to_picture( -// stream, context, 0.0, -1.0, -// "mine_path_16.png", path16_boxes, "[Mine endpoint 16]" -// )) { -// return false; -// } -// -// // Skip 17 and find the actual stopping point at 18. -// if (!oreburgh_mine_align_to_18(stream, context)) { -// return false; -// } -// -// // Position 19 is the upward turn toward Roark. -// pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); -// pbf_wait(context, 500ms); -// context.wait_for_all_requests(); -// -// stream.log( -// "[Mine endpoint 19] Position 18 confirmed; north turn sent.", -// COLOR_GREEN -// ); -// return true; -// } - - static bool oreburgh_mine_14_to_19( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array path15_boxes = { - ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate - ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate - ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock - ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock - }; - - const MinePictureStep path15_boxes_step = { - "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path15_boxes_step)) { - return false; - } - - // Move below the worker before continuing right. - const std::array path16_boxes = { - ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate - ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate - ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock - ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock - }; - - const MinePictureStep path16_boxes_step = { - "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path16_boxes_step)) { - return false; - } - - // Skip 17 and find the actual stopping point at 18. - if (!oreburgh_mine_align_to_18(stream, context)) { - return false; - } - - // Position 19 is the upward turn toward Roark. - pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - stream.log( - "[Mine endpoint 19] Position 18 confirmed; north turn sent.", - COLOR_GREEN - ); - return true; - } - - enum class MineRouteStage { - ALIGN_ENTRANCE, - FIRST_ROCK, - RIGHT_OF_FIRST_ROCK, - BELOW_FIRST_ROCK, - CENTER_GAP, - POSITION_07, - POSITION_08, - POSITION_09, - ITEM_AT_14, - ROARK_AT_19 - }; - - struct MineRouteTestOptions { - MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; - MineRouteStage last = MineRouteStage::ROARK_AT_19; - - // False: stop the test on failure. - // True: let the checkpoint retry handler reset the game. - bool reset_on_failure = false; - }; - - static bool oreburgh_mine_first_leg_test( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options = MineRouteTestOptions() - ) { - struct Step { - const char* name; - bool (*run)(VideoStream&, ProControllerContext&); - }; - - const std::array steps = { { - { - "Align entrance", - oreburgh_mine_alignment_test - }, - { - "Walk to first rock", - [](VideoStream& video, ProControllerContext& controller) { - return oreburgh_mine_walk_to_first_rock( - video, controller, false - ); - } - }, - { - "Move right of first rock", - oreburgh_mine_move_right - }, - { - "Move below first rock", - oreburgh_mine_down_past_central_rock - }, - { - "Move left into center gap", - oreburgh_mine_move_left_to_gap - }, - { - "Reach position 07", - oreburgh_mine_timed_to_07 - }, - { - "Reach position 08", - oreburgh_mine_left_to_08 - }, - { - "Reach position 09", - oreburgh_mine_down_to_09 - }, - { - "Reach 14 and collect item", - oreburgh_mine_09_to_14 - }, - { - "Reach 18 and turn north for 19", - oreburgh_mine_14_to_19 - } - } }; - - const size_t first = static_cast(options.first); - const size_t last = static_cast(options.last); - - if (first > last || last >= steps.size()) { - stream.log("[Mine route] Invalid test range.", COLOR_ORANGE); - return false; - } - - for (size_t index = first; index <= last; index++) { - const Step& step = steps[index]; - - stream.log( - std::string("[Mine route] Starting: ") + step.name - ); - - bool succeeded = false; - - try { - succeeded = step.run(stream, context); - } - catch (OperationFailedException& error) { - stream.log( - std::string("[Mine route] Failed during ") + - step.name + ": " + error.message(), - COLOR_ORANGE - ); - - if (options.reset_on_failure) { - throw; - } - - stream.log( - "[Mine route] Test stopped without requesting a reset.", - COLOR_ORANGE - ); - return false; - } - - if (!succeeded) { - stream.log( - std::string("[Mine route] Stage failed: ") + step.name, - COLOR_ORANGE - ); - - if (options.reset_on_failure) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - std::string("Mine route failed during: ") + step.name, - stream - ); - } - - return false; - } - - stream.log( - std::string("[Mine route] Completed: ") + step.name, - COLOR_GREEN - ); - } - - stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); - return true; - } - - static void oreburgh_mine_nav( - VideoStream& stream, ProControllerContext& context) { - walk_through_grass(stream, context, 0, -1, 1, "down into mine"); - context.wait_for_all_requests(); - walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); - context.wait_for_all_requests(); - pbf_wait(context, 1824ms); - walk_through_grass(stream, context, 0, -1, 8, "down through mine"); - pbf_wait(context, 571ms); - walk_through_grass(stream, context, 1, 0, 2, "right through mine"); - pbf_wait(context, 504ms); - walk_through_grass(stream, context, 0, -1, 2, "down through mine"); - pbf_wait(context, 470ms); - walk_through_grass(stream, context, -1, 0, 2, "left through mine"); - pbf_wait(context, 574ms); - walk_through_grass(stream, context, 0, -1, 4, "down through mine"); - pbf_wait(context, 440ms); - walk_through_grass(stream, context, 0, -1, 3, "down through mine"); - pbf_wait(context, 4426ms); - - walk_through_grass(stream, context, 0, -1, 2, "down through mine"); - pbf_wait(context, 672ms); - walk_through_grass(stream, context, -1, 0, 9, "left through mine"); - pbf_wait(context, 325ms); - walk_through_grass(stream, context, 0, -1, 4, "down through mine"); - pbf_wait(context, 390ms); - walk_through_grass(stream, context, -1, 0, 3, "left through mine"); - pbf_wait(context, 205ms); - walk_through_grass(stream, context, 0, -1, 4, "down through mine"); - pbf_wait(context, 156ms); - walk_through_grass(stream, context, 1, 0, 4, "right through mine"); - pbf_wait(context, 956ms); - walk_through_grass(stream, context, 0, 1, 1, "up through mine"); - pbf_wait(context, 1206ms); - walk_through_grass(stream, context, 1, 0, 7, "right through mine"); - pbf_wait(context, 3473ms); - walk_through_grass(stream, context, 0, -1, 5, "down through mine"); - pbf_wait(context, 874ms); - walk_through_grass(stream, context, -1, 0, 1, "left through mine"); - pbf_wait(context, 2590ms); - pbf_mash_button(context, BUTTON_A, 1000ms); - walk_through_grass(stream, context, 1, 0, 12, "right through mine"); - pbf_wait(context, 539ms); - align_between_rocks_and_talk(stream, context); - pbf_press_button(context, BUTTON_X, 142ms, 0ms); - pbf_wait(context, 1707ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 162ms, 0ms); - pbf_wait(context, 457ms); - pbf_press_button(context, BUTTON_A, 144ms, 0ms); - pbf_wait(context, 1704ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 159ms, 0ms); - pbf_wait(context, 640ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 160ms, 0ms); - pbf_wait(context, 555ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 180ms, 0ms); - pbf_wait(context, 494ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 171ms, 0ms); - pbf_wait(context, 826ms); - pbf_press_button(context, BUTTON_A, 92ms, 0ms); - pbf_wait(context, 823ms); - pbf_press_button(context, BUTTON_A, 110ms, 0ms); - - } - // --------------------------------------------------------------------------- - // Segment 1 Part 3: Oreburgh Mine to Gym - // Assumes that escape rope was picked up and used to exit the mine. - // Navigates to the gym. - // ---------------------------------------------------------------------------- - static void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4443ms, 0ms); - pbf_wait(context, 1165ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 540ms, 0ms); - pbf_wait(context, 281ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 858ms, 0ms); - pbf_wait(context, 764ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2972ms, 0ms); - pbf_wait(context, 677ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 160ms, 0ms); - pbf_press_button(context, BUTTON_UP, 180ms, 0ms); - } - - // --------------------------------------------------------------------------- - // Segment 1 Part 4: Gym navigation - // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. - // --------------------------------------------------------------------------- - - static void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - - //Upon entering the Gym. - - struct Move { - DpadPosition dir; - int count; - }; - const Move sequence[] = { - {DPAD_UP, 3}, - {DPAD_LEFT, 4}, - {DPAD_UP, 5}, - {DPAD_RIGHT, 8}, - {DPAD_UP, 11}, - {DPAD_LEFT, 5}, - {DPAD_UP, 4}, - }; - - for (const auto& move : sequence) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate the Gym battle. - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - //Barry 1 battle logic can be used here since the moves don't matter this early. - Barry_1_fight(env, stream, context); - - //Button presses after battle with small cutscene of badge being put in case between dialogue boxes. - - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - - for (int i = 0; i < 21; i++) { - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } - - //Exits Buiilding - pbf_wait(context, 1000ms); - } - std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } - std::string AutoStory_Segment_01::start_text() const { return "Start: Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_Segment_01::start_text() const { return "Start: Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } void AutoStory_Segment_01::run_segment( @@ -3723,7 +33,7 @@ static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& std::string AutoStory_checkpoint_0101::name() const { return "01.01 - Maxed Starter from Home"; } - std::string AutoStory_checkpoint_0101::start_text() const { return "Recieved Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_checkpoint_0101::start_text() const { return "Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } std::string AutoStory_checkpoint_0101::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } // ======================== // Checkpoint 0101 Wrapper @@ -3757,9 +67,9 @@ static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& // Checkpoint 0102 Wrapper // ======================== - std::string AutoStory_checkpoint_0102::name() const { return "01.02 - Oreburgh Gate"; } + std::string AutoStory_checkpoint_0102::name() const { return "01.02 - Oreburgh Mine"; } std::string AutoStory_checkpoint_0102::start_text() const { return AutoStory_checkpoint_0101().end_text(); } - std::string AutoStory_checkpoint_0102::end_text() const { return "Standing At the first gym leader after defeating him."; } + std::string AutoStory_checkpoint_0102::end_text() const { return "Inside Oreburgh Gym, ready to navigate to Roark."; } void AutoStory_checkpoint_0102::run_checkpoint( SingleSwitchProgramEnvironment& env, @@ -3780,11 +90,11 @@ static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/) { //oreburgh_gate_to_mine(env, env.console, context); - //oreburgh_mine_nav(env.console, context); - oreburgh_mine_first_leg_test(env.console, context); - //oreburgh_mine_to_gym(env, env.console, context); - //#. Oreburgh Gym navigation and battle - + oreburgh_mine_nav(env.console, context); + // Standalone test entry points are not part of the checkpoint route. + // oreburgh_mine_first_leg_test(env.console, context); + // oreburgh_mine_route_and_escape_test(env.console, context); + oreburgh_mine_to_gym(env, env.console, context); }, true @@ -3797,7 +107,7 @@ static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& std::string AutoStory_checkpoint_0103::name() const { return "01.03 - Oreburgh Gym"; } std::string AutoStory_checkpoint_0103::start_text() const { return AutoStory_checkpoint_0102().end_text(); } - std::string AutoStory_checkpoint_0103::end_text() const { return "Standing At the first gym leader after defeating him."; } + std::string AutoStory_checkpoint_0103::end_text() const { return "Standing at Roark after defeating him and acquiring the first badge."; } void AutoStory_checkpoint_0103::run_checkpoint( SingleSwitchProgramEnvironment& env, @@ -3818,7 +128,7 @@ static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, [&](size_t /*attempt*/) { //#. Oreburgh Gym navigation and battle - //gym_Puzzle_01(env, env.console, context); + gym_Puzzle_01(env, env.console, context); }, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp similarity index 100% rename from SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp rename to SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h similarity index 96% rename from SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h rename to SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index 3bafa3d457..786553ba83 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -1,7 +1,7 @@ #pragma once #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" -#include "PokemonBDSP_AutoStory_Segment_00.h" +#include "../StorySegments/PokemonBDSP_AutoStory_Segment_00.h" #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp new file mode 100644 index 0000000000..35a144a84c --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -0,0 +1,2408 @@ +/* BDSP AutoStory - Segment 01 + * From after choosing the starter Pokemon until after acquiring the first badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTools/ImageDiff.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + // Prefer the source Packages resources when running a development build. + // Installed builds continue to use their bundled Resources directory. + static const std::string& mine_navigation_resource_path() { + static const std::string path = [] { + QDir directory(QCoreApplication::applicationDirPath()); + for (size_t level = 0; level < 5; level++) { + const QString candidate = directory.filePath( + "Packages/Resources/PokemonBDSP/Autostory/Oreburg_mine_nav/" + ); + if (QFileInfo(candidate).isDir()) { + return QDir::cleanPath(candidate).toStdString() + "/"; + } + if (!directory.cdUp()) { + break; + } + } + return RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; + }(); + return path; + } + + enum class MoveDirection { UP, DOWN, LEFT, RIGHT }; + + // Left joystick vector for a direction (matches the game's joystick convention used in this file). + static std::pair joystick_for(MoveDirection direction) { + switch (direction) { + case MoveDirection::UP: return { 0.0, 1.0 }; + case MoveDirection::DOWN: return { 0.0, -1.0 }; + case MoveDirection::LEFT: return { -1.0, 0.0 }; + case MoveDirection::RIGHT: return { 1.0, 0.0 }; + } + return { 0.0, 0.0 }; + } + + // Runs from a wild battle that is already in progress (menu visible or about to be). + // Throws OperationFailedException if it cannot escape within `timeout`. + // Previous escape completion check retained for comparison. + // static void escape_wild_battle( + // VideoStream& stream, + // ProControllerContext& context, + // BattleType battle_type = BattleType::STANDARD, + // std::chrono::seconds timeout = 30s + // ) { + // bool escaped = false; + // auto deadline = std::chrono::steady_clock::now() + timeout; + // while (std::chrono::steady_clock::now() < deadline) { + // pbf_press_button(context, BUTTON_B, 40ms, 200ms); + // context.wait_for_all_requests(); + // + // BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + // if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + // continue; + // } + // + // pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); + // pbf_press_button(context, BUTTON_A, 60ms, 0ms); + // context.wait_for_all_requests(); + // + // EndBattleWatcher battle_end; + // BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); + // if (wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}) == 0) { + // escaped = true; + // break; + // } + // } + // if (!escaped) { + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // "escape_wild_battle: Failed to run from battle.", + // stream + // ); + // } + // OverworldWatcher overworld; + // wait_until(stream, context, 5s, {{overworld}}); + // } + // + + // The main game menu is independent evidence that battle has ended. + static bool mine_confirm_escape_via_menu( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type + ) { + MenuWatcher game_menu; + OverworldWatcher overworld; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + for (size_t attempt = 1; attempt <= 3; attempt++) { + // Do not accept the absence-based overworld detector before menu proof. + // Previous order: {game_menu}, {overworld}, {battle_menu}. + const int state = wait_until(stream, context, 300ms, {{game_menu}, {battle_menu}}); + if (state == 1) { + stream.log("[Mine escape] Battle menu still present; retrying Run."); + return false; + } + int detected = state; + if (state != 0) { + stream.log("[Mine escape] Main menu fallback attempt " + std::to_string(attempt) + "/3."); + detected = run_until( + stream, context, + [](ProControllerContext& controller) { + pbf_press_button(controller, BUTTON_X, 160ms, 840ms); + pbf_wait(controller, 3s); + controller.wait_for_all_requests(); + }, + {game_menu, battle_menu} + ); + if (detected == 1) { + return false; + } + } + if (detected != 0) { + continue; + } + stream.log("[Mine escape] Main game menu confirmed; battle has ended.", COLOR_GREEN); + // Close the confirmed menu before returning control to navigation. + MenuDetector menu_visible; + for (size_t close = 0; close < 3; close++) { + pbf_press_button(context, BUTTON_B, 80ms, 500ms); + context.wait_for_all_requests(); + if (wait_until(stream, context, 1s, {{overworld}}) == 0) { + return true; + } + BlackScreenWatcher visible( + COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms + ); + if (wait_until(stream, context, 2s, {{visible}}) == 0) { + const VideoSnapshot frame = stream.video().snapshot(); + if (frame && !menu_visible.detect(frame)) { + stream.log("[Mine escape] Confirmed game menu closed; returning to navigation.", COLOR_GREEN); + return true; + } + } + } + stream.log("[Mine escape] Main menu detected but could not confirm it closed.", COLOR_ORANGE); + return false; + } + return false; + } + + // Previous early overworld completion check retained for comparison. + // static void escape_wild_battle( + // VideoStream& stream, + // ProControllerContext& context, + // BattleType battle_type = BattleType::STANDARD, + // std::chrono::seconds timeout = 30s + // ) { + // // Keep transition history across retries and observe the Run input. + // EndBattleWatcher battle_end; + // OverworldWatcher overworld; + // bool escaped = false; + // const auto deadline = std::chrono::steady_clock::now() + timeout; + // while (std::chrono::steady_clock::now() < deadline) { + // // Accept a completed escape even if its black transition was missed. + // if (wait_until(stream, context, 200ms, {{overworld}}) == 0) { + // escaped = true; + // break; + // } + // pbf_press_button(context, BUTTON_B, 40ms, 200ms); + // context.wait_for_all_requests(); + // BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + // const int ready = wait_until(stream, context, 3s, {{overworld}, {menu_ready}}); + // if (ready == 0) { + // escaped = true; + // break; + // } + // if (ready != 1) { + // // No battle menu and no observed exit: ask the main menu. + // if (mine_confirm_escape_via_menu(stream, context, battle_type)) { + // return; + // } + // continue; + // } + // pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); + // context.wait_for_all_requests(); + // stream.log("[Mine escape] Selecting Run; watching transition and overworld."); + // const int result = run_until( + // stream, context, + // [](ProControllerContext& controller) { + // pbf_press_button(controller, BUTTON_A, 60ms, 0ms); + // pbf_wait(controller, 4s); + // controller.wait_for_all_requests(); + // }, + // {battle_end, overworld} + // ); + // if (result >= 0) { + // escaped = true; + // break; + // } + // } + // // An exit animation can finish just after the retry deadline. + // // Confirm the overworld before reporting failure or resuming movement. + // if (wait_until(stream, context, 10s, {{overworld}}) == 0) { + // stream.log("[Mine escape] Overworld confirmed. Escape succeeded.", COLOR_GREEN); + // return; + // } + // if (mine_confirm_escape_via_menu(stream, context, battle_type)) { + // return; + // } + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // escaped + // ? "escape_wild_battle: Exit detected, but overworld did not return." + // : "escape_wild_battle: Exit, overworld, and main-menu fallback could not confirm escape.", + // stream + // ); + // } + // + + static bool mine_select_run( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type + ) { + BattleMenuDetector cursor(battle_type, COLOR_YELLOW); + size_t matching_frames = 0; + for (size_t check = 0; check < 16; check++) { + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + return false; + } + const BattleMenuOption selected = cursor.selected_option(frame); + stream.log("[Mine escape] Battle cursor row=" + + std::to_string(static_cast(selected) + 1) + "; target Run=4."); + if (selected == BattleMenuOption::RUN) { + if (++matching_frames >= 3) { + stream.log("[Mine escape] Run cursor confirmed.", COLOR_GREEN); + return true; + } + pbf_wait(context, 100ms); + } + else { + matching_frames = 0; + if (selected == BattleMenuOption::UNKNOWN) { + // Do not send a direction or A without a known cursor. + pbf_wait(context, 150ms); + } + else { + // Previous navigation always moved Down. + // Battle wraps upward directly to Run. + const DpadPosition direction = selected == BattleMenuOption::BATTLE + ? DPAD_UP : DPAD_DOWN; + pbf_press_dpad(context, direction, 80ms, 250ms); + } + } + } + stream.log("[Mine escape] Could not confirm Run selection; A was not pressed.", COLOR_ORANGE); + return false; + } + + static void escape_wild_battle( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD, + std::chrono::seconds timeout = 30s + ) { + EndBattleWatcher battle_end; + bool exit_transition = false; + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + const int ready = wait_until(stream, context, 3s, {{battle_end}, {menu_ready}}); + if (ready == 0) { + exit_transition = true; + break; + } + if (ready != 1) { + continue; + } + // Previous blind selection: + // pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); + // context.wait_for_all_requests(); + if (!mine_select_run(stream, context, battle_type)) { + continue; + } + stream.log("[Mine escape] Run input sent; watching for black-screen exit."); + const int result = run_until( + stream, context, + [](ProControllerContext& controller) { + pbf_press_button(controller, BUTTON_A, 60ms, 0ms); + pbf_wait(controller, 4s); + controller.wait_for_all_requests(); + }, + {battle_end} + ); + if (result == 0) { + exit_transition = true; + break; + } + } + if (!exit_transition && wait_until(stream, context, 10s, {{battle_end}}) == 0) { + exit_transition = true; + } + if (exit_transition) { + // EndBattleWatcher requires black followed by a visible screen. + stream.log("[Mine escape] Black-screen exit transition confirmed.", COLOR_GREEN); + pbf_wait(context, 800ms); + context.wait_for_all_requests(); + return; + } + // Only probe X after normal escape attempts and the final transition wait. + // Absence of battle UI by itself is not evidence of an overworld return. + stream.log("[Mine escape] Exit transition missed; trying late main-menu fallback."); + if (mine_confirm_escape_via_menu(stream, context, battle_type)) { + return; + } + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", + stream + ); + } + + // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. + // Returns false (without throwing) only if every attempt times out, so callers can skip + // an optional interaction. Multiple attempts guard against a single missed detection. + static bool try_detect_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label, + size_t attempts = 3, + std::chrono::milliseconds window = 5000ms + ) { + for (size_t attempt = 0; attempt < attempts; attempt++) { + ShortDialogWatcher watcher_cyan(COLOR_CYAN); + ShortDialogWatcher watcher_red(COLOR_RED); + if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0) { + stream.log(label + " dialogue detected."); + return true; + } + stream.log( + label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + std::to_string(attempts) + ").", + COLOR_ORANGE + ); + } + return false; + } + + struct DoorwayWalkOptions { + BattleType battle_type = BattleType::STANDARD; + // Active movement per step. Time spent in battles or waiting is never counted. + std::chrono::milliseconds step = 200ms; + // Extra walking allowed beyond `travel_time` before giving up. + std::chrono::milliseconds grace = 1600ms; + // How long to watch for a black screen after each step. + std::chrono::milliseconds black_screen_window = 1500ms; + // After a black screen, how long to wait for a battle menu before deciding it was a doorway. + std::chrono::milliseconds battle_menu_window = 8000ms; + size_t max_battles = 10; + // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). + bool detect_white_screen = false; + }; + + // Reusable: walk in `direction` for a cumulative `travel_time` of active movement until + // a black screen NOT followed by a battle menu (a doorway/area transition) is seen. + // Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are + // escaped and do not consume the time budget. + // Throws OperationFailedException if the doorway is not found within + // travel_time + options.grace, if more than options.max_battles occur, or if a battle + // cannot be escaped. + // Returns the total active walking time used. + static std::chrono::milliseconds walk_until_doorway( + VideoStream& stream, + ProControllerContext& context, + MoveDirection direction, + std::chrono::milliseconds travel_time, + const DoorwayWalkOptions& options = DoorwayWalkOptions() + ) { + enum class NavState { WALKING, BLACK_SCREEN, IN_BATTLE, DONE }; + + const auto [x, y] = joystick_for(direction); + const std::chrono::milliseconds max_travel = travel_time + options.grace; + + std::chrono::milliseconds walked = 0ms; + size_t battles = 0; + NavState state = NavState::WALKING; + + while (state != NavState::DONE) { + switch (state) { + case NavState::WALKING: { + if (walked >= max_travel) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "walk_until_doorway: Doorway not found within time budget.", + stream + ); + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {x, y}, options.step, 0ms); + context.wait_for_all_requests(); + walked += options.step; + + BlackScreenWatcher black_screen; + WhiteScreenWatcher white_screen; + if (options.detect_white_screen) { + int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); + if (ret == 0) { + state = NavState::BLACK_SCREEN; + } else if (ret == 1) { + stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); + state = NavState::DONE; + } + } else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0) { + state = NavState::BLACK_SCREEN; + } + break; + } + case NavState::BLACK_SCREEN: { + stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); + BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); + if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0) { + state = NavState::IN_BATTLE; + } else { + state = NavState::DONE; + } + break; + } + case NavState::IN_BATTLE: { + if (++battles > options.max_battles) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "walk_until_doorway: Too many battles while walking.", + stream + ); + } + stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + std::to_string(travel_time.count()) + "ms.", COLOR_ORANGE); + escape_wild_battle(stream, context, options.battle_type); + state = NavState::WALKING; + break; + } + case NavState::DONE: + break; + } + } + stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", COLOR_GREEN); + return walked; + } + + static void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + stream.log("Starting Barry 1 battle..."); + + // State machine: loop until EndBattleWatcher fires + for (size_t c = 0; c < 20; c++) { + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(BattleType::STANDARD); + EndBattleWatcher end_battle; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_ZL, 120000ms); + }, + { + battle_menu, + end_battle, + } + ); + switch (ret) { + case 0: + stream.log("Battle menu detected - using first move."); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + break; + case 1: + stream.log("Barry 1 battle complete."); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + return; + default: + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Barry_1_fight: Timed out waiting for battle menu or end.", + stream + ); + } + } + + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Barry_1_fight: Battle did not end after 20 turns.", + stream + ); + } + + + + + + // ---------------------------------------------------------------- + // Segment 1 Part 1: Jubilife City to Oreburgh Gate + // Navigates the player from Jubilife City to the Oreburgh Gate, including the + // first Barry fight. + // --------------------------------------------------------------------------- + void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + // Wait for game to fully accept input after save/overworld return + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1465ms, 0ms); + pbf_wait(context, 127ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); + pbf_wait(context, 698ms); + leave_pokemon_center_before_barry_1(stream, context); + pbf_wait(context, 2340ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); + pbf_wait(context, 348ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2694ms, 0ms); + pbf_wait(context, 482ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3810ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); + pbf_wait(context, 1647ms); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + wait_for_dialogue(stream, context, "Dallas fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Dallas + context.wait_for_all_requests(); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); + walk_through_grass(stream, context, 1, 0, 10, "right through grass"); + //there is a small chance that the player dodges sSebastian, so this + //function addresses that edge case + if (try_detect_dialogue(stream, context, "Sebastian fight")) { + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Sebastian + context.wait_for_all_requests(); + } else { + stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); + } + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2000ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { + BlackScreenWatcher black_screen; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + for (size_t i = 0; i < 10; i++) { + pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + } + pbf_move_left_joystick(context, { 0, 1 }, 10000ms, 0ms); + }, + { black_screen } + ); + if (ret == 0) { + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + + if (!black_screen_found) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", + stream + ); + } + } + void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {1.000000, -0.000000 }, 879ms, 0ms); + wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); + pbf_mash_button(context, BUTTON_B, 15000ms); + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + //Similar to sebastian, an edge case exists where we run into too many + //wild battles and don't reach curtis or diana. This detect dialog function solves that. + while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)) { + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Curtis + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)) { + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Diana + // Walk right until the gate exit transition (white screen, or black screen without a battle menu). + // Wild battles are escaped without using up the budget. Throws on failure so the + // checkpoint is retried. + DoorwayWalkOptions gate_exit_options; + gate_exit_options.detect_white_screen = true; + walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); + context.wait_for_all_requests(); + } + // --------------------------------------------------------------------------- + // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine + // Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. + // Navigates to the mine. + // ---------------------------------------------------------------------------- + void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); + while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)) { + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); + } + pbf_mash_button(context, BUTTON_B, 12000ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); + pbf_wait(context, 23ms); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { + BlackScreenWatcher black_screen; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); + }, + { black_screen } + ); + if (ret == 0) { + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + + if (!black_screen_found) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "oreburgh_gate_to_mine: No black screen after 3 attempts.", + stream + ); + } + + // Let the mine entrance transition fully finish so the next step's black screen + // watcher can't pick up the tail of this one. + BlackScreenOverWatcher black_over; + wait_until(stream, context, 6000ms, {{black_over}}); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + } + + // Diagnostic only: identifies the entrance start without moving the player. + + + + struct MineCenterOffset { + bool valid; + double offset; + double score; + }; + + // Finds the sideways shift of stationary scenery relative to the center picture. + // Positive offset: scenery shifted right, so the player is left of center. + static MineCenterOffset measure_mine_center_offset( + const ImageViewRGB32& reference, + const ImageViewRGB32& live + ) { + if (!reference || !live) { + return { false, 0.0, 1000.0 }; + } + + // Use a fixed working size so the search works at different capture resolutions. + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const ImageRGB32 live_scaled = live.scale_to(960, 540); + + // Entrance beam and both posts. Avoid the player and shadow. + const std::array boxes = { + ImageFloatBox(0.35, 0.285, 0.30, 0.07), + ImageFloatBox(0.355, 0.37, 0.035, 0.23), + ImageFloatBox(0.615, 0.37, 0.035, 0.23) + }; + + std::array scores = {}; + std::array worst_regions = {}; + + int best_shift = 0; + double best_score = 1000.0; + + // Search up to 10% of the screen width in either direction. + for (int shift = -96; shift <= 96; shift++) { + double total = 0.0; + double worst = 0.0; + + for (const ImageFloatBox& box : boxes) { + const ImageViewRGB32 reference_crop = + extract_box_reference(reference_scaled, box); + + // Translate the same crop horizontally in the live image. + const ImageViewRGB32 live_crop = + extract_box_reference(live_scaled, box, shift, 0); + + const double score = + ImageMatch::pixel_RMSD(reference_crop, live_crop); + + total += score; + if (score > worst) { + worst = score; + } + } + + const double average = total / boxes.size(); + const size_t index = static_cast(shift + 96); + + scores[index] = average; + worst_regions[index] = worst; + + if (average < best_score) { + best_score = average; + best_shift = shift; + } + } + + // Reject another similarly good match well away from the chosen position. + double alternative_score = 1000.0; + + for (int shift = -96; shift <= 96; shift++) { + if (std::abs(shift - best_shift) <= 12) { + continue; + } + + const double score = scores[static_cast(shift + 96)]; + if (score < alternative_score) { + alternative_score = score; + } + } + + const size_t best_index = static_cast(best_shift + 96); + + const bool valid = + best_score <= 20.0 && + worst_regions[best_index] <= 30.0 && + alternative_score - best_score >= 3.0 && + std::abs(best_shift) < 96; + + return { + valid, + static_cast(best_shift) / 960.0, + best_score + }; + } + // Test only: align with the entrance center, then stop. + static bool oreburgh_mine_alignment_test( + VideoStream& stream, + ProControllerContext& context + ) { + const ImageRGB32 reference( + // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_start_center.png" + mine_navigation_resource_path() + "mine_start_center.png" + ); + + constexpr size_t MAX_MOVEMENTS = 12; + constexpr double CENTER_TOLERANCE = 0.008; + + size_t movements = 0; + size_t center_streak = 0; + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + while (true) { + const VideoSnapshot frame = stream.video().snapshot(); + + const MineCenterOffset measurement = + measure_mine_center_offset(reference, frame); + + stream.log( + "[Mine align] Offset=" + + std::to_string(measurement.offset) + + ", score=" + std::to_string(measurement.score) + + (measurement.valid ? ", trusted" : ", unsure") + ); + + if (!measurement.valid) { + stream.log( + "[Mine align] Entrance position unsure. Stopping.", + COLOR_ORANGE + ); + return false; + } + + if (std::abs(measurement.offset) <= CENTER_TOLERANCE) { + center_streak++; + + if (center_streak >= 3) { + stream.log( + "[Mine align] Center reached. Test complete.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; + } + + center_streak = 0; + + // Check the latest position before enforcing the movement limit. + if (movements >= MAX_MOVEMENTS) { + stream.log( + "[Mine align] Movement limit reached. Stopping.", + COLOR_ORANGE + ); + return false; + } + + const double x = measurement.offset > 0.0 ? 1.0 : -1.0; + + // Smaller movements when close to the center. + const auto duration = + std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; + + stream.log( + x > 0.0 + ? "[Mine align] Moving a little right." + : "[Mine align] Moving a little left." + ); + + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + + const int result = run_until( + stream, + context, + [x, duration](ProControllerContext& ctx) { + pbf_move_left_joystick( + ctx, { x, 0.0 }, duration, 0ms + ); + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + }, + { black, menu, dialog } + ); + + if (result >= 0) { + stream.log( + "[Mine align] Battle or transition detected. " + "Stopping the alignment test.", + COLOR_ORANGE + ); + return false; + } + + movements++; + } + } + // Confirm a battle before using the existing escape helper. + static bool mine_escape_and_settle( + VideoStream& stream, + ProControllerContext& context + ) { + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + + if (wait_until(stream, context, 12s, { {menu} }) != 0) { + stream.log( + "[Mine encounter] Battle menu not confirmed. Stopping.", + COLOR_ORANGE + ); + return false; + } + + stream.log("[Mine encounter] Battle confirmed. Escaping."); + escape_wild_battle(stream, context); + context.wait_for_all_requests(); + + // Check the return waits instead of ignoring their results. + BlackScreenWatcher visible( + COLOR_YELLOW, + ImageFloatBox(0.1, 0.1, 0.8, 0.8), + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms + ); + + if (wait_until(stream, context, 8s, { {visible} }) != 0) { + stream.log( + "[Mine encounter] Screen did not return. Stopping.", + COLOR_ORANGE + ); + return false; + } + + // Previous duplicate overworld-only check. Escape helper now + // confirms either the overworld or opening and closing the main menu. + // OverworldWatcher overworld; + // + // if (wait_until(stream, context, 5s, { {overworld} }) != 0) { + // stream.log( + // "[Mine encounter] Overworld not detected. Stopping.", + // COLOR_ORANGE + // ); + // return false; + // } + // + + pbf_wait(context, 800ms); + context.wait_for_all_requests(); + + stream.log( + "[Mine encounter] Escaped. Returning to the rock-walking loop.", + COLOR_GREEN + ); + return true; + } + + // Temporary test: wiggle along the corridor until a battle is detected. + static bool mine_force_encounter_test( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine encounter] Starting forced encounter wiggle."); + + const auto deadline = + std::chrono::steady_clock::now() + 60s; + + size_t pulses = 0; + + while (std::chrono::steady_clock::now() < deadline) { + // Alternate down and up. Do not move sideways. + const double y = pulses % 2 == 0 ? -1.0 : 1.0; + + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + + const int result = run_until( + stream, + context, + [y](ProControllerContext& ctx) { + pbf_move_left_joystick( + ctx, { 0.0, y }, 120ms, 0ms + ); + pbf_wait(ctx, 400ms); + ctx.wait_for_all_requests(); + }, + { black, menu, dialog } + ); + + if (result >= 0) { + stream.log( + "[Mine encounter] Wiggle interrupted. Checking for battle." + ); + return mine_escape_and_settle(stream, context); + } + + pulses++; + } + + stream.log( + "[Mine encounter] No encounter within 60 seconds. " + "Test incomplete; stopping.", + COLOR_ORANGE + ); + return false; + } + + // Watch throughout an action, including its movement and picture checks. + // After an encounter, restart the action using fresh watcher state. + template + static bool mine_run_with_encounters( + VideoStream& stream, + ProControllerContext& context, + Action action + ) { + for (size_t encounters = 0;;) { + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + bool completed = false; + + const int result = run_until( + stream, context, + [&](ProControllerContext& ctx) { + completed = action(ctx); + }, + { black, menu, dialog } + ); + + if (result < 0) { + return completed; + } + + if (++encounters > 10) { + stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); + return false; + } + + if (!mine_escape_and_settle(stream, context)) { + return false; + } + + stream.log("[Mine encounter] Escaped; resuming interrupted action."); + } + } + + static bool mine_timed_walk( + VideoStream& stream, + ProControllerContext& context, + double x, + double y, + std::chrono::milliseconds duration + ) { + auto remaining = duration; + + return mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + while (remaining > 0ms) { + const auto pulse = remaining < 100ms ? remaining : 100ms; + pbf_move_left_joystick(ctx, { x, y }, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + + // An interrupted pulse is retried after escaping. + // This is a timing budget, not exact distance tracking. + remaining -= pulse; + } + return true; + } + ); + } + + + + struct MinePictureScore { + double average = 1000.0; + double worst = 1000.0; + int horizontal = 0; + int vertical = 0; + }; + + // Supports four, five, or six regions. Existing four-region callers keep + // their original search ranges through the default arguments. + template + static MinePictureScore mine_score_picture( + const ImageRGB32& reference, + const ImageRGB32& live, + const std::array& boxes, + int horizontal_range = 16, + int vertical_range = 8 + ) { + MinePictureScore best; + for (int y = -vertical_range; y <= vertical_range; y++) { + for (int x = -horizontal_range; x <= horizontal_range; x++) { + double total = 0.0; + double worst = 0.0; + for (const ImageFloatBox& box : boxes) { + const ImageViewRGB32 ref = extract_box_reference(reference, box); + const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); + const double score = ImageMatch::pixel_RMSD(ref, patch); + total += score; + if (score > worst) worst = score; + } + const double average = total / boxes.size(); + if (average < best.average) { + best.average = average; + best.worst = worst; + best.horizontal = x; + best.vertical = y; + } + } + } + return best; + } + + + static bool mine_check_picture( + VideoStream& stream, + ProControllerContext& context, + const char* filename, + const std::array& boxes, + const std::string& label, + size_t max_attempts = 8, + double max_average = 20.0, + double max_patch = 30.0 + ) { + const ImageRGB32 reference( + // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + mine_navigation_resource_path() + filename + ); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + + const bool confirmed = mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + // The watcher stays active throughout settling and sampling. + // There is no separate 500ms watcher wait before each sample. + ctx.wait_for_all_requests(); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + + // Restart confirmation after a battle; do not reuse old matches. + size_t matching_frames = 0; + + for (size_t attempt = 0; attempt < max_attempts; attempt++) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + const MinePictureScore score = + mine_score_picture(reference_scaled, live, boxes); + + stream.log( + label + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + ); + + if (score.average <= max_average && score.worst <= max_patch) { + if (++matching_frames >= 3) { + return true; + } + } + else { + matching_frames = 0; + } + + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + } + + return false; + } + ); + + stream.log( + label + (confirmed ? ": confirmed." : ": not confirmed."), + confirmed ? COLOR_GREEN : COLOR_ORANGE + ); + return confirmed; + } + + struct MineWalkOptions { + double x = 0.0; + double y = 0.0; + std::chrono::milliseconds pulse = 40ms; + std::chrono::milliseconds settle = 500ms; + std::chrono::milliseconds initial_settle = 0ms; + size_t max_movements = 30; + int horizontal_range = 12; + int vertical_range = 6; + double max_average = 20.0; + double max_patch = 30.0; + }; + + template + static bool mine_walk_to_endpoints( + VideoStream& stream, + ProControllerContext& context, + const std::array& files, + const std::array, Endpoints>& boxes, + const MineWalkOptions& options, + const std::string& label, + bool force_encounter = false + ) { + std::array, Endpoints> references; + for (size_t index = 0; index < Endpoints; index++) { + const ImageRGB32 image( + // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" + mine_navigation_resource_path() + files[index] + ); + references[index] = std::make_unique(image.scale_to(960, 540)); + } + + size_t movements = 0; + bool forced_completed = false; + + while (true) { + bool request_forced_test = false; + const bool reached = mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + if (options.initial_settle > 0ms) { + pbf_wait(ctx, options.initial_settle); + ctx.wait_for_all_requests(); + } + // Reset the match history whenever an encounter interrupts us. + size_t matching_frames = 0; + int previous_endpoint = -1; + + while (true) { + ctx.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + int accepted = -1; + double accepted_average = 1000.0; + + for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { + const MinePictureScore score = mine_score_picture( + *references[endpoint], live, boxes[endpoint], + options.horizontal_range, options.vertical_range + ); + stream.log( + label + " Endpoint " + std::to_string(endpoint + 1) + + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + + ", offset=(" + std::to_string(score.horizontal) + + "," + std::to_string(score.vertical) + ")" + ); + + if (score.average <= options.max_average && + score.worst <= options.max_patch && + score.average < accepted_average) { + accepted = static_cast(endpoint); + accepted_average = score.average; + } + } + + if (accepted >= 0) { + matching_frames = accepted == previous_endpoint + ? matching_frames + 1 : 1; + previous_endpoint = accepted; + if (matching_frames >= 3) { + stream.log( + label + " Endpoint " + std::to_string(accepted + 1) + + " confirmed.", COLOR_GREEN + ); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + previous_endpoint = -1; + + // Exit the guard before invoking the diagnostic wiggle: + // it has its own encounter watcher and must not be nested. + if (force_encounter && !forced_completed && movements >= 4) { + request_forced_test = true; + return false; + } + + if (movements >= options.max_movements) { + stream.log(label + ": movement limit reached.", COLOR_ORANGE); + return false; + } + + // Count issued commands, including interrupted commands. + movements++; + pbf_move_left_joystick( + ctx, { options.x, options.y }, options.pulse, 0ms + ); + pbf_wait(ctx, options.settle); + ctx.wait_for_all_requests(); + } + } + ); + + if (reached) return true; + if (!request_forced_test) return false; + if (!mine_force_encounter_test(stream, context)) return false; + forced_completed = true; + stream.log(label + ": forced encounter completed; resuming."); + } + } + + static bool oreburgh_mine_walk_to_first_rock( + VideoStream& stream, + ProControllerContext& context, + bool force_encounter = false + ) { + const std::array files = { "mine_path_01.png" }; + const std::array, 1> boxes = {{ + {{ + ImageFloatBox(0.482, 0.585, 0.055, 0.055), + ImageFloatBox(0.335, 0.670, 0.050, 0.055), + ImageFloatBox(0.412, 0.670, 0.050, 0.055), + ImageFloatBox(0.570, 0.752, 0.045, 0.055), + ImageFloatBox(0.645, 0.752, 0.045, 0.055), + ImageFloatBox(0.570, 0.290, 0.080, 0.120) + }} + }}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 80ms; + options.max_movements = 40; + options.horizontal_range = 24; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine path 01]", force_encounter + ); + } + + static bool oreburgh_mine_move_right( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; + const std::array, 2> boxes = {{ + {{ + ImageFloatBox(0.400, 0.585, 0.055, 0.055), + ImageFloatBox(0.245, 0.670, 0.050, 0.055), + ImageFloatBox(0.322, 0.670, 0.050, 0.055), + ImageFloatBox(0.480, 0.752, 0.045, 0.055), + ImageFloatBox(0.555, 0.752, 0.045, 0.055), + ImageFloatBox(0.485, 0.290, 0.080, 0.090) + }}, + {{ + ImageFloatBox(0.325, 0.585, 0.055, 0.055), + ImageFloatBox(0.170, 0.670, 0.050, 0.055), + ImageFloatBox(0.244, 0.670, 0.050, 0.055), + ImageFloatBox(0.395, 0.752, 0.045, 0.055), + ImageFloatBox(0.470, 0.752, 0.045, 0.055), + ImageFloatBox(0.415, 0.290, 0.080, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = 1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 20; + options.horizontal_range = 12; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine right]" + ); + } + + static bool oreburgh_mine_down_past_central_rock( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; + const std::array, 2> boxes = {{ + {{ + ImageFloatBox(0.345, 0.420, 0.040, 0.045), + ImageFloatBox(0.195, 0.500, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.410, 0.590, 0.040, 0.045), + ImageFloatBox(0.480, 0.590, 0.040, 0.045), + ImageFloatBox(0.445, 0.180, 0.060, 0.090) + }}, + {{ + ImageFloatBox(0.420, 0.420, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.345, 0.500, 0.040, 0.045), + ImageFloatBox(0.485, 0.590, 0.040, 0.045), + ImageFloatBox(0.555, 0.590, 0.040, 0.045), + ImageFloatBox(0.520, 0.180, 0.060, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 40ms; + options.max_movements = 30; + // options.horizontal_range = 12; + // Path 02 can be shifted horizontally after the first right endpoint. + // The screenshot regression matches at -20 pixels on the 960px image. + options.horizontal_range = 24; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine down 02]" + ); + } + + static bool oreburgh_mine_move_left_to_gap( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array files = { "mine_path_03.png" }; + const std::array, 1> boxes = {{ + {{ + ImageFloatBox(0.340, 0.495, 0.040, 0.045), + ImageFloatBox(0.410, 0.495, 0.040, 0.045), + ImageFloatBox(0.555, 0.580, 0.040, 0.045), + ImageFloatBox(0.625, 0.580, 0.040, 0.045), + ImageFloatBox(0.580, 0.180, 0.060, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = -1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 30; + options.horizontal_range = 12; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine left 03]" + ); + } + + + + // Confirm we are far enough down before beginning the leftward section. + + + // Each picture stage owns its movement and matching settings. + struct MinePictureStep { + const char* file; + const char* label; + std::array boxes; + double x; + double y; + std::chrono::milliseconds pulse = 80ms; + size_t max_movements = 30; + double max_average = 20.0; + double max_patch = 30.0; + }; + + static bool mine_run_picture_step( + VideoStream& stream, ProControllerContext& context, + const MinePictureStep& step + ) { + MineWalkOptions options; + options.x = step.x; + options.y = step.y; + options.pulse = step.pulse; + options.max_movements = step.max_movements; + options.max_average = step.max_average; + options.max_patch = step.max_patch; + options.horizontal_range = 16; + options.vertical_range = 8; + options.initial_settle = 700ms; + options.settle = 700ms; + const std::array files = {step.file}; + const std::array, 1> boxes = {{step.boxes}}; + return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); + } + + static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_05.png", "[Mine endpoint 05]", + { + ImageFloatBox(0.240, 0.240, 0.055, 0.080), + ImageFloatBox(0.390, 0.220, 0.050, 0.090), + ImageFloatBox(0.360, 0.090, 0.030, 0.030), + ImageFloatBox(0.550, 0.140, 0.040, 0.040) + }, + 0.0, -1.0, 60ms, 10, 20.0, 30.0 + }; + return mine_run_picture_step(stream, context, step); + } + + // Timed movement through paths 6 and 7, with picture checks. + static bool oreburgh_mine_timed_to_07( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine 04-07] Walking down for 1500ms."); + + if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)) { + return false; + } + + if (!oreburgh_mine_confirm_05(stream, context)) { + return false; + } + + stream.log("[Mine 04-07] Walking left for 3000ms."); + + if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)) { + return false; + } + + // Path 6: crate, nearby small rock, large rock, upper-right small rock. + const std::array path6_boxes = { + ImageFloatBox(0.350, 0.250, 0.085, 0.075), + ImageFloatBox(0.405, 0.510, 0.035, 0.040), + ImageFloatBox(0.520, 0.300, 0.050, 0.080), + ImageFloatBox(0.620, 0.350, 0.030, 0.040) + }; + + if (!mine_check_picture( + stream, context, + "mine_path_06.png", path6_boxes, + "[Mine midpoint 06]" + )) { + return false; + } + + stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); + + if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)) { + return false; + } + + // Path 7: crate and three visible rock surfaces. + const std::array path7_boxes = { + ImageFloatBox(0.365, 0.025, 0.070, 0.055), + ImageFloatBox(0.420, 0.270, 0.035, 0.040), + ImageFloatBox(0.560, 0.415, 0.025, 0.040), + ImageFloatBox(0.530, 0.650, 0.060, 0.090) + }; + + return mine_check_picture( + stream, context, + "mine_path_07.png", path7_boxes, + "[Mine endpoint 07]" + ); + } + + // Move left from path 7 to the position shown in path 8. + static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_08.png", "[Mine endpoint 08]", + { + ImageFloatBox(0.495, 0.035, 0.070, 0.055), + ImageFloatBox(0.550, 0.270, 0.035, 0.040), + ImageFloatBox(0.695, 0.420, 0.030, 0.040), + ImageFloatBox(0.555, 0.585, 0.035, 0.040) + }, + -1.0, 0.0, 40ms, 20, 20.0, 30.0 + }; + return mine_run_picture_step(stream, context, step); + } + + // Walk down along the left wall to path 9. + + static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_09.png", "[Mine endpoint 09]", + { + ImageFloatBox(0.550, 0.115, 0.030, 0.040), + ImageFloatBox(0.550, 0.390, 0.035, 0.040), + ImageFloatBox(0.620, 0.390, 0.025, 0.035), + ImageFloatBox(0.500, 0.720, 0.085, 0.080) + }, + 0.0, -1.0, 40ms, 25, 25.0, 35.0 + }; + return mine_run_picture_step(stream, context, step); + } + + + + // Require dialogue to appear first. + // Then finish only after it has been absent for one continuous second. + static bool mine_finish_item_dialogue( + VideoStream& stream, + ProControllerContext& context + ) { + ShortDialogDetector dialogue(COLOR_YELLOW); + + bool saw_dialogue = false; + auto absent_since = std::chrono::steady_clock::now(); + + const auto start = std::chrono::steady_clock::now(); + const auto deadline = start + 30s; + + stream.log("[Mine item] Starting interaction."); + + while (std::chrono::steady_clock::now() < deadline) { + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + + if (!frame) { + stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); + return false; + } + + const auto now = std::chrono::steady_clock::now(); + const bool visible = dialogue.detect(frame); + + if (visible) { + if (!saw_dialogue) { + stream.log("[Mine item] Dialogue detected."); + } + + saw_dialogue = true; + absent_since = now; + + // Short mash bursts allow frequent checks. + pbf_mash_button(context, BUTTON_A, 100ms); + } + else if (!saw_dialogue) { + if (now - start >= 10s) { + stream.log( + "[Mine item] No dialogue appeared. Stopping.", + COLOR_ORANGE + ); + return false; + } + + // Try to start the interaction. + pbf_press_button(context, BUTTON_A, 60ms, 100ms); + } + else if (now - absent_since >= 1s) { + stream.log( + "[Mine item] Dialogue absent for one second. Finished.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + + stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); + return false; + } + + + static bool oreburgh_mine_09_to_14( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array path10_boxes = { + ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate + ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock + ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock + ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock + }; + + const MinePictureStep path10_boxes_step = { + "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path10_boxes_step)) { + return false; + } + + const std::array path11_boxes = { + ImageFloatBox(0.405, 0.560, 0.070, 0.080), + ImageFloatBox(0.475, 0.270, 0.030, 0.040), + ImageFloatBox(0.575, 0.290, 0.045, 0.080), + ImageFloatBox(0.550, 0.575, 0.030, 0.040) + }; + + const MinePictureStep path11_boxes_step = { + "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path11_boxes_step)) { + return false; + } + + const std::array path12_boxes = { + ImageFloatBox(0.260, 0.560, 0.080, 0.080), + ImageFloatBox(0.355, 0.270, 0.025, 0.035), + ImageFloatBox(0.435, 0.290, 0.040, 0.070), + ImageFloatBox(0.415, 0.585, 0.025, 0.035) + }; + + const MinePictureStep path12_boxes_step = { + "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path12_boxes_step)) { + return false; + } + + const std::array path13_boxes = { + ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate + ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate + ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock + ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock + }; + + const MinePictureStep path13_boxes_step = { + "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path13_boxes_step)) { + return false; + } + + // Turn left, then verify position before starting the interaction. + pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + if (!mine_check_picture( + stream, context, + "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 + )) { + return false; + } + stream.log("[Mine item] Position confirmed. Pressing A."); + + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + context.wait_for_all_requests(); + + return mine_finish_item_dialogue(stream, context); + } + + // Compare a reference patch with a shifted live patch using the same + // brightness adjustment as the native ImageMatchDetector. + static double mine_offset_patch_score( + const ImageRGB32& reference, + const ImageRGB32& live, + const ImageFloatBox& box, + int horizontal, + int vertical + ) { + const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); + const ImageViewRGB32 live_crop = + extract_box_reference(live, box, horizontal, vertical); + ImageRGB32 adjusted = live_crop.scale_to( + reference_crop.width(), reference_crop.height() + ); + + const auto reference_average = + ImageMatch::pixel_average(reference_crop, reference_crop); + const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); + auto scale = reference_average / live_average; + if (std::isnan(scale.r)) scale.r = 1.0; + if (std::isnan(scale.g)) scale.g = 1.0; + if (std::isnan(scale.b)) scale.b = 1.0; + scale.bound(0.8, 1.2); + ImageMatch::scale_brightness(adjusted, scale); + + return ImageMatch::pixel_RMSD(reference_crop, adjusted); + } + + struct MineRoarkOffsetScore { + double left = 1000.0; + double right = 1000.0; + double helmet = 1000.0; + int horizontal = 0; + int vertical = 0; + int helmet_horizontal = 0; + int helmet_vertical = 0; + }; + + static MineRoarkOffsetScore mine_score_roark_offsets( + const ImageRGB32& reference, + const ImageRGB32& live + ) { + const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); + const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); + const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); + + MineRoarkOffsetScore best; + double best_worst_rock = 1000.0; + double best_total = 2000.0; + + // Both rocks must use the SAME offset. They cannot match independently. + // Offsets are pixels on the 960 x 540 comparison image. + for (int y = -8; y <= 8; y++) { + for (int x = -16; x <= 16; x++) { + const double left = mine_offset_patch_score(reference, live, left_box, x, y); + const double right = mine_offset_patch_score(reference, live, right_box, x, y); + const double worst = left > right ? left : right; + const double total = left + right; + + if (worst < best_worst_rock || + (worst == best_worst_rock && total < best_total)) { + best_worst_rock = worst; + best_total = total; + best.left = left; + best.right = right; + best.horizontal = x; + best.vertical = y; + } + } + } + + // Roark's helmet may move slightly relative to the stationary rocks. + // Keep this extra search small so another red object cannot match far away. + for (int extra_y = -6; extra_y <= 6; extra_y++) { + for (int extra_x = -6; extra_x <= 6; extra_x++) { + const double score = mine_offset_patch_score( + reference, live, helmet_box, + best.horizontal + extra_x, + best.vertical + extra_y + ); + if (score < best.helmet) { + best.helmet = score; + best.helmet_horizontal = extra_x; + best.helmet_vertical = extra_y; + } + } + } + + return best; + } + + static bool oreburgh_mine_align_to_18( + VideoStream& stream, + ProControllerContext& context + ) { + const ImageRGB32 reference( + // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/Mine_path_18.png" + mine_navigation_resource_path() + "Mine_path_18.png" + ); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const double max_rmsd = 65.0; + size_t movements = 0; + + return mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + // Fresh confirmation after any encounter. + size_t matching_frames = 0; + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + + while (true) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + const MineRoarkOffsetScore score = + mine_score_roark_offsets(reference_scaled, live); + + stream.log( + "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + + ", Right rock=" + std::to_string(score.right) + + ", Helmet=" + std::to_string(score.helmet) + + ", shared offset=(" + std::to_string(score.horizontal) + + "," + std::to_string(score.vertical) + + "), helmet extra=(" + std::to_string(score.helmet_horizontal) + + "," + std::to_string(score.helmet_vertical) + ")" + ); + + if (score.left <= max_rmsd && score.right <= max_rmsd && + score.helmet <= max_rmsd) { + if (++matching_frames >= 3) { + stream.log( + "[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN + ); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + if (movements >= 80) { + stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); + return false; + } + + const bool approaching = score.left <= 120.0 && + score.right <= 120.0 && score.helmet <= 150.0; + const bool near = score.left <= 100.0 && + score.right <= 100.0 && score.helmet <= 100.0; + const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; + + // Use the outer encounter watcher; do not nest mine_timed_walk. + // Count issued pulses, including any interrupted by a battle. + movements++; + pbf_move_left_joystick(ctx, { 1.0, 0.0 }, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + } + } + ); + } + + static bool oreburgh_mine_14_to_19( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array path15_boxes = { + ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate + ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate + ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path15_boxes_step = { + "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path15_boxes_step)) { + return false; + } + + // Move below the worker before continuing right. + const std::array path16_boxes = { + ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate + ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate + ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path16_boxes_step = { + "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path16_boxes_step)) { + return false; + } + + // Skip 17 and find the actual stopping point at 18. + if (!oreburgh_mine_align_to_18(stream, context)) { + return false; + } + + // Position 19 is the upward turn toward Roark. + pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + stream.log( + "[Mine endpoint 19] Position 18 confirmed; north turn sent.", + COLOR_GREEN + ); + return true; + } + + bool oreburgh_mine_route( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options + ) { + stream.log("[Mine resources] " + mine_navigation_resource_path()); + struct Step { + const char* name; + bool (*run)(VideoStream&, ProControllerContext&); + }; + + const std::array steps = { { + { + "Align entrance", + oreburgh_mine_alignment_test + }, + { + "Walk to first rock", + [](VideoStream& video, ProControllerContext& controller) { + return oreburgh_mine_walk_to_first_rock( + video, controller, false + ); + } + }, + { + "Move right of first rock", + oreburgh_mine_move_right + }, + { + "Move below first rock", + oreburgh_mine_down_past_central_rock + }, + { + "Move left into center gap", + oreburgh_mine_move_left_to_gap + }, + { + "Reach position 07", + oreburgh_mine_timed_to_07 + }, + { + "Reach position 08", + oreburgh_mine_left_to_08 + }, + { + "Reach position 09", + oreburgh_mine_down_to_09 + }, + { + "Reach 14 and collect item", + oreburgh_mine_09_to_14 + }, + { + "Reach 18 and turn north for 19", + oreburgh_mine_14_to_19 + } + } }; + + const size_t first = static_cast(options.first); + const size_t last = static_cast(options.last); + + if (first > last || last >= steps.size()) { + stream.log("[Mine route] Invalid stage range.", COLOR_ORANGE); + return false; + } + + for (size_t index = first; index <= last; index++) { + const Step& step = steps[index]; + + stream.log( + std::string("[Mine route] Starting: ") + step.name + ); + + bool succeeded = false; + + try { + succeeded = step.run(stream, context); + } + catch (OperationFailedException& error) { + stream.log( + std::string("[Mine route] Failed during ") + + step.name + ": " + error.message(), + COLOR_ORANGE + ); + + if (options.reset_on_failure) { + throw; + } + + stream.log( + "[Mine route] Test stopped without requesting a reset.", + COLOR_ORANGE + ); + return false; + } + + if (!succeeded) { + stream.log( + std::string("[Mine route] Stage failed: ") + step.name, + COLOR_ORANGE + ); + + if (options.reset_on_failure) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + std::string("Mine route failed during: ") + step.name, + stream + ); + } + + return false; + } + + stream.log( + std::string("[Mine route] Completed: ") + step.name, + COLOR_GREEN + ); + } + + stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); + return true; + } + + // Start Roark's conversation once, then advance it with B. + // Require dialogue to be seen before accepting its disappearance. + // Previous polling conversation retained for comparison. + // static bool mine_talk_to_roark( + // VideoStream& stream, + // ProControllerContext& context + // ) { + // ShortDialogDetector dialogue(COLOR_YELLOW); + // const auto start = std::chrono::steady_clock::now(); + // const auto deadline = start + 60s; + // bool saw_dialogue = false; + // bool timing_absence = false; + // auto absent_since = start; + // + // stream.log("[Mine Roark] Pressing A to start conversation."); + // pbf_press_button(context, BUTTON_A, 80ms, 100ms); + // context.wait_for_all_requests(); + // + // while (std::chrono::steady_clock::now() < deadline) { + // const VideoSnapshot frame = stream.video().snapshot(); + // if (!frame) { + // stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); + // return false; + // } + // const auto now = std::chrono::steady_clock::now(); + // if (dialogue.detect(frame)) { + // saw_dialogue = true; + // timing_absence = false; + // } + // else if (saw_dialogue) { + // if (!timing_absence) { + // timing_absence = true; + // absent_since = now; + // } + // if (now - absent_since >= 1500ms) { + // stream.log( + // "[Mine Roark] Dialogue absent for 1.5 seconds. Finished.", + // COLOR_GREEN + // ); + // return true; + // } + // } + // else if (now - start >= 10s) { + // stream.log("[Mine Roark] No dialogue appeared.", COLOR_ORANGE); + // return false; + // } + // + // // Wait for the first box before mashing, so it isn't skipped + // // before the detector has observed the conversation. + // if (saw_dialogue) { + // pbf_mash_button(context, BUTTON_B, 100ms); + // } + // else { + // pbf_wait(context, 100ms); + // } + // context.wait_for_all_requests(); + // } + // stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); + // return false; + // } + + static bool mine_talk_to_roark( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine Roark] Pressing A to start conversation."); + pbf_press_button(context, BUTTON_A, 80ms, 100ms); + context.wait_for_all_requests(); + + // The opening cutscene precedes the first dialogue box. + // Do not mash B or measure dialogue absence during that cutscene. + wait_for_dialogue(stream, context, "[Mine Roark] First box", 30s); + + ShortDialogDetector dialogue(COLOR_YELLOW); + const auto deadline = std::chrono::steady_clock::now() + 60s; + bool timing_absence = false; + auto absent_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); + return false; + } + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(frame)) { + timing_absence = false; + } + else { + if (!timing_absence) { + timing_absence = true; + absent_since = now; + } + // Previous quiet period: 1500ms. + if (now - absent_since >= 4s) { + stream.log( + "[Mine Roark] Dialogue absent for 4 seconds. Finished.", + COLOR_GREEN + ); + return true; + } + } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); + return false; + } + + // Retain the developer test entry point; both callers share the same route. + bool oreburgh_mine_first_leg_test( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options + ) { + return oreburgh_mine_route(stream, context, options); + } + + // Shared final step for the live route and the entrance-start test. + static bool mine_talk_and_escape(VideoStream& stream, ProControllerContext& context) { + if (!mine_talk_to_roark(stream, context)) { + return false; + } + stream.log("[Mine exit] Finding Escape Rope in the Bag."); + if (!use_escape_rope(stream, context, 8)) { + return false; + } + stream.log("[Mine exit] Roark conversation and Escape Rope exit succeeded.", COLOR_GREEN); + return true; + } + + bool oreburgh_mine_route_and_escape_test( + VideoStream& stream, + ProControllerContext& context + ) { + MineRouteTestOptions route; + route.reset_on_failure = false; + if (!oreburgh_mine_first_leg_test(stream, context, route)) { + return false; + } + try { + if (!mine_talk_and_escape(stream, context)) { + stream.log("[Mine exit test] Failed. Stopping without resetting.", COLOR_ORANGE); + return false; + } + } + catch (OperationFailedException& error) { + stream.log("[Mine exit test] " + error.message(), COLOR_ORANGE); + stream.log("[Mine exit test] Stopped without requesting a reset.", COLOR_ORANGE); + return false; + } + return true; + } + + void oreburgh_mine_nav( + VideoStream& stream, ProControllerContext& context) { + walk_through_grass(stream, context, 0, -1, 1, "down into mine"); + context.wait_for_all_requests(); + walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); + context.wait_for_all_requests(); + pbf_wait(context, 1824ms); + + MineRouteTestOptions route; + route.reset_on_failure = true; + + // if (!oreburgh_mine_first_leg_test(stream, context, route)) { return; } + if (!oreburgh_mine_route(stream, context, route)) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, "Mine route did not complete.", stream + ); + } + + // Previous conversation sequence retained for comparison. + // // Position 19 already faces north toward Roark. + // pbf_press_button(context, BUTTON_A, 200ms, 800ms); + // pbf_mash_button(context, BUTTON_B, 12000ms); + // context.wait_for_all_requests(); + // + // if (!mine_finish_item_dialogue(stream, context)) { + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // "Mine route: Roark dialogue did not finish.", + // stream + // ); + // } + + // Previous inline conversation and exit retained for comparison. + // // Position 19 already faces north toward Roark. + // if (!mine_talk_to_roark(stream, context)) { + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // "Mine route: Roark dialogue did not finish.", + // stream + // ); + // } + // + // // Previous Escape Rope button sequence retained for comparison. + // // pbf_press_button(context, BUTTON_X, 142ms, 0ms); + // // pbf_wait(context, 1707ms); + // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 162ms, 0ms); + // // pbf_wait(context, 457ms); + // // pbf_press_button(context, BUTTON_A, 144ms, 0ms); + // // pbf_wait(context, 1704ms); + // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 159ms, 0ms); + // // pbf_wait(context, 640ms); + // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 160ms, 0ms); + // // pbf_wait(context, 555ms); + // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 180ms, 0ms); + // // pbf_wait(context, 494ms); + // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 171ms, 0ms); + // // pbf_wait(context, 826ms); + // // pbf_press_button(context, BUTTON_A, 92ms, 0ms); + // // pbf_wait(context, 823ms); + // // pbf_press_button(context, BUTTON_A, 110ms, 0ms); + // + // if (!use_escape_rope(stream, context, 8)) { + // OperationFailedException::fire( + // ErrorReport::SEND_ERROR_REPORT, + // "Mine route: failed to find or use Escape Rope.", + // stream + // ); + // } + if (!mine_talk_and_escape(stream, context)) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Mine route: Roark conversation or Escape Rope exit failed.", + stream + ); + } + + } + // --------------------------------------------------------------------------- + // Segment 1 Part 3: Oreburgh Mine to Gym + // Assumes that escape rope was picked up and used to exit the mine. + // Navigates to the gym. + // ---------------------------------------------------------------------------- + void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + stream.log("[Mine to gym] Following recorded route from the mine exit."); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2382ms, 0ms); + pbf_wait(context, 10234ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2034ms, 0ms); + pbf_wait(context, 3635ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 861ms, 0ms); + pbf_wait(context, 939ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 83ms, 0ms); + pbf_wait(context, 521ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 111ms, 0ms); + pbf_wait(context, 5495ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); + pbf_wait(context, 3795ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1065ms, 0ms); + pbf_wait(context, 602ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 912ms, 0ms); + pbf_wait(context, 4134ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); + pbf_wait(context, 5069ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); + pbf_wait(context, 605ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); + pbf_wait(context, 1081ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); + pbf_wait(context, 2642ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); + pbf_wait(context, 5340ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); + pbf_wait(context, 140ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); + context.wait_for_all_requests(); + stream.log("[Mine to gym] Entering the gym and checking the doorway transition."); + walk_until_doorway(stream, context, MoveDirection::UP, 5s); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + context.wait_for_all_requests(); + stream.log("[Mine to gym] Gym doorway transition confirmed.", COLOR_GREEN); + } + + // --------------------------------------------------------------------------- + // Segment 1 Part 4: Gym navigation + // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. + // --------------------------------------------------------------------------- + + void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + }; + const Move sequence[] = { + {DPAD_UP, 3}, + {DPAD_LEFT, 4}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 8}, + {DPAD_UP, 11}, + {DPAD_LEFT, 5}, + {DPAD_UP, 4}, + }; + + for (const auto& move : sequence) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate the Gym battle. + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + //Barry 1 battle logic can be used here since the moves don't matter this early. + Barry_1_fight(env, stream, context); + + //Button presses after battle with small cutscene of badge being put in case between dialogue boxes. + + wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + + for (int i = 0; i < 21; i++) { + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + //Exits Buiilding + pbf_wait(context, 1000ms); + } + + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h new file mode 100644 index 0000000000..778657b90a --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h @@ -0,0 +1,70 @@ +// Navigation and battle helpers for AutoStory Segment 01. +#pragma once + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + enum class MineRouteStage { + ALIGN_ENTRANCE, + FIRST_ROCK, + RIGHT_OF_FIRST_ROCK, + BELOW_FIRST_ROCK, + CENTER_GAP, + POSITION_07, + POSITION_08, + POSITION_09, + ITEM_AT_14, + ROARK_AT_19 + }; + + struct MineRouteTestOptions { + MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; + MineRouteStage last = MineRouteStage::ROARK_AT_19; + + // False: stop the test on failure. + // True: let the checkpoint retry handler reset the game. + bool reset_on_failure = false; + }; + + void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void oreburgh_mine_nav( + VideoStream& stream, ProControllerContext& context); + + void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + // Starts inside the mine at an entrance position; stops outside after + // talking to Roark and using Escape Rope. Does not reset on failure. + bool oreburgh_mine_route_and_escape_test( + VideoStream& stream, + ProControllerContext& context + ); + + // Shared stage runner for production navigation and isolated route tests. + bool oreburgh_mine_route( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options + ); + + bool oreburgh_mine_first_leg_test( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options = MineRouteTestOptions() + ); + } + } +} diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 746cb31e14..91353df4a8 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1487,6 +1487,12 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Resources/Pokemon_PokemonNames.h Source/Pokemon/Resources/Pokemon_PokemonSlugs.cpp Source/Pokemon/Resources/Pokemon_PokemonSlugs.h + Source/PokemonBDSP/PokemonBDSP_Tests.cpp + Source/PokemonBDSP/PokemonBDSP_Tests.h + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleBallReader.cpp Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleBallReader.h Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp @@ -1579,8 +1585,6 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h - Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.cpp - Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00_tools.h Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp From ac0d7356faeb5b0c49d0c502dd87efe6f192cf2a Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 4 Oct 2026 21:50:26 -0500 Subject: [PATCH 20/39] Add Oreburgh mart alignment and dialogue guards Introduces a vision-based Oreburgh mart alignment step (template matching with ambiguity checks) and wires it into the Roark-to-Mars movement path to correct positioning before continuing. It also tightens gym flow timing by waiting through entry cutscene and confirming post-battle dialogue completion via detector-based absence checks, while removing large blocks of obsolete commented-out mine escape/navigation code. --- .../PokemonBDSP_AutoStory_Segment_02.cpp | 88 +++++ ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 367 +++++------------- 2 files changed, 175 insertions(+), 280 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 6f8f8a9c4f..ff7b110968 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -5,6 +5,11 @@ * */ +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" @@ -16,7 +21,90 @@ using namespace std::chrono_literals; namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ + // Match only the mart emblem, avoiding moving NPCs and changing ground shadows. + static cv::Mat mart_grayscale(const ImageRGB32& image) { + cv::Mat pixels((int)image.height(), (int)image.width(), CV_8UC4, + const_cast(image.data()), image.bytes_per_row()); + cv::Mat gray; + cv::cvtColor(pixels, gray, cv::COLOR_BGRA2GRAY); + return gray; + } + + static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& context) { + const ImageRGB32 reference(RESOURCE_PATH() + + "PokemonBDSP/Autostory/OreburghMart/MartEmblem.png"); + const ImageRGB32 small_reference = reference.scale_to(54, 43); + const cv::Mat emblem = mart_grayscale(small_reference); + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmations = 0; + for (size_t attempt = 0; attempt < 40; attempt++) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Mart alignment: No video frame.", stream); + } + const ImageRGB32 small_frame = frame->scale_to(960, 540); + const cv::Mat gray = mart_grayscale(small_frame); + cv::Mat scores; + cv::matchTemplate(gray(cv::Rect(0, 0, 960, 360)), emblem, scores, cv::TM_CCOEFF_NORMED); + double score; + cv::Point location; + cv::minMaxLoc(scores, nullptr, &score, nullptr, &location); + // Reject an ambiguous landmark rather than steering toward another building. + cv::Mat competitors = scores.clone(); + const cv::Rect exclusion = cv::Rect(location.x - 15, location.y - 15, 31, 31) + & cv::Rect(0, 0, scores.cols, scores.rows); + competitors(exclusion).setTo(-1); + double runner_up; + cv::minMaxLoc(competitors, nullptr, &runner_up); + if (score < 0.93 || score - runner_up < 0.10) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Mart alignment: Emblem match missing or ambiguous.", stream); + } + // Reference emblem top-left at half resolution. The reference player + // stands directly outside the door, 64 full-resolution pixels left + // of the emblem center; do not steer to the emblem center itself. + const int dx = location.x - 482; + const int dy = location.y - 127; + stream.log("[Mart alignment] score=" + std::to_string(score) + + ", offset=(" + std::to_string(dx * 2) + "," + std::to_string(dy * 2) + ")"); + if (std::abs(dx) <= 3 && std::abs(dy) <= 3) { + if (++confirmations >= 3) { + stream.log("[Mart alignment] Door position confirmed."); + return; + } + pbf_wait(context, 250ms); + } else { + confirmations = 0; + const bool horizontal = std::abs(dx) >= std::abs(dy); + const int error = horizontal ? dx : dy; + const auto duration = std::abs(error) > 15 ? 80ms : 40ms; + pbf_move_left_joystick(context, + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, + horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, duration, 0ms); + pbf_wait(context, 350ms); + } + context.wait_for_all_requests(); + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Mart alignment: Could not reach the doorway reference position.", stream); + } + void roark_to_mars(VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 3995ms, 0ms); + pbf_wait(context, 672ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 681ms, 0ms); + pbf_wait(context, 468ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1081ms, 0ms); + pbf_wait(context, 420ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2445ms, 0ms); + pbf_wait(context, 1056ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 95ms, 0ms); + align_to_oreburgh_mart(stream, context); + pbf_wait(context, 4787ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1309ms, 0ms); } void mars_to_eterna_forest(VideoStream& stream, ProControllerContext& context) { diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 35a144a84c..7b03f59a23 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -68,46 +68,6 @@ namespace PokemonAutomation { // Runs from a wild battle that is already in progress (menu visible or about to be). // Throws OperationFailedException if it cannot escape within `timeout`. - // Previous escape completion check retained for comparison. - // static void escape_wild_battle( - // VideoStream& stream, - // ProControllerContext& context, - // BattleType battle_type = BattleType::STANDARD, - // std::chrono::seconds timeout = 30s - // ) { - // bool escaped = false; - // auto deadline = std::chrono::steady_clock::now() + timeout; - // while (std::chrono::steady_clock::now() < deadline) { - // pbf_press_button(context, BUTTON_B, 40ms, 200ms); - // context.wait_for_all_requests(); - // - // BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - // if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - // continue; - // } - // - // pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - // pbf_press_button(context, BUTTON_A, 60ms, 0ms); - // context.wait_for_all_requests(); - // - // EndBattleWatcher battle_end; - // BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - // if (wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}) == 0) { - // escaped = true; - // break; - // } - // } - // if (!escaped) { - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, - // "escape_wild_battle: Failed to run from battle.", - // stream - // ); - // } - // OverworldWatcher overworld; - // wait_until(stream, context, 5s, {{overworld}}); - // } - // // The main game menu is independent evidence that battle has ended. static bool mine_confirm_escape_via_menu( @@ -120,7 +80,6 @@ namespace PokemonAutomation { BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); for (size_t attempt = 1; attempt <= 3; attempt++) { // Do not accept the absence-based overworld detector before menu proof. - // Previous order: {game_menu}, {overworld}, {battle_menu}. const int state = wait_until(stream, context, 300ms, {{game_menu}, {battle_menu}}); if (state == 1) { stream.log("[Mine escape] Battle menu still present; retrying Run."); @@ -172,74 +131,6 @@ namespace PokemonAutomation { return false; } - // Previous early overworld completion check retained for comparison. - // static void escape_wild_battle( - // VideoStream& stream, - // ProControllerContext& context, - // BattleType battle_type = BattleType::STANDARD, - // std::chrono::seconds timeout = 30s - // ) { - // // Keep transition history across retries and observe the Run input. - // EndBattleWatcher battle_end; - // OverworldWatcher overworld; - // bool escaped = false; - // const auto deadline = std::chrono::steady_clock::now() + timeout; - // while (std::chrono::steady_clock::now() < deadline) { - // // Accept a completed escape even if its black transition was missed. - // if (wait_until(stream, context, 200ms, {{overworld}}) == 0) { - // escaped = true; - // break; - // } - // pbf_press_button(context, BUTTON_B, 40ms, 200ms); - // context.wait_for_all_requests(); - // BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - // const int ready = wait_until(stream, context, 3s, {{overworld}, {menu_ready}}); - // if (ready == 0) { - // escaped = true; - // break; - // } - // if (ready != 1) { - // // No battle menu and no observed exit: ask the main menu. - // if (mine_confirm_escape_via_menu(stream, context, battle_type)) { - // return; - // } - // continue; - // } - // pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - // context.wait_for_all_requests(); - // stream.log("[Mine escape] Selecting Run; watching transition and overworld."); - // const int result = run_until( - // stream, context, - // [](ProControllerContext& controller) { - // pbf_press_button(controller, BUTTON_A, 60ms, 0ms); - // pbf_wait(controller, 4s); - // controller.wait_for_all_requests(); - // }, - // {battle_end, overworld} - // ); - // if (result >= 0) { - // escaped = true; - // break; - // } - // } - // // An exit animation can finish just after the retry deadline. - // // Confirm the overworld before reporting failure or resuming movement. - // if (wait_until(stream, context, 10s, {{overworld}}) == 0) { - // stream.log("[Mine escape] Overworld confirmed. Escape succeeded.", COLOR_GREEN); - // return; - // } - // if (mine_confirm_escape_via_menu(stream, context, battle_type)) { - // return; - // } - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, - // escaped - // ? "escape_wild_battle: Exit detected, but overworld did not return." - // : "escape_wild_battle: Exit, overworld, and main-menu fallback could not confirm escape.", - // stream - // ); - // } - // static bool mine_select_run( VideoStream& stream, @@ -271,8 +162,6 @@ namespace PokemonAutomation { pbf_wait(context, 150ms); } else { - // Previous navigation always moved Down. - // Battle wraps upward directly to Run. const DpadPosition direction = selected == BattleMenuOption::BATTLE ? DPAD_UP : DPAD_DOWN; pbf_press_dpad(context, direction, 80ms, 250ms); @@ -304,9 +193,6 @@ namespace PokemonAutomation { if (ready != 1) { continue; } - // Previous blind selection: - // pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - // context.wait_for_all_requests(); if (!mine_select_run(stream, context, battle_type)) { continue; } @@ -788,7 +674,6 @@ namespace PokemonAutomation { ProControllerContext& context ) { const ImageRGB32 reference( - // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/mine_start_center.png" mine_navigation_resource_path() + "mine_start_center.png" ); @@ -928,18 +813,6 @@ namespace PokemonAutomation { return false; } - // Previous duplicate overworld-only check. Escape helper now - // confirms either the overworld or opening and closing the main menu. - // OverworldWatcher overworld; - // - // if (wait_until(stream, context, 5s, { {overworld} }) != 0) { - // stream.log( - // "[Mine encounter] Overworld not detected. Stopping.", - // COLOR_ORANGE - // ); - // return false; - // } - // pbf_wait(context, 800ms); context.wait_for_all_requests(); @@ -1123,7 +996,6 @@ namespace PokemonAutomation { double max_patch = 30.0 ) { const ImageRGB32 reference( - // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" mine_navigation_resource_path() + filename ); const ImageRGB32 reference_scaled = reference.scale_to(960, 540); @@ -1206,7 +1078,6 @@ namespace PokemonAutomation { std::array, Endpoints> references; for (size_t index = 0; index < Endpoints; index++) { const ImageRGB32 image( - // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/" mine_navigation_resource_path() + files[index] ); references[index] = std::make_unique(image.scale_to(960, 540)); @@ -1829,7 +1700,6 @@ namespace PokemonAutomation { ProControllerContext& context ) { const ImageRGB32 reference( - // Previous resource reference: RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/Mine_path_18.png" mine_navigation_resource_path() + "Mine_path_18.png" ); const ImageRGB32 reference_scaled = reference.scale_to(960, 540); @@ -2078,64 +1948,6 @@ namespace PokemonAutomation { // Start Roark's conversation once, then advance it with B. // Require dialogue to be seen before accepting its disappearance. - // Previous polling conversation retained for comparison. - // static bool mine_talk_to_roark( - // VideoStream& stream, - // ProControllerContext& context - // ) { - // ShortDialogDetector dialogue(COLOR_YELLOW); - // const auto start = std::chrono::steady_clock::now(); - // const auto deadline = start + 60s; - // bool saw_dialogue = false; - // bool timing_absence = false; - // auto absent_since = start; - // - // stream.log("[Mine Roark] Pressing A to start conversation."); - // pbf_press_button(context, BUTTON_A, 80ms, 100ms); - // context.wait_for_all_requests(); - // - // while (std::chrono::steady_clock::now() < deadline) { - // const VideoSnapshot frame = stream.video().snapshot(); - // if (!frame) { - // stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); - // return false; - // } - // const auto now = std::chrono::steady_clock::now(); - // if (dialogue.detect(frame)) { - // saw_dialogue = true; - // timing_absence = false; - // } - // else if (saw_dialogue) { - // if (!timing_absence) { - // timing_absence = true; - // absent_since = now; - // } - // if (now - absent_since >= 1500ms) { - // stream.log( - // "[Mine Roark] Dialogue absent for 1.5 seconds. Finished.", - // COLOR_GREEN - // ); - // return true; - // } - // } - // else if (now - start >= 10s) { - // stream.log("[Mine Roark] No dialogue appeared.", COLOR_ORANGE); - // return false; - // } - // - // // Wait for the first box before mashing, so it isn't skipped - // // before the detector has observed the conversation. - // if (saw_dialogue) { - // pbf_mash_button(context, BUTTON_B, 100ms); - // } - // else { - // pbf_wait(context, 100ms); - // } - // context.wait_for_all_requests(); - // } - // stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); - // return false; - // } static bool mine_talk_to_roark( VideoStream& stream, @@ -2168,7 +1980,6 @@ namespace PokemonAutomation { timing_absence = true; absent_since = now; } - // Previous quiet period: 1500ms. if (now - absent_since >= 4s) { stream.log( "[Mine Roark] Dialogue absent for 4 seconds. Finished.", @@ -2240,63 +2051,13 @@ namespace PokemonAutomation { MineRouteTestOptions route; route.reset_on_failure = true; - // if (!oreburgh_mine_first_leg_test(stream, context, route)) { return; } if (!oreburgh_mine_route(stream, context, route)) { OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, "Mine route did not complete.", stream ); } - // Previous conversation sequence retained for comparison. - // // Position 19 already faces north toward Roark. - // pbf_press_button(context, BUTTON_A, 200ms, 800ms); - // pbf_mash_button(context, BUTTON_B, 12000ms); - // context.wait_for_all_requests(); - // - // if (!mine_finish_item_dialogue(stream, context)) { - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, - // "Mine route: Roark dialogue did not finish.", - // stream - // ); - // } - - // Previous inline conversation and exit retained for comparison. - // // Position 19 already faces north toward Roark. - // if (!mine_talk_to_roark(stream, context)) { - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, - // "Mine route: Roark dialogue did not finish.", - // stream - // ); - // } - // - // // Previous Escape Rope button sequence retained for comparison. - // // pbf_press_button(context, BUTTON_X, 142ms, 0ms); - // // pbf_wait(context, 1707ms); - // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 162ms, 0ms); - // // pbf_wait(context, 457ms); - // // pbf_press_button(context, BUTTON_A, 144ms, 0ms); - // // pbf_wait(context, 1704ms); - // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 159ms, 0ms); - // // pbf_wait(context, 640ms); - // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 160ms, 0ms); - // // pbf_wait(context, 555ms); - // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 180ms, 0ms); - // // pbf_wait(context, 494ms); - // // pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 171ms, 0ms); - // // pbf_wait(context, 826ms); - // // pbf_press_button(context, BUTTON_A, 92ms, 0ms); - // // pbf_wait(context, 823ms); - // // pbf_press_button(context, BUTTON_A, 110ms, 0ms); - // - // if (!use_escape_rope(stream, context, 8)) { - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, - // "Mine route: failed to find or use Escape Rope.", - // stream - // ); - // } + if (!mine_talk_and_escape(stream, context)) { OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, @@ -2309,47 +2070,59 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- // Segment 1 Part 3: Oreburgh Mine to Gym // Assumes that escape rope was picked up and used to exit the mine. - // Navigates to the gym. + // Follows the validated blind route into the gym. // ---------------------------------------------------------------------------- void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + stream.log("[Mine to gym] Following recorded route from the mine exit."); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2382ms, 0ms); - pbf_wait(context, 10234ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2034ms, 0ms); - pbf_wait(context, 3635ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 861ms, 0ms); - pbf_wait(context, 939ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 83ms, 0ms); - pbf_wait(context, 521ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 111ms, 0ms); - pbf_wait(context, 5495ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); - pbf_wait(context, 3795ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1065ms, 0ms); - pbf_wait(context, 602ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 912ms, 0ms); - pbf_wait(context, 4134ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); - pbf_wait(context, 5069ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); - pbf_wait(context, 605ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); - pbf_wait(context, 1081ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); - pbf_wait(context, 2642ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); - pbf_wait(context, 5340ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); - pbf_wait(context, 140ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); + pbf_wait(context, 700ms); context.wait_for_all_requests(); - stream.log("[Mine to gym] Entering the gym and checking the doorway transition."); - walk_until_doorway(stream, context, MoveDirection::UP, 5s); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 3995ms, 0ms); + pbf_wait(context, 672ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 681ms, 0ms); + pbf_wait(context, 468ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1081ms, 0ms); + pbf_wait(context, 420ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2445ms, 0ms); + pbf_wait(context, 1056ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 95ms, 0ms); + pbf_wait(context, 4787ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1309ms, 0ms); + + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4450ms, 0ms); + //pbf_wait(context, 3635ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); + //pbf_wait(context, 3795ms); context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - context.wait_for_all_requests(); - stream.log("[Mine to gym] Gym doorway transition confirmed.", COLOR_GREEN); + + //stream.log("[Mine to gym] Staying west of the hiker; moving north along the left fence."); + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); + //pbf_wait(context, 602ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); + //pbf_wait(context, 5069ms); + //pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); + //pbf_wait(context, 605ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); + //pbf_wait(context, 1081ms); + //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); + //pbf_wait(context, 2642ms); + //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); + //pbf_wait(context, 5340ms); + //context.wait_for_all_requests(); + + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); + //pbf_wait(context, 140ms); + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); + //context.wait_for_all_requests(); + //stream.log("[Mine to gym] Recorded movement finished; waiting for gym entry to settle."); + // walk_until_doorway(stream, context, MoveDirection::UP, 5s); + // Finish entering the doorway with a bounded blind input. + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); + //context.wait_for_all_requests(); + //pbf_wait(context, 1500ms); + //context.wait_for_all_requests(); + stream.log("[Mine to gym] Blind mine-to-gym sequence completed.", COLOR_GREEN); } // --------------------------------------------------------------------------- @@ -2361,6 +2134,12 @@ namespace PokemonAutomation { VideoStream& stream, ProControllerContext& context) { //Upon entering the Gym. + // The entry camera sequence lasts about 7.5 seconds in the reference + // video. Allow another 500ms before sending puzzle movement inputs. + context.wait_for_all_requests(); + env.log("[Gym puzzle] Waiting for the entry cutscene to finish."); + pbf_wait(context, 8000ms); + context.wait_for_all_requests(); struct Move { DpadPosition dir; @@ -2389,17 +2168,45 @@ namespace PokemonAutomation { //Barry 1 battle logic can be used here since the moves don't matter this early. Barry_1_fight(env, stream, context); - //Button presses after battle with small cutscene of badge being put in case between dialogue boxes. - - wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); + ShortDialogDetector post_battle_dialogue(COLOR_YELLOW); + const auto dialogue_deadline = std::chrono::steady_clock::now() + 180s; + auto dialogue_absent_since = std::chrono::steady_clock::now(); + bool dialogue_finished = false; + stream.log("[Gym post battle] Clearing dialogue until absent for 4 seconds."); + while (std::chrono::steady_clock::now() < dialogue_deadline) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Gym post battle: No video frame while clearing dialogue.", stream + ); + } + const auto now = std::chrono::steady_clock::now(); + if (post_battle_dialogue.detect(frame)) { + dialogue_absent_since = now; + } + else if (now - dialogue_absent_since >= 4s) { + dialogue_finished = true; + break; + } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + if (!dialogue_finished) { + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Gym post battle: Dialogue did not finish within 180 seconds.", stream + ); + } + stream.log("[Gym post battle] Dialogue absent for 4 seconds. Walking down."); for (int i = 0; i < 21; i++) { pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); } - //Exits Buiilding + // Exit the building. pbf_wait(context, 1000ms); } From da0bc4e0598d64873af57145459e05b622fcbc83 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 11 Oct 2026 20:34:56 -0500 Subject: [PATCH 21/39] Harden BDSP AutoStory tutorial and first-badge route Timed movements, retained menu cursors, incomplete battle cleanup, and resets after catching could leave the tutorial at a different position from the one the next action assumed. Add visual confirmation and reusable BDSP helpers to make Segments 00 and 01 resume and navigate more reliably. - Split tutorial and main-story selections and add the initial settings checkpoint. Detect menu/option positions, retry detection, log the selected settings, verify Autosave is off, and confirm the settings exit sequence. - Establish a saved boundary after the tutorial catch, finish capture and battle-result dialogue before moving, and preserve the diagnostic post-catch entry's continuation into checkpoint 00.05. - Consolidate grass movement around the shared encounter escape routine and optional visually confirmed destinations. Add screen-relative landmark matching for Poketch, the Center, Sandgem, and Route 201/202 positions. - Add a registered hourly lighting test with matcher selection, captures, and match data to evaluate daylight and shadow changes consistently. - Navigate to the Bag explicitly and verify slot-1 HP before/after Potion use. Add one wild training fight after the first Potion pickup, heal afterward, and select Ember for Tristan using the detected move cursor. - Extend BDSP battle-menu inference to count one through four populated move panels and identify the selected slot without assuming four moves exist. - Reuse Segment 01's confirmed Run escape, express mine offsets/tolerances relative to the screen, and restore the Gate-to-mine movement call. Validation: the exact staged source set builds and links SerialPrograms in Debug with Segment 02, later-segment edits, and CommonTools OCR edits excluded. Native screenshot checks cover battle move selection, landmark matching, translated positions, and capture resolution changes; recorded lighting runs were used to refine the tutorial matchers. Live full-route validation remains an ongoing task, not a guarantee supplied by this commit. Scope: includes BDSP panel/source registration and required local helpers; no shared CommonTools or other-game implementation changes. Segment 02 route, its new street/plaza matchers, and the Repel-tab repair remain uncommitted. Resources are published separately to Developer-Butters/Packages. Development limits: diagnostic checkboxes remain explicitly marked for removal before production; option-value templates still require multilingual work. Faint handling and stronger save-result verification remain follow-up work. --- .../PokemonBDSP_BattleMenuDetector.cpp | 71 + .../Battles/PokemonBDSP_BattleMenuDetector.h | 10 + .../Source/PokemonBDSP/PokemonBDSP_Panels.cpp | 2 + .../AutoStory/PokemonBDSP_AutoStory.cpp | 234 ++- .../AutoStory/PokemonBDSP_AutoStory.h | 10 + .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 254 ++- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 22 +- .../PokemonBDSP_AutoStory_Segment_00.cpp | 490 +++++- .../PokemonBDSP_AutoStory_Segment_00.h | 20 + .../PokemonBDSP_AutoStory_Segment_01.cpp | 8 +- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 1565 ++++++++--------- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 40 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 122 +- .../PokemonBDSP_Autostory_Segment_01_tools.h | 12 + .../Utils/PokemonBDSP_OverworldMatchers.cpp | 261 +++ .../Utils/PokemonBDSP_OverworldMatchers.h | 35 + .../PokemonBDSP_OverworldLightingTest.cpp | 74 + .../PokemonBDSP_OverworldLightingTest.h | 18 + SerialPrograms/cmake/SourceFiles.cmake | 4 + 19 files changed, 2062 insertions(+), 1190 deletions(-) create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp index 6fba38e28a..6abbeadb16 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.cpp @@ -5,6 +5,8 @@ */ #include +#include +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" #include "PokemonBDSP_BattleMenuDetector.h" @@ -205,6 +207,75 @@ BattleMenuOption BattleMenuDetector::selected_option(const ImageViewRGB32& scree return static_cast(selected); } +BattleMoveMenuState detect_battle_move_menu(const ImageViewRGB32& screen){ + BattleMoveMenuState result; + if (!screen) return result; + // Every populated move has a white PP panel, irrespective of move type or + // selected row. Find connected panels rather than assume a 4-row layout. + const ImageFloatBox search{0.90, 0.48, 0.075, 0.505}; + const ImageViewRGB32 patch = extract_box_reference(screen, search); + cv::Mat white((int)patch.height(), (int)patch.width(), CV_8UC1); + for (size_t y = 0; y < patch.height(); y++) for (size_t x = 0; x < patch.width(); x++) { + uint32_t p = patch.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + white.at((int)y, (int)x) = std::min(r, std::min(g, b)) > 225 && + std::max(r, std::max(g, b)) - std::min(r, std::min(g, b)) < 30 ? 255 : 0; + } + cv::Mat labels, stats, centers; + int components = cv::connectedComponentsWithStats(white, labels, stats, centers); + std::vector panels; + for (int component = 1; component < components; component++) { + cv::Rect box(stats.at(component, cv::CC_STAT_LEFT), stats.at(component, cv::CC_STAT_TOP), + stats.at(component, cv::CC_STAT_WIDTH), stats.at(component, cv::CC_STAT_HEIGHT)); + if (box.width < patch.width() * 0.60 || box.height < screen.height() * 0.055 || + box.height > screen.height() * 0.13) continue; + double area = (double)stats.at(component, cv::CC_STAT_AREA); + if (area / box.area() < 0.55) continue; + panels.push_back(box); + } + if (panels.empty() || panels.size() > 4) return result; + std::sort(panels.begin(), panels.end(), [](const cv::Rect& a, const cv::Rect& b){ return a.y < b.y; }); + // Reject inconsistent/overlapping geometry rather than count noise as moves. + for (size_t i = 1; i < panels.size(); i++) { + if (panels[i].y <= panels[i - 1].y + panels[i - 1].height || + std::abs(panels[i].height - panels[0].height) > screen.height() * 0.025) return result; + } + result.move_count = panels.size(); + auto is_corner = [&](double x, double y){ + if (x < 0 || y < 0 || x + 0.018 > 1 || y + 0.023 > 1) return false; + ImageViewRGB32 corner = extract_box_reference(screen, ImageFloatBox{x, y, 0.018, 0.023}); + size_t white_count = 0, total = corner.width() * corner.height(); + for (size_t py = 0; py < corner.height(); py++) for (size_t px = 0; px < corner.width(); px++) { + uint32_t p = corner.pixel(px, py); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + white_count += std::min(r, std::min(g, b)) > 210 && + std::max(r, std::max(g, b)) - std::min(r, std::min(g, b)) < 35; + } + return total && (double)white_count / total >= 0.15; + }; + for (size_t slot = 0; slot < panels.size(); slot++) { + double top = search.y + (double)panels[slot].y / screen.height(); + double bottom = top + (double)panels[slot].height / screen.height(); + result.buttons[slot] = ImageFloatBox{0.665, std::max(0.0, top - 0.01), 0.333, + std::min(1.0, bottom + 0.01) - std::max(0.0, top - 0.01)}; + bool selected = false; + for (double dy : {0.0, -0.003, 0.003, -0.006, 0.006, -0.010, 0.010}) { + for (double dx : {0.0, -0.003, 0.003}) { + if (is_corner(0.663 + dx, top - 0.010 + dy) && + is_corner(0.976 + dx, top - 0.010 + dy) && + is_corner(0.663 + dx, bottom - 0.014 + dy) && + is_corner(0.976 + dx, bottom - 0.014 + dy)) { selected = true; break; } + } + if (selected) break; + } + if (selected) { + if (result.selected_slot) { result.selected_slot = 0; return result; } + result.selected_slot = slot + 1; + } + } + return result; +} + BattleMenuWatcher::BattleMenuWatcher(BattleType battle_type, Color color) : BattleMenuDetector(battle_type, color) , VisualInferenceCallback("BattleMenuWatcher") diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h index 5fa9450dbc..21c3e5f858 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonBDSP_BattleMenuDetector_H #define PokemonAutomation_PokemonBDSP_BattleMenuDetector_H +#include #include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -34,6 +35,15 @@ enum class BattleMenuOption{ RUN = 3, }; +// Logical move slots are ordered top-to-bottom among populated move buttons. +// selected_slot is 1-4, or 0 when missing/ambiguous. Never guess on uncertainty. +struct BattleMoveMenuState { + size_t move_count = 0; + size_t selected_slot = 0; + std::array buttons; +}; +BattleMoveMenuState detect_battle_move_menu(const ImageViewRGB32& screen); + class BattleMenuDetector : public StaticScreenDetector{ public: BattleMenuDetector(BattleType battle_type, Color color = COLOR_RED); diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp index 1054f4dd03..17b53f04e9 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp @@ -8,6 +8,7 @@ #include "CommonFramework/GlobalAutoPaths.h" #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h" +#include "PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h" #include "PokemonBDSP_Panels.h" #include "PokemonBDSP_Settings.h" @@ -95,6 +96,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back("---- Auto Story ----"); ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); ret.emplace_back("---- Glitches (v1.1.3) ----"); ret.emplace_back(make_SingleSwitchProgram()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index c715c42a84..042cba38ed 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -56,6 +56,7 @@ static std::vector> make_checkpoint_list() std::vector> checkpoint_list; //Segment 00 + checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); @@ -249,35 +250,35 @@ static const std::vector>& ALL_CHECKPOINTS // Segment database // --------------------------------------------------------------------------- -StringSelectDatabase make_segment_database(){ - StringSelectDatabase db; - int index = 0; - for (const auto& segment : ALL_SEGMENTS()){ - db.add_entry(StringSelectEntry(std::to_string(index), segment->name())); - index++; - } - return db; -} -// --------------------------------------------------------------------------- -// Checkpoint database (SV-style) -// --------------------------------------------------------------------------- -const StringSelectDatabase& SEGMENT_DATABASE(){ - static StringSelectDatabase db = make_segment_database(); - return db; -} -StringSelectDatabase make_checkpoint_database(){ - StringSelectDatabase db; - int index = 0; - for (const auto& checkpoint : ALL_CHECKPOINTS()){ - db.add_entry(StringSelectEntry(std::to_string(index), checkpoint->name())); - index++; - } - return db; -} - -const StringSelectDatabase& CHECKPOINT_DATABASE(){ - static StringSelectDatabase db = make_checkpoint_database(); - return db; +// Section databases retain global index slugs. Never execute across the +// tutorial/main-story boundary: the starter must be returned from HOME first. +static constexpr size_t TUTORIAL_CHECKPOINT_COUNT = 6; +static const StringSelectDatabase& section_database(bool checkpoints, bool tutorial){ + static const StringSelectDatabase tutorial_segments = []{ + StringSelectDatabase db; + db.add_entry(StringSelectEntry("0", ALL_SEGMENTS()[0]->name())); + return db; + }(); + static const StringSelectDatabase main_segments = []{ + StringSelectDatabase db; + for (size_t i = 1; i < ALL_SEGMENTS().size(); i++) + db.add_entry(StringSelectEntry(std::to_string(i), ALL_SEGMENTS()[i]->name())); + return db; + }(); + static const StringSelectDatabase tutorial_checkpoints = []{ + StringSelectDatabase db; + for (size_t i = 0; i < TUTORIAL_CHECKPOINT_COUNT; i++) + db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + return db; + }(); + static const StringSelectDatabase main_checkpoints = []{ + StringSelectDatabase db; + for (size_t i = TUTORIAL_CHECKPOINT_COUNT; i < ALL_CHECKPOINTS().size(); i++) + db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + return db; + }(); + return checkpoints ? (tutorial ? tutorial_checkpoints : main_checkpoints) + : (tutorial ? tutorial_segments : main_segments); } // --------------------------------------------------------------------------- @@ -293,7 +294,9 @@ BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() "Automatically completes BDSP story segments.", ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, - AllowCommandsWhenRunning::DISABLE_COMMANDS + AllowCommandsWhenRunning::DISABLE_COMMANDS, + false, + {"Tesseract"} ) {} @@ -316,6 +319,17 @@ BDSPAutoStory_Descriptor::make_stats() const{ BDSPAutoStory::~BDSPAutoStory(){ OUTSIDE_HELP_POKEMON.remove_listener(*this); HELP_MODE.remove_listener(*this); + STORY_SECTION.remove_listener(*this); + START_SEGMENT_MAINSTORY.remove_listener(*this); + END_SEGMENT_MAINSTORY.remove_listener(*this); + START_CHECKPOINT_MAINSTORY.remove_listener(*this); + END_CHECKPOINT_MAINSTORY.remove_listener(*this); + START_CHECKPOINT_TUTORIAL.remove_listener(*this); + END_CHECKPOINT_TUTORIAL.remove_listener(*this); + START_SEGMENT.remove_listener(*this); + END_SEGMENT.remove_listener(*this); + ENABLE_ADVANCED_MODE.remove_listener(*this); + } @@ -326,6 +340,29 @@ BDSPAutoStory::~BDSPAutoStory(){ BDSPAutoStory::BDSPAutoStory() : SingleSwitchProgramInstance() + // DIAGNOSTIC ONLY: Remove before pushing to production. + , DIAGNOSTIC_POST_CATCH_ONLY( + "Diagnostic only: Run 00.04 after catch through 00.05
" + "Start at the saved position immediately after catching the second Pokemon. " + "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " + "Remove before pushing to production.", + LockMode::LOCK_WHILE_RUNNING, + false + ) + // DIAGNOSTIC ONLY: Remove before pushing to production. + , DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( + "Diagnostic only: Fake checkpoint saves
" + "Use the fake save helper instead of normal checkpoint saving. " + "Checkpoint saves are not verified in this mode. Remove before pushing to production.", + LockMode::LOCK_WHILE_RUNNING, false) + , STORY_SECTION( + "Story Section:", + {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, + {StorySection::MAIN_STORY, "main-story", "Main Story"}}, + LockMode::LOCK_WHILE_RUNNING, StorySection::TUTORIAL + ) + , MAINSTORY_NOTE("", "Return your maxed starter from Pokemon HOME before starting Main Story. " + "Use the required moveset and the starting position shown below.") , ENABLE_ADVANCED_MODE( "Advanced mode:
" "Use checkpoints instead of segments.", @@ -334,15 +371,15 @@ BDSPAutoStory::BDSPAutoStory() ) , START_CHECKPOINT_TUTORIAL( "Start Checkpoint:", - CHECKPOINT_DATABASE(), + section_database(true, true), LockMode::LOCK_WHILE_RUNNING, "0" ) , END_CHECKPOINT_TUTORIAL( "End Checkpoint:", - CHECKPOINT_DATABASE(), + section_database(true, true), LockMode::UNLOCK_WHILE_RUNNING, - "2" + "5" ) , START_DESCRIPTION_SEGMENT("Start:
", "") @@ -353,32 +390,38 @@ BDSPAutoStory::BDSPAutoStory() , START_CHECKPOINT_MAINSTORY( "Start Checkpoint (Main):", - CHECKPOINT_DATABASE(), + section_database(true, false), LockMode::LOCK_WHILE_RUNNING, - "0" + "6" ) , END_CHECKPOINT_MAINSTORY( "End Checkpoint (Main):", - CHECKPOINT_DATABASE(), + section_database(true, false), LockMode::UNLOCK_WHILE_RUNNING, - "2" + std::to_string(ALL_CHECKPOINTS().size() - 1) ) , START_SEGMENT( "Start Segment:", - SEGMENT_DATABASE(), + section_database(false, true), LockMode::LOCK_WHILE_RUNNING, "0" ) , END_SEGMENT( "End Segment:", - SEGMENT_DATABASE(), + section_database(false, true), LockMode::UNLOCK_WHILE_RUNNING, - std::to_string(ALL_SEGMENTS().size() - 1) + "0" ) + , START_SEGMENT_MAINSTORY( + "Start Segment:", section_database(false, false), + LockMode::LOCK_WHILE_RUNNING, "1") + , END_SEGMENT_MAINSTORY( + "End Segment:", section_database(false, false), + LockMode::LOCK_WHILE_RUNNING, std::to_string(ALL_SEGMENTS().size() - 1)) , STARTERCHOICE( "Starter Pokemon:", { @@ -522,20 +565,28 @@ BDSPAutoStory::BDSPAutoStory() // Options // ----------------------------------------------------------------------- + PA_ADD_OPTION(DIAGNOSTIC_POST_CATCH_ONLY); + PA_ADD_OPTION(DIAGNOSTIC_FAKE_CHECKPOINT_SAVE); + PA_ADD_OPTION(STORY_SECTION); + PA_ADD_OPTION(MAINSTORY_NOTE); PA_ADD_OPTION(ENABLE_ADVANCED_MODE); // Segment UI PA_ADD_OPTION(START_SEGMENT); + PA_ADD_OPTION(START_SEGMENT_MAINSTORY); PA_ADD_OPTION(START_DESCRIPTION_SEGMENT); PA_ADD_OPTION(END_SEGMENT); + PA_ADD_OPTION(END_SEGMENT_MAINSTORY); PA_ADD_OPTION(END_DESCRIPTION_SEGMENT); // Checkpoint UI PA_ADD_OPTION(START_CHECKPOINT_TUTORIAL); + PA_ADD_OPTION(START_CHECKPOINT_MAINSTORY); PA_ADD_OPTION(START_DESCRIPTION_CHECKPOINT); PA_ADD_OPTION(END_CHECKPOINT_TUTORIAL); + PA_ADD_OPTION(END_CHECKPOINT_MAINSTORY); PA_ADD_OPTION(END_DESCRIPTION_CHECKPOINT); @@ -560,36 +611,32 @@ BDSPAutoStory::BDSPAutoStory() ENABLE_ADVANCED_MODE.add_listener(*this); START_SEGMENT.add_listener(*this); END_SEGMENT.add_listener(*this); - update_ui_state(); + STORY_SECTION.add_listener(*this); + START_SEGMENT_MAINSTORY.add_listener(*this); + END_SEGMENT_MAINSTORY.add_listener(*this); + START_CHECKPOINT_MAINSTORY.add_listener(*this); + END_CHECKPOINT_MAINSTORY.add_listener(*this); + on_config_value_changed(nullptr); } void BDSPAutoStory::update_ui_state(){ - bool advanced = ENABLE_ADVANCED_MODE; - START_DESCRIPTION_SEGMENT.set_visibility( - advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED - ); - END_DESCRIPTION_SEGMENT.set_visibility( - advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED - ); - - START_DESCRIPTION_CHECKPOINT.set_visibility( - advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); - END_DESCRIPTION_CHECKPOINT.set_visibility( - advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); - START_DESCRIPTION_SEGMENT.set_visibility( - advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED - ); - END_DESCRIPTION_SEGMENT.set_visibility( - advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED - ); - - START_DESCRIPTION_CHECKPOINT.set_visibility( - advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); - END_DESCRIPTION_CHECKPOINT.set_visibility( - advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); + const bool advanced = ENABLE_ADVANCED_MODE; + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; + auto visible = [](ConfigOption& option, bool show){ + option.set_visibility(show ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + }; + visible(START_SEGMENT, !advanced && tutorial); + visible(END_SEGMENT, !advanced && tutorial); + visible(START_SEGMENT_MAINSTORY, !advanced && !tutorial); + visible(END_SEGMENT_MAINSTORY, !advanced && !tutorial); + visible(START_CHECKPOINT_TUTORIAL, advanced && tutorial); + visible(END_CHECKPOINT_TUTORIAL, advanced && tutorial); + visible(START_CHECKPOINT_MAINSTORY, advanced && !tutorial); + visible(END_CHECKPOINT_MAINSTORY, advanced && !tutorial); + visible(START_DESCRIPTION_SEGMENT, !advanced); + visible(END_DESCRIPTION_SEGMENT, !advanced); + visible(START_DESCRIPTION_CHECKPOINT, advanced); + visible(END_DESCRIPTION_CHECKPOINT, advanced); + visible(MAINSTORY_NOTE, !tutorial); bool show_help = ((HelpMode)HELP_MODE == HelpMode::OUTSIDE_HELP); @@ -603,28 +650,12 @@ void BDSPAutoStory::update_ui_state(){ OUTSIDE_HELP_ATTACK_4.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); } void BDSPAutoStory::on_config_value_changed(void*){ - bool advanced = ENABLE_ADVANCED_MODE; - - // Segments - START_SEGMENT.set_visibility( - advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED - ); - END_SEGMENT.set_visibility( - advanced ? ConfigOptionState::HIDDEN : ConfigOptionState::ENABLED - ); - - // Checkpoints (tutorial for now) - START_CHECKPOINT_TUTORIAL.set_visibility( - advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); - END_CHECKPOINT_TUTORIAL.set_visibility( - advanced ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; if (ENABLE_ADVANCED_MODE){ const auto& checkpoints = ALL_CHECKPOINTS(); - size_t start = START_CHECKPOINT_TUTORIAL.index(); - size_t end = END_CHECKPOINT_TUTORIAL.index(); + size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); if (start < checkpoints.size()){ START_DESCRIPTION_CHECKPOINT.set_text( @@ -640,8 +671,8 @@ void BDSPAutoStory::on_config_value_changed(void*){ }else{ const auto& segments = ALL_SEGMENTS(); - size_t start = START_SEGMENT.index(); - size_t end = END_SEGMENT.index(); + size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); if (start < segments.size()){ START_DESCRIPTION_SEGMENT.set_text( @@ -680,16 +711,27 @@ void BDSPAutoStory::program( (TorterraMove)OUTSIDE_HELP_ATTACK_3, (TorterraMove)OUTSIDE_HELP_ATTACK_4, } - } + }, + DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL }; - const auto& segments = ALL_SEGMENTS(); - + // DIAGNOSTIC ONLY: Remove this branch before pushing to production. + if (DIAGNOSTIC_POST_CATCH_ONLY && ENABLE_ADVANCED_MODE && + STORY_SECTION == StorySection::TUTORIAL && START_CHECKPOINT_TUTORIAL.slug() == "4"){ + env.log("Diagnostic only: Running checkpoint 00.04 after catch."); + diagnostic_checkpoint_0004_after_catch(env, context, options, stats); + env.log("Diagnostic continuation: Running checkpoint 00.05."); + AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + return; + } + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; + const auto& segments = ALL_SEGMENTS(); if (ENABLE_ADVANCED_MODE){ - size_t start = START_CHECKPOINT_TUTORIAL.index(); - size_t end = END_CHECKPOINT_TUTORIAL.index(); + size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); env.log("Starting AutoStory from checkpoint " + std::to_string(start) + " to " + std::to_string(end)); @@ -702,8 +744,8 @@ void BDSPAutoStory::program( } }else{ - size_t start = START_SEGMENT.index(); - size_t end = END_SEGMENT.index(); + size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); for (size_t i = start; i <= end && i < segments.size(); i++){ env.log("Running Segment: " + std::to_string(i)); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h index b4d6b0a9fc..736864da09 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h @@ -14,6 +14,8 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +enum class StorySection { TUTORIAL, MAIN_STORY }; + class BDSPAutoStory_Descriptor : public SingleSwitchProgramDescriptor{ public: BDSPAutoStory_Descriptor(); @@ -38,6 +40,12 @@ class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::L virtual void on_config_value_changed(void* object) override; void update_ui_state(); private: + // DIAGNOSTIC ONLY: Remove before pushing to production. + BooleanCheckBoxOption DIAGNOSTIC_POST_CATCH_ONLY; + // DIAGNOSTIC ONLY: Remove before pushing to production. + BooleanCheckBoxOption DIAGNOSTIC_FAKE_CHECKPOINT_SAVE; + EnumDropdownOption STORY_SECTION; + StaticTextOption MAINSTORY_NOTE; BooleanCheckBoxOption ENABLE_ADVANCED_MODE; StringSelectOption START_CHECKPOINT_TUTORIAL; @@ -52,6 +60,8 @@ class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::L StringSelectOption END_CHECKPOINT_MAINSTORY; StringSelectOption START_SEGMENT; StringSelectOption END_SEGMENT; + StringSelectOption START_SEGMENT_MAINSTORY; + StringSelectOption END_SEGMENT_MAINSTORY; EnumDropdownOption STARTERCHOICE; BooleanCheckBoxOption STARTER_SHINY; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 2609f0f1a9..76a3f0a46f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -107,6 +107,10 @@ bool save_game( // Return to overworld cleanly. pbf_mash_button(context, BUTTON_B, 1000ms); + // Drain R/confirm/B before detection; an earlier overworld frame + // must not report success while saving commands are still queued. + context.wait_for_all_requests(); + // Verify overworld. Generous timeout: save animation + fade can be slow. OverworldWatcher overworld; int ret = wait_until( @@ -155,9 +159,13 @@ void checkpoint_save( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EventNotificationOption& notif_status_update, - AutoStoryStats& stats + AutoStoryStats& stats, + CheckpointSaveMode save_mode ){ - bool success = fake_save_game(env, context); + const bool fake = save_mode == CheckpointSaveMode::FAKE; + env.console.log(fake ? "[Checkpoint save] DIAGNOSTIC FAKE mode: save is not verified." + : "[Checkpoint save] REAL mode."); + bool success = fake ? fake_save_game(env, context) : save_game(env, context); stats.m_checkpoint++; env.update_stats(); @@ -165,7 +173,7 @@ void checkpoint_save( send_program_status_notification( env, notif_status_update, - success + fake ? "Diagnostic fake checkpoint save (not verified)." : success ? "Saved at checkpoint (verified)." : "Saved at checkpoint (blind fallback)." ); @@ -190,7 +198,8 @@ void checkpoint_reattempt_loop( EventNotificationOption& notif_status_update, AutoStoryStats& stats, std::function&& action, - bool save + bool save, + CheckpointSaveMode save_mode ){ static constexpr size_t MAX_ATTEMPTS = 20; @@ -198,7 +207,7 @@ void checkpoint_reattempt_loop( try{ if (i == 0){ if (save || stats.m_first_run){ - checkpoint_save(env, context, notif_status_update, stats); + checkpoint_save(env, context, notif_status_update, stats, save_mode); } stats.m_first_run = false; } @@ -848,157 +857,96 @@ bool fly_to( // // --------------------------------------------------------------------------- -void use_potion_first_pokemon( - VideoStream & stream, - ProControllerContext & context -) { - stream.log( - "[AutoStory] Starting arrow-based potion helper...", - COLOR_BLUE - ); - pbf_press_button( - context, - BUTTON_X, - 160ms, - 1500ms - ); - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - // ------------------------------------------------------------ - // Move from item pane to pokemon pane. - // ------------------------------------------------------------ - - pbf_press_dpad( - context, - DPAD_LEFT, - 100ms, - 500ms - ); - - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - - // ------------------------------------------------------------ - // Select pokemon menu pokemon. - // ------------------------------------------------------------ - - pbf_press_button( - context, - BUTTON_A, - 160ms, - 1500ms - ); - - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - - // ------------------------------------------------------------ - // Select first pokemon. - // ------------------------------------------------------------ - - pbf_press_button( - context, - BUTTON_A, - 160ms, - 1500ms - ); - - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - - - // ------------------------------------------------------------ - // Move from "Check summary" to "Restore". - // ------------------------------------------------------------ - - pbf_press_dpad( - context, - DPAD_DOWN, - 80ms, - 250ms - ); - - pbf_press_dpad( - context, - DPAD_DOWN, - 80ms, - 500ms - ); - - // ------------------------------------------------------------ - // Mash A. - // ------------------------------------------------------------ - - stream.log( - "[AutoStory] Mashing A...", - COLOR_BLUE - ); - - pbf_mash_button( - context, - BUTTON_A, - 3000ms - ); - - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - - // ------------------------------------------------------------ - // Mash B to fully exit. - // ------------------------------------------------------------ - - stream.log( - "[AutoStory] Exiting menus...", - COLOR_BLUE - ); - - pbf_mash_button( - context, - BUTTON_B, - 3000ms - ); - - context.wait_for_all_requests(); - pbf_wait(context, 3000ms); - // ------------------------------------------------------------ - // open menu and reset cursor to bag. - // ------------------------------------------------------------ - stream.log( - "[AutoStory] Exiting menus...", - COLOR_BLUE - ); - - pbf_press_button( - context, - BUTTON_X, - 160ms, - 1500ms - ); +// Slot 1's colored HP bar in the Bag. Read fill position rather than OCR. +static double potion_hp_fraction(const ImageViewRGB32& frame) { + if (bag_pocket_position(frame) == 0) return -1; + const ImageViewRGB32 bar = extract_box_reference(frame, ImageFloatBox{0.137, 0.160, 0.202, 0.016}); + size_t filled = 0; + for (size_t x = 0; x < bar.width(); x++) { + size_t color = 0; + for (size_t y = 0; y < bar.height(); y++) { + uint32_t p = bar.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + const bool green = g > r + 30 && g > b + 20; + const bool yellow = r > 150 && g > 120 && b < 100; + const bool red = r > 150 && r > g + 50 && r > b + 50; + color += green || yellow || red; + } + filled += color > bar.height() / 4; + } + return bar.width() ? (double)filled / bar.width() : -1; +} - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); +static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& context) { + double previous = -1; + size_t stable = 0; + for (size_t check = 0; check < 20; check++) { + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + double hp = frame ? potion_hp_fraction(frame) : -1; + if (hp >= 0 && previous >= 0 && std::abs(hp - previous) <= 0.015) { + if (++stable >= 3) return hp; + } else stable = 0; + previous = hp; + pbf_wait(context, 150ms); + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Slot 1 HP bar could not be confirmed.", stream); +} - pbf_press_dpad( - context, - DPAD_RIGHT, - 100ms, - 500ms - ); - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - pbf_press_button( - context, - BUTTON_X, - 160ms, - 1500ms - ); +void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context) { + OverlayBoxScope hp_overlay(stream.overlay(), COLOR_GREEN, {0.137, 0.160, 0.202, 0.016}, ""); + BagItemTemplate potion; + potion.reference_path = "PokemonBDSP/Autostory/Bag_Potion.png"; + potion.label = "Potion"; + potion.reference_row = 1; + bool healed = false; + for (size_t dose = 0; dose < 3; dose++) { + stream.log("[Potion] Finding Bag/Medicine/Potion; dose " + std::to_string(dose + 1) + "/3."); + if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Could not verify Potion in the Medicine pocket.", stream); + } + const double before = confirmed_potion_hp(stream, context); + stream.log("[Potion] Slot 1 HP bar before use=" + std::to_string(before * 100) + "%."); + if (before >= 0.98) { healed = true; break; } + if (before <= 0.005) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Slot 1 appears fainted; a Potion cannot revive it.", stream); + } + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + OverworldWatcher overworld; + if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Did not return to the overworld after use.", stream); + } + // Reopen Bag to verify the result, including when the last Potion was used. + open_menu(stream, context, MenuCursorPosition::BAG, 5); + pbf_wait(context, 750ms); + context.wait_for_all_requests(); + const double after = confirmed_potion_hp(stream, context); + stream.log("[Potion] Slot 1 HP bar after use=" + std::to_string(after * 100) + "%."); + if (after <= before + 0.005) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Slot 1 HP did not increase; refusing to continue movement.", stream); + } + if (after >= 0.98) { healed = true; break; } + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + } + if (!healed) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Full HP not confirmed after three uses.", stream); + pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - - stream.log( - "[AutoStory] Potion helper complete.", - COLOR_GREEN - ); + OverworldWatcher overworld; + if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Potion: Final overworld return not confirmed.", stream); + stream.log("[Potion] Slot 1 HP bar full and overworld confirmed."); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 8d6cb72937..986c3a005f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -102,12 +102,15 @@ struct OutsideHelpConfig{ TorterraMove attacks[4]; }; +enum class CheckpointSaveMode { REAL, FAKE }; + struct AutoStoryOptions{ Language language; StarterChoice starter_choice; EventNotificationOption& notif_status_update; HelpMode help_mode; OutsideHelpConfig outside_help; + CheckpointSaveMode checkpoint_save_mode = CheckpointSaveMode::REAL; }; @@ -167,7 +170,8 @@ void checkpoint_save( SingleSwitchProgramEnvironment& env, ProControllerContext& context, EventNotificationOption& notif_status_update, - AutoStoryStats& stats + AutoStoryStats& stats, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL ); // Reload the game from the most recent backup save (auto-save). @@ -188,9 +192,20 @@ void checkpoint_reattempt_loop( EventNotificationOption& notif_status_update, AutoStoryStats& stats, std::function&& action, - bool save = true + bool save = true, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL ); +// Forward the explicit checkpoint save mode selected by AutoStory's options. +inline void checkpoint_reattempt_loop( + SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats, + std::function&& action, bool save = true +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + std::move(action), save, options.checkpoint_save_mode); +} + // Execute every action in `actions` in order, repeated `times` times. void repeat(std::initializer_list> actions, size_t times); @@ -307,6 +322,9 @@ bool use_escape_rope( int num_icons = 8 ); +// Reuse the tutorial's verified Jubilife Center alignment and entry. +void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context); + // Open the bag, navigate to the repel compartment tab, and use a repel. // Returns false if the repel compartment tab could not be found. // num_icons: 5 (early-game layout) or 8 (late-game layout). diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 41baf16203..2f877658d7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -7,7 +7,14 @@ #include "PokemonBDSP_AutoStory_Segment_00.h" #include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" - +#include "../Utils/PokemonBDSP_OverworldMatchers.h" + +#include +#include +#include +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" @@ -18,6 +25,7 @@ #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP/Programs/ShinyHunting/PokemonBDSP_StarterReset.h" #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" @@ -33,6 +41,34 @@ namespace PokemonAutomation { // Starter selection // --------------------------------------------------------------------------- + // Watch before movement starts, release on the fade, and hold the + // following recorded wait until the destination is visible again. + static void recorded_move_until_transition( + VideoStream& stream, ProControllerContext& context, + double x, double y, Milliseconds duration, const std::string& label + ) { + context.wait_for_all_requests(); + stream.log("[Tutorial transition] " + label + ": approaching fade."); + BlackScreenWatcher black; + int ret = run_until(stream, context, + [x, y, duration](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + // Keep observing a delayed fade after the recorded hold. + pbf_wait(ctx, 5000ms); + }, {{black}}); + if (ret != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Black transition not detected.", stream); + } + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, + 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Transition did not finish.", stream); + } + stream.log("[Tutorial transition] " + label + ": destination visible."); + } + static void select_turtwig(VideoStream& stream, ProControllerContext& context) { stream.log("[DEBUG] Turtwig Selection: Starting starter selection"); @@ -131,7 +167,7 @@ namespace PokemonAutomation { // They may also be modified or removed without warning. // --------------------------------------------------------------------------- - // Segment 00-000 begins + // Segment 00-01 begins // Navigates from player's room to Route 201 through Mom/Barry dialogues. // --------------------------------------------------------------------------- @@ -184,8 +220,31 @@ namespace PokemonAutomation { pbf_wait(context, 800ms); pbf_wait(context, 1306ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); - pbf_wait(context, 1422ms); + context.wait_for_all_requests(); + stream.log("[Barry house] Entering; waiting for entry fade."); + BlackScreenWatcher entry_black; + int entry_ret = run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0, 1}, 5000ms, 0ms); + }, {{entry_black}}); + if (entry_ret != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Barry house: Entry fade not detected.", stream); + } + // Release the joystick during loading; resume indoor movement only + // after the screen has stayed non-black for half a second. + BlackScreenWatcher entry_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, + 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{entry_visible}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Barry house: Entry fade did not finish.", stream); + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + stream.log("[Barry house] Entry settled; starting stair approach."); + // Align left after entry before the next upward stair approach. + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 400ms, 0ms); + pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 1600ms, 0ms); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2722ms, 0ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1600ms, 0ms); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 501ms, 0ms); @@ -198,13 +257,25 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_A, 10000ms); pbf_wait(context, 856ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 920ms, 0ms); - pbf_wait(context, 631ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 2183ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1076ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 781ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2713ms, 0ms); + + + // Recorded route from the top of Barry's stairs. Moves rounded to + // 100ms, waits to 50ms; transition waits gate both floor changes. + recorded_move_until_transition(stream, context, -1.0, 0.0, 900ms, + "Barry house downstairs"); + pbf_wait(context, 750ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 2000ms, 0ms); + pbf_wait(context, 250ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 2000ms, 0ms); + pbf_wait(context, 150ms); + pbf_move_left_joystick(context, {1.0, 0.0}, 1400ms, 0ms); + pbf_wait(context, 500ms); + recorded_move_until_transition(stream, context, 0.0, -1.0, 1200ms, + "Barry house exit"); + // First-run video placed the player east of the northbound path. + // Shorten the rightward approach; tune further from the next recording. + stream.log("[Route 201] Aligning east for 2300ms before northbound approach."); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2300ms, 0ms); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5861ms, 0ms); pbf_wait(context, 134ms); @@ -225,11 +296,15 @@ namespace PokemonAutomation { pbf_wait(context, 1000ms); - save_game(stream, context); + // save_game(stream, context); // Disabled: checkpoint save mode controls saving. context.wait_for_all_requests(); } // --------------------------------------------------------------------------- - // Segment 00-001 begins + // End of Segment 00 - 01 + // Standing in front lake varity ready to enter lake Verity for starter selection. + // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Segment 00-02 begins // Navigates into lake Verity to recieve starter // --------------------------------------------------------------------------- static void starter_nav(VideoStream& stream, ProControllerContext& context) { @@ -283,7 +358,11 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 60000ms); } // --------------------------------------------------------------------------- - // Segment 00-002 begins + // End of Segment 00 - 02 + // Standing in front lake varity after starter selection. + // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Segment 00-03 begins // Navigates from Exit of lake Verity to receipt of the Pokedex // --------------------------------------------------------------------------- @@ -314,9 +393,9 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 745ms, 0ms); DateTime t{ 2026, 1, 1, 0, 0, 50 }; set_time_fast(env, context, t); - walk_right_until_on_path(stream, context, BattleType::STARTER); - walk_up_through_grass_2(stream, context, BattleType::STARTER); - walk_right_through_grass_2(stream, context, BattleType::STARTER); + walk_through_grass(stream, context, 1, 0, 20, "right through grass to path", BattleType::STARTER); + walk_through_grass(stream, context, 0, 1, 18, "up through grass 2", BattleType::STARTER); + walk_through_grass(stream, context, 1, 0, 10, "right through grass 2", BattleType::STARTER); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 730ms, 0ms); pbf_wait(context, 941ms); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1950ms, 0ms); @@ -333,12 +412,12 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- - // End of Segment 00 - 002 + // End of Segment 00 - 03 // Standing in front of the professor after receiving the pokedex. // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - // Begin Segment 00 - 003 + // Begin Segment 00 - 04 // Will heal at Sandgem pokeCenter to reliably catch a second pokemon for Home // transfer. // Collects poketech and stands in front of the PC to end the tutorial section @@ -346,7 +425,11 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - /*pbf_move_left_joystick(context, {0.000000, -1.000000}, 2010ms, 0ms); + // Arm exit detection before movement and finish the fade before + // starting the dialogue watcher, which cancels queued commands. + recorded_move_until_transition(stream, context, 0.0, -1.0, 2010ms, + "Rowan lab exit"); + context.wait_for_all_requests(); wait_for_dialogue(stream, context, "Phase 14 (dawn tour)"); @@ -405,9 +488,9 @@ namespace PokemonAutomation { DateTime t{ 2026, 1, 1, 0, 0, 50 }; set_time_fast(env, context, t); - walk_right_until_on_path(stream, context, BattleType::STARTER); - walk_up_through_grass_2(stream, context, BattleType::STARTER); - walk_right_through_grass_2(stream, context, BattleType::STARTER); + walk_through_grass(stream, context, 1, 0, 20, "right through grass to path", BattleType::STARTER); + walk_through_grass(stream, context, 0, 1, 18, "up through grass 2", BattleType::STARTER); + walk_through_grass(stream, context, 1, 0, 10, "right through grass 2", BattleType::STARTER); context.wait_for_all_requests(); pbf_wait(context, 1500ms); @@ -429,17 +512,105 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 70000ms); context.wait_for_all_requests(); - pbf_wait(context, 2000ms);*/ + pbf_wait(context, 2000ms); catch_1_pokemon(stream, context); pbf_wait(context, 1500ms); context.wait_for_all_requests(); - save_game(stream, context); + } + + // Align to a route reference before continuing recorded navigation. + // Matcher offsets and thresholds are fractions of screen width/height. + static void align_to_tutorial_landmarks( + VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label + ) { + context.wait_for_all_requests(); + pbf_wait(context, 700ms); context.wait_for_all_requests(); + size_t confirmations = 0, coarse_confirmations = 0; + double previous_dx = 0, previous_dy = 0; + for (size_t attempt = 0; attempt < 60; attempt++) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + const auto result = matcher(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[" + label + "] " + landmark.name + + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + + " accepted=" + std::to_string(landmark.accepted) + + " screen-relative offset=(" + std::to_string(landmark.dx) + + "," + std::to_string(landmark.dy) + ")"); + } + stream.log("[" + label + "] located=" + std::to_string(result.located) + + " mode=" + (result.coarse ? "vertical acquisition" : "fine alignment") + + " reference screen-relative offset=(" + std::to_string(result.dx) + + "," + std::to_string(result.dy) + ")"); + if (result.coarse && result.located) { + if (coarse_confirmations && std::abs(result.dx-previous_dx)<=0.00625 && + std::abs(result.dy-previous_dy)<=1.0/90) coarse_confirmations++; + else coarse_confirmations=1; + previous_dx=result.dx; + previous_dy=result.dy; + } else coarse_confirmations=0; + if (!result.located) { + confirmations = 0; + pbf_wait(context, 250ms); + } else if (result.coarse && coarse_confirmations<3) { + confirmations = 0; + stream.log("[" + label + "] Waiting for consistent vertical acquisition."); + pbf_wait(context, 250ms); + } else if (result.at_target) { + if (++confirmations >= 3) { + stream.log("[" + label + "] Reference position confirmed."); + return; + } + pbf_wait(context, 300ms); + } else { + confirmations = 0; + const bool horizontal = !result.coarse && + std::abs(result.dx) >= std::abs(result.dy)*9.0/16; + const double error = horizontal ? result.dx : result.dy*9.0/16; + const auto duration = std::abs(error) > 1.0/24 ? 80ms : + std::abs(error) > 0.015625 ? 40ms : 16ms; + stream.log("[" + label + "] Correcting " + + std::string(horizontal ? "horizontal" : "vertical") + " axis " + + (horizontal ? (result.dx>0 ? "right" : "left") : (result.dy<0 ? "up" : "down")) + + " for " + std::to_string(duration.count()) + "ms."); + // Reuse confirmed-Run encounter handling if correction enters grass. + walk_through_grass(stream, context, + horizontal ? (result.dx > 0 ? 1 : -1) : 0, + horizontal ? 0 : (result.dy < 0 ? 1 : -1), + 1, label, BattleType::STANDARD, duration); + } + context.wait_for_all_requests(); + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Reference position not confirmed; route movement stopped.", stream); + } + + static void align_to_sandgem_junction(VideoStream& stream, ProControllerContext& context) { + align_to_tutorial_landmarks(stream, context, match_sandgem_junction, "Sandgem junction"); + } + + // Resumes from the save made after catching the second Pokemon. + static void tutorial_part_3_after_catch_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { pbf_wait(context, 1500ms); //-------------------------------------------------------- // To get potions // ------------------------------------------------------- - walk_right_through_grass_2(stream, context); + walk_through_grass(stream, context, 1, 0, 10, "Route 202 grass exit", + BattleType::STANDARD, 200ms, match_route202_grass_exit); + // Recording: the exit reference was reached after two 200ms steps. + // Eight further steps (1600ms) reached the eastern tree boundary. + // Continue outside grass to preserve the next down movement's start. + stream.log("[Tutorial] Route 202 grass exit confirmed; continuing right to the tree boundary."); + pbf_move_left_joystick(context, {1.0, 0.0}, 1600ms, 0ms); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1421ms, 0ms); pbf_wait(context, 52ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1013ms, 0ms); @@ -451,14 +622,26 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 856ms, 0ms); context.wait_for_all_requests(); pbf_wait(context, 1500ms); - walk_left_through_grass_3(stream, context); + walk_through_grass(stream, context, -1, 0, 18, "left through grass 3"); pbf_press_button(context, BUTTON_A, 180ms, 0ms); wait_for_dialogue(stream, context, "Phase 17 (accept potions)"); pbf_mash_button(context, BUTTON_B, 10000ms); - - walk_right_through_grass_2(stream, context); + context.wait_for_all_requests(); + // Catching the second Pokemon can damage the starter. Verify healing + // once Potions are available, before continuing toward Tristan. + stream.log("[Tutorial] Checking starter HP after the first Potion pickup."); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + // Start with one training encounter toward level 6 / Ember. The helper + // waits through XP/automatic move learning and heals after the fight. + battle_1_pokemon(stream, context); + + // Stop on the verified Route 201 reference instead of overshooting + // with ten fixed steps. The limit also allows small final corrections. + walk_through_grass(stream, context, 1, 0, 20, "Route 201 Potion exit", + BattleType::STANDARD, 200ms, match_route201_potion_exit); context.wait_for_all_requests(); pbf_wait(context, 1500ms); @@ -471,6 +654,7 @@ namespace PokemonAutomation { pbf_wait(context, 1500ms); pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1000ms, 0ms); pbf_wait(context, 1500ms); + align_to_sandgem_junction(stream, context); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1855ms, 0ms); pbf_wait(context, 1500ms); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 430ms, 0ms); @@ -480,8 +664,8 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1120ms, 0ms); // Continue on to jubilife - walk_left_through_grass_3(stream, context); - walk_up_through_grass_3(stream, context); + walk_through_grass(stream, context, -1, 0, 18, "left through grass 3"); + walk_through_grass(stream, context, 0, 1, 18, "up through grass 3"); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1000ms, 0ms); wait_for_dialogue(stream, context, "Phase 18 (tristan encounter dialog)"); @@ -530,16 +714,178 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 5000ms); } // --------------------------------------------------------------------------- - // End of Segment 00 - 003 + // End of Segment 00 - 04 // Standing in Jubilife after speaking to dawn. // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - // Begin Segment 00 - 004 + // Begin Segment 00 - 05 // Standing at the entrance to jubilife after speaking to dawn, will navigate to the poketech challenge. // Collects poketech and stands in front of the PC to end the tutorial section // and send the starter to pokemon HOME. // --------------------------------------------------------------------------- + // Stop the approach as soon as buildings can guide it, instead of + // overshooting south and then blindly walking back north. + static void approach_poketch_landmarks(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + Milliseconds walked = 0ms; + while (walked < 1900ms) { + auto frame = stream.video().snapshot(); + if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Poketch approach: No video frame.", stream); + const auto result = match_poketch_landmarks(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Poketch approach] " + landmark.name + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin)); + } + if (result.located) { + stream.log("[Poketch approach] Landmarks acquired after " + std::to_string(walked.count()) + "ms; ending downward approach."); + return; + } + const Milliseconds step = std::min(200ms, 1900ms - walked); + pbf_move_left_joystick(context, {0, -1}, step, 0ms); + walked += step; + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + } + stream.log("[Poketch approach] Reached 1900ms downward limit; handing off to locator."); + } + + static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmed = 0, coarse_confirmed = 0; + double previous_dx = 0, previous_dy = 0; + size_t acquisition_misses = 0, vertical_probe = 0; + // Two small excursions around the approach position. This is + // visibility recovery, not permission to interact without a match. + const int vertical_probes[] = {-80, 160, -240, 320, -160, -120, 240, -360, 480, -240}; + for (size_t attempt = 0; attempt < 80; attempt++) { + auto frame = stream.video().snapshot(); + if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Poketch interaction: No video frame.", stream); + const OverworldMatchResult result = match_poketch_landmarks(*frame.frame); + stream.log(result.coarse ? "[Poketch landmarks] Coarse acquisition." : "[Poketch landmarks] Fine alignment."); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Poketch landmarks] " + landmark.name + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + + " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + if (result.located) acquisition_misses = 0; + if (result.coarse && result.located) { + if (coarse_confirmed && std::abs(result.dx - previous_dx) <= 0.00625 && std::abs(result.dy - previous_dy) <= 1.0/90) + coarse_confirmed++; + else coarse_confirmed = 1; + previous_dx = result.dx; previous_dy = result.dy; + } else coarse_confirmed = 0; + if (!result.located) { + confirmed = 0; + if (++acquisition_misses >= 4 && vertical_probe < sizeof(vertical_probes) / sizeof(vertical_probes[0])) { + int probe = vertical_probes[vertical_probe++]; + acquisition_misses = 0; + stream.log("[Poketch landmarks] Bounded vertical visibility probe " + std::to_string(vertical_probe) + + "/10: " + (probe < 0 ? "down " : "up ") + std::to_string(std::abs(probe)) + "ms."); + pbf_move_left_joystick(context, {0, probe < 0 ? -1.0 : 1.0}, Milliseconds(std::abs(probe)), 0ms); + pbf_wait(context, 700ms); + } else { + stream.log("[Poketch landmarks] No trustworthy acquisition; waiting for another frame."); + pbf_wait(context, 250ms); + } + } else if (result.coarse && coarse_confirmed < 3) { + confirmed = 0; + stream.log("[Poketch landmarks] Waiting for consistent coarse offset before movement."); + pbf_wait(context, 250ms); + } else if (result.at_target) { + if (++confirmed >= 3) { + stream.log("[Poketch landmarks] Above-NPC reference confirmed; turning down."); + pbf_press_dpad(context, DPAD_DOWN, 40ms, 400ms); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Phase 27 (poketech challenge completed dialog)", 10s); + return; + } + pbf_wait(context, 300ms); + } else { + confirmed = 0; + const double dx = result.dx, dy = result.dy; + stream.log("[Poketch landmarks] Reference screen-relative offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")."); + const bool horizontal = std::abs(dx) > 7.0/960 && + (std::abs(dy) <= 7.0/540 || std::abs(dx) >= std::abs(dy)*9.0/16); + double error = horizontal ? dx : dy*9.0/16; + auto duration = std::abs(error) > 0.03125 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + if (std::abs(dx) <= 7.0/960 && std::abs(dy) <= 7.0/540) { + stream.log("[Poketch landmarks] Coarse position near target; waiting for fine confirmation."); + pbf_wait(context, 300ms); + context.wait_for_all_requests(); + continue; + } + stream.log("[Poketch landmarks] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + + " axis for " + std::to_string(duration.count()) + "ms."); + pbf_move_left_joystick(context, + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, + horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, duration, 0ms); + pbf_wait(context, 700ms); + } + context.wait_for_all_requests(); + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Poketch interaction: Building landmarks did not confirm the reference position; A not pressed.", stream); + } + + void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmed = 0; + for (size_t attempt = 0; attempt < 60; attempt++) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Jubilife Center alignment: No video frame.", stream); + const auto result = match_jubilife_center_entrance(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Jubilife Center alignment] score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + " screen-relative offset=(" + + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + if (!result.located) { + confirmed = 0; + pbf_wait(context, 250ms); + } else if (result.at_target) { + if (++confirmed >= 3) { + stream.log("[Jubilife Center alignment] Door position confirmed; entering."); + recorded_move_until_transition(stream, context, 0, 1, 2000ms, "Jubilife Pokemon Center entry"); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + return; + } + pbf_wait(context, 300ms); + } else { + confirmed = 0; + bool horizontal = std::abs(result.dx) >= std::abs(result.dy)*9.0/16; + double error = horizontal ? result.dx : result.dy*9.0/16; + const auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + pbf_move_left_joystick(context, + {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, + horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0)}, duration, 0ms); + pbf_wait(context, 700ms); + } + context.wait_for_all_requests(); + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Jubilife Center alignment: Door reference not confirmed; entry not attempted.", stream); + } + static void tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2580ms, 0ms); pbf_wait(context, 504ms); @@ -665,33 +1011,10 @@ namespace PokemonAutomation { pbf_wait(context, 2521ms); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3600ms, 0ms); pbf_wait(context, 610ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1907ms, 0ms); - pbf_wait(context, 529ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 275ms, 0ms); - pbf_wait(context, 3600ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 899ms, 0ms); - pbf_wait(context, 1308ms); + approach_poketch_landmarks(stream, context); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 192ms, 0ms); pbf_wait(context, 3238ms); - pbf_press_dpad(context, DPAD_UP, 108ms, 0ms); - pbf_wait(context, 433ms); - pbf_press_dpad(context, DPAD_UP, 260ms, 0ms); - pbf_wait(context, 365ms); - pbf_press_dpad(context, DPAD_UP, 187ms, 0ms); - pbf_wait(context, 542ms); - pbf_press_dpad(context, DPAD_UP, 243ms, 0ms); - pbf_wait(context, 370ms); - pbf_press_dpad(context, DPAD_UP, 217ms, 0ms); - pbf_wait(context, 495ms); - pbf_press_dpad(context, DPAD_UP, 219ms, 0ms); - pbf_wait(context, 572ms); - pbf_press_dpad(context, DPAD_UP, 231ms, 0ms); - pbf_wait(context, 1757ms); - pbf_press_dpad(context, DPAD_RIGHT, 115ms, 0ms); - pbf_press_button(context, BUTTON_A, 150ms, 0ms); - pbf_wait(context, 795ms); - context.wait_for_all_requests(); - wait_for_dialogue(stream, context, "Phase 27 (poketech challenge completed dialog)"); + align_and_talk_to_poketch_man(stream, context); pbf_mash_button(context, BUTTON_B, 15000ms); context.wait_for_all_requests(); @@ -703,8 +1026,7 @@ namespace PokemonAutomation { pbf_wait(context, 1522ms); pbf_press_dpad(context, DPAD_RIGHT, 144ms, 0ms); pbf_wait(context, 1767ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1344ms, 0ms); - pbf_wait(context, 3428ms); + align_and_enter_jubilife_center(stream, context); pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1099ms, 0ms); pbf_wait(context, 1222ms); pbf_press_dpad(context, DPAD_RIGHT, 228ms, 0ms); @@ -718,7 +1040,7 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 642ms, 0ms); } // --------------------------------------------------------------------------- - // Segment 00-04 ends + // Segment 00-05 ends // Standing in front of the Jubilife City Pokemon Center PC. // Player will need to deposit pokemon in the pc and transfer to Pokemon Home // and level to 100 before starting the next segment. @@ -743,6 +1065,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); env.console.log("Start Segment " + name(), COLOR_ORANGE); + AutoStory_Checkpoint_0000().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0001().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0002().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0003().run_checkpoint(env, context, options, stats); @@ -753,13 +1076,26 @@ namespace PokemonAutomation { env.console.log("End Segment " + name(), COLOR_GREEN); } + std::string AutoStory_Checkpoint_0000::name() const { return "00.00 - Verify Settings"; } + std::string AutoStory_Checkpoint_0000::start_text() const { return "Standing in the initial bedroom before beginning the tutorial."; } + std::string AutoStory_Checkpoint_0000::end_text() const { return "Required game settings verified and saved; ready for 00.01."; } + void AutoStory_Checkpoint_0000::run_checkpoint( + SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats + ) const { + // Before any route movement or checkpoint save: fail visibly if + // options cannot be read, rather than navigate with unknown settings. + verify_tutorial_settings(env.console, context); + context.wait_for_all_requests(); + } + static void checkpoint_0001( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // Segment 00-00: Intro and starter selection tutorial_part_1_navigation(env, env.console, context); @@ -773,7 +1109,7 @@ namespace PokemonAutomation { AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // Segment 00-01: Starter acquisition starter_nav(env.console, context); @@ -795,7 +1131,7 @@ namespace PokemonAutomation { AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // Segment 00-002: Pokedex acquisition tutorial_part_2_navigation(env, env.console, context); @@ -809,21 +1145,43 @@ namespace PokemonAutomation { AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // Segment 00-003: Poketech acquisition tutorial_part_3_navigation(env, env.console, context); }, true ); + + // Save and advance progress at the catch boundary. Retries of this + // section must start from the caught-Pokemon save, not the old route. + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + tutorial_part_3_after_catch_navigation(env, env.console, context); + }, + true + ); + } + // DIAGNOSTIC ONLY: Remove before pushing to production. + void diagnostic_checkpoint_0004_after_catch( + SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + tutorial_part_3_after_catch_navigation(env, env.console, context); + }, + true + ); } + static void checkpoint_0005( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // Segment 00-004: Poketech acquisition tutorial_part_4_navigation(env, env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h index 806e3889ba..3d6b73b7a5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h @@ -15,6 +15,13 @@ namespace NintendoSwitch{ namespace PokemonBDSP{ +// DIAGNOSTIC ONLY: Remove before pushing to production. +// Starts at the post-catch save and runs only the rest of checkpoint 00.04. +void diagnostic_checkpoint_0004_after_catch( + SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats +); + class AutoStory_Segment_00 : public AutoStory_Segment{ public: virtual std::string name() const override; @@ -28,6 +35,19 @@ class AutoStory_Segment_00 : public AutoStory_Segment{ ) const override; }; +class AutoStory_Checkpoint_0000 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint{ public: virtual std::string name() const override; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index b41ff215cb..1740c14a4a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -54,7 +54,7 @@ namespace PokemonAutomation { AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { jubilife_to_oreburgh_gate(env, env.console, context); oreburgh_gate_nav(env, env.console, context); @@ -87,9 +87,9 @@ namespace PokemonAutomation { AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { - //oreburgh_gate_to_mine(env, env.console, context); + oreburgh_gate_to_mine(env, env.console, context); oreburgh_mine_nav(env.console, context); // Standalone test entry points are not part of the checkpoint route. // oreburgh_mine_first_leg_test(env.console, context); @@ -125,7 +125,7 @@ namespace PokemonAutomation { AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { //#. Oreburgh Gym navigation and battle gym_Puzzle_01(env, env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 12d9282b00..a5ac3f311b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1,4 +1,4 @@ -/* BDSP AutoStory - Segment 00 Tools +/* BDSP AutoStory - Segment 00 Tools * * Helper implementations for Segment 00. * @@ -7,13 +7,19 @@ */ +#include +#include #include +#include + #include "PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP_Autostory_Segment_01_tools.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" @@ -117,7 +123,8 @@ namespace PokemonAutomation { "[AutoStory] Clown helper timed out finding dialog.", COLOR_RED ); - return; + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "[AutoStory] Clown helper timed out finding dialog.", stream); } context.wait_for_all_requests(); @@ -153,6 +160,403 @@ namespace PokemonAutomation { // Time helper // --------------------------------------------------------------------------- + // Options chrome and position detectors are independent of setting values. + class TutorialOptionsDetector { + public: + bool detect(const ImageViewRGB32& screen) const { + ImageViewRGB32 panel = extract_box_reference(screen, ImageFloatBox{0.04, 0.065, 0.93, 0.70}); + size_t white = 0; + for (size_t y = 0; y < panel.height(); y += 4) + for (size_t x = 0; x < panel.width(); x += 4) { + uint32_t p = panel.pixel(x, y); + if (((p >> 16) & 255) > 225 && ((p >> 8) & 255) > 225 && (p & 255) > 225) white++; + } + double samples = (double)(((panel.width() + 3) / 4) * ((panel.height() + 3) / 4)); + // Distinguish Options from the icon menu and bag using its + // blue heading and full-width white settings panel. + ImageViewRGB32 heading = extract_box_reference(screen, ImageFloatBox{0.01, 0.005, 0.20, 0.025}); + size_t blue = 0, total = 0; + for (size_t y = 0; y < heading.height(); y++) + for (size_t x = 0; x < heading.width(); x++) { + uint32_t p = heading.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + blue += b > r + 20 && g > r + 10; + total++; + } + return samples > 0 && white / samples > 0.70 && total > 0 && blue > total / 4; + } + }; + + class TutorialOptionPositionDetector { + public: + // Locate the two long red horizontal lines surrounding the + // selected row. Scrolling changes their position automatically. + double selected_row(const ImageViewRGB32& screen) const { + int first = -1, last = -1; + for (size_t y = (size_t)(screen.height() * 0.08); y < screen.height() * 0.75; y++) { + size_t red = 0, total = 0; + for (size_t x = (size_t)(screen.width() * 0.08); x < screen.width() * 0.90; x += 2) { + uint32_t p = screen.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + red += r > 150 && r > g + 40 && r > b + 40; + total++; + } + if (total && red > total * 0.70) { + if (first < 0) first = (int)y; + last = (int)y; + } + } + if (first < 0 || last - first < screen.height() * 0.04 || last - first > screen.height() * 0.12) return -1; + return (first + last) / (2.0 * screen.height()); + } + // Locate selected red value text separately from the row border. + bool selected_value(const ImageViewRGB32& screen, double y) const { + ImageViewRGB32 value = extract_box_reference(screen, ImageFloatBox{0.33, y - 0.025, 0.54, 0.05}); + size_t red = 0; + for (size_t py = 0; py < value.height(); py++) + for (size_t px = 0; px < value.width(); px++) { + uint32_t p = value.pixel(px, py); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + red += r > g + 20 && r > b + 20; + } + return red > value.width() * value.height() * 0.0005; + } + }; + + + // Compare binary visual shapes with small alignment tolerance. + // No text recognition is needed for fixed English option labels. + static double option_image_score(const ImageViewRGB32& screen, + const ImageFloatBox& box, const ImageRGB32& reference, bool red_only) { + ImageRGB32 actual = extract_box_reference(screen, box).scale_to(384, 32); + ImageRGB32 expected = reference.scale_to(384, 32); + auto ink = [red_only](uint32_t p){ + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + return red_only ? r > g + 20 && r > b + 20 : r + g + b < 500; + }; + double best = 0; + for (int dy = -2; dy <= 2; dy++) for (int dx = -2; dx <= 2; dx++) { + size_t overlap = 0, count = 0; + for (int y = 2; y < 30; y++) for (int x = 2; x < 382; x++) { + bool a = ink(actual.pixel(x + dx, y + dy)); + bool b = ink(expected.pixel(x, y)); + overlap += a && b; + count += a + b; + } + if (count) best = std::max(best, 2.0 * overlap / count); + } + return best; + } + + static bool autosave_off_highlight(const ImageViewRGB32& screen, double y) { + auto red_count = [&](const ImageFloatBox& box){ + ImageViewRGB32 image = extract_box_reference(screen, box); + size_t red = 0; + for (size_t py = 0; py < image.height(); py++) + for (size_t px = 0; px < image.width(); px++) { + uint32_t p = image.pixel(px, py); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + red += r > g + 30 && r > b + 30; + } + return red; + }; + size_t off = red_count({0.63, y - 0.022, 0.055, 0.044}); + size_t on = red_count({0.34, y - 0.022, 0.055, 0.044}); + // Require a clear Off highlight and reject any competing On highlight. + return off >= 10 && on * 10 < off; + } + + static void verify_tutorial_settings_once(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + MenuDetector menu; + VideoOverlaySet detector_overlays(stream.overlay()); + menu.make_overlays(detector_overlays); + detector_overlays.add(COLOR_CYAN, {0.04, 0.065, 0.93, 0.70}, ""); + detector_overlays.add(COLOR_BLUE, {0.01, 0.005, 0.20, 0.025}, ""); + detector_overlays.add(COLOR_RED, {0.08, 0.08, 0.82, 0.67}, ""); + auto initial = stream.video().snapshot(); + if (!initial || !menu.detect(initial)) { + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + context.wait_for_all_requests(); + } + MenuWatcher menu_open; + if (wait_until(stream, context, 5s, {{menu_open}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Settings checkpoint: Main menu not detected.", stream); + } + stream.log("[Tutorial settings] Main menu detected; settling for 500ms before Options navigation."); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + TutorialOptionsDetector options_screen; + TutorialOptionPositionDetector position; + // Only two icons are available at this initial checkpoint. + // Verify the main menu before navigating to the neighboring icon. + bool options_open = false; + for (size_t attempt = 0; attempt < 2; attempt++) { + pbf_press_dpad(context, attempt == 0 ? DPAD_RIGHT : DPAD_LEFT, 80ms, 350ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + if (frame && options_screen.detect(frame)) { + options_open = true; + break; + } + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + MenuWatcher back_to_menu; + if (wait_until(stream, context, 5s, {{back_to_menu}}) != 0) break; + } + if (!options_open) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Settings checkpoint: Options screen not detected.", stream); + } + stream.log("[Tutorial settings] Options screen detected."); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + struct Setting { const char* label; const char* value; }; + const Setting settings[] = { + {"textspeed", "fast"}, {"changewindow", "type1"}, + {"battleeffects", "off"}, {"battlestyle", "set"}, + {"sendtoboxes", "automatic"}, {"givenicknames", "dontgive"}, + {"gyroscope", "use"}, {"autosave", "off"}, + {"shownicknames", "show"}, {"backgroundmusic", "8"}, + {"soundeffects", "8"}, + }; + for (size_t row = 0; row < sizeof(settings) / sizeof(settings[0]); row++) { + // Detect the selected row afresh after every navigation/correction. + double y = -1; + const std::string base = RESOURCE_PATH() + "PokemonBDSP/Autostory/Options/" + settings[row].label; + const ImageRGB32 label_reference(base + "_label.png"); + const ImageRGB32 value_reference(base + "_value.png"); + bool slider_reset = false; + bool correct = false; + for (size_t attempt = 0; attempt < 30; attempt++) { + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + if (frame && options_screen.detect(frame)) y = position.selected_row(frame); + else y = -1; + const bool value_visible = y >= 0 && position.selected_value(frame, y); + const double label_score = y >= 0 ? option_image_score(frame, + {0.08, y - 0.025, 0.24, 0.05}, label_reference, false) : 0; + stream.log("[Tutorial settings] Expected row=" + std::string(settings[row].label) + + " row_y=" + std::to_string(y) + " highlighted_value=" + std::to_string(value_visible) + + " label_match=" + std::to_string(label_score)); + if (y < 0 || !value_visible || label_score < 0.80) { + // A menu can be detected while its row/text is still + // fading in. Re-read without directional inputs first. + if (attempt < 4) { + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + continue; + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Settings checkpoint: Unable to confirm expected row " + std::string(settings[row].label), stream); + } + // These are regular video-overlay boxes, so the existing + // overlay display control can show/hide every active ROI. + OverlayBoxScope row_overlay(stream.overlay(), COLOR_RED, + {0.07, y - 0.04, 0.84, 0.08}, ""); + OverlayBoxScope label_overlay(stream.overlay(), COLOR_YELLOW, + {0.08, y - 0.025, 0.24, 0.05}, ""); + OverlayBoxScope value_overlay(stream.overlay(), COLOR_GREEN, + {0.33, y - 0.025, 0.54, 0.05}, ""); + VideoOverlaySet autosave_rois(stream.overlay()); + if (row == 7) { + autosave_rois.add(COLOR_GREEN, {0.63, y - 0.022, 0.055, 0.044}); + autosave_rois.add(COLOR_RED, {0.34, y - 0.022, 0.055, 0.044}); + } + double score; + if (row >= 9) { + // Compare the numeric value rather than the long slider: + // neighboring slider levels otherwise look too similar. + ImageViewRGB32 number = value_reference.sub_image( + value_reference.width() * 82 / 100, 0, + value_reference.width() - value_reference.width() * 82 / 100, + value_reference.height()); + ImageRGB32 number_reference = number.scale_to(96, 32); + score = option_image_score(frame, + {0.7728, y - 0.025, 0.0972, 0.05}, number_reference, true); + } else { + score = option_image_score(frame, + {0.33, y - 0.025, 0.54, 0.05}, value_reference, true); + } + stream.log("[Tutorial settings] " + std::string(settings[row].label) + + " target match=" + std::to_string(score)); + // The reference recording uses inactive pale-red text; + // the focused live row has a thicker dark-red glyph mask. + // Verified Fast scores 0.734 while Slow/Normal score 0. + if (score >= 0.65 && (row != 7 || autosave_off_highlight(frame, y))) { + if (row == 7) { + for (size_t confirmation = 0; confirmation < 3; confirmation++) { + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + auto check = stream.video().snapshot(); + double check_y = check ? position.selected_row(check) : -1; + if (!check || !options_screen.detect(check) || check_y < 0 || + option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, label_reference, false) < 0.80 || + !autosave_off_highlight(check, check_y)) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Autosave Off could not be confirmed. Stopping before movement.", stream); + } + } + } + stream.log("[Tutorial settings] Verified " + std::string(settings[row].label) + "=" + settings[row].value); + correct = true; + break; + } + stream.log("[Tutorial settings] Correcting " + std::string(settings[row].label)); + if (row >= 9 && !slider_reset) { + // Sliders clamp at zero. Establish a known endpoint, + // then increment; verify the target image afterward. + for (size_t step = 0; step < 12; step++) + pbf_press_dpad(context, DPAD_LEFT, 80ms, 100ms); + slider_reset = true; + } else { + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); + } + context.wait_for_all_requests(); + } + if (!correct) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Settings checkpoint: Could not apply " + std::string(settings[row].label), stream); + } + if (row + 1 < sizeof(settings) / sizeof(settings[0])) + pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); + } + // Confirm settings before exiting: A A A A B B. + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + OverworldWatcher overworld; + if (wait_until(stream, context, 10s, {{overworld}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Settings checkpoint: Did not return to the overworld after saving options.", stream); + } + // Reopen the saved Options screen and independently verify Autosave. + // No route movement is permitted until this persisted-value check passes. + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + context.wait_for_all_requests(); + MenuWatcher verify_menu; + if (wait_until(stream, context, 5s, {{verify_menu}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Autosave verification: Main menu not detected.", stream); + } + bool reopened = false; + for (size_t attempt = 0; attempt < 2; attempt++) { + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + context.wait_for_all_requests(); + auto check = stream.video().snapshot(); + if (check && options_screen.detect(check)) { reopened = true; break; } + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + MenuWatcher return_menu; + if (wait_until(stream, context, 5s, {{return_menu}}) != 0) break; + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); + } + if (!reopened) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Autosave verification: Could not reopen saved Options.", stream); + } + // Options starts on Text Speed; Autosave is seven rows below it. + for (size_t step = 0; step < 7; step++) + pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); + context.wait_for_all_requests(); + const ImageRGB32 autosave_label(RESOURCE_PATH() + "PokemonBDSP/Autostory/Options/autosave_label.png"); + for (size_t confirmation = 0; confirmation < 3; confirmation++) { + auto check = stream.video().snapshot(); + double check_y = check ? position.selected_row(check) : -1; + if (!check || !options_screen.detect(check) || check_y < 0 || + option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, autosave_label, false) < 0.80 || + !autosave_off_highlight(check, check_y)) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Saved Autosave setting is not verified Off. Stopping before movement.", stream); + } + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + } + stream.log("[Tutorial settings] Autosave = Off: confirmed after reopening saved Options (3 frames)."); + // Confirm settings before exiting: A A A A B B. + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + OverworldWatcher verified_exit; + if (wait_until(stream, context, 10s, {{verified_exit}}) != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Autosave verification: Could not return to the overworld.", stream); + } + // Report inferred values explicitly: these are visual matches, + // allowing the user to compare our conclusions with the screen. + stream.log("[Tutorial settings] Completed. Values believed applied (visual matches before saving):"); + stream.log("[Tutorial settings] Text Speed = Fast; Change Window = Type 1; Battle Effects = Off; Battle Style = Set."); + stream.log("[Tutorial settings] Send to Boxes = Automatic; Give Nicknames = Don't give; Gyroscope = Use."); + stream.log("[Tutorial settings] Autosave = Off; Show Nicknames = Show; Background Music = 8; Sound Effects = 8."); + } + + + void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context) { + // Settings-only recovery: initial attempt plus three retries. + // Never permit route movement unless the complete check succeeds. + constexpr size_t MAX_RETRIES = 3; + for (size_t attempt = 0; attempt <= MAX_RETRIES; attempt++) { + stream.log("[Tutorial settings] Attempt " + std::to_string(attempt + 1) + "/4."); + try { + verify_tutorial_settings_once(stream, context); + return; + } catch (OperationFailedException& error) { + stream.log("[Tutorial settings] Attempt failed: " + error.message(), COLOR_ORANGE); + if (attempt == MAX_RETRIES) { + stream.log("[Tutorial settings] All three retries exhausted; stopping before movement.", COLOR_RED); + throw; + } + // Back out of Options, save prompts, bag, or menu without + // moving the player or resetting/loading the game. + stream.log("[Tutorial settings] Returning to overworld before retry."); + context.wait_for_all_requests(); + TutorialOptionsDetector options_screen; + const auto failed_frame = stream.video().snapshot(); + if (failed_frame && options_screen.detect(failed_frame)) { + // Finish the known Options save/exit sequence before + // observing state; never cancel it on an Options frame + // misclassified as overworld by the generic detector. + for (size_t press = 0; press < 4; press++) + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + } + pbf_mash_button(context, BUTTON_B, 3000ms); + context.wait_for_all_requests(); + OverworldDetector overworld; + MenuDetector menu; + BlackScreenDetector black; + bool recovered = false; + size_t confirmations = 0; + for (size_t check = 0; check < 15; check++) { + const auto frame = stream.video().snapshot(); + const bool clear = frame && !options_screen.detect(frame) && + !menu.detect(frame) && !black.detect(frame) && bag_pocket_position(frame) == 0 && overworld.detect(frame); + if (clear) { + if (++confirmations >= 3) { recovered = true; break; } + pbf_wait(context, 200ms); + } else { + confirmations = 0; + pbf_mash_button(context, BUTTON_B, 500ms); + } + context.wait_for_all_requests(); + } + stream.log(recovered ? "[Tutorial settings] Recovery confirmed outside Options/menu; retrying." + : "[Tutorial settings] Recovery not confirmed; next attempt will recheck the screen."); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + } + } + } + void set_time_fast( SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -207,6 +611,71 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- // Natalie battle // --------------------------------------------------------------------------- + static void select_battle_move_slot(VideoStream& stream, ProControllerContext& context, + BattleType battle_type, size_t target, const std::string& label) { + BattleMenuDetector commands(battle_type); + VideoOverlaySet overlays(stream.overlay()); + commands.make_overlays(overlays); + overlays.add(COLOR_GREEN, {0.90, 0.48, 0.075, 0.505}); + bool fight = false; + size_t confirmed = 0; + for (size_t check = 0; check < 30; check++) { + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + BattleMenuOption selected = frame ? commands.selected_option(frame) : BattleMenuOption::UNKNOWN; + stream.log("[" + label + "] Command cursor=" + std::to_string((int)selected + 1) + "; target Battle=1."); + if (selected == BattleMenuOption::BATTLE) { + if (++confirmed >= 3) { fight = true; break; } + pbf_wait(context, 100ms); + } else { + confirmed = 0; + if (selected == BattleMenuOption::UNKNOWN) pbf_wait(context, 150ms); + else pbf_press_dpad(context, DPAD_UP, 80ms, 250ms); + } + } + if (!fight) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Could not confirm Battle command; A not pressed.", stream); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + confirmed = 0; + size_t previous_move_count = 0, stable_move_count = 0; + for (size_t check = 0; check < 30; check++) { + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + const BattleMoveMenuState moves = frame ? detect_battle_move_menu(frame) : BattleMoveMenuState{}; + const int slot = (int)moves.selected_slot; + VideoOverlaySet move_overlays(stream.overlay()); + for (size_t i = 0; i < moves.move_count; i++) + move_overlays.add(moves.selected_slot == i + 1 ? COLOR_GREEN : COLOR_YELLOW, moves.buttons[i]); + stream.log("[" + label + "] Available moves=" + std::to_string(moves.move_count) + + "; selected slot=" + std::to_string(slot) + "/4; target=" + std::to_string(target) + "."); + if (moves.move_count == previous_move_count && moves.move_count > 0) stable_move_count++; + else { previous_move_count = moves.move_count; stable_move_count = 1; confirmed = 0; } + if (moves.move_count > 0 && moves.move_count < target && stable_move_count >= 3) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Move slot " + std::to_string(target) + " is absent; no move confirmed.", stream); + } + if (slot == (int)target && stable_move_count >= 3) { + if (++confirmed >= 3) { + stream.log("[" + label + "] Move " + std::to_string(target) + " confirmed; using it."); + pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + return; + } + pbf_wait(context, 100ms); + } else { + confirmed = 0; + if (slot == 0 || slot == (int)target) pbf_wait(context, 150ms); + else pbf_press_dpad(context, slot < (int)target ? DPAD_DOWN : DPAD_UP, 80ms, 250ms); + } + context.wait_for_all_requests(); + } + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Could not confirm move " + std::to_string(target) + "; A not pressed.", stream); + } + + static void select_natalie_move_three(VideoStream& stream, ProControllerContext& context) { + select_battle_move_slot(stream, context, BattleType::TRAINER, 3, "Natalie"); + } + void fight_Natalie( VideoStream& stream, ProControllerContext& context @@ -275,58 +744,7 @@ namespace PokemonAutomation { battle_menu_seen = true; - // Open Fight menu. - pbf_press_button( - context, - BUTTON_A, - 80ms, - 500ms - ); - - // Hard reset move cursor to top. - pbf_press_dpad( - context, - DPAD_UP, - 80ms, - 150ms - ); - - pbf_press_dpad( - context, - DPAD_UP, - 80ms, - 150ms - ); - - pbf_press_dpad( - context, - DPAD_UP, - 80ms, - 150ms - ); - - // Move to move slot 3. - pbf_press_dpad( - context, - DPAD_DOWN, - 80ms, - 150ms - ); - - pbf_press_dpad( - context, - DPAD_DOWN, - 80ms, - 250ms - ); - - // Use move slot 3. - pbf_press_button( - context, - BUTTON_A, - 80ms, - 3000ms - ); + select_natalie_move_three(stream, context); break; } @@ -394,7 +812,8 @@ namespace PokemonAutomation { 5000ms ); - return; + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "[AutoStory] Natalie battle timed out.", stream); } } @@ -448,18 +867,22 @@ namespace PokemonAutomation { // Mash through XP / level-up / dialog screens back to overworld. OverworldWatcher overworld; - run_until( + int cleanup_ret = run_until( stream, context, [](ProControllerContext& ctx) { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, { {overworld} } ); + if (cleanup_ret != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Starly battle: Overworld cleanup timed out.", stream); + } return; } default: - stream.log("[AutoStory] Starly battle timed out.", COLOR_RED); - return; + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Starly battle timed out.", stream); } } } @@ -467,126 +890,18 @@ namespace PokemonAutomation { // --------------------------------------------------------------------------- // Tristan battle // --------------------------------------------------------------------------- - void fight_tristan( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log( - "[AutoStory] Starting Tristan battle...", - COLOR_BLUE - ); - - // Wait for battle transition to fully finish. - pbf_wait(context, 5000ms); - - bool battle_menu_seen = false; - - while (true) { - - context.wait_for_all_requests(); - - BattleMenuWatcher battle_menu(BattleType::TRAINER); - EndBattleWatcher end_battle; - - int ret = run_until( - stream, - context, - [](ProControllerContext& context) { - // Mash through battle text continuously. - pbf_mash_button( - context, - BUTTON_B, - 10000ms - ); - }, - { - {battle_menu}, - battle_menu_seen - ? PeriodicInferenceCallback{end_battle} - : PeriodicInferenceCallback{}, - } - ); - - switch (ret) { - - // ------------------------------------------------- - // Battle menu detected. - // ------------------------------------------------- - case 0: - battle_menu_seen = true; - - stream.log( - "[AutoStory] Battle menu detected.", - COLOR_BLUE - ); - - // Select Fight. - pbf_press_button( - context, - BUTTON_A, - 120ms, - 500ms - ); - - // Select move slot 1 (Scratch). - pbf_press_button( - context, - BUTTON_A, - 120ms, - 3000ms - ); - - break; - - // ------------------------------------------------- - // End battle detected. - // ------------------------------------------------- - case 1: - stream.log( - "[AutoStory] Tristan battle complete.", - COLOR_GREEN - ); - - pbf_mash_button( - context, - BUTTON_B, - 5000ms - ); - - return; - - default: - stream.log( - "[AutoStory] Tristan battle timeout.", - COLOR_RED - ); - - return; - } - } - } - - // --------------------------------------------------------------------------- - // Catch 1 Pokemon - // --------------------------------------------------------------------------- - - void catch_1_pokemon( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); - + static void encounter_one_wild_pokemon(VideoStream& stream, ProControllerContext& context) { context.wait_for_all_requests(); pbf_wait(context, 1000ms); - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - bool encounter_found = false; + const auto deadline = std::chrono::steady_clock::now() + 180s; // Wiggle left/right until battle starts. - while (!encounter_found) { + while (!encounter_found && std::chrono::steady_clock::now() < deadline) { + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); // Turn left. pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); @@ -629,6 +944,8 @@ namespace PokemonAutomation { } } + if (!encounter_found) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Wild encounter: Left/right wiggle did not find a battle within three minutes.", stream); stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); // Wait until the actual battle menu appears. @@ -648,7 +965,8 @@ namespace PokemonAutomation { "[AutoStory] Failed to detect battle menu.", COLOR_RED ); - return; + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "[AutoStory] Failed to detect battle menu.", stream); } stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); @@ -656,60 +974,236 @@ namespace PokemonAutomation { context.wait_for_all_requests(); pbf_wait(context, 500ms); - // Catch the pokemon. - stream.log("[AutoStory] Throwing Poke Ball (manual bag open)...", COLOR_BLUE); - for (int throw_attempt = 0; throw_attempt < 10; throw_attempt++) { - // Open bag from battle menu (X), confirm first slot (ZL). - pbf_press_button(context, BUTTON_X, 160ms, 840ms); - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); - context.wait_for_all_requests(); - - // Wait to see if the battle menu comes back (ball failed) - // or if the battle ends (caught or fainted). - BattleMenuWatcher menu_back(BattleType::STANDARD, COLOR_YELLOW); - EndBattleWatcher battle_ended; - int throw_ret = wait_until( - stream, context, 30s, - { - {menu_back}, - {battle_ended}, - } - ); + } - if (throw_ret == 1) { - // Battle ended - caught or fainted. Either way we're done. - stream.log("[AutoStory] Battle ended after throw.", COLOR_GREEN); + static void confirm_battle_overworld(VideoStream& stream, ProControllerContext& context, + const std::string& label) { + OverworldDetector overworld; + ShortDialogDetector dialogue(COLOR_YELLOW); + BlackScreenDetector black; + VideoOverlaySet overlays(stream.overlay()); + overworld.make_overlays(overlays); + dialogue.make_overlays(overlays); + black.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 180s; + auto quiet_since = std::chrono::steady_clock::now(); + bool finished = false; + while (std::chrono::steady_clock::now() < deadline) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(frame) || black.detect(frame) || !overworld.detect(frame)) { + quiet_since = now; + } else if (now - quiet_since >= 4s) { + finished = true; break; } - // throw_ret == 0: menu came back, ball failed - try again. - // throw_ret < 0: timed out - fall through to B mash cleanup. - if (throw_ret < 0) { - stream.log("[AutoStory] Throw timed out, proceeding to cleanup.", COLOR_ORANGE); - break; - } - stream.log( - "[AutoStory] Ball failed, retrying (attempt " + std::to_string(throw_attempt + 2) + ")...", - COLOR_ORANGE - ); + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); } - + if (!finished) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Dialogue-free overworld not confirmed for four seconds.", stream); context.wait_for_all_requests(); + } - // Force clear any remaining text boxes / summaries. - pbf_mash_button(context, BUTTON_B, 15000ms); + static void fight_using_move_slot( + VideoStream& stream, ProControllerContext& context, + BattleType battle_type, size_t move_slot, const std::string& label + ) { + stream.log( + "[AutoStory] Starting " + label + " battle...", + COLOR_BLUE + ); - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - } + // Wait for battle transition to fully finish. + pbf_wait(context, 5000ms); - // --------------------------------------------------------------------------- - // catch_1_pokemon_v2 - burst wiggle variant for testing. - // Uses WIGGLES_PER_BURST consecutive left/right moves before checking for a - // battle, reducing total wait time compared to checking after every move. - // Commented out - enable for testing when ready. - // --------------------------------------------------------------------------- - // void catch_1_pokemon_v2( + bool battle_menu_seen = false; + EndBattleWatcher end_battle; + + while (true) { + + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(battle_type); + + int ret = run_until( + stream, + context, + [](ProControllerContext& context) { + // Mash through battle text continuously. + pbf_mash_button( + context, + BUTTON_B, + 120000ms + ); + }, + { + {battle_menu}, + battle_menu_seen + ? PeriodicInferenceCallback{end_battle} + : PeriodicInferenceCallback{}, + } + ); + + switch (ret) { + + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: + battle_menu_seen = true; + + stream.log( + "[AutoStory] Battle menu detected.", + COLOR_BLUE + ); + + select_battle_move_slot(stream, context, battle_type, move_slot, label); + + break; + + // ------------------------------------------------- + // End battle detected. + // ------------------------------------------------- + case 1: { + stream.log( + "[AutoStory] " + label + " battle complete.", + COLOR_GREEN + ); + + // Clear experience, level-up, move-learning and trainer + // dialogue until the overworld is actually confirmed. + OverworldWatcher overworld; + int cleanup_ret = run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, {{overworld}}); + if (cleanup_ret != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + label + ": Post-battle cleanup timed out.", stream); + } + context.wait_for_all_requests(); + confirm_battle_overworld(stream, context, label + " cleanup"); + return; + } + + default: + stream.log( + "[AutoStory] " + label + " battle timeout.", + COLOR_RED + ); + + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "[AutoStory] " + label + " battle timeout.", stream); + } + } + } + + void fight_tristan(VideoStream& stream, ProControllerContext& context) { + // Chimchar learns Ember into slot 3 at level 6. Confirm the move + // cursor visually; missing slot 3 fails instead of using Scratch. + fight_using_move_slot(stream, context, BattleType::TRAINER, 3, "Tristan (Ember)"); + } + + void battle_1_pokemon(VideoStream& stream, ProControllerContext& context) { + stream.log("[Training] Finding one wild Pokemon using the catch encounter wiggle.", COLOR_BLUE); + encounter_one_wild_pokemon(stream, context); + // Use the same battle loop as Tristan, but Scratch is slot 1. + fight_using_move_slot(stream, context, BattleType::STANDARD, 1, "Training (Scratch)"); + stream.log("[Training] Fight and level-up cleanup complete; verifying Potion healing."); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + stream.log("[Training] One wild battle completed; slot 1 healing verified.", COLOR_GREEN); + } + + // --------------------------------------------------------------------------- + // Catch 1 Pokemon + // --------------------------------------------------------------------------- + + void catch_1_pokemon( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); + + encounter_one_wild_pokemon(stream, context); + + // Catch the pokemon. + stream.log("[AutoStory] Throwing Poke Ball (manual bag open)...", COLOR_BLUE); + ReceivePokemonDetector received_pokemon; + // Preserve exit-transition state from the ball outcome through + // receipt/experience cleanup; do not recreate it between phases. + EndBattleWatcher battle_ended; + bool caught = false; + bool exit_seen = false; + for (int throw_attempt = 0; throw_attempt < 10; throw_attempt++) { + // Open bag from battle menu (X), confirm first slot (ZL). + pbf_press_button(context, BUTTON_X, 160ms, 840ms); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + context.wait_for_all_requests(); + + // Wait to see if the battle menu comes back (ball failed) + // or if the battle ends (caught or fainted). + BattleMenuWatcher menu_back(BattleType::STANDARD, COLOR_YELLOW); + int throw_ret = run_until( + stream, context, + [](ProControllerContext& ctx) { + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{menu_back}, {received_pokemon}, {battle_ended}} + ); + + if (throw_ret == 1 || (throw_ret == 2 && received_pokemon.received())) { + caught = true; + exit_seen = throw_ret == 2; + stream.log("[AutoStory] Pokemon receipt verified.", COLOR_GREEN); + break; + } + if (throw_ret < 0 || throw_ret == 2) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Catch failed: No verified Pokemon receipt before battle ended or timed out.", stream); + } + stream.log( + "[AutoStory] Ball failed, retrying (attempt " + std::to_string(throw_attempt + 2) + ")...", + COLOR_ORANGE + ); + } + + context.wait_for_all_requests(); + + if (!caught) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Catch failed: Exhausted ten ball attempts.", stream); + } + // Receipt alone is not an overworld return: clear experience, + // automatic move learning, dex registration and nickname screens + // until the same completed-black-transition watcher used in battles fires. + if (!exit_seen) { + stream.log("[Catch cleanup] Receipt confirmed; waiting for battle exit transition."); + const int cleanup_ret = run_until(stream, context, + [](ProControllerContext& ctx) { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, + {{battle_ended}}); + if (cleanup_ret != 0) { + OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Catch cleanup: Battle exit transition did not finish.", stream); + } + } + context.wait_for_all_requests(); + stream.log("[Catch cleanup] Battle exit transition confirmed; checking stable overworld."); + confirm_battle_overworld(stream, context, "Catch cleanup"); + stream.log("[Catch cleanup] Verified receipt, completed battle exit, and four seconds of clear overworld.", COLOR_GREEN); + } + + // --------------------------------------------------------------------------- + // catch_1_pokemon_v2 - burst wiggle variant for testing. + // Uses WIGGLES_PER_BURST consecutive left/right moves before checking for a + // battle, reducing total wait time compared to checking after every move. + // Commented out - enable for testing when ready. + // --------------------------------------------------------------------------- + // void catch_1_pokemon_v2( // SingleSwitchProgramEnvironment& env, // VideoStream& stream, // ProControllerContext& context @@ -977,584 +1471,89 @@ namespace PokemonAutomation { // specified steps, attempting to run from any battles that are encountered. // --------------------------------------------------------------------------- void walk_through_grass( - VideoStream& stream, - ProControllerContext& context, - int8_t x, - int8_t y, - size_t steps, - const std::string& direction_name, - BattleType battle_type + VideoStream& stream, ProControllerContext& context, + int8_t x, int8_t y, size_t steps, const std::string& direction_name, + BattleType battle_type, Milliseconds movement_duration, + OverworldMatchResult (*destination)(const ImageRGB32&) ) { - BlackScreenWatcher battle_start; - WhiteScreenOverWatcher white_screen(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}); - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < steps; step++) { - - context.wait_for_all_requests(); - - // Move. - pbf_move_left_joystick( - context, - {(double)x, (double)y}, - 200ms, - 0ms - ); - + size_t movements = 0, confirmations = 0; + while (true) { context.wait_for_all_requests(); - - // Check for battle. - int battle_ret = wait_until( - stream, - context, - 2500ms, - { - {white_screen}, - {battle_start}, - {battle_dialog}, - } - ); - - if (battle_ret >= 0) { - - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - // If battle_ret == 0/1 (black/white transition), wait for the - // battle menu to fully appear before attempting to navigate. - if (battle_ret == 0 || battle_ret == 1) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); + // Destination checks happen before input and after each step/escape. + // With no matcher, retain the original fixed-step behavior. + VideoOverlaySet overlays(stream.overlay()); + OverworldMatchResult location; + if (destination) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Grass destination: No video frame.", stream); + location = destination(*frame.frame); + for (const auto& landmark : location.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Grass destination] " + direction_name + " " + landmark.name + + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + + " accepted=" + std::to_string(landmark.accepted)); } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - - // Advance any dialog / transition text. - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - // Wait for the action-selection menu to be visible before navigating. - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - // Menu not yet visible, loop again. - continue; - } - - // Navigate to Run. Menu is 1x4 (Fight/Bag/Pokemon/Run) and wraps, - // so pressing up once from Fight lands on Run. - pbf_move_left_joystick(context, {0, -1}, 80ms, 80ms); - - // Select Run. - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - // Watch for escape success or "can't escape" return to menu. - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; + stream.log("[Grass destination] located=" + std::to_string(location.located) + + " at_target=" + std::to_string(location.at_target) + + " screen-relative offset=(" + std::to_string(location.dx) + + "," + std::to_string(location.dy) + ")"); + if (location.at_target) { + if (++confirmations >= 3) { + stream.log("[Grass] Destination confirmed; stopped " + direction_name + + " after " + std::to_string(movements) + "/" + std::to_string(steps) + " movements.", COLOR_GREEN); + return; } - // run_result == 1: failed to run, back at battle menu - retry. - // run_result == -1: timed out - retry. - } - - if (!escaped) { - stream.log( - "[AutoStory] Failed to run from battle.", - COLOR_RED - ); - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - } - } - - stream.log( - "[AutoStory] Completed grass movement: " + direction_name, - COLOR_GREEN - ); - } - - // --------------------------------------------------------------------------- - // Helper function for 1st grass patch - // --------------------------------------------------------------------------- - void walk_right_until_on_path(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 20; step++) { - - context.wait_for_all_requests(); - - // Move right (fixed step) - pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { + pbf_wait(context, 250ms); continue; } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed right movement (18 steps).", COLOR_GREEN); - } - - // --------------------------------------------------------------------------- - // Helper function to walk up through the second grass patch - // --------------------------------------------------------------------------- - void walk_up_through_grass_2(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 18; step++) { - - context.wait_for_all_requests(); - - // Move right (fixed step) - pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); + confirmations = 0; } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } + if (movements >= steps) { + if (destination) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + "Grass destination not confirmed within movement limit: " + direction_name, stream); + stream.log("[Grass] Completed " + direction_name + " (" + std::to_string(steps) + " steps).", COLOR_GREEN); + return; } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving up.", - stream - ); + int8_t move_x=x, move_y=y; + Milliseconds duration=movement_duration; + if (destination && location.located && !location.coarse && + std::abs(location.dx)<=0.10 && std::abs(location.dy)<=0.12) { + // Small corrections prevent stepping past a narrow stop target. + const bool horizontal=std::abs(location.dx)>=std::abs(location.dy)*9.0/16; + const double error=horizontal ? location.dx : location.dy*9.0/16; + duration=std::min(duration, std::abs(error)>0.03125 ? 80ms : + std::abs(error)>0.015625 ? 40ms : 16ms); + move_x=horizontal ? (location.dx>0 ? 1 : -1) : 0; + move_y=horizontal ? 0 : (location.dy<0 ? 1 : -1); + stream.log("[Grass destination] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + + " axis for " + std::to_string(duration.count()) + "ms."); } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); - } - // --------------------------------------------------------------------------- - // Helper function to walk right through the second grass patch - // --------------------------------------------------------------------------- - void walk_right_through_grass_2(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 10; step++) { - - context.wait_for_all_requests(); - - // Move right (fixed step) - pbf_move_left_joystick(context, { +1, 0 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving right.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed right movement (10 steps).", COLOR_GREEN); - } - // --------------------------------------------------------------------------- - // Helper function to walk left through the third grass patch - // --------------------------------------------------------------------------- - void walk_left_through_grass_3(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 18; step++) { - - context.wait_for_all_requests(); - - // Move left (fixed step) - pbf_move_left_joystick(context, { -1, 0 }, 200ms, 0ms); - + // Each movement gets fresh watcher state. A completed white + // transition from a prior encounter must not trigger again. + BlackScreenWatcher battle_start; + WhiteScreenOverWatcher white_screen(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}); + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + pbf_move_left_joystick(context, {(double)move_x, (double)move_y}, duration, 0ms); + movements++; context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving left.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; + int ret = wait_until(stream, context, 2500ms, + {{white_screen}, {battle_start}, {battle_dialog}, {battle_menu}}); + if (ret >= 0) { + confirmations = 0; + stream.log("[Grass] " + direction_name + " step " + std::to_string(movements) + + "/" + std::to_string(steps) + ": encounter trigger=" + std::to_string(ret) + + "; using shared confirmed-Run escape."); + escape_wild_battle(stream, context, battle_type); + context.wait_for_all_requests(); + stream.log("[Grass] Escape confirmed; resuming " + direction_name + "."); } } - - stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); } - // --------------------------------------------------------------------------- - // Helper function to walk up through the third grass patch - // --------------------------------------------------------------------------- - void walk_up_through_grass_3(VideoStream& stream, ProControllerContext& context, BattleType battle_type) { - - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - - for (size_t step = 0; step < 18; step++) { - - context.wait_for_all_requests(); - - // Move up (fixed step) - pbf_move_left_joystick(context, { 0, 1 }, 200ms, 0ms); - - context.wait_for_all_requests(); - - // Check for battle after movement - int battle_ret = wait_until( - stream, context, - 2500ms, - { - {battle_start}, // 0 - {battle_menu}, // 1 - {battle_dialog}, // 2 - } - ); - - if (battle_ret >= 0) { - stream.log( - "[AutoStory] Battle detected during movement.", - COLOR_ORANGE - ); - - if (battle_ret == 0) { - BattleMenuWatcher wait_for_menu(battle_type, COLOR_YELLOW); - wait_until(stream, context, 10s, {{wait_for_menu}}); - context.wait_for_all_requests(); - } - bool escaped = false; - auto deadline = std::chrono::steady_clock::now() + 30s; - - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{menu_ready}}) != 0) { - continue; - } - - // Press up once to wrap from Fight to Run. - pbf_move_left_joystick(context, { 0, 1 }, 80ms, 80ms); - - pbf_press_button(context, BUTTON_A, 60ms, 0ms); - context.wait_for_all_requests(); - - EndBattleWatcher battle_end; - BattleMenuWatcher back_to_menu(battle_type, COLOR_YELLOW); - int run_result = wait_until(stream, context, 4s, {{battle_end}, {back_to_menu}}); - if (run_result == 0) { - escaped = true; - break; - } - } - - if (!escaped) { - stream.log("[AutoStory] Failed to run from battle.", COLOR_RED); - - pbf_press_button(context, BUTTON_HOME, 80ms, 1200ms); - pbf_press_button(context, BUTTON_X, 80ms, 300ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, - "Failed to run from battle while moving up.", - stream - ); - } - - // Wait until truly back in the overworld before the next step. - OverworldWatcher overworld; - wait_until(stream, context, 5s, {{overworld}}); - - continue; - } - } - - stream.log("[AutoStory] Completed fixed up movement (18 steps).", COLOR_GREEN); - } } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index 786553ba83..2d31058029 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -1,5 +1,7 @@ #pragma once +#include "PokemonBDSP_OverworldMatchers.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "../StorySegments/PokemonBDSP_AutoStory_Segment_00.h" #include "CommonTools/Async/InferenceRoutines.h" @@ -25,6 +27,8 @@ namespace PokemonAutomation { // Time / setup helpers // --------------------------------------------------------------------------- + void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context); + void set_time_fast( SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -59,6 +63,9 @@ namespace PokemonAutomation { ProControllerContext& context ); + // One wild encounter using Scratch, followed by verified Potion healing. + void battle_1_pokemon(VideoStream& stream, ProControllerContext& context); + // Burst wiggle variant of catch_1_pokemon for testing. // Commented out in the .cpp - uncomment both to enable. // void catch_1_pokemon_v2( @@ -76,35 +83,6 @@ namespace PokemonAutomation { // Grass movement helpers // --------------------------------------------------------------------------- - void walk_right_until_on_path( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD - ); - - void walk_up_through_grass_2( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD - ); - - void walk_right_through_grass_2( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD - ); - - void walk_left_through_grass_3( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD - ); - - void walk_up_through_grass_3( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD - ); void walk_through_grass( VideoStream& stream, ProControllerContext& context, @@ -112,7 +90,9 @@ namespace PokemonAutomation { int8_t y, size_t steps, const std::string& direction_name, - BattleType battle_type = BattleType::STANDARD + BattleType battle_type = BattleType::STANDARD, + Milliseconds movement_duration = Milliseconds(200), + OverworldMatchResult (*destination)(const ImageRGB32&) = nullptr ); bool detect_black_screen_after_movement( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 7b03f59a23..5028935f29 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -172,11 +172,11 @@ namespace PokemonAutomation { return false; } - static void escape_wild_battle( + void escape_wild_battle( VideoStream& stream, ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD, - std::chrono::seconds timeout = 30s + BattleType battle_type, + std::chrono::seconds timeout ) { EndBattleWatcher battle_end; bool exit_transition = false; @@ -359,7 +359,7 @@ namespace PokemonAutomation { return walked; } - static void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { stream.log("Starting Barry 1 battle..."); // State machine: loop until EndBattleWatcher fires @@ -600,14 +600,15 @@ namespace PokemonAutomation { ImageFloatBox(0.615, 0.37, 0.035, 0.23) }; - std::array scores = {}; - std::array worst_regions = {}; + const int search_radius = (int)std::lround(0.10*live_scaled.width()); + std::vector scores(2*search_radius+1); + std::vector worst_regions(2*search_radius+1); int best_shift = 0; double best_score = 1000.0; // Search up to 10% of the screen width in either direction. - for (int shift = -96; shift <= 96; shift++) { + for (int shift = -search_radius; shift <= search_radius; shift++) { double total = 0.0; double worst = 0.0; @@ -629,7 +630,7 @@ namespace PokemonAutomation { } const double average = total / boxes.size(); - const size_t index = static_cast(shift + 96); + const size_t index = static_cast(shift + search_radius); scores[index] = average; worst_regions[index] = worst; @@ -643,28 +644,28 @@ namespace PokemonAutomation { // Reject another similarly good match well away from the chosen position. double alternative_score = 1000.0; - for (int shift = -96; shift <= 96; shift++) { - if (std::abs(shift - best_shift) <= 12) { + for (int shift = -search_radius; shift <= search_radius; shift++) { + if (std::abs(shift - best_shift) <= std::lround(0.0125*live_scaled.width())) { continue; } - const double score = scores[static_cast(shift + 96)]; + const double score = scores[static_cast(shift + search_radius)]; if (score < alternative_score) { alternative_score = score; } } - const size_t best_index = static_cast(best_shift + 96); + const size_t best_index = static_cast(best_shift + search_radius); const bool valid = best_score <= 20.0 && worst_regions[best_index] <= 30.0 && alternative_score - best_score >= 3.0 && - std::abs(best_shift) < 96; + std::abs(best_shift) < search_radius; return { valid, - static_cast(best_shift) / 960.0, + static_cast(best_shift) / live_scaled.width(), best_score }; } @@ -946,8 +947,8 @@ namespace PokemonAutomation { struct MinePictureScore { double average = 1000.0; double worst = 1000.0; - int horizontal = 0; - int vertical = 0; + double horizontal = 0; + double vertical = 0; }; // Supports four, five, or six regions. Existing four-region callers keep @@ -957,12 +958,14 @@ namespace PokemonAutomation { const ImageRGB32& reference, const ImageRGB32& live, const std::array& boxes, - int horizontal_range = 16, - int vertical_range = 8 + double horizontal_range = 1.0/60, + double vertical_range = 2.0/135 ) { MinePictureScore best; - for (int y = -vertical_range; y <= vertical_range; y++) { - for (int x = -horizontal_range; x <= horizontal_range; x++) { + const int radius_x = (int)std::lround(horizontal_range*live.width()); + const int radius_y = (int)std::lround(vertical_range*live.height()); + for (int y = -radius_y; y <= radius_y; y++) { + for (int x = -radius_x; x <= radius_x; x++) { double total = 0.0; double worst = 0.0; for (const ImageFloatBox& box : boxes) { @@ -976,8 +979,8 @@ namespace PokemonAutomation { if (average < best.average) { best.average = average; best.worst = worst; - best.horizontal = x; - best.vertical = y; + best.horizontal = (double)x/live.width(); + best.vertical = (double)y/live.height(); } } } @@ -1059,8 +1062,8 @@ namespace PokemonAutomation { std::chrono::milliseconds settle = 500ms; std::chrono::milliseconds initial_settle = 0ms; size_t max_movements = 30; - int horizontal_range = 12; - int vertical_range = 6; + double horizontal_range = 0.0125; + double vertical_range = 1.0/90; double max_average = 20.0; double max_patch = 30.0; }; @@ -1120,7 +1123,7 @@ namespace PokemonAutomation { label + " Endpoint " + std::to_string(endpoint + 1) + ": average=" + std::to_string(score.average) + ", worst patch=" + std::to_string(score.worst) + - ", offset=(" + std::to_string(score.horizontal) + + ", screen-relative offset=(" + std::to_string(score.horizontal) + "," + std::to_string(score.vertical) + ")" ); @@ -1204,7 +1207,7 @@ namespace PokemonAutomation { options.y = -1.0; options.pulse = 80ms; options.max_movements = 40; - options.horizontal_range = 24; + options.horizontal_range = 0.025; return mine_walk_to_endpoints( stream, context, files, boxes, options, "[Mine path 01]", force_encounter ); @@ -1239,7 +1242,7 @@ namespace PokemonAutomation { options.y = 0.0; options.pulse = 40ms; options.max_movements = 20; - options.horizontal_range = 12; + options.horizontal_range = 0.0125; return mine_walk_to_endpoints( stream, context, files, boxes, options, "[Mine right]" ); @@ -1274,10 +1277,10 @@ namespace PokemonAutomation { options.y = -1.0; options.pulse = 40ms; options.max_movements = 30; - // options.horizontal_range = 12; + // options.horizontal_range = 0.0125; // Path 02 can be shifted horizontally after the first right endpoint. - // The screenshot regression matches at -20 pixels on the 960px image. - options.horizontal_range = 24; + // The screenshot regression matches at about -2.08% of screen width. + options.horizontal_range = 0.025; return mine_walk_to_endpoints( stream, context, files, boxes, options, "[Mine down 02]" ); @@ -1303,7 +1306,7 @@ namespace PokemonAutomation { options.y = 0.0; options.pulse = 40ms; options.max_movements = 30; - options.horizontal_range = 12; + options.horizontal_range = 0.0125; return mine_walk_to_endpoints( stream, context, files, boxes, options, "[Mine left 03]" ); @@ -1338,8 +1341,8 @@ namespace PokemonAutomation { options.max_movements = step.max_movements; options.max_average = step.max_average; options.max_patch = step.max_patch; - options.horizontal_range = 16; - options.vertical_range = 8; + options.horizontal_range = 1.0/60; + options.vertical_range = 2.0/135; options.initial_settle = 700ms; options.settle = 700ms; const std::array files = {step.file}; @@ -1609,12 +1612,14 @@ namespace PokemonAutomation { const ImageRGB32& reference, const ImageRGB32& live, const ImageFloatBox& box, - int horizontal, - int vertical + double horizontal, + double vertical ) { const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); - const ImageViewRGB32 live_crop = - extract_box_reference(live, box, horizontal, vertical); + ImageFloatBox shifted = box; + shifted.x += horizontal; + shifted.y += vertical; + const ImageViewRGB32 live_crop = extract_box_reference(live, shifted); ImageRGB32 adjusted = live_crop.scale_to( reference_crop.width(), reference_crop.height() ); @@ -1636,10 +1641,10 @@ namespace PokemonAutomation { double left = 1000.0; double right = 1000.0; double helmet = 1000.0; - int horizontal = 0; - int vertical = 0; - int helmet_horizontal = 0; - int helmet_vertical = 0; + double horizontal = 0; + double vertical = 0; + double helmet_horizontal = 0; + double helmet_vertical = 0; }; static MineRoarkOffsetScore mine_score_roark_offsets( @@ -1655,11 +1660,14 @@ namespace PokemonAutomation { double best_total = 2000.0; // Both rocks must use the SAME offset. They cannot match independently. - // Offsets are pixels on the 960 x 540 comparison image. - for (int y = -8; y <= 8; y++) { - for (int x = -16; x <= 16; x++) { - const double left = mine_offset_patch_score(reference, live, left_box, x, y); - const double right = mine_offset_patch_score(reference, live, right_box, x, y); + // Search ranges are screen fractions; enumerate pixels only for scoring. + const int radius_x = (int)std::lround(live.width()/60.0); + const int radius_y = (int)std::lround(live.height()*2.0/135); + for (int y = -radius_y; y <= radius_y; y++) { + for (int x = -radius_x; x <= radius_x; x++) { + const double dx = (double)x/live.width(), dy = (double)y/live.height(); + const double left = mine_offset_patch_score(reference, live, left_box, dx, dy); + const double right = mine_offset_patch_score(reference, live, right_box, dx, dy); const double worst = left > right ? left : right; const double total = left + right; @@ -1669,25 +1677,27 @@ namespace PokemonAutomation { best_total = total; best.left = left; best.right = right; - best.horizontal = x; - best.vertical = y; + best.horizontal = (double)x/live.width(); + best.vertical = (double)y/live.height(); } } } // Roark's helmet may move slightly relative to the stationary rocks. // Keep this extra search small so another red object cannot match far away. - for (int extra_y = -6; extra_y <= 6; extra_y++) { - for (int extra_x = -6; extra_x <= 6; extra_x++) { + const int helmet_radius_x = (int)std::lround(live.width()*0.00625); + const int helmet_radius_y = (int)std::lround(live.height()/90.0); + for (int extra_y = -helmet_radius_y; extra_y <= helmet_radius_y; extra_y++) { + for (int extra_x = -helmet_radius_x; extra_x <= helmet_radius_x; extra_x++) { const double score = mine_offset_patch_score( reference, live, helmet_box, - best.horizontal + extra_x, - best.vertical + extra_y + best.horizontal + (double)extra_x/live.width(), + best.vertical + (double)extra_y/live.height() ); if (score < best.helmet) { best.helmet = score; - best.helmet_horizontal = extra_x; - best.helmet_vertical = extra_y; + best.helmet_horizontal = (double)extra_x/live.width(); + best.helmet_vertical = (double)extra_y/live.height(); } } } @@ -1729,9 +1739,9 @@ namespace PokemonAutomation { "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + ", Right rock=" + std::to_string(score.right) + ", Helmet=" + std::to_string(score.helmet) + - ", shared offset=(" + std::to_string(score.horizontal) + + ", shared screen-relative offset=(" + std::to_string(score.horizontal) + "," + std::to_string(score.vertical) + - "), helmet extra=(" + std::to_string(score.helmet_horizontal) + + "), helmet screen-relative extra=(" + std::to_string(score.helmet_horizontal) + "," + std::to_string(score.helmet_vertical) + ")" ); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h index 778657b90a..8ab2bb38dd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h @@ -2,10 +2,18 @@ #pragma once #include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { + // Shared confirmed-Run escape: verifies exit transition or main menu. + void escape_wild_battle( + VideoStream& stream, ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD, + std::chrono::seconds timeout = std::chrono::seconds(30) + ); + enum class MineRouteStage { ALIGN_ENTRANCE, FIRST_ROCK, @@ -28,6 +36,10 @@ namespace PokemonAutomation { bool reset_on_failure = false; }; + // Shared battle routine: use the first move until the battle ends. + void Barry_1_fight(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp new file mode 100644 index 0000000000..0edd6461ba --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include "CommonFramework/Globals.h" +#include "PokemonBDSP_OverworldMatchers.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +struct LandmarkConfig { const char* name; const char* path; ImageFloatBox target,search; double threshold,margin; }; +static cv::Mat gray_image(const ImageRGB32& image){ + cv::Mat pixels((int)image.height(),(int)image.width(),CV_8UC4, + const_cast(image.data()),image.bytes_per_row()); + cv::Mat gray; cv::cvtColor(pixels,gray,cv::COLOR_BGRA2GRAY); return gray; +} +static OverworldMatchResult match_landmarks(const ImageRGB32& frame, + const std::vector& configs, size_t required, double tolerance_x, double tolerance_y, bool smooth_resize=false){ + OverworldMatchResult result; + if (!frame) return result; + cv::Mat gray; + if (smooth_resize){ + // Center matching uses explicit area averaging for stable emblem edges. + // Other matchers retain their existing ImageRGB32 resize behavior. + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + }else{ + ImageRGB32 scaled=frame.scale_to(960,540); + gray=gray_image(scaled); + } + for(const auto& config: configs){ + OverworldLandmarkResult landmark;landmark.name=config.name;landmark.search=config.search; + ImageRGB32 reference(RESOURCE_PATH()+config.path); + // Convert normalized geometry to pixels only at the image-processing boundary. + const int template_width = std::max(1, (int)std::lround(config.target.width*gray.cols)); + const int template_height = std::max(1, (int)std::lround(config.target.height*gray.rows)); + cv::Mat templ; + if (smooth_resize){ + cv::resize(gray_image(reference),templ,cv::Size(template_width,template_height),0,0,cv::INTER_AREA); + }else{ + ImageRGB32 small=reference.scale_to(template_width,template_height); + templ=gray_image(small); + } + cv::Rect search((int)(config.search.x*960),(int)(config.search.y*540), + (int)(config.search.width*960),(int)(config.search.height*540)); + search &= cv::Rect(0,0,960,540); + if(search.width=config.threshold && landmark.margin>=config.margin; + landmark.found={(double)location.x/960,(double)location.y/540,(double)templ.cols/960,(double)templ.rows/540}; + result.landmarks.push_back(landmark); + } + if(required==1 && result.landmarks.size()==1){ + result.located=result.landmarks[0].accepted;result.dx=result.landmarks[0].dx;result.dy=result.landmarks[0].dy; + }else{ + size_t best_support=0; bool ambiguous=false; + for(const auto& candidate:result.landmarks){ + if(!candidate.accepted)continue; + size_t support=0;double dx=0,dy=0; + for(const auto& other:result.landmarks){ + if(other.accepted && std::abs(candidate.dx-other.dx)<=1.0/192 && std::abs(candidate.dy-other.dy)<=1.0/108){ + support++;dx+=other.dx;dy+=other.dy; + } + } + if(supportbest_support){best_support=support;result.dx=dx;result.dy=dy;ambiguous=false;} + else if(support==best_support && (std::abs(dx-result.dx)>1.0/192 || std::abs(dy-result.dy)>1.0/108)) ambiguous=true; + } + result.located=best_support>=required && !ambiguous; + } + result.at_target=result.located && std::abs(result.dx)<=tolerance_x && std::abs(result.dy)<=tolerance_y; + return result; +} +OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ + // Same cropped reference emblems and local search windows as production. + OverworldMatchResult fine = match_landmarks(frame,{ + {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{598.0/960,0,356.0/960,212.0/540},.90,0}, + {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{724.0/960,264.0/540,236.0/960,276.0/540},.90,0}, + {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,268.0/540,240.0/960,272.0/540},.90,0} + },2,7.0/960,7.0/540); + if (fine.located) return fine; + // Coarse acquisition searches the whole frame. Agreement and a peak margin + // guard against confusing the Mart/Center emblems with another building. + OverworldMatchResult coarse = match_landmarks(frame,{ + {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{0,0,1,1},.93,.12}, + {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{0,0,1,1},.93,.12}, + {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,0,1,1},.92,.40} + },2,7.0/960,7.0/540); + if (!coarse.located) { + // Acquisition may expose just one building. School keeps its strong + // spatial margin; Mart/Center require a higher score than pair matching. + // A single anchor guides coarse movement only, never interaction. + const OverworldLandmarkResult* anchor = nullptr; + for (const auto& landmark : coarse.landmarks) { + const bool strong = landmark.accepted && (landmark.name == "School" || + (landmark.score >= .95 && landmark.margin >= .12)); + if (strong && (!anchor || landmark.score > anchor->score)) anchor = &landmark; + } + if (anchor) { + bool contradictory = false; + for (const auto& other : coarse.landmarks) + if (other.accepted && (std::abs(other.dx - anchor->dx) > 1.0/192 || std::abs(other.dy - anchor->dy) > 1.0/108)) + contradictory = true; + if (!contradictory) { + coarse.located = true; + coarse.dx = anchor->dx; + coarse.dy = anchor->dy; + } + } + } + coarse.coarse = true; + // A coarse match may guide movement, but never authorize interaction. + // Final position must pass the normal local two-landmark confirmation. + coarse.at_target = false; + return coarse; +} +OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame){ + return match_landmarks(frame,{{"Mart emblem","PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", + {0.502083333333,0.235185185185,0.056250000000,0.079629629630},{0,0,1,360.0/540},.93,.10}},1,1.0/128,1.0/72); +} +OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame){ + // Native lighting-test captures avoid resizing artifacts in the reference. + // The emblem is about 5.73% of screen width left of the player at approach: + // shift its target right to center the door, preserving the approach height. + OverworldMatchResult clear = match_landmarks(frame,{{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", + {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,1.0/128,1.0/72,true); + OverworldMatchResult shadow = match_landmarks(frame,{{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", + {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,1.0/128,1.0/72,true); + // Lighting alternatives must identify the same position when both pass. + if (clear.located && shadow.located && + (std::abs(clear.dx-shadow.dx)>1.0/192 || std::abs(clear.dy-shadow.dy)>1.0/108)){ + clear.located = false; + clear.at_target = false; + return clear; + } + if (shadow.located && (!clear.located || shadow.landmarks[0].score>clear.landmarks[0].score)) + return shadow; + return clear; +} +OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame){ + // The sign face is stable across lighting; its post and surrounding grass are not. + // Path shadows change shape, so validate the corner against four lighting states. + // Each alternative still requires the sign and path to agree on position. + const char* corners[]={ + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour0.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png" + }; + OverworldMatchResult best; + double best_score=-1; + for (const char* corner : corners){ + auto candidate=match_landmarks(frame,{ + {"Route sign face","PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", + {0.292708333333,0.470370370370,0.109375,0.072222222222},{0,0,1,1},.94,.20}, + {"Path corner",corner, + {0.202083333333,0.648148148148,0.133333333333,0.174074074074},{0,0,1,1},.90,.10} + },2,1.0/128,1.0/72,true); + if (best.located && candidate.located && + (std::abs(best.dx-candidate.dx)>1.0/192 || std::abs(best.dy-candidate.dy)>1.0/108)){ + best.located=false; + best.at_target=false; + return best; + } + if (candidate.landmarks.size()!=2) continue; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate); + best_score=score; + } + } + return best; +} +OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame){ + // Building emblems contain no localized words. Ignore NPCs and door lights. + auto result = match_landmarks(frame,{ + {"Center emblem","PokemonBDSP/Autostory/SandgemJunction/Center.png", + {0.171875,0.2,0.058333333333,0.092592592593},{0,0,1,1},.90,.08}, + {"Mart emblem","PokemonBDSP/Autostory/SandgemJunction/Mart.png", + {0.726041666667,0.222222222222,0.0625,0.088888888889},{0,0,1,1},.90,.08} + },2,1.0/128,1.0/72,true); + if (result.located || result.landmarks.size()!=2) return result; + const auto& center=result.landmarks[0]; + const auto& mart=result.landmarks[1]; + const double dy=(center.dy+mart.dy)/2; + const double horizontal_disagreement=center.dx-mart.dx; + // At a different approach height, perspective changes the buildings' spacing. + // Trust matching height offsets for vertical acquisition only. Bound the + // horizontal discrepancy and require its direction to agree with perspective: + // scenery above target contracts, scenery below target expands. + if (center.accepted && mart.accepted && + std::abs(center.dy-mart.dy)<=1.0/108 && + std::abs(dy)>1.0/72 && std::abs(dy)<=0.20 && + std::abs(horizontal_disagreement)<=0.04 && + std::abs(horizontal_disagreement)<=0.35*std::abs(dy)+1.0/192 && + horizontal_disagreement*dy<=0){ + result.located=true; + result.coarse=true; + result.dx=(center.dx+mart.dx)/2; + result.dy=dy; + // Acquisition may move vertically; it never confirms the route endpoint. + result.at_target=false; + } + return result; +} +OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ + // Route marker and upper path bend avoid the player, NPCs, and localized text. + // Morning tree shadows need a second corner reference. Each alternative + // must still agree with the sign; neither can confirm the destination alone. + const char* corners[]={ + "PokemonBDSP/Autostory/Route202GrassExit/PathCorner.png", + "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png" + }; + OverworldMatchResult best; + double best_score=-1; + for (const char* corner : corners){ + auto candidate=match_landmarks(frame,{ + {"Route 202 sign","PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", + {0.59375,0.194444444444,0.091666666667,0.059259259259},{0,0,1,1},.94,.20}, + {"Path corner",corner, + {0.405208333333,0.331481481481,0.1375,0.062962962963},{0,0,1,1},.90,.08} + },2,1.0/128,1.0/72,true); + if (best.located && candidate.located && + (std::abs(best.dx-candidate.dx)>1.0/192 || std::abs(best.dy-candidate.dy)>1.0/108)){ + best.located=false; + best.at_target=false; + return best; + } + if (candidate.landmarks.size()!=2) continue; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate); + best_score=score; + } + } + return best; +} +const std::vector& overworld_matchers(){ + static const std::vector matchers={ + {"poketch-landmarks","Jubilife Poketch landmarks",match_poketch_landmarks}, + {"oreburgh-mart","Oreburgh Mart emblem",match_oreburgh_mart}, + {"jubilife-center","Jubilife Pokemon Center entrance",match_jubilife_center_entrance}, + {"route201-potion-exit","Route 201 after Potion pickup",match_route201_potion_exit}, + {"sandgem-junction","Sandgem Center/Mart junction",match_sandgem_junction}, + {"route202-grass-exit","Route 202 after catching: grass exit",match_route202_grass_exit}, + }; + return matchers; +} +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h new file mode 100644 index 0000000000..7d2b5c25d3 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +struct OverworldLandmarkResult { + std::string name; + double score = 0, margin = 0; + // Signed fractions of screen width/height, independent of capture resolution. + double dx = 0, dy = 0; + bool accepted = false; + ImageFloatBox search, found; +}; +struct OverworldMatchResult { + std::vector landmarks; + bool located = false, at_target = false; + bool coarse = false; + // Signed fractions of screen width/height, independent of capture resolution. + double dx = 0, dy = 0; +}; +struct OverworldMatcher { + std::string slug, name; + std::function match; +}; +// Register additional functions here; each owns its landmarks and coordinate system. +const std::vector& overworld_matchers(); +OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame); +OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame); +OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame); +OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame); +OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame); +OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame); +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp new file mode 100644 index 0000000000..4ed5cde7a3 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP_OverworldLightingTest.h" +#include "../AutoStory/Utils/PokemonBDSP_OverworldMatchers.h" +#include "../AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +using namespace std::chrono_literals; +static const StringSelectDatabase& lighting_matcher_database(){ + static const StringSelectDatabase db=[](){ + StringSelectDatabase result; + for(const auto& matcher:overworld_matchers())result.add_entry({matcher.slug,matcher.name}); + return result; + }();return db; +} +OverworldLightingTest_Descriptor::OverworldLightingTest_Descriptor() + : SingleSwitchProgramDescriptor("PokemonBDSP:OverworldLightingTest","Pokemon BDSP","Overworld Matcher Lighting Test","", + "Place the player at the selected matcher's reference location. Tests 24 hourly lighting settings without moving the player. " + "Changes console date/time; saves screenshots and CSV results under Screenshots/OverworldLightingTest.", + ProgramControllerClass::StandardController_NoRestrictions,FeedbackType::REQUIRED,AllowCommandsWhenRunning::DISABLE_COMMANDS) +{} +OverworldLightingTest::OverworldLightingTest() + : MATCHER("Matcher:",lighting_matcher_database(),LockMode::LOCK_WHILE_RUNNING,"poketch-landmarks") + , START_HOUR("Starting hour:
Establishes the initial time, then advances one hour for each of 24 samples.",LockMode::LOCK_WHILE_RUNNING,0,0,23) + , SETTLE_SECONDS("Wait after returning to game (seconds):",LockMode::LOCK_WHILE_RUNNING,5,1,60) +{PA_ADD_OPTION(MATCHER);PA_ADD_OPTION(START_HOUR);PA_ADD_OPTION(SETTLE_SECONDS);} +void OverworldLightingTest::program(SingleSwitchProgramEnvironment& env,ProControllerContext& context){ + const auto& matcher=overworld_matchers()[MATCHER.index()]; + std::filesystem::path output=std::filesystem::path("Screenshots/OverworldLightingTest")/ + (matcher.slug+"-"+std::to_string(std::time(nullptr))); + std::filesystem::create_directories(output); + std::ofstream csv(output/"results.csv"); + if(!csv)OperationFailedException::fire(ErrorReport::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); + csv<<"sample,hour,frame,landmark,score,margin,accepted,dx_screen,dy_screen,located,at_target,consensus_dx_screen,consensus_dy_screen,coarse\n"; + csv<save((output/name).string()))OperationFailedException::fire(ErrorReport::NO_ERROR_REPORT,"Lighting test: Screenshot save failed.",env.console); + csv.flush(); + pbf_wait(context,250ms);context.wait_for_all_requests(); + } + } + env.log("[Lighting test] Complete: "+std::to_string(target_frames)+"/120 frames at target. Results: "+output.string()); +} +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h new file mode 100644 index 0000000000..52580778c8 --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h @@ -0,0 +1,18 @@ +#pragma once +#include "Common/Cpp/Options/SimpleIntegerOption.h" +#include "CommonTools/Options/StringSelectOption.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +class OverworldLightingTest_Descriptor : public SingleSwitchProgramDescriptor{ +public: OverworldLightingTest_Descriptor(); +}; +class OverworldLightingTest : public SingleSwitchProgramInstance{ +public: + OverworldLightingTest(); + void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; +private: + StringSelectOption MATCHER; + SimpleIntegerOption START_HOUR; + SimpleIntegerOption SETTLE_SECONDS; +}; +}}} diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 91353df4a8..1b44843448 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1487,6 +1487,10 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Resources/Pokemon_PokemonNames.h Source/Pokemon/Resources/Pokemon_PokemonSlugs.cpp Source/Pokemon/Resources/Pokemon_PokemonSlugs.h + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp + Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h + Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp + Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h Source/PokemonBDSP/PokemonBDSP_Tests.cpp Source/PokemonBDSP/PokemonBDSP_Tests.h Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp From eab8b2ecd69955e35ce1af8300b5803c0273c8eb Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 11 Oct 2026 20:43:26 -0500 Subject: [PATCH 22/39] Forward checkpoint save options through BDSP Segments 03-09 Pass AutoStoryOptions to checkpoint_reattempt_loop in the 113 checkpoint calls across Segments 03 through 09. This forwards the selected real/fake checkpoint-save mode consistently instead of passing only the status notification option and falling back to the default save mode. Restore the original LF line endings so the diff contains only the argument changes. No route movements, battle behavior, or checkpoint ordering change. Validation: byte comparison against the parent confirms that substituting the original checkpoint argument produces identical files in all seven segments. The same source contents passed the full Debug build before this line-ending cleanup; the cleanup does not change compilation semantics. Scope: BDSP Segments 03-09 only. Segment 02, its matcher additions and Repel helper repair remain staged and uncommitted. Shared CommonTools OCR edits are excluded. --- .../PokemonBDSP_AutoStory_Segment_03.cpp | 14 ++--- .../PokemonBDSP_AutoStory_Segment_04.cpp | 34 +++++------ .../PokemonBDSP_AutoStory_Segment_05.cpp | 34 +++++------ .../PokemonBDSP_AutoStory_Segment_06.cpp | 22 ++++---- .../PokemonBDSP_AutoStory_Segment_07.cpp | 38 ++++++------- .../PokemonBDSP_AutoStory_Segment_08.cpp | 56 +++++++++---------- .../PokemonBDSP_AutoStory_Segment_09.cpp | 28 +++++----- 7 files changed, 113 insertions(+), 113 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 155769e01c..864db1871d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -308,7 +308,7 @@ void Checkpoint_0301( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 03 gameplay logic eterna_gym_to_jupiter(env.console, context); @@ -338,7 +338,7 @@ void Checkpoint_0302( AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // TODO: implement Segment 03 gameplay logic jupiter_to_coronet(env.console, context); @@ -368,7 +368,7 @@ void Checkpoint_0303( AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // TODO: implement Segment 03 gameplay logic coronet1(env.console, context); @@ -398,7 +398,7 @@ void Checkpoint_0304( AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // TODO: implement Segment 03 gameplay logic coronet_to_heartholme(env.console, context); @@ -428,7 +428,7 @@ void Checkpoint_0305( AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // TODO: implement Segment 03 gameplay logic heartholme_to_lost_tower(env.console, context); @@ -460,7 +460,7 @@ void Checkpoint_0306( AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { // TODO: implement Segment 03 gameplay logic lost_tower_to_veilstone_gym(env.console, context); @@ -489,7 +489,7 @@ void Checkpoint_0307( AutoStoryOptions options, AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { //#. Veilstone Gym navigation and battle gym_Puzzle_03(env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 057b494a7f..d2f3f73687 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -1006,7 +1006,7 @@ static bool go_to_pastoria_gym( } void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); @@ -1017,7 +1017,7 @@ void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_veilstone_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_veilstone_pokemon_center: transition not detected.", env.console); @@ -1028,7 +1028,7 @@ void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); @@ -1039,7 +1039,7 @@ void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_warehouse: transition not detected.", env.console); @@ -1050,7 +1050,7 @@ void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); @@ -1061,7 +1061,7 @@ void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_galactic_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_galactic_warehouse: transition not detected.", env.console); @@ -1072,7 +1072,7 @@ void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!exit_galactic_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_galactic_warehouse: transition not detected.", env.console); @@ -1083,7 +1083,7 @@ void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_route_214: transition not detected.", env.console); @@ -1094,7 +1094,7 @@ void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); @@ -1105,7 +1105,7 @@ void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); @@ -1116,7 +1116,7 @@ void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!pass_by_valor_lakefront(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pass_by_valor_lakefront: transition not detected.", env.console); @@ -1127,7 +1127,7 @@ void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); @@ -1138,7 +1138,7 @@ void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_213(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); @@ -1149,7 +1149,7 @@ void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); @@ -1160,7 +1160,7 @@ void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_pastoria(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); @@ -1171,7 +1171,7 @@ void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); @@ -1182,7 +1182,7 @@ void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index 8a553bd3a1..5b9f01375f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -1483,7 +1483,7 @@ static bool get_into_hearthome_gym( } void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); @@ -1494,7 +1494,7 @@ void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!exit_pastoria_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_pastoria_pokemon_center: transition not detected.", env.console); @@ -1505,7 +1505,7 @@ void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!pastoria_city_drama(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); @@ -1516,7 +1516,7 @@ void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); @@ -1527,7 +1527,7 @@ void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_213_chasing(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213_chasing: transition not detected.", env.console); @@ -1538,7 +1538,7 @@ void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); @@ -1549,7 +1549,7 @@ void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); @@ -1560,7 +1560,7 @@ void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_inside_solaceon_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", env.console); @@ -1571,7 +1571,7 @@ void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_solaceon_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", env.console); @@ -1582,7 +1582,7 @@ void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_the_psyduck_route_210(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_psyduck_route_210: transition not detected.", env.console); @@ -1593,7 +1593,7 @@ void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_celestic_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", env.console); @@ -1604,7 +1604,7 @@ void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!celestic_town_drama(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); @@ -1615,7 +1615,7 @@ void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!discover_the_paintings(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "discover_the_paintings: transition not detected.", env.console); @@ -1626,7 +1626,7 @@ void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); @@ -1637,7 +1637,7 @@ void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_hearthome_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", env.console); @@ -1648,7 +1648,7 @@ void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); @@ -1659,7 +1659,7 @@ void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 8d148ce592..5650ae52ef 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -625,7 +625,7 @@ static bool go_to_canalave_gym( void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ /*if (!test(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "test: transition not detected.", env.console); @@ -639,7 +639,7 @@ void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_jubilife_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_jubilife_pokemon_center: transition not detected.", env.console); @@ -650,7 +650,7 @@ void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); @@ -661,7 +661,7 @@ void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "move_to_jubilife_west_exit: transition not detected.", env.console); @@ -672,7 +672,7 @@ void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_jubilife_west_exit: transition not detected.", env.console); @@ -683,7 +683,7 @@ void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_218(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); @@ -694,7 +694,7 @@ void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); @@ -705,7 +705,7 @@ void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_clint_and_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_clint_and_pokemon_center: transition not detected.", env.console); @@ -716,7 +716,7 @@ void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); @@ -727,7 +727,7 @@ void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); @@ -738,7 +738,7 @@ void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 1553291c20..f511d1fe66 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -1763,7 +1763,7 @@ static bool go_to_snowpoint_gym( void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); @@ -1774,7 +1774,7 @@ void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_the_library(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); @@ -1785,7 +1785,7 @@ void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!drama_at_the_library(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); @@ -1796,7 +1796,7 @@ void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_fly_and_enter_mount_coronet(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); @@ -1807,7 +1807,7 @@ void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); @@ -1818,7 +1818,7 @@ void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_216(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); @@ -1829,7 +1829,7 @@ void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_small_house_route_216(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_small_house_route_216: transition not detected.", env.console); @@ -1840,7 +1840,7 @@ void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!continue_through_route_216(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "continue_through_route_216: transition not detected.", env.console); @@ -1851,7 +1851,7 @@ void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); @@ -1862,7 +1862,7 @@ void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); @@ -1873,7 +1873,7 @@ void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); @@ -1884,7 +1884,7 @@ void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); @@ -1895,7 +1895,7 @@ void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_lake_valor(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); @@ -1906,7 +1906,7 @@ void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_home(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_home: transition not detected.", env.console); @@ -1917,7 +1917,7 @@ void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_lake_verity(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); @@ -1928,7 +1928,7 @@ void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!commander_mars_and_snowpoint(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "commander_mars_and_snowpoint: transition not detected.", env.console); @@ -1939,7 +1939,7 @@ void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); @@ -1950,7 +1950,7 @@ void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); @@ -1961,7 +1961,7 @@ void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 91528bce6b..256f737145 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -2316,7 +2316,7 @@ static bool go_to_sunyshore_gym( void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); @@ -2327,7 +2327,7 @@ void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); @@ -2338,7 +2338,7 @@ void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Snowpoint City")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_snowpoint_city: transition not detected.", env.console); @@ -2349,7 +2349,7 @@ void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_lake_acuity(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); @@ -2360,7 +2360,7 @@ void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_clint_then_go_veilstone(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); @@ -2371,7 +2371,7 @@ void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); @@ -2382,7 +2382,7 @@ void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_key_and_enter_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); @@ -2393,7 +2393,7 @@ void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_warehouse(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); @@ -2404,7 +2404,7 @@ void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_veilstone_pokemon_center(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); @@ -2415,7 +2415,7 @@ void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); @@ -2426,7 +2426,7 @@ void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_hq(env.console, context)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); @@ -2437,7 +2437,7 @@ void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_galactic_hq(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); @@ -2448,7 +2448,7 @@ void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_oreburgh(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); @@ -2459,7 +2459,7 @@ void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Oreburgh City")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_oreburgh_city: transition not detected.", env.console); @@ -2470,7 +2470,7 @@ void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_mount_coronet(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); @@ -2481,7 +2481,7 @@ void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); @@ -2492,7 +2492,7 @@ void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_veilstone(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_veilstone: transition not detected.", env.console); @@ -2503,7 +2503,7 @@ void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); @@ -2514,7 +2514,7 @@ void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); @@ -2525,7 +2525,7 @@ void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); @@ -2536,7 +2536,7 @@ void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_west_sunyshore(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_west_sunyshore: transition not detected.", env.console); @@ -2547,7 +2547,7 @@ void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_west_sunyshore(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", env.console); @@ -2558,7 +2558,7 @@ void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_lighthouse(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_lighthouse: transition not detected.", env.console); @@ -2569,7 +2569,7 @@ void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& } void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", env.console); @@ -2585,7 +2585,7 @@ void checkpoint_0825( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); @@ -2601,7 +2601,7 @@ void checkpoint_0826( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); @@ -2941,7 +2941,7 @@ void checkpoint_021( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 08 gameplay logic (seventh badge area) }, @@ -2955,7 +2955,7 @@ void checkpoint_021_legendary( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ approach_legendary(env.console, context); enter_legendary_battle(env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index c28427e8ab..e49ea66c86 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -1433,7 +1433,7 @@ void checkpoint_0901( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_sunyshore_pokemon_center(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_sunyshore_pokemon_center: transition not detected.", env.console); @@ -1449,7 +1449,7 @@ void checkpoint_0902( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Sunyshore City")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_1: transition not detected.", env.console); @@ -1465,7 +1465,7 @@ void checkpoint_0903( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_victory_road_pokemon_center(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", env.console); @@ -1481,7 +1481,7 @@ void checkpoint_0904( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Victory Road")){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_2: transition not detected.", env.console); @@ -1497,7 +1497,7 @@ void checkpoint_0905( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_victory_road_cave(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_victory_road_cave: transition not detected.", env.console); @@ -1513,7 +1513,7 @@ void checkpoint_0906( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_victory_road_cave(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_victory_road_cave: transition not detected.", env.console); @@ -1529,7 +1529,7 @@ void checkpoint_0907( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_elite_4_pokemon_center(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_elite_4_pokemon_center: transition not detected.", env.console); @@ -1545,7 +1545,7 @@ void checkpoint_0908( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!ready_in_front_of_elite_4(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "ready_in_front_of_elite_4: transition not detected.", env.console); @@ -1561,7 +1561,7 @@ void checkpoint_0909( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_the_elite_4_entrance(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_the_elite_4_entrance: transition not detected.", env.console); @@ -1577,7 +1577,7 @@ void checkpoint_0910( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_aaron_elite_4(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_aaron_elite_4: transition not detected.", env.console); @@ -1593,7 +1593,7 @@ void checkpoint_0911( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_bertha_elite_4(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_bertha_elite_4: transition not detected.", env.console); @@ -1609,7 +1609,7 @@ void checkpoint_0912( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_flint_elite_4(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_flint_elite_4: transition not detected.", env.console); @@ -1625,7 +1625,7 @@ void checkpoint_0913( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_lucian_elite_4(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_lucian_elite_4: transition not detected.", env.console); @@ -1641,7 +1641,7 @@ void checkpoint_0914( AutoStoryOptions options, AutoStoryStats& stats ){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_cynthia_and_finish(env.console, context, options)){ OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_cynthia_and_finish: transition not detected.", env.console); From 6d82a68684baf6966dbdb98d1613406192c05b82 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 11 Oct 2026 20:45:12 -0500 Subject: [PATCH 23/39] Add Jubilife approach matchers and repair BDSP Repel-tab detection Register street and Galactic-plaza reference positions for the existing overworld matcher and lighting-test infrastructure. Match static building and fence geometry instead of player/NPC animation or localized signs, require two agreeing landmarks, and expose screen-relative offsets and destination tolerances through the existing matcher result interface. Repair activate_repel's resource path casing and provide native/small selected-pocket references to accommodate capture-resize sampling. Accept either reference while preserving the match threshold and the bounded eight-tab search; no shared image-processing behavior changes. Validation: full Debug application build passed with these changes. All 22 new matcher checks passed across 720p, 1080p, and 1440p captures, positive/negative offsets in both axes, and rejection of unrelated scenes. Earlier Repel reference checks accepted resized selected-pocket captures and rejected the wrong pocket. Full-day lighting and live route validation for the new Jubilife positions remain follow-up work. Required templates are committed to Developer-Butters/Packages on branch BDSP-Autostory. The Segment 02 route file and its locator integration calls remain staged and uncommitted; this commit only publishes reusable BDSP matchers and Bag tools, with no shared infrastructure edits. --- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 12 ++++++++--- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 20 +++++++++++++++++++ .../Utils/PokemonBDSP_OverworldMatchers.h | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 76a3f0a46f..21d850a943 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1741,7 +1741,12 @@ bool activate_repel( pbf_wait(context, 1000ms); auto repel_tab_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/AutoStory/Bag_RepelTab.png" + RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTab.png" + ); + // A smaller reference covers capture-resize sampling differences without + // loosening the match threshold or changing shared image processing. + auto repel_tab_small_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabSmall.png" ); const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; const double repel_tab_rmsd = 50.0; @@ -1753,8 +1758,9 @@ bool activate_repel( context.wait_for_all_requests(); ImageMatchWatcher watcher(repel_tab_ref, repel_tab_box, repel_tab_rmsd); - int ret = wait_until(stream, context, 400ms, {{watcher}}); - if (ret == 0){ + ImageMatchWatcher small_watcher(repel_tab_small_ref, repel_tab_box, repel_tab_rmsd); + int ret = wait_until(stream, context, 400ms, {{watcher}, {small_watcher}}); + if (ret >= 0){ stream.log("[AutoStory] activate_repel: repel tab found after " + std::to_string(press) + " press(es).", COLOR_GREEN); found = true; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index 0edd6461ba..4f9ed1f99c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -247,8 +247,28 @@ OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ } return best; } +OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame){ + // Static building/fence geometry excludes people and localized signs. + return match_landmarks(frame,{ + {"Door","PokemonBDSP/Autostory/JubilifeStreet/Door.png", + {0.677083333333,0.439814814815,0.213541666667,0.162037037037},{0,0,1,1},.90,.04}, + {"Facade","PokemonBDSP/Autostory/JubilifeStreet/Facade.png", + {0.583333333333,0.138888888889,0.322916666667,0.250000000000},{0,0,1,1},.90,.04} + },2,1.0/128,1.0/72,true); +} +OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame){ + // Static building/fence geometry excludes people and localized signs. + return match_landmarks(frame,{ + {"Roof","PokemonBDSP/Autostory/JubilifePlaza/Roof.png", + {0.640625000000,0.069444444444,0.260416666667,0.189814814815},{0,0,1,1},.90,.04}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/Fence.png", + {0.041666666667,0.319444444444,0.169270833333,0.120370370370},{0,0,1,1},.90,.04} + },2,1.0/128,1.0/72,true); +} const std::vector& overworld_matchers(){ static const std::vector matchers={ + {"jubilife-street-approach","Jubilife street before Galactic plaza",match_jubilife_street_approach}, + {"jubilife-plaza-approach","Jubilife Galactic plaza approach",match_jubilife_plaza_approach}, {"poketch-landmarks","Jubilife Poketch landmarks",match_poketch_landmarks}, {"oreburgh-mart","Oreburgh Mart emblem",match_oreburgh_mart}, {"jubilife-center","Jubilife Pokemon Center entrance",match_jubilife_center_entrance}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 7d2b5c25d3..2e419a10ce 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -26,6 +26,8 @@ struct OverworldMatcher { }; // Register additional functions here; each owns its landmarks and coordinate system. const std::vector& overworld_matchers(); +OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame); +OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame); OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame); OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame); OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame); From 3588b70cee6b87d58ed5125c13eb2ab079d148bf Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 11 Oct 2026 21:09:41 -0500 Subject: [PATCH 24/39] Adapt BDSP AutoStory to current upstream interfaces After rebasing the 23 AutoStory commits onto PokemonAutomation main, update BDSP code to the APIs introduced by the 299 intervening commits: - Use ErrorReportMode and OperationFailedExceptionWithScreenshot for the existing stream/screenshot failure calls. Keep the base exception catches so the same checkpoint retry handling still consumes these failures. - Include GlobalAutoPaths.h where RESOURCE_PATH and screenshot paths are used, following upstream's separation of those declarations from Globals. - Follow the new SingleSwitchProgram factory and Descriptor-alias pattern for AutoStory and its lighting tester. Replace the old boolean descriptor argument with PanelDeprecation::NOT_DEPRECATED. - Point the BDSP battle helper at the relocated VideoGames/PokemonSwSh type-matchup header; the shared type calculation itself stays untouched. The rebase keeps upstream's shared test tools and deleted TestMap authoritative instead of restoring the fork's obsolete developer-program checkboxes. SourceFiles conflicts were resolved by retaining upstream's build list and applying only BDSP source additions and the existing tool-path relocation. Validation: full Debug SerialPrograms build and link passed on the upstream base. These are interface/header adaptations; controller movements, battle selection, matcher thresholds, and checkpoint ordering are unchanged. The uncommitted Segment 02 route remains preserved outside the rebase. Only upstream interface adaptations to its already-committed code are included. The transition watcher localization and shared-file restoration are documented in a separate commit; no other-game implementations are changed here. --- .../Inference/PokemonBDSP_MenuDetector.cpp | 1 + .../Source/PokemonBDSP/PokemonBDSP_Panels.cpp | 4 +- .../Source/PokemonBDSP/PokemonBDSP_Tests.cpp | 1 + .../Data/PokemonBDSP_AutoStory_BDSPData.cpp | 1 + .../AutoStory/PokemonBDSP_AutoStory.cpp | 6 +- .../AutoStory/PokemonBDSP_AutoStory.h | 2 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 44 +++++++------ .../AutoStory/PokemonBDSP_AutoStoryTools.h | 1 + .../PokemonBDSP_AutoStory_Segment_00.cpp | 28 ++++---- .../PokemonBDSP_AutoStory_Segment_02.cpp | 8 ++- .../PokemonBDSP_AutoStory_Segment_04.cpp | 35 +++++----- .../PokemonBDSP_AutoStory_Segment_05.cpp | 35 +++++----- .../PokemonBDSP_AutoStory_Segment_06.cpp | 25 +++---- .../PokemonBDSP_AutoStory_Segment_07.cpp | 39 +++++------ .../PokemonBDSP_AutoStory_Segment_08.cpp | 53 +++++++-------- .../PokemonBDSP_AutoStory_Segment_09.cpp | 29 ++++---- .../Utils/PokemonBDSP_AutoStory_Battle.cpp | 7 +- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 66 ++++++++++--------- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 50 +++++++------- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 1 + .../PokemonBDSP_OverworldLightingTest.cpp | 7 +- .../PokemonBDSP_OverworldLightingTest.h | 1 + 22 files changed, 232 insertions(+), 212 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp index b84334cdb2..189ad4fe27 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp index 17b53f04e9..1d7a5b3132 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Panels.cpp @@ -95,8 +95,8 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_SingleSwitchProgram()); ret.emplace_back("---- Auto Story ----"); - ret.emplace_back(make_single_switch_program()); - ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_SingleSwitchProgram()); + ret.emplace_back(make_SingleSwitchProgram()); ret.emplace_back("---- Glitches (v1.1.3) ----"); ret.emplace_back(make_SingleSwitchProgram()); diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp index faf2f6a044..b2ec5602d8 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp @@ -3,6 +3,7 @@ * From: https://github.com/PokemonAutomation/ */ +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp index 788156b93e..1066596daf 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/GlobalAutoPaths.h" #include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/Json/JsonValue.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 042cba38ed..48c2627b40 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -295,16 +295,12 @@ BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, AllowCommandsWhenRunning::DISABLE_COMMANDS, - false, + PanelDeprecation::NOT_DEPRECATED, {"Tesseract"} ) {} -std::unique_ptr -BDSPAutoStory_Descriptor::make_instance() const{ - return std::make_unique(); -} std::unique_ptr BDSPAutoStory_Descriptor::make_stats() const{ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h index 736864da09..da56201aa9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h @@ -20,13 +20,13 @@ class BDSPAutoStory_Descriptor : public SingleSwitchProgramDescriptor{ public: BDSPAutoStory_Descriptor(); - virtual std::unique_ptr make_instance() const override; virtual std::unique_ptr make_stats() const override; }; class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::Listener{ public: + using Descriptor = BDSPAutoStory_Descriptor; ~BDSPAutoStory(); BDSPAutoStory(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 21d850a943..a9f8134285 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1,3 +1,4 @@ +#include "CommonFramework/GlobalAutoPaths.h" #include /* BDSP AutoStory Tools * @@ -8,6 +9,7 @@ #include "CommonFramework/ImageTools/ImageDiff.h" #include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/ImageTools/ImageBoxes.h" @@ -120,8 +122,8 @@ bool save_game( { overworld } ); if (ret < 0) { - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, + throw OperationFailedExceptionWithScreenshot( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to detect overworld after save.", env.console ); @@ -228,8 +230,8 @@ void checkpoint_reattempt_loop( env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); if (!ok){ - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, + throw OperationFailedExceptionWithScreenshot( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to reset game from HOME during checkpoint retry.", env.console ); @@ -285,8 +287,8 @@ void wait_for_dialogue( ); if (ret == 0){ log_detected(); return; } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", stream ); @@ -308,8 +310,8 @@ void mash_until_dialogue_ends( {{overworld}} ); if (ret < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "mash_until_dialogue_ends: overworld not reached within timeout.", stream ); @@ -890,7 +892,7 @@ static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& con previous = hp; pbf_wait(context, 150ms); } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Slot 1 HP bar could not be confirmed.", stream); } @@ -904,14 +906,14 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context for (size_t dose = 0; dose < 3; dose++) { stream.log("[Potion] Finding Bag/Medicine/Potion; dose " + std::to_string(dose + 1) + "/3."); if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Could not verify Potion in the Medicine pocket.", stream); } const double before = confirmed_potion_hp(stream, context); stream.log("[Potion] Slot 1 HP bar before use=" + std::to_string(before * 100) + "%."); if (before >= 0.98) { healed = true; break; } if (before <= 0.005) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Slot 1 appears fainted; a Potion cannot revive it.", stream); } pbf_press_button(context, BUTTON_A, 80ms, 500ms); @@ -921,7 +923,7 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context context.wait_for_all_requests(); OverworldWatcher overworld; if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Did not return to the overworld after use.", stream); } // Reopen Bag to verify the result, including when the last Potion was used. @@ -931,20 +933,20 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context const double after = confirmed_potion_hp(stream, context); stream.log("[Potion] Slot 1 HP bar after use=" + std::to_string(after * 100) + "%."); if (after <= before + 0.005) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Slot 1 HP did not increase; refusing to continue movement.", stream); } if (after >= 0.98) { healed = true; break; } pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); } - if (!healed) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!healed) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Full HP not confirmed after three uses.", stream); pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); OverworldWatcher overworld; if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Potion: Final overworld return not confirmed.", stream); stream.log("[Potion] Slot 1 HP bar full and overworld confirmed."); } @@ -1020,8 +1022,8 @@ void open_menu( // if (snap) cur = detector.detect(snap); // } // if (cur == MenuCursorPosition::UNKNOWN){ - // OperationFailedException::fire( - // ErrorReport::SEND_ERROR_REPORT, + // OperationFailedExceptionWithScreenshot::fire( + // ErrorReportMode::SEND_ERROR_REPORT, // "Menu cursor not detected after opening menu.", // stream // ); @@ -1057,8 +1059,8 @@ void open_menu( } } if (cur == MenuCursorPosition::UNKNOWN){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Menu or cursor detection failed after 3 opening attempts.", stream ); @@ -1067,8 +1069,8 @@ void open_menu( IconGrid cur_g = icon_to_grid(cur, num_icons); IconGrid tgt_g = icon_to_grid(target, num_icons); if (tgt_g.row == -1){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Unknown target menu icon.", stream ); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 986c3a005f..636c836fd6 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStoryTools_H #define PokemonAutomation_PokemonBDSP_AutoStoryTools_H +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 2f877658d7..7422087fa2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -5,6 +5,7 @@ * */ +#include "CommonFramework/GlobalAutoPaths.h" #include "PokemonBDSP_AutoStory_Segment_00.h" #include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" #include "../Utils/PokemonBDSP_OverworldMatchers.h" @@ -16,6 +17,7 @@ #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" @@ -57,13 +59,13 @@ namespace PokemonAutomation { pbf_wait(ctx, 5000ms); }, {{black}}); if (ret != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Black transition not detected.", stream); } BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 15s, {{visible}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Transition did not finish.", stream); } stream.log("[Tutorial transition] " + label + ": destination visible."); @@ -228,7 +230,7 @@ namespace PokemonAutomation { pbf_move_left_joystick(ctx, {0, 1}, 5000ms, 0ms); }, {{entry_black}}); if (entry_ret != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Barry house: Entry fade not detected.", stream); } // Release the joystick during loading; resume indoor movement only @@ -236,7 +238,7 @@ namespace PokemonAutomation { BlackScreenWatcher entry_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 15s, {{entry_visible}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Barry house: Entry fade did not finish.", stream); } pbf_wait(context, 500ms); @@ -343,8 +345,8 @@ namespace PokemonAutomation { } else { stream.log("Timed out waiting for briefcase."); - throw OperationFailedException( - ErrorReport::SEND_ERROR_REPORT, + throw OperationFailedExceptionWithScreenshot( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to detect briefcase in Segment 00.", stream ); @@ -531,7 +533,7 @@ namespace PokemonAutomation { double previous_dx = 0, previous_dy = 0; for (size_t attempt = 0; attempt < 60; attempt++) { const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": No video frame.", stream); const auto result = matcher(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -588,7 +590,7 @@ namespace PokemonAutomation { } context.wait_for_all_requests(); } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Reference position not confirmed; route movement stopped.", stream); } @@ -731,7 +733,7 @@ namespace PokemonAutomation { Milliseconds walked = 0ms; while (walked < 1900ms) { auto frame = stream.video().snapshot(); - if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Poketch approach: No video frame.", stream); const auto result = match_poketch_landmarks(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -766,7 +768,7 @@ namespace PokemonAutomation { const int vertical_probes[] = {-80, 160, -240, 320, -160, -120, 240, -360, 480, -240}; for (size_t attempt = 0; attempt < 80; attempt++) { auto frame = stream.video().snapshot(); - if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Poketch interaction: No video frame.", stream); const OverworldMatchResult result = match_poketch_landmarks(*frame.frame); stream.log(result.coarse ? "[Poketch landmarks] Coarse acquisition." : "[Poketch landmarks] Fine alignment."); @@ -836,7 +838,7 @@ namespace PokemonAutomation { } context.wait_for_all_requests(); } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Poketch interaction: Building landmarks did not confirm the reference position; A not pressed.", stream); } @@ -847,7 +849,7 @@ namespace PokemonAutomation { size_t confirmed = 0; for (size_t attempt = 0; attempt < 60; attempt++) { const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Jubilife Center alignment: No video frame.", stream); const auto result = match_jubilife_center_entrance(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -882,7 +884,7 @@ namespace PokemonAutomation { } context.wait_for_all_requests(); } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Jubilife Center alignment: Door reference not confirmed; entry not attempted.", stream); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index ff7b110968..751441dc8e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -5,9 +5,11 @@ * */ +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" @@ -42,7 +44,7 @@ namespace PokemonBDSP{ for (size_t attempt = 0; attempt < 40; attempt++) { const VideoSnapshot frame = stream.video().snapshot(); if (!frame) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: No video frame.", stream); } const ImageRGB32 small_frame = frame->scale_to(960, 540); @@ -60,7 +62,7 @@ namespace PokemonBDSP{ double runner_up; cv::minMaxLoc(competitors, nullptr, &runner_up); if (score < 0.93 || score - runner_up < 0.10) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: Emblem match missing or ambiguous.", stream); } // Reference emblem top-left at half resolution. The reference player @@ -88,7 +90,7 @@ namespace PokemonBDSP{ } context.wait_for_all_requests(); } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: Could not reach the doorway reference position.", stream); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index d2f3f73687..db8db47593 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -6,6 +6,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" @@ -1009,7 +1010,7 @@ void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); } }, true @@ -1020,7 +1021,7 @@ void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_veilstone_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_veilstone_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_veilstone_pokemon_center: transition not detected.", env.console); } }, false @@ -1031,7 +1032,7 @@ void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); } }, false @@ -1042,7 +1043,7 @@ void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_galactic_warehouse: transition not detected.", env.console); } }, false @@ -1053,7 +1054,7 @@ void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); } }, false @@ -1064,7 +1065,7 @@ void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_galactic_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_galactic_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_galactic_warehouse: transition not detected.", env.console); } }, false @@ -1075,7 +1076,7 @@ void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!exit_galactic_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_galactic_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "exit_galactic_warehouse: transition not detected.", env.console); } }, false @@ -1086,7 +1087,7 @@ void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_route_214(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_route_214: transition not detected.", env.console); } }, false @@ -1097,7 +1098,7 @@ void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); } }, false @@ -1108,7 +1109,7 @@ void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); } }, false @@ -1119,7 +1120,7 @@ void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!pass_by_valor_lakefront(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pass_by_valor_lakefront: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "pass_by_valor_lakefront: transition not detected.", env.console); } }, false @@ -1130,7 +1131,7 @@ void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); } }, false @@ -1141,7 +1142,7 @@ void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_213(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); } }, false @@ -1152,7 +1153,7 @@ void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); } }, false @@ -1163,7 +1164,7 @@ void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_pastoria(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); } }, false @@ -1174,7 +1175,7 @@ void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); } }, false @@ -1185,7 +1186,7 @@ void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); } }, false diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index 5b9f01375f..e8f8afa606 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -6,6 +6,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" @@ -1486,7 +1487,7 @@ void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); } }, true @@ -1497,7 +1498,7 @@ void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!exit_pastoria_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "exit_pastoria_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "exit_pastoria_pokemon_center: transition not detected.", env.console); } }, false @@ -1508,7 +1509,7 @@ void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!pastoria_city_drama(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); } }, false @@ -1519,7 +1520,7 @@ void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); } }, false @@ -1530,7 +1531,7 @@ void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_213_chasing(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_213_chasing: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_213_chasing: transition not detected.", env.console); } }, false @@ -1541,7 +1542,7 @@ void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); } }, false @@ -1552,7 +1553,7 @@ void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_214(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); } }, false @@ -1563,7 +1564,7 @@ void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_inside_solaceon_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", env.console); } }, false @@ -1574,7 +1575,7 @@ void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_solaceon_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", env.console); } }, false @@ -1585,7 +1586,7 @@ void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_the_psyduck_route_210(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_psyduck_route_210: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_the_psyduck_route_210: transition not detected.", env.console); } }, false @@ -1596,7 +1597,7 @@ void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_celestic_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", env.console); } }, false @@ -1607,7 +1608,7 @@ void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!celestic_town_drama(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); } }, false @@ -1618,7 +1619,7 @@ void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!discover_the_paintings(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "discover_the_paintings: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "discover_the_paintings: transition not detected.", env.console); } }, false @@ -1629,7 +1630,7 @@ void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); } }, false @@ -1640,7 +1641,7 @@ void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_hearthome_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", env.console); } }, false @@ -1651,7 +1652,7 @@ void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); } }, false @@ -1662,7 +1663,7 @@ void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); } }, false diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 5650ae52ef..c9d0361ee1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -6,6 +6,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" @@ -628,10 +629,10 @@ void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ /*if (!test(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "test: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "test: transition not detected.", env.console); }*/ if (!cynthia_and_fly_to_jubilife(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); } }, true @@ -642,7 +643,7 @@ void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_jubilife_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_jubilife_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_jubilife_pokemon_center: transition not detected.", env.console); } }, false @@ -653,7 +654,7 @@ void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); } }, false @@ -664,7 +665,7 @@ void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "move_to_jubilife_west_exit: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "move_to_jubilife_west_exit: transition not detected.", env.console); } }, false @@ -675,7 +676,7 @@ void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_jubilife_west_exit: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_jubilife_west_exit: transition not detected.", env.console); } }, false @@ -686,7 +687,7 @@ void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_218(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); } }, false @@ -697,7 +698,7 @@ void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); } }, false @@ -708,7 +709,7 @@ void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_clint_and_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_clint_and_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_clint_and_pokemon_center: transition not detected.", env.console); } }, false @@ -719,7 +720,7 @@ void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); } }, false @@ -730,7 +731,7 @@ void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); } }, false @@ -741,7 +742,7 @@ void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); } }, false diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index f511d1fe66..9888b29981 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -6,6 +6,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" @@ -1766,7 +1767,7 @@ void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); } }, true @@ -1777,7 +1778,7 @@ void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_the_library(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); } }, false @@ -1788,7 +1789,7 @@ void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!drama_at_the_library(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); } }, false @@ -1799,7 +1800,7 @@ void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_fly_and_enter_mount_coronet(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); } }, false @@ -1810,7 +1811,7 @@ void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); } }, false @@ -1821,7 +1822,7 @@ void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_216(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); } }, false @@ -1832,7 +1833,7 @@ void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_small_house_route_216(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_small_house_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_small_house_route_216: transition not detected.", env.console); } }, false @@ -1843,7 +1844,7 @@ void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!continue_through_route_216(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "continue_through_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "continue_through_route_216: transition not detected.", env.console); } }, false @@ -1854,7 +1855,7 @@ void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); } }, false @@ -1865,7 +1866,7 @@ void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); } }, false @@ -1876,7 +1877,7 @@ void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); } }, false @@ -1887,7 +1888,7 @@ void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); } }, false @@ -1898,7 +1899,7 @@ void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_lake_valor(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); } }, false @@ -1909,7 +1910,7 @@ void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_home(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_home: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_home: transition not detected.", env.console); } }, false @@ -1920,7 +1921,7 @@ void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_lake_verity(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); } }, false @@ -1931,7 +1932,7 @@ void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!commander_mars_and_snowpoint(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "commander_mars_and_snowpoint: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "commander_mars_and_snowpoint: transition not detected.", env.console); } }, false @@ -1942,7 +1943,7 @@ void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); } }, false @@ -1953,7 +1954,7 @@ void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); } }, false @@ -1964,7 +1965,7 @@ void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); } }, false diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 256f737145..00b32a5843 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -6,6 +6,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" @@ -2319,7 +2320,7 @@ void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); } }, true @@ -2330,7 +2331,7 @@ void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); } }, false @@ -2341,7 +2342,7 @@ void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Snowpoint City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_snowpoint_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_snowpoint_city: transition not detected.", env.console); } }, false @@ -2352,7 +2353,7 @@ void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_lake_acuity(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); } }, false @@ -2363,7 +2364,7 @@ void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_clint_then_go_veilstone(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); } }, false @@ -2374,7 +2375,7 @@ void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); } }, false @@ -2385,7 +2386,7 @@ void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_key_and_enter_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); } }, false @@ -2396,7 +2397,7 @@ void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_warehouse(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); } }, false @@ -2407,7 +2408,7 @@ void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_veilstone_pokemon_center(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); } }, false @@ -2418,7 +2419,7 @@ void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); } }, false @@ -2429,7 +2430,7 @@ void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_hq(env.console, context)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); } }, false @@ -2440,7 +2441,7 @@ void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_galactic_hq(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); } }, false @@ -2451,7 +2452,7 @@ void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_oreburgh(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); } }, false @@ -2462,7 +2463,7 @@ void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Oreburgh City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_oreburgh_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_oreburgh_city: transition not detected.", env.console); } }, false @@ -2473,7 +2474,7 @@ void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_mount_coronet(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); } }, false @@ -2484,7 +2485,7 @@ void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); } }, false @@ -2495,7 +2496,7 @@ void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_veilstone(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "fly_and_heal_at_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "fly_and_heal_at_veilstone: transition not detected.", env.console); } }, false @@ -2506,7 +2507,7 @@ void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); } }, false @@ -2517,7 +2518,7 @@ void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); } }, false @@ -2528,7 +2529,7 @@ void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); } }, false @@ -2539,7 +2540,7 @@ void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_west_sunyshore(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_house_west_sunyshore: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_house_west_sunyshore: transition not detected.", env.console); } }, false @@ -2550,7 +2551,7 @@ void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_west_sunyshore(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", env.console); } }, false @@ -2561,7 +2562,7 @@ void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_lighthouse(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_lighthouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_lighthouse: transition not detected.", env.console); } }, false @@ -2572,7 +2573,7 @@ void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", env.console); } }, false @@ -2588,7 +2589,7 @@ void checkpoint_0825( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); } }, false @@ -2604,7 +2605,7 @@ void checkpoint_0826( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); } }, false diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index e49ea66c86..024678b9f1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -6,6 +6,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" @@ -1436,7 +1437,7 @@ void checkpoint_0901( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_sunyshore_pokemon_center(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_sunyshore_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_sunyshore_pokemon_center: transition not detected.", env.console); } }, true @@ -1452,7 +1453,7 @@ void checkpoint_0902( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Sunyshore City")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_1: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_1: transition not detected.", env.console); } }, false @@ -1468,7 +1469,7 @@ void checkpoint_0903( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_victory_road_pokemon_center(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", env.console); } }, false @@ -1484,7 +1485,7 @@ void checkpoint_0904( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Victory Road")){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "heal_and_exit_2: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_2: transition not detected.", env.console); } }, false @@ -1500,7 +1501,7 @@ void checkpoint_0905( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_victory_road_cave(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_victory_road_cave: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_victory_road_cave: transition not detected.", env.console); } }, false @@ -1516,7 +1517,7 @@ void checkpoint_0906( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_victory_road_cave(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "go_through_victory_road_cave: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_victory_road_cave: transition not detected.", env.console); } }, false @@ -1532,7 +1533,7 @@ void checkpoint_0907( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_elite_4_pokemon_center(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "enter_elite_4_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_elite_4_pokemon_center: transition not detected.", env.console); } }, false @@ -1548,7 +1549,7 @@ void checkpoint_0908( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!ready_in_front_of_elite_4(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "ready_in_front_of_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "ready_in_front_of_elite_4: transition not detected.", env.console); } }, false @@ -1564,7 +1565,7 @@ void checkpoint_0909( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_the_elite_4_entrance(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "get_into_the_elite_4_entrance: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_into_the_elite_4_entrance: transition not detected.", env.console); } }, true @@ -1580,7 +1581,7 @@ void checkpoint_0910( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_aaron_elite_4(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_aaron_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_aaron_elite_4: transition not detected.", env.console); } }, false @@ -1596,7 +1597,7 @@ void checkpoint_0911( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_bertha_elite_4(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_bertha_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_bertha_elite_4: transition not detected.", env.console); } }, false @@ -1612,7 +1613,7 @@ void checkpoint_0912( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_flint_elite_4(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_flint_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_flint_elite_4: transition not detected.", env.console); } }, false @@ -1628,7 +1629,7 @@ void checkpoint_0913( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_lucian_elite_4(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_lucian_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_lucian_elite_4: transition not detected.", env.console); } }, false @@ -1644,7 +1645,7 @@ void checkpoint_0914( checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_cynthia_and_finish(env.console, context, options)){ - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, "deal_with_cynthia_and_finish: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_cynthia_and_finish: transition not detected.", env.console); } }, false diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp index ac2e61fa78..b039bb81e8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -11,6 +11,7 @@ #include #include #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" @@ -20,7 +21,7 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Inference/Pokemon_NameReader.h" -#include "PokemonSwSh/Resources/PokemonSwSh_TypeMatchup.h" +#include "VideoGames/PokemonSwSh/Resources/PokemonSwSh_TypeMatchup.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" @@ -90,8 +91,8 @@ void handle_unexpected_battle( run_battle_press_A(info, stream, context); recovery_action(info, stream, context); } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "handle_unexpected_battle: exceeded max attempts", stream ); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index a5ac3f311b..5047449c0b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -7,6 +7,7 @@ */ +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include @@ -16,6 +17,7 @@ #include "PokemonBDSP_Autostory_Segment_01_tools.h" #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" @@ -123,7 +125,7 @@ namespace PokemonAutomation { "[AutoStory] Clown helper timed out finding dialog.", COLOR_RED ); - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "[AutoStory] Clown helper timed out finding dialog.", stream); } @@ -281,7 +283,7 @@ namespace PokemonAutomation { } MenuWatcher menu_open; if (wait_until(stream, context, 5s, {{menu_open}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Settings checkpoint: Main menu not detected.", stream); } stream.log("[Tutorial settings] Main menu detected; settling for 500ms before Options navigation."); @@ -307,7 +309,7 @@ namespace PokemonAutomation { if (wait_until(stream, context, 5s, {{back_to_menu}}) != 0) break; } if (!options_open) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Settings checkpoint: Options screen not detected.", stream); } stream.log("[Tutorial settings] Options screen detected."); @@ -349,7 +351,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); continue; } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Settings checkpoint: Unable to confirm expected row " + std::string(settings[row].label), stream); } // These are regular video-overlay boxes, so the existing @@ -395,7 +397,7 @@ namespace PokemonAutomation { if (!check || !options_screen.detect(check) || check_y < 0 || option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, label_reference, false) < 0.80 || !autosave_off_highlight(check, check_y)) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Autosave Off could not be confirmed. Stopping before movement.", stream); } } @@ -417,7 +419,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); } if (!correct) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Settings checkpoint: Could not apply " + std::string(settings[row].label), stream); } if (row + 1 < sizeof(settings) / sizeof(settings[0])) @@ -433,7 +435,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); OverworldWatcher overworld; if (wait_until(stream, context, 10s, {{overworld}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Settings checkpoint: Did not return to the overworld after saving options.", stream); } // Reopen the saved Options screen and independently verify Autosave. @@ -442,7 +444,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); MenuWatcher verify_menu; if (wait_until(stream, context, 5s, {{verify_menu}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Autosave verification: Main menu not detected.", stream); } bool reopened = false; @@ -458,7 +460,7 @@ namespace PokemonAutomation { pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); } if (!reopened) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Autosave verification: Could not reopen saved Options.", stream); } // Options starts on Text Speed; Autosave is seven rows below it. @@ -472,7 +474,7 @@ namespace PokemonAutomation { if (!check || !options_screen.detect(check) || check_y < 0 || option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, autosave_label, false) < 0.80 || !autosave_off_highlight(check, check_y)) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Saved Autosave setting is not verified Off. Stopping before movement.", stream); } pbf_wait(context, 200ms); @@ -489,7 +491,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); OverworldWatcher verified_exit; if (wait_until(stream, context, 10s, {{verified_exit}}) != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Autosave verification: Could not return to the overworld.", stream); } // Report inferred values explicitly: these are visual matches, @@ -580,8 +582,8 @@ namespace PokemonAutomation { { date_reader } ); if (ret < 0) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to enter Date Change screen.", env.console ); @@ -633,7 +635,7 @@ namespace PokemonAutomation { else pbf_press_dpad(context, DPAD_UP, 80ms, 250ms); } } - if (!fight) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!fight) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Could not confirm Battle command; A not pressed.", stream); pbf_press_button(context, BUTTON_A, 80ms, 500ms); confirmed = 0; @@ -651,7 +653,7 @@ namespace PokemonAutomation { if (moves.move_count == previous_move_count && moves.move_count > 0) stable_move_count++; else { previous_move_count = moves.move_count; stable_move_count = 1; confirmed = 0; } if (moves.move_count > 0 && moves.move_count < target && stable_move_count >= 3) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Move slot " + std::to_string(target) + " is absent; no move confirmed.", stream); } if (slot == (int)target && stable_move_count >= 3) { @@ -668,7 +670,7 @@ namespace PokemonAutomation { } context.wait_for_all_requests(); } - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Could not confirm move " + std::to_string(target) + "; A not pressed.", stream); } @@ -812,7 +814,7 @@ namespace PokemonAutomation { 5000ms ); - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "[AutoStory] Natalie battle timed out.", stream); } @@ -875,13 +877,13 @@ namespace PokemonAutomation { { {overworld} } ); if (cleanup_ret != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Starly battle: Overworld cleanup timed out.", stream); } return; } default: - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Starly battle timed out.", stream); } } @@ -944,7 +946,7 @@ namespace PokemonAutomation { } } - if (!encounter_found) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!encounter_found) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Wild encounter: Left/right wiggle did not find a battle within three minutes.", stream); stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); @@ -965,7 +967,7 @@ namespace PokemonAutomation { "[AutoStory] Failed to detect battle menu.", COLOR_RED ); - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "[AutoStory] Failed to detect battle menu.", stream); } @@ -990,7 +992,7 @@ namespace PokemonAutomation { bool finished = false; while (std::chrono::steady_clock::now() < deadline) { const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": No video frame.", stream); const auto now = std::chrono::steady_clock::now(); if (dialogue.detect(frame) || black.detect(frame) || !overworld.detect(frame)) { @@ -1002,7 +1004,7 @@ namespace PokemonAutomation { pbf_mash_button(context, BUTTON_B, 100ms); context.wait_for_all_requests(); } - if (!finished) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!finished) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Dialogue-free overworld not confirmed for four seconds.", stream); context.wait_for_all_requests(); } @@ -1081,7 +1083,7 @@ namespace PokemonAutomation { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, {{overworld}}); if (cleanup_ret != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Post-battle cleanup timed out.", stream); } context.wait_for_all_requests(); @@ -1095,7 +1097,7 @@ namespace PokemonAutomation { COLOR_RED ); - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "[AutoStory] " + label + " battle timeout.", stream); } } @@ -1163,7 +1165,7 @@ namespace PokemonAutomation { break; } if (throw_ret < 0 || throw_ret == 2) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Catch failed: No verified Pokemon receipt before battle ended or timed out.", stream); } stream.log( @@ -1175,7 +1177,7 @@ namespace PokemonAutomation { context.wait_for_all_requests(); if (!caught) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Catch failed: Exhausted ten ball attempts.", stream); } // Receipt alone is not an overworld return: clear experience, @@ -1187,7 +1189,7 @@ namespace PokemonAutomation { [](ProControllerContext& ctx) { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, {{battle_ended}}); if (cleanup_ret != 0) { - OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Catch cleanup: Battle exit transition did not finish.", stream); } } @@ -1459,8 +1461,8 @@ namespace PokemonAutomation { return; } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Failed to find Pokemon Center exit black screen before Barry 1. Triggering checkpoint retry.", stream ); @@ -1485,7 +1487,7 @@ namespace PokemonAutomation { OverworldMatchResult location; if (destination) { const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Grass destination: No video frame.", stream); location = destination(*frame.frame); for (const auto& landmark : location.landmarks) { @@ -1512,7 +1514,7 @@ namespace PokemonAutomation { confirmations = 0; } if (movements >= steps) { - if (destination) OperationFailedException::fire(ErrorReport::SEND_ERROR_REPORT, + if (destination) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Grass destination not confirmed within movement limit: " + direction_name, stream); stream.log("[Grass] Completed " + direction_name + " (" + std::to_string(steps) + " steps).", COLOR_GREEN); return; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 5028935f29..76752254d9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -5,6 +5,7 @@ * */ +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include @@ -12,6 +13,7 @@ #include #include #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTools/ImageDiff.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" @@ -227,8 +229,8 @@ namespace PokemonAutomation { if (mine_confirm_escape_via_menu(stream, context, battle_type)) { return; } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", stream ); @@ -302,8 +304,8 @@ namespace PokemonAutomation { switch (state) { case NavState::WALKING: { if (walked >= max_travel) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "walk_until_doorway: Doorway not found within time budget.", stream ); @@ -340,8 +342,8 @@ namespace PokemonAutomation { } case NavState::IN_BATTLE: { if (++battles > options.max_battles) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "walk_until_doorway: Too many battles while walking.", stream ); @@ -389,16 +391,16 @@ namespace PokemonAutomation { context.wait_for_all_requests(); return; default: - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Barry_1_fight: Timed out waiting for battle menu or end.", stream ); } } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Barry_1_fight: Battle did not end after 20 turns.", stream ); @@ -482,8 +484,8 @@ namespace PokemonAutomation { context.wait_for_all_requests(); if (!black_screen_found) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", stream ); @@ -553,8 +555,8 @@ namespace PokemonAutomation { context.wait_for_all_requests(); if (!black_screen_found) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "oreburgh_gate_to_mine: No black screen after 3 attempts.", stream ); @@ -1936,8 +1938,8 @@ namespace PokemonAutomation { ); if (options.reset_on_failure) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, std::string("Mine route failed during: ") + step.name, stream ); @@ -2062,15 +2064,15 @@ namespace PokemonAutomation { route.reset_on_failure = true; if (!oreburgh_mine_route(stream, context, route)) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, "Mine route did not complete.", stream + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Mine route did not complete.", stream ); } if (!mine_talk_and_escape(stream, context)) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Mine route: Roark conversation or Escape Rope exit failed.", stream ); @@ -2188,8 +2190,8 @@ namespace PokemonAutomation { while (std::chrono::steady_clock::now() < dialogue_deadline) { const VideoSnapshot frame = stream.video().snapshot(); if (!frame) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Gym post battle: No video frame while clearing dialogue.", stream ); } @@ -2205,8 +2207,8 @@ namespace PokemonAutomation { context.wait_for_all_requests(); } if (!dialogue_finished) { - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Gym post battle: Dialogue did not finish within 180 seconds.", stream ); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index 4f9ed1f99c..d0a73d01bd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -1,3 +1,4 @@ +#include "CommonFramework/GlobalAutoPaths.h" #include #include #include diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp index 4ed5cde7a3..335d02cb30 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp @@ -3,6 +3,7 @@ #include #include #include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" @@ -35,7 +36,7 @@ void OverworldLightingTest::program(SingleSwitchProgramEnvironment& env,ProContr (matcher.slug+"-"+std::to_string(std::time(nullptr))); std::filesystem::create_directories(output); std::ofstream csv(output/"results.csv"); - if(!csv)OperationFailedException::fire(ErrorReport::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); + if(!csv)OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); csv<<"sample,hour,frame,landmark,score,margin,accepted,dx_screen,dy_screen,located,at_target,consensus_dx_screen,consensus_dy_screen,coarse\n"; csv<save((output/name).string()))OperationFailedException::fire(ErrorReport::NO_ERROR_REPORT,"Lighting test: Screenshot save failed.",env.console); + if(!snapshot->save((output/name).string()))OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: Screenshot save failed.",env.console); csv.flush(); pbf_wait(context,250ms);context.wait_for_all_requests(); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h index 52580778c8..dc77fe8770 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h @@ -8,6 +8,7 @@ public: OverworldLightingTest_Descriptor(); }; class OverworldLightingTest : public SingleSwitchProgramInstance{ public: + using Descriptor = OverworldLightingTest_Descriptor; OverworldLightingTest(); void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; private: From c0c4cdabf516a55198c5209a675ed1e6cf7da4c8 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sun, 11 Oct 2026 21:09:41 -0500 Subject: [PATCH 25/39] Keep BDSP transition extensions out of shared detectors Restore CommonTools/VisualDetectors/BlackScreenDetector.cpp and .h to the exact current PokemonAutomation upstream versions. The fork had extended these shared files with BDSP white-screen and brief black-flash watchers, including a change to the behavior of the shared WhiteScreenOverWatcher. Those changes could otherwise affect callers in every game. Move the extensions into PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers and update only their AutoStory callers and source registration. Preserve the existing BDSP hold/release timings and latched completion behavior. No global aliases or shared-detector API additions are introduced. Why needed: upstream is authoritative and BDSP-specific behavior does not justify changing the global implementation. Localizing these classes keeps the tested AutoStory behavior while removing that cross-game exposure. Validation: full upstream-based Debug application build/link passed. Nineteen focused checks passed for sustained white-screen entry/release, short flashes, latched completion, and the brief black transition. The same harness confirms the upstream shared white-over watcher retains its original semantics. The two shared detector files and shared developer/unit-test implementations are byte-identical to upstream; therefore this commit introduces no detector behavior changes to other games relative to upstream. Gameplay timing still needs continued BDSP recordings as route validation proceeds. Shared changes are intentionally isolated in this commit for review. The staged Segment 02 route work remains uncommitted. --- .../VisualDetectors/BlackScreenDetector.cpp | 32 ++++------- .../VisualDetectors/BlackScreenDetector.h | 37 +----------- .../PokemonBDSP_TransitionWatchers.cpp | 36 ++++++++++++ .../PokemonBDSP_TransitionWatchers.h | 56 +++++++++++++++++++ .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 1 + .../PokemonBDSP_AutoStory_Segment_07.cpp | 1 + .../PokemonBDSP_AutoStory_Segment_08.cpp | 1 + .../PokemonBDSP_AutoStory_Segment_09.cpp | 1 + ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 1 + ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 1 + SerialPrograms/cmake/SourceFiles.cmake | 2 + 11 files changed, 113 insertions(+), 56 deletions(-) create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp create mode 100644 SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp index caaeff3c1f..d4d919960f 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.cpp @@ -101,34 +101,24 @@ bool BlackScreenOverWatcher::black_is_over(const ImageViewRGB32& frame){ WhiteScreenOverWatcher::WhiteScreenOverWatcher( Color color, const ImageFloatBox& box, double min_rgb_sum, - double max_stddev_sum, - std::chrono::milliseconds hold_duration, - std::chrono::milliseconds release_duration + double max_stddev_sum ) - : VisualInferenceCallback("WhiteScreenOverWatcher") - , m_on(color, box, min_rgb_sum, max_stddev_sum, WhiteScreenWatcher::FinderType::PRESENT, hold_duration) - , m_off(color, box, min_rgb_sum, max_stddev_sum, WhiteScreenWatcher::FinderType::GONE, release_duration) + : VisualInferenceCallback("BlackScreenOverWatcher") + , m_detector(color, box, min_rgb_sum, max_stddev_sum) {} void WhiteScreenOverWatcher::make_overlays(VideoOverlaySet& items) const{ - m_on.make_overlays(items); + m_detector.make_overlays(items); } + bool WhiteScreenOverWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ - if (m_white_is_over.load(std::memory_order_acquire)){ - return true; - } - if (!m_has_been_white){ - m_has_been_white = m_on.process_frame(frame, timestamp); - return false; - } - bool is_over = m_off.process_frame(frame, timestamp); - if (!is_over){ - return false; - } - m_white_is_over.store(true, std::memory_order_release); - return true; + return white_is_over(frame); } bool WhiteScreenOverWatcher::white_is_over(const ImageViewRGB32& frame){ - return m_white_is_over.load(std::memory_order_acquire); + if (m_detector.detect(frame)){ + m_has_been_white = true; + return false; + } + return m_has_been_white; } diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h index 80066081f2..12c99b2a01 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackScreenDetector.h @@ -71,19 +71,6 @@ class BlackScreenWatcher : public DetectorToFinder{ : DetectorToFinder("BlackScreenWatcher", finder_type, duration, color, box, max_rgb_sum, max_stddev_sum) {} }; -class WhiteScreenWatcher : public DetectorToFinder{ -public: - WhiteScreenWatcher( - Color color = COLOR_RED, - const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, - double min_rgb_sum = 500, - double max_stddev_sum = 10, - FinderType finder_type = FinderType::PRESENT, - std::chrono::milliseconds duration = std::chrono::milliseconds(100) - ) - : DetectorToFinder("WhiteScreenWatcher", finder_type, duration, color, box, min_rgb_sum, max_stddev_sum) - {} -}; // Detect when a period of black screen is over class BlackScreenOverWatcher : public VisualInferenceCallback{ @@ -110,22 +97,6 @@ class BlackScreenOverWatcher : public VisualInferenceCallback{ std::atomic m_black_is_over = false; }; -// Detect when a brief flash of black screen (1-2 frames) is over. -// Use instead of BlackScreenOverWatcher when the transition is too short for the 100ms hold. -class BlackScreenFlashWatcher : public BlackScreenOverWatcher{ -public: - BlackScreenFlashWatcher( - Color color = COLOR_RED, - const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, - double max_rgb_sum = 100, - double max_stddev_sum = 10, - std::chrono::milliseconds hold_duration = std::chrono::milliseconds(20), - std::chrono::milliseconds release_duration = std::chrono::milliseconds(100) - ) - : BlackScreenOverWatcher(color, box, max_rgb_sum, max_stddev_sum, hold_duration, release_duration) - {} -}; - @@ -135,9 +106,7 @@ class WhiteScreenOverWatcher : public VisualInferenceCallback{ Color color = COLOR_RED, const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, double min_rgb_sum = 500, - double max_stddev_sum = 10, - std::chrono::milliseconds hold_duration = std::chrono::milliseconds(100), - std::chrono::milliseconds release_duration = std::chrono::milliseconds(100) + double max_stddev_sum = 10 ); bool white_is_over(const ImageViewRGB32& frame); @@ -147,10 +116,8 @@ class WhiteScreenOverWatcher : public VisualInferenceCallback{ virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; private: - WhiteScreenWatcher m_on; - WhiteScreenWatcher m_off; + WhiteScreenDetector m_detector; bool m_has_been_white = false; - std::atomic m_white_is_over = false; }; diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp new file mode 100644 index 0000000000..3655b5244d --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp @@ -0,0 +1,36 @@ +#include "PokemonBDSP_TransitionWatchers.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +WhiteScreenOverWatcher::WhiteScreenOverWatcher( + Color color, const ImageFloatBox& box, + double min_rgb_sum, + double max_stddev_sum, + std::chrono::milliseconds hold_duration, + std::chrono::milliseconds release_duration +) + : VisualInferenceCallback("WhiteScreenOverWatcher") + , m_on(color, box, min_rgb_sum, max_stddev_sum, WhiteScreenWatcher::FinderType::PRESENT, hold_duration) + , m_off(color, box, min_rgb_sum, max_stddev_sum, WhiteScreenWatcher::FinderType::GONE, release_duration) +{} +void WhiteScreenOverWatcher::make_overlays(VideoOverlaySet& items) const{ + m_on.make_overlays(items); +} +bool WhiteScreenOverWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ + if (m_white_is_over.load(std::memory_order_acquire)){ + return true; + } + if (!m_has_been_white){ + m_has_been_white = m_on.process_frame(frame, timestamp); + return false; + } + bool is_over = m_off.process_frame(frame, timestamp); + if (!is_over){ + return false; + } + m_white_is_over.store(true, std::memory_order_release); + return true; +} +bool WhiteScreenOverWatcher::white_is_over(const ImageViewRGB32& frame){ + return m_white_is_over.load(std::memory_order_acquire); +} + +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h new file mode 100644 index 0000000000..ce5a34d2cb --- /dev/null +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h @@ -0,0 +1,56 @@ +#pragma once +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +// AutoStory-specific transition handling; shared watchers retain upstream behavior. +class WhiteScreenWatcher : public DetectorToFinder{ +public: + WhiteScreenWatcher( + Color color = COLOR_RED, + const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, + double min_rgb_sum = 500, + double max_stddev_sum = 10, + FinderType finder_type = FinderType::PRESENT, + std::chrono::milliseconds duration = std::chrono::milliseconds(100) + ) + : DetectorToFinder("WhiteScreenWatcher", finder_type, duration, color, box, min_rgb_sum, max_stddev_sum) + {} +}; + +class BlackScreenFlashWatcher : public BlackScreenOverWatcher{ +public: + BlackScreenFlashWatcher( + Color color = COLOR_RED, + const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, + double max_rgb_sum = 100, + double max_stddev_sum = 10, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(20), + std::chrono::milliseconds release_duration = std::chrono::milliseconds(100) + ) + : BlackScreenOverWatcher(color, box, max_rgb_sum, max_stddev_sum, hold_duration, release_duration) + {} +}; + +class WhiteScreenOverWatcher : public VisualInferenceCallback{ +public: + WhiteScreenOverWatcher( + Color color = COLOR_RED, + const ImageFloatBox& box = {0.1, 0.1, 0.8, 0.8}, + double min_rgb_sum = 500, + double max_stddev_sum = 10, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(100), + std::chrono::milliseconds release_duration = std::chrono::milliseconds(100) + ); + + bool white_is_over(const ImageViewRGB32& frame); + + virtual void make_overlays(VideoOverlaySet& items) const override; + + virtual bool process_frame(const ImageViewRGB32& frame, WallClock timestamp) override; + +private: + WhiteScreenWatcher m_on; + WhiteScreenWatcher m_off; + bool m_has_been_white = false; + std::atomic m_white_is_over = false; +}; +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index a9f8134285..4f244154d1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1,3 +1,4 @@ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/GlobalAutoPaths.h" #include /* BDSP AutoStory Tools diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 9888b29981..e413755de3 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -5,6 +5,7 @@ * */ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 00b32a5843..c567b2bdb9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -5,6 +5,7 @@ * */ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 024678b9f1..206b90bc18 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -5,6 +5,7 @@ * */ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 5047449c0b..5f9410e6af 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -7,6 +7,7 @@ */ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/GlobalAutoPaths.h" #include #include diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 76752254d9..7ad7e071a1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -5,6 +5,7 @@ * */ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/GlobalAutoPaths.h" #include #include diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 1b44843448..cce6604a51 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1525,6 +1525,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/Inference/PokemonBDSP_MapDetector.h Source/PokemonBDSP/Inference/PokemonBDSP_MarkFinder.cpp Source/PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h + Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp + Source/PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h Source/PokemonBDSP/Inference/PokemonBDSP_PokeballSpriteMatcher.cpp From d41033bdb3c60b91de9932da2cd3953935087988 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Tue, 20 Oct 2026 20:44:51 -0500 Subject: [PATCH 26/39] Expand BDSP story routing and detector resilience Implement large AutoStory progression updates across Segments 01-03, including fully scripted Route 204/Floaroma/Windworks/Eterna flows, new Segment 02 checkpoint boundaries, and Veilstone pathing/battles in Segment 03. The change adds bounded trainer recovery and battle/menu confirmation logic to prevent movement during dialogue or combat states. Detection and alignment were hardened with new/expanded overworld matchers, lighting-aware reference variants, coarse-vs-final positioning rules, and richer menu/debug diagnostics. It also updates checkpoint retry behavior for fake saves (selected-range retries), improves bag/repel handling (including repel type selection and NEW-flag clearing), and adds supporting documentation/context for the current BDSP handoff. --- .../Inference/PokemonBDSP_DialogDetector.cpp | 14 +- .../Inference/PokemonBDSP_MenuDetector.cpp | 91 +- .../Inference/PokemonBDSP_MenuDetector.h | 4 + .../AutoStory/PokemonBDSP_AutoStory.cpp | 1530 +++---- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 3948 +++++++++-------- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 947 ++-- .../PokemonBDSP_AutoStory_Segment_01.cpp | 2299 +++++++++- .../PokemonBDSP_AutoStory_Segment_01.h | 259 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 2851 ++++++++++-- .../PokemonBDSP_AutoStory_Segment_02.h | 30 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 2455 +++++++--- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 362 +- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 19 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 2477 ++--------- .../PokemonBDSP_Autostory_Segment_01_tools.h | 98 +- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 1506 +++++-- .../Utils/PokemonBDSP_OverworldMatchers.h | 103 +- .../Programs/PokemonBDSP_GameNavigation.cpp | 8 + 18 files changed, 12006 insertions(+), 6995 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp index c8636d99cb..3a06a4e2a9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp @@ -20,9 +20,11 @@ namespace PokemonBDSP{ ShortDialogDetector::ShortDialogDetector(Color color) : m_color(color) // , m_bottom(0.50, 0.91, 0.29, 0.05) - , m_left_white(0.21, 0.835, 0.008, 0.12) + // Preserve the established inner-margin samples. detect() checks an + // alternate blank margin when a long Gardenia reward line overlaps one. + , m_left_white(0.221, 0.86, 0.002, 0.025) , m_left(0.18, 0.835, 0.02, 0.12) - , m_right_white(0.785, 0.835, 0.008, 0.12) + , m_right_white(0.798, 0.86, 0.002, 0.025) , m_right(0.822, 0.835, 0.02, 0.12) {} void ShortDialogDetector::make_overlays(VideoOverlaySet& items) const{ @@ -36,12 +38,16 @@ bool ShortDialogDetector::detect(const ImageViewRGB32& screen){ ImageStats left_white = image_stats(extract_box_reference(screen, m_left_white)); // cout << left_white.average << left_white.stddev << endl; if (!is_white(left_white)){ - return false; + left_white=image_stats(extract_box_reference(screen,ImageFloatBox{.208,.86,.002,.025})); + if (!is_white(left_white)) return false; } ImageStats right_white = image_stats(extract_box_reference(screen, m_right_white)); // cout << right_white.average << right_white.stddev << endl; if (!is_white(right_white)){ - return false; + // Gardenia's opening reward line can reach this strip. Retain the + // same white test in a second blank margin, below that long line. + right_white=image_stats(extract_box_reference(screen,ImageFloatBox{.790,.915,.002,.025})); + if (!is_white(right_white)) return false; } // ImageStats bottom = image_stats(extract_box_reference(screen, m_bottom)); // if (!is_white(bottom)){ diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp index 189ad4fe27..eb66d7ff64 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp @@ -44,6 +44,7 @@ MenuDetector::MenuDetector(Color color, bool skip_new_banner) , m_panel_right(0.835, 0.125, 0.015, 0.460) , m_footer(0.050, 0.963, 0.600, 0.025) , m_confirm_button(0.736, 0.960, 0.022, 0.038) + , m_confirm_button_with_swap(0.653, 0.960, 0.022, 0.038) , m_save_button(0.835, 0.960, 0.024, 0.038) , m_close_button(0.915, 0.960, 0.024, 0.038) , m_save_heading(0.920, 0.755, 0.070, 0.040) @@ -63,6 +64,7 @@ void MenuDetector::make_overlays(VideoOverlaySet& items) const{ items.add(m_color, m_panel_right); items.add(m_color, m_footer); items.add(m_color, m_confirm_button); + items.add(m_color, m_confirm_button_with_swap); items.add(m_color, m_save_button); items.add(m_color, m_close_button); items.add(m_color, m_save_heading); @@ -104,7 +106,14 @@ bool MenuDetector::detect_menu_chrome(const ImageViewRGB32& screen) const{ footer.average.g < footer.average.r + 20 || footer.stddev.sum() > 60){ return false; } - for (const ImageFloatBox& box : {m_confirm_button, m_save_button, m_close_button, m_save_shortcut}){ + // Unlocking the Swap prompt shifts A Confirm left. Require the same + // button contrast at either documented footer position, with all other + // panel, footer and Save checks unchanged. + if (!has_menu_button(extract_box_reference(screen, m_confirm_button)) && + !has_menu_button(extract_box_reference(screen, m_confirm_button_with_swap))){ + return false; + } + for (const ImageFloatBox& box : {m_save_button, m_close_button, m_save_shortcut}){ if (!has_menu_button(extract_box_reference(screen, box))){ return false; } @@ -118,6 +127,86 @@ bool MenuDetector::detect_menu_chrome(const ImageViewRGB32& screen) const{ background.average.b > background.average.r + 40 && background.stddev.sum() < 90; } +static std::string fmt(double value){ + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.3f", value); + return buffer; +} +static std::string describe_stats(const ImageStats& stats){ + return "avg=(" + fmt(stats.average.r) + "," + fmt(stats.average.g) + "," + fmt(stats.average.b) + + ") stddev_sum=" + fmt(stats.stddev.sum()); +} +static std::string describe_button(const ImageViewRGB32& patch){ + size_t dark = 0; + size_t light = 0; + for (size_t y = 0; y < patch.height(); y++){ + for (size_t x = 0; x < patch.width(); x++){ + const uint32_t pixel = patch.pixel(x, y); + const int r = (pixel >> 16) & 0xff; + const int g = (pixel >> 8) & 0xff; + const int b = pixel & 0xff; + const int low = std::min(r, std::min(g, b)); + const int high = std::max(r, std::max(g, b)); + if (high - low < 35){ + dark += high < 160; + light += low > 200; + } + } + } + const double pixels = (double)patch.width() * patch.height(); + const double dark_ratio = pixels > 0 ? dark / pixels : 0; + const double light_ratio = pixels > 0 ? light / pixels : 0; + return "dark=" + fmt(dark_ratio) + " (need>=0.150) light=" + fmt(light_ratio) + " (need>=0.080)"; +} + +std::string MenuDetector::debug_report(const ImageViewRGB32& screen) const{ + std::string report = "MenuDetector report (" + std::to_string(screen.width()) + "x" + + std::to_string(screen.height()) + "):\n"; + auto line = [&](const std::string& name, bool ok, const std::string& detail){ + report += std::string(" [") + (ok ? "SEEN " : "MISSING") + "] " + name + ": " + detail + "\n"; + }; + const std::pair panels[] = { + {"panel top", &m_panel_top}, {"panel left", &m_panel_left}, {"panel right", &m_panel_right}, + }; + for (const auto& panel : panels){ + const ImageStats stats = image_stats(extract_box_reference(screen, *panel.second)); + line(panel.first, is_white(stats), describe_stats(stats)); + } + const ImageStats footer = image_stats(extract_box_reference(screen, m_footer)); + const bool footer_ok = !(footer.average.b < footer.average.r + 35 || + footer.average.g < footer.average.r + 20 || footer.stddev.sum() > 60); + line("footer (need b>=r+35, g>=r+20, stddev<=60)", footer_ok, describe_stats(footer)); + const std::pair buttons[] = { + {"confirm button (without Swap)", &m_confirm_button}, + {"confirm button (with Swap)", &m_confirm_button_with_swap}, + {"save button", &m_save_button}, + {"close button", &m_close_button}, {"save shortcut", &m_save_shortcut}, + }; + for (const auto& button : buttons){ + const ImageViewRGB32 patch = extract_box_reference(screen, *button.second); + line(button.first, has_menu_button(patch), describe_button(patch)); + } + const ImageStats heading = image_stats(extract_box_reference(screen, m_save_heading)); + const ImageStats background = image_stats(extract_box_reference(screen, m_save_background)); + line("save heading (need rgb>180, stddev>30)", + heading.average.r > 180 && heading.average.g > 180 && heading.average.b > 180 && heading.stddev.sum() > 30, + describe_stats(heading)); + line("save background (need g,b>=r+40, stddev<90)", + background.average.g > background.average.r + 40 && background.average.b > background.average.r + 40 && + background.stddev.sum() < 90, + describe_stats(background)); + line("chrome overall", detect_menu_chrome(screen), ""); + + const ImageFloatBox* stripes[] = {&m_line0, &m_line1, &m_line2, &m_line3, &m_line4}; + for (size_t c = 0; c < 5; c++){ + const ImageStats stats = image_stats(extract_box_reference(screen, *stripes[c])); + line("legacy stripe " + std::to_string(c), is_white(stats), describe_stats(stats)); + } + const ImageStats cross = image_stats(extract_box_reference(screen, m_cross)); + line("legacy cross (need stddev>=100)", cross.stddev.sum() >= 100, describe_stats(cross)); + return report; +} + bool MenuDetector::detect(const ImageViewRGB32& screen){ if (detect_menu_chrome(screen)){ return true; diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h index e7ef54f78a..a56bcc7bc1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h @@ -31,6 +31,9 @@ class MenuDetector : public StaticScreenDetector{ virtual void make_overlays(VideoOverlaySet& items) const override; virtual bool detect(const ImageViewRGB32& screen) override; + // Multi-line description of every check: what passed, what failed, and the measured values. + std::string debug_report(const ImageViewRGB32& screen) const; + private: bool detect_menu_chrome(const ImageViewRGB32& screen) const; @@ -46,6 +49,7 @@ class MenuDetector : public StaticScreenDetector{ ImageFloatBox m_panel_right; ImageFloatBox m_footer; ImageFloatBox m_confirm_button; + ImageFloatBox m_confirm_button_with_swap; ImageFloatBox m_save_button; ImageFloatBox m_close_button; ImageFloatBox m_save_heading; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 48c2627b40..373777c222 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -1,759 +1,773 @@ -#include -#include - -#include "PokemonBDSP_AutoStory.h" - -#include "CommonFramework/Globals.h" -#include "CommonFramework/Notifications/ProgramNotifications.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" - -#include "StorySegments/PokemonBDSP_AutoStory_Segment_00.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_01.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_02.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_03.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_04.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_05.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_06.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_07.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_08.h" -#include "StorySegments/PokemonBDSP_AutoStory_Segment_09.h" - -#include "CommonTools/Options/StringSelectOption.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - -using namespace std::chrono_literals; - - -// --------------------------------------------------------------------------- -// Segment list -// --------------------------------------------------------------------------- - -static std::vector> make_segment_list(){ - std::vector> segment_list; - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - segment_list.emplace_back(std::make_unique()); - return segment_list; -} - -static const std::vector>& ALL_SEGMENTS(){ - static std::vector> list = make_segment_list(); - return list; -} -static std::vector> make_checkpoint_list(){ - std::vector> checkpoint_list; - - //Segment 00 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - //Segment 01 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - //Segment 02 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - //Segment 03 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - // Segment 04 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - // Segment 05 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - // Segment 06 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - // Segment 07 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - // Segment 08 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - // Segment 09 - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - - - - //checkpoint_list.emplace_back(std::make_unique()); - /*checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique());*/ - - return checkpoint_list; -} - -static const std::vector>& ALL_CHECKPOINTS(){ - static std::vector> list = make_checkpoint_list(); - return list; -} - -// --------------------------------------------------------------------------- -// Segment database -// --------------------------------------------------------------------------- - -// Section databases retain global index slugs. Never execute across the -// tutorial/main-story boundary: the starter must be returned from HOME first. -static constexpr size_t TUTORIAL_CHECKPOINT_COUNT = 6; -static const StringSelectDatabase& section_database(bool checkpoints, bool tutorial){ - static const StringSelectDatabase tutorial_segments = []{ - StringSelectDatabase db; - db.add_entry(StringSelectEntry("0", ALL_SEGMENTS()[0]->name())); - return db; - }(); - static const StringSelectDatabase main_segments = []{ - StringSelectDatabase db; - for (size_t i = 1; i < ALL_SEGMENTS().size(); i++) - db.add_entry(StringSelectEntry(std::to_string(i), ALL_SEGMENTS()[i]->name())); - return db; - }(); - static const StringSelectDatabase tutorial_checkpoints = []{ - StringSelectDatabase db; - for (size_t i = 0; i < TUTORIAL_CHECKPOINT_COUNT; i++) - db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); - return db; - }(); - static const StringSelectDatabase main_checkpoints = []{ - StringSelectDatabase db; - for (size_t i = TUTORIAL_CHECKPOINT_COUNT; i < ALL_CHECKPOINTS().size(); i++) - db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); - return db; - }(); - return checkpoints ? (tutorial ? tutorial_checkpoints : main_checkpoints) - : (tutorial ? tutorial_segments : main_segments); -} - -// --------------------------------------------------------------------------- -// Descriptor -// --------------------------------------------------------------------------- - -BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() - : SingleSwitchProgramDescriptor( - "PokemonBDSP:AutoStory", - "Pokemon BDSP", - "Auto Story", - "Programs/PokemonBDSP/AutoStory.html", - "Automatically completes BDSP story segments.", - ProgramControllerClass::StandardController_NoRestrictions, - FeedbackType::REQUIRED, - AllowCommandsWhenRunning::DISABLE_COMMANDS, - PanelDeprecation::NOT_DEPRECATED, - {"Tesseract"} - ) -{} - - - -std::unique_ptr -BDSPAutoStory_Descriptor::make_stats() const{ - return std::make_unique(); -} - - -// --------------------------------------------------------------------------- -// Program Instance -// --------------------------------------------------------------------------- - -BDSPAutoStory::~BDSPAutoStory(){ - OUTSIDE_HELP_POKEMON.remove_listener(*this); - HELP_MODE.remove_listener(*this); - STORY_SECTION.remove_listener(*this); - START_SEGMENT_MAINSTORY.remove_listener(*this); - END_SEGMENT_MAINSTORY.remove_listener(*this); - START_CHECKPOINT_MAINSTORY.remove_listener(*this); - END_CHECKPOINT_MAINSTORY.remove_listener(*this); - START_CHECKPOINT_TUTORIAL.remove_listener(*this); - END_CHECKPOINT_TUTORIAL.remove_listener(*this); - START_SEGMENT.remove_listener(*this); - END_SEGMENT.remove_listener(*this); - ENABLE_ADVANCED_MODE.remove_listener(*this); - - -} - - -// --------------------------------------------------------------------------- -// Constructor -// --------------------------------------------------------------------------- - -BDSPAutoStory::BDSPAutoStory() - : SingleSwitchProgramInstance() - // DIAGNOSTIC ONLY: Remove before pushing to production. - , DIAGNOSTIC_POST_CATCH_ONLY( - "Diagnostic only: Run 00.04 after catch through 00.05
" - "Start at the saved position immediately after catching the second Pokemon. " - "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " - "Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, - false - ) - // DIAGNOSTIC ONLY: Remove before pushing to production. - , DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( - "Diagnostic only: Fake checkpoint saves
" - "Use the fake save helper instead of normal checkpoint saving. " - "Checkpoint saves are not verified in this mode. Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, false) - , STORY_SECTION( - "Story Section:", - {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, - {StorySection::MAIN_STORY, "main-story", "Main Story"}}, - LockMode::LOCK_WHILE_RUNNING, StorySection::TUTORIAL - ) - , MAINSTORY_NOTE("", "Return your maxed starter from Pokemon HOME before starting Main Story. " - "Use the required moveset and the starting position shown below.") - , ENABLE_ADVANCED_MODE( - "Advanced mode:
" - "Use checkpoints instead of segments.", - LockMode::UNLOCK_WHILE_RUNNING, - false - ) - , START_CHECKPOINT_TUTORIAL( - "Start Checkpoint:", - section_database(true, true), - LockMode::LOCK_WHILE_RUNNING, - "0" - ) - , END_CHECKPOINT_TUTORIAL( - "End Checkpoint:", - section_database(true, true), - LockMode::UNLOCK_WHILE_RUNNING, - "5" - ) - - , START_DESCRIPTION_SEGMENT("Start:
", "") - , END_DESCRIPTION_SEGMENT("End:
", "") - - , START_DESCRIPTION_CHECKPOINT("Start:
", "") - , END_DESCRIPTION_CHECKPOINT("End:
", "") - - , START_CHECKPOINT_MAINSTORY( - "Start Checkpoint (Main):", - section_database(true, false), - LockMode::LOCK_WHILE_RUNNING, - "6" - ) - - , END_CHECKPOINT_MAINSTORY( - "End Checkpoint (Main):", - section_database(true, false), - LockMode::UNLOCK_WHILE_RUNNING, - std::to_string(ALL_CHECKPOINTS().size() - 1) - ) - - , START_SEGMENT( - "Start Segment:", - section_database(false, true), - LockMode::LOCK_WHILE_RUNNING, - "0" - ) - - , END_SEGMENT( - "End Segment:", - section_database(false, true), - LockMode::UNLOCK_WHILE_RUNNING, - "0" - ) - - , START_SEGMENT_MAINSTORY( - "Start Segment:", section_database(false, false), - LockMode::LOCK_WHILE_RUNNING, "1") - , END_SEGMENT_MAINSTORY( - "End Segment:", section_database(false, false), - LockMode::LOCK_WHILE_RUNNING, std::to_string(ALL_SEGMENTS().size() - 1)) - , STARTERCHOICE( - "Starter Pokemon:", - { - {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, - {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, - {StarterChoice::PIPLUP, "piplup", "Piplup"}, - }, - LockMode::LOCK_WHILE_RUNNING, - StarterChoice::CHIMCHAR - ) - - , STARTER_SHINY( - "Shiny Starter:", - LockMode::LOCK_WHILE_RUNNING, - false - ) - - , CATCH_LEGENDARY( - "Catch Box Legendary:", - LockMode::LOCK_WHILE_RUNNING, - false - ) - - , HELP_MODE( - "Help Mode:", - { - {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, - {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, - }, - LockMode::LOCK_WHILE_RUNNING, - HelpMode::NO_OUTSIDE_HELP - ) - - , OUTSIDE_HELP_POKEMON( - "Pokemon:", - { - {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, - }, - LockMode::LOCK_WHILE_RUNNING, - OutsideHelpPokemon::TORTERRA - ) - - , OUTSIDE_HELP_ATTACK_1( - "Attack 1:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::EARTHQUAKE - ) - - , OUTSIDE_HELP_ATTACK_2( - "Attack 2:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::ROCK_SLIDE - ) - - , OUTSIDE_HELP_ATTACK_3( - "Attack 3:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::WOOD_HAMMER - ) - - , OUTSIDE_HELP_ATTACK_4( - "Attack 4:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::CRUNCH - ) - - , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)) - , NOTIFICATIONS({ - &NOTIFICATION_STATUS_UPDATE, - &NOTIFICATION_PROGRAM_FINISH, - &NOTIFICATION_ERROR_RECOVERABLE, - &NOTIFICATION_ERROR_FATAL, - }) - -{ - // ----------------------------------------------------------------------- - // Options - // ----------------------------------------------------------------------- - - PA_ADD_OPTION(DIAGNOSTIC_POST_CATCH_ONLY); - PA_ADD_OPTION(DIAGNOSTIC_FAKE_CHECKPOINT_SAVE); - PA_ADD_OPTION(STORY_SECTION); - PA_ADD_OPTION(MAINSTORY_NOTE); - PA_ADD_OPTION(ENABLE_ADVANCED_MODE); - - // Segment UI - PA_ADD_OPTION(START_SEGMENT); - PA_ADD_OPTION(START_SEGMENT_MAINSTORY); - PA_ADD_OPTION(START_DESCRIPTION_SEGMENT); - - PA_ADD_OPTION(END_SEGMENT); - PA_ADD_OPTION(END_SEGMENT_MAINSTORY); - PA_ADD_OPTION(END_DESCRIPTION_SEGMENT); - - // Checkpoint UI - PA_ADD_OPTION(START_CHECKPOINT_TUTORIAL); - PA_ADD_OPTION(START_CHECKPOINT_MAINSTORY); - PA_ADD_OPTION(START_DESCRIPTION_CHECKPOINT); - - PA_ADD_OPTION(END_CHECKPOINT_TUTORIAL); - PA_ADD_OPTION(END_CHECKPOINT_MAINSTORY); - PA_ADD_OPTION(END_DESCRIPTION_CHECKPOINT); - - - PA_ADD_OPTION(STARTERCHOICE); - PA_ADD_OPTION(STARTER_SHINY); - PA_ADD_OPTION(CATCH_LEGENDARY); - - PA_ADD_OPTION(HELP_MODE); - PA_ADD_OPTION(OUTSIDE_HELP_POKEMON); - - PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_1); - PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_2); - PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_3); - PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_4); - - PA_ADD_OPTION(NOTIFICATIONS); - - HELP_MODE.add_listener(*this); - OUTSIDE_HELP_POKEMON.add_listener(*this); - START_CHECKPOINT_TUTORIAL.add_listener(*this); - END_CHECKPOINT_TUTORIAL.add_listener(*this); - ENABLE_ADVANCED_MODE.add_listener(*this); - START_SEGMENT.add_listener(*this); - END_SEGMENT.add_listener(*this); - STORY_SECTION.add_listener(*this); - START_SEGMENT_MAINSTORY.add_listener(*this); - END_SEGMENT_MAINSTORY.add_listener(*this); - START_CHECKPOINT_MAINSTORY.add_listener(*this); - END_CHECKPOINT_MAINSTORY.add_listener(*this); - on_config_value_changed(nullptr); -} -void BDSPAutoStory::update_ui_state(){ - const bool advanced = ENABLE_ADVANCED_MODE; - const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; - auto visible = [](ConfigOption& option, bool show){ - option.set_visibility(show ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); - }; - visible(START_SEGMENT, !advanced && tutorial); - visible(END_SEGMENT, !advanced && tutorial); - visible(START_SEGMENT_MAINSTORY, !advanced && !tutorial); - visible(END_SEGMENT_MAINSTORY, !advanced && !tutorial); - visible(START_CHECKPOINT_TUTORIAL, advanced && tutorial); - visible(END_CHECKPOINT_TUTORIAL, advanced && tutorial); - visible(START_CHECKPOINT_MAINSTORY, advanced && !tutorial); - visible(END_CHECKPOINT_MAINSTORY, advanced && !tutorial); - visible(START_DESCRIPTION_SEGMENT, !advanced); - visible(END_DESCRIPTION_SEGMENT, !advanced); - visible(START_DESCRIPTION_CHECKPOINT, advanced); - visible(END_DESCRIPTION_CHECKPOINT, advanced); - visible(MAINSTORY_NOTE, !tutorial); - - bool show_help = ((HelpMode)HELP_MODE == HelpMode::OUTSIDE_HELP); - - OUTSIDE_HELP_POKEMON.set_visibility( - show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); - - OUTSIDE_HELP_ATTACK_1.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); - OUTSIDE_HELP_ATTACK_2.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); - OUTSIDE_HELP_ATTACK_3.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); - OUTSIDE_HELP_ATTACK_4.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); -} -void BDSPAutoStory::on_config_value_changed(void*){ - const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; - if (ENABLE_ADVANCED_MODE){ - const auto& checkpoints = ALL_CHECKPOINTS(); - - size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); - - if (start < checkpoints.size()){ - START_DESCRIPTION_CHECKPOINT.set_text( - "Start: " + checkpoints[start]->start_text() - ); - } - if (end < checkpoints.size()){ - END_DESCRIPTION_CHECKPOINT.set_text( - "End: " + checkpoints[end]->end_text() - ); - } - - }else{ - const auto& segments = ALL_SEGMENTS(); - - size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); - - if (start < segments.size()){ - START_DESCRIPTION_SEGMENT.set_text( - "Start: " + segments[start]->start_text() - ); - } - if (end < segments.size()){ - END_DESCRIPTION_SEGMENT.set_text( - "End: " + segments[end]->end_text() - ); - } - } - - update_ui_state(); -} -// --------------------------------------------------------------------------- -// Program logic -// --------------------------------------------------------------------------- - -void BDSPAutoStory::program( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -){ - AutoStoryStats& stats = env.current_stats(); - - AutoStoryOptions options{ - Language::English, - STARTERCHOICE, - NOTIFICATION_STATUS_UPDATE, - (HelpMode)HELP_MODE, - OutsideHelpConfig{ - (OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, - { - (TorterraMove)OUTSIDE_HELP_ATTACK_1, - (TorterraMove)OUTSIDE_HELP_ATTACK_2, - (TorterraMove)OUTSIDE_HELP_ATTACK_3, - (TorterraMove)OUTSIDE_HELP_ATTACK_4, - } - }, - DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL - }; - - // DIAGNOSTIC ONLY: Remove this branch before pushing to production. - if (DIAGNOSTIC_POST_CATCH_ONLY && ENABLE_ADVANCED_MODE && - STORY_SECTION == StorySection::TUTORIAL && START_CHECKPOINT_TUTORIAL.slug() == "4"){ - env.log("Diagnostic only: Running checkpoint 00.04 after catch."); - diagnostic_checkpoint_0004_after_catch(env, context, options, stats); - env.log("Diagnostic continuation: Running checkpoint 00.05."); - AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); - return; - } - - const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; - const auto& segments = ALL_SEGMENTS(); - - if (ENABLE_ADVANCED_MODE){ - size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); - - env.log("Starting AutoStory from checkpoint " + std::to_string(start) + - " to " + std::to_string(end)); - - const auto& checkpoints = ALL_CHECKPOINTS(); - - for (size_t i = start; i <= end && i < checkpoints.size(); i++){ - env.log("Running Checkpoint: " + std::to_string(i)); - checkpoints[i]->run_checkpoint(env, context, options, stats); - } - - }else{ - size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); - - for (size_t i = start; i <= end && i < segments.size(); i++){ - env.log("Running Segment: " + std::to_string(i)); - segments[i]->run_segment(env, context, options, stats); - } - } - - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); -} - - - -} -} +#include +#include + +#include "PokemonBDSP_AutoStory.h" + +#include "CommonFramework/Globals.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + +#include "StorySegments/PokemonBDSP_AutoStory_Segment_00.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_01.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_02.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_03.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_04.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_05.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_06.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_07.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_08.h" +#include "StorySegments/PokemonBDSP_AutoStory_Segment_09.h" + +#include "CommonTools/Options/StringSelectOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + +using namespace std::chrono_literals; + + +// --------------------------------------------------------------------------- +// Segment list +// --------------------------------------------------------------------------- + +static std::vector> make_segment_list(){ + std::vector> segment_list; + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + segment_list.emplace_back(std::make_unique()); + return segment_list; +} + +static const std::vector>& ALL_SEGMENTS(){ + static std::vector> list = make_segment_list(); + return list; +} +static std::vector> make_checkpoint_list(){ + std::vector> checkpoint_list; + + //Segment 00 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + //Segment 01 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + //Segment 02 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + //Segment 03 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 04 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 05 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 06 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 07 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 08 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + // Segment 09 + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + + + + //checkpoint_list.emplace_back(std::make_unique()); + /*checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique());*/ + + return checkpoint_list; +} + +static const std::vector>& ALL_CHECKPOINTS(){ + static std::vector> list = make_checkpoint_list(); + return list; +} + +// --------------------------------------------------------------------------- +// Segment database +// --------------------------------------------------------------------------- + +// Section databases retain global index slugs. Never execute across the +// tutorial/main-story boundary: the starter must be returned from HOME first. +static constexpr size_t TUTORIAL_CHECKPOINT_COUNT = 6; +static const StringSelectDatabase& section_database(bool checkpoints, bool tutorial){ + static const StringSelectDatabase tutorial_segments = []{ + StringSelectDatabase db; + db.add_entry(StringSelectEntry("0", ALL_SEGMENTS()[0]->name())); + return db; + }(); + static const StringSelectDatabase main_segments = []{ + StringSelectDatabase db; + for (size_t i = 1; i < ALL_SEGMENTS().size(); i++) + db.add_entry(StringSelectEntry(std::to_string(i), ALL_SEGMENTS()[i]->name())); + return db; + }(); + static const StringSelectDatabase tutorial_checkpoints = []{ + StringSelectDatabase db; + for (size_t i = 0; i < TUTORIAL_CHECKPOINT_COUNT; i++) + db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + return db; + }(); + static const StringSelectDatabase main_checkpoints = []{ + StringSelectDatabase db; + for (size_t i = TUTORIAL_CHECKPOINT_COUNT; i < ALL_CHECKPOINTS().size(); i++) + db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + return db; + }(); + return checkpoints ? (tutorial ? tutorial_checkpoints : main_checkpoints) + : (tutorial ? tutorial_segments : main_segments); +} + +// --------------------------------------------------------------------------- +// Descriptor +// --------------------------------------------------------------------------- + +BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonBDSP:AutoStory", + "Pokemon BDSP", + "Auto Story", + "Programs/PokemonBDSP/AutoStory.html", + "Automatically completes BDSP story segments.", + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS, + PanelDeprecation::NOT_DEPRECATED, + {"Tesseract"} + ) +{} + + + +std::unique_ptr +BDSPAutoStory_Descriptor::make_stats() const{ + return std::make_unique(); +} + + +// --------------------------------------------------------------------------- +// Program Instance +// --------------------------------------------------------------------------- + +BDSPAutoStory::~BDSPAutoStory(){ + OUTSIDE_HELP_POKEMON.remove_listener(*this); + HELP_MODE.remove_listener(*this); + STORY_SECTION.remove_listener(*this); + START_SEGMENT_MAINSTORY.remove_listener(*this); + END_SEGMENT_MAINSTORY.remove_listener(*this); + START_CHECKPOINT_MAINSTORY.remove_listener(*this); + END_CHECKPOINT_MAINSTORY.remove_listener(*this); + START_CHECKPOINT_TUTORIAL.remove_listener(*this); + END_CHECKPOINT_TUTORIAL.remove_listener(*this); + START_SEGMENT.remove_listener(*this); + END_SEGMENT.remove_listener(*this); + ENABLE_ADVANCED_MODE.remove_listener(*this); + + +} + + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- + +BDSPAutoStory::BDSPAutoStory() + : SingleSwitchProgramInstance() + // DIAGNOSTIC ONLY: Remove before pushing to production. + , DIAGNOSTIC_POST_CATCH_ONLY( + "Diagnostic only: Run 00.04 after catch through 00.05
" + "Start at the saved position immediately after catching the second Pokemon. " + "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " + "Remove before pushing to production.", + LockMode::LOCK_WHILE_RUNNING, + false + ) + // DIAGNOSTIC ONLY: Remove before pushing to production. + , DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( + "Diagnostic only: Fake checkpoint saves
" + "Use the fake save helper instead of normal checkpoint saving. " + "Use an existing in-game save at the selected starting checkpoint or segment. " + "Any failure reloads that save and reruns the entire selected range from its start. " + "Checkpoint saves are not verified in this mode. Remove before pushing to production.", + LockMode::LOCK_WHILE_RUNNING, false) + , STORY_SECTION( + "Story Section:", + {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, + {StorySection::MAIN_STORY, "main-story", "Main Story"}}, + LockMode::LOCK_WHILE_RUNNING, StorySection::TUTORIAL + ) + , MAINSTORY_NOTE("", "Return your maxed starter from Pokemon HOME before starting Main Story. " + "Use the required moveset and the starting position shown below.") + , ENABLE_ADVANCED_MODE( + "Advanced mode:
" + "Use checkpoints instead of segments.", + LockMode::UNLOCK_WHILE_RUNNING, + false + ) + , START_CHECKPOINT_TUTORIAL( + "Start Checkpoint:", + section_database(true, true), + LockMode::LOCK_WHILE_RUNNING, + "0" + ) + , END_CHECKPOINT_TUTORIAL( + "End Checkpoint:", + section_database(true, true), + LockMode::UNLOCK_WHILE_RUNNING, + "5" + ) + + , START_DESCRIPTION_SEGMENT("Start:
", "") + , END_DESCRIPTION_SEGMENT("End:
", "") + + , START_DESCRIPTION_CHECKPOINT("Start:
", "") + , END_DESCRIPTION_CHECKPOINT("End:
", "") + + , START_CHECKPOINT_MAINSTORY( + "Start Checkpoint (Main):", + section_database(true, false), + LockMode::LOCK_WHILE_RUNNING, + "6" + ) + + , END_CHECKPOINT_MAINSTORY( + "End Checkpoint (Main):", + section_database(true, false), + LockMode::UNLOCK_WHILE_RUNNING, + std::to_string(ALL_CHECKPOINTS().size() - 1) + ) + + , START_SEGMENT( + "Start Segment:", + section_database(false, true), + LockMode::LOCK_WHILE_RUNNING, + "0" + ) + + , END_SEGMENT( + "End Segment:", + section_database(false, true), + LockMode::UNLOCK_WHILE_RUNNING, + "0" + ) + + , START_SEGMENT_MAINSTORY( + "Start Segment:", section_database(false, false), + LockMode::LOCK_WHILE_RUNNING, "1") + , END_SEGMENT_MAINSTORY( + "End Segment:", section_database(false, false), + LockMode::LOCK_WHILE_RUNNING, std::to_string(ALL_SEGMENTS().size() - 1)) + , STARTERCHOICE( + "Starter Pokemon:", + { + {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, + {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, + {StarterChoice::PIPLUP, "piplup", "Piplup"}, + }, + LockMode::LOCK_WHILE_RUNNING, + StarterChoice::CHIMCHAR + ) + + , STARTER_SHINY( + "Shiny Starter:", + LockMode::LOCK_WHILE_RUNNING, + false + ) + + , CATCH_LEGENDARY( + "Catch Box Legendary:", + LockMode::LOCK_WHILE_RUNNING, + false + ) + + , HELP_MODE( + "Help Mode:", + { + {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, + {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, + }, + LockMode::LOCK_WHILE_RUNNING, + HelpMode::NO_OUTSIDE_HELP + ) + + , OUTSIDE_HELP_POKEMON( + "Pokemon:", + { + {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, + }, + LockMode::LOCK_WHILE_RUNNING, + OutsideHelpPokemon::TORTERRA + ) + + , OUTSIDE_HELP_ATTACK_1( + "Attack 1:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::EARTHQUAKE + ) + + , OUTSIDE_HELP_ATTACK_2( + "Attack 2:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::ROCK_SLIDE + ) + + , OUTSIDE_HELP_ATTACK_3( + "Attack 3:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::WOOD_HAMMER + ) + + , OUTSIDE_HELP_ATTACK_4( + "Attack 4:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::CRUNCH + ) + + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)) + , NOTIFICATIONS({ + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_RECOVERABLE, + &NOTIFICATION_ERROR_FATAL, + }) + +{ + // ----------------------------------------------------------------------- + // Options + // ----------------------------------------------------------------------- + + PA_ADD_OPTION(DIAGNOSTIC_POST_CATCH_ONLY); + PA_ADD_OPTION(DIAGNOSTIC_FAKE_CHECKPOINT_SAVE); + PA_ADD_OPTION(STORY_SECTION); + PA_ADD_OPTION(MAINSTORY_NOTE); + PA_ADD_OPTION(ENABLE_ADVANCED_MODE); + + // Segment UI + PA_ADD_OPTION(START_SEGMENT); + PA_ADD_OPTION(START_SEGMENT_MAINSTORY); + PA_ADD_OPTION(START_DESCRIPTION_SEGMENT); + + PA_ADD_OPTION(END_SEGMENT); + PA_ADD_OPTION(END_SEGMENT_MAINSTORY); + PA_ADD_OPTION(END_DESCRIPTION_SEGMENT); + + // Checkpoint UI + PA_ADD_OPTION(START_CHECKPOINT_TUTORIAL); + PA_ADD_OPTION(START_CHECKPOINT_MAINSTORY); + PA_ADD_OPTION(START_DESCRIPTION_CHECKPOINT); + + PA_ADD_OPTION(END_CHECKPOINT_TUTORIAL); + PA_ADD_OPTION(END_CHECKPOINT_MAINSTORY); + PA_ADD_OPTION(END_DESCRIPTION_CHECKPOINT); + + + PA_ADD_OPTION(STARTERCHOICE); + PA_ADD_OPTION(STARTER_SHINY); + PA_ADD_OPTION(CATCH_LEGENDARY); + + PA_ADD_OPTION(HELP_MODE); + PA_ADD_OPTION(OUTSIDE_HELP_POKEMON); + + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_1); + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_2); + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_3); + PA_ADD_OPTION(OUTSIDE_HELP_ATTACK_4); + + PA_ADD_OPTION(NOTIFICATIONS); + + HELP_MODE.add_listener(*this); + OUTSIDE_HELP_POKEMON.add_listener(*this); + START_CHECKPOINT_TUTORIAL.add_listener(*this); + END_CHECKPOINT_TUTORIAL.add_listener(*this); + ENABLE_ADVANCED_MODE.add_listener(*this); + START_SEGMENT.add_listener(*this); + END_SEGMENT.add_listener(*this); + STORY_SECTION.add_listener(*this); + START_SEGMENT_MAINSTORY.add_listener(*this); + END_SEGMENT_MAINSTORY.add_listener(*this); + START_CHECKPOINT_MAINSTORY.add_listener(*this); + END_CHECKPOINT_MAINSTORY.add_listener(*this); + on_config_value_changed(nullptr); +} +void BDSPAutoStory::update_ui_state(){ + const bool advanced = ENABLE_ADVANCED_MODE; + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; + auto visible = [](ConfigOption& option, bool show){ + option.set_visibility(show ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + }; + visible(START_SEGMENT, !advanced && tutorial); + visible(END_SEGMENT, !advanced && tutorial); + visible(START_SEGMENT_MAINSTORY, !advanced && !tutorial); + visible(END_SEGMENT_MAINSTORY, !advanced && !tutorial); + visible(START_CHECKPOINT_TUTORIAL, advanced && tutorial); + visible(END_CHECKPOINT_TUTORIAL, advanced && tutorial); + visible(START_CHECKPOINT_MAINSTORY, advanced && !tutorial); + visible(END_CHECKPOINT_MAINSTORY, advanced && !tutorial); + visible(START_DESCRIPTION_SEGMENT, !advanced); + visible(END_DESCRIPTION_SEGMENT, !advanced); + visible(START_DESCRIPTION_CHECKPOINT, advanced); + visible(END_DESCRIPTION_CHECKPOINT, advanced); + visible(MAINSTORY_NOTE, !tutorial); + + bool show_help = ((HelpMode)HELP_MODE == HelpMode::OUTSIDE_HELP); + + OUTSIDE_HELP_POKEMON.set_visibility( + show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + + OUTSIDE_HELP_ATTACK_1.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + OUTSIDE_HELP_ATTACK_2.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + OUTSIDE_HELP_ATTACK_3.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); + OUTSIDE_HELP_ATTACK_4.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); +} +void BDSPAutoStory::on_config_value_changed(void*){ + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; + if (ENABLE_ADVANCED_MODE){ + const auto& checkpoints = ALL_CHECKPOINTS(); + + size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); + + if (start < checkpoints.size()){ + START_DESCRIPTION_CHECKPOINT.set_text( + "Start: " + checkpoints[start]->start_text() + ); + } + if (end < checkpoints.size()){ + END_DESCRIPTION_CHECKPOINT.set_text( + "End: " + checkpoints[end]->end_text() + ); + } + + }else{ + const auto& segments = ALL_SEGMENTS(); + + size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); + + if (start < segments.size()){ + START_DESCRIPTION_SEGMENT.set_text( + "Start: " + segments[start]->start_text() + ); + } + if (end < segments.size()){ + END_DESCRIPTION_SEGMENT.set_text( + "End: " + segments[end]->end_text() + ); + } + } + + update_ui_state(); +} +// --------------------------------------------------------------------------- +// Program logic +// --------------------------------------------------------------------------- + +void BDSPAutoStory::program( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + AutoStoryStats& stats = env.current_stats(); + + AutoStoryOptions options{ + Language::English, + STARTERCHOICE, + NOTIFICATION_STATUS_UPDATE, + (HelpMode)HELP_MODE, + OutsideHelpConfig{ + (OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, + { + (TorterraMove)OUTSIDE_HELP_ATTACK_1, + (TorterraMove)OUTSIDE_HELP_ATTACK_2, + (TorterraMove)OUTSIDE_HELP_ATTACK_3, + (TorterraMove)OUTSIDE_HELP_ATTACK_4, + } + }, + DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL + }; + + const bool fake_saves = options.checkpoint_save_mode == CheckpointSaveMode::FAKE; + if (fake_saves) options.checkpoint_retry_scope = CheckpointRetryScope::DEFER_TO_SELECTED_RANGE; + auto run_selected_range = [&](size_t attempt){ + if (attempt > 0) env.log("[Fake saves] Reloaded initial save; restarting the entire selected range from its start."); + // DIAGNOSTIC ONLY: Remove this branch before pushing to production. + if (DIAGNOSTIC_POST_CATCH_ONLY && ENABLE_ADVANCED_MODE && + STORY_SECTION == StorySection::TUTORIAL && START_CHECKPOINT_TUTORIAL.slug() == "4"){ + env.log("Diagnostic only: Running checkpoint 00.04 after catch."); + diagnostic_checkpoint_0004_after_catch(env, context, options, stats); + env.log("Diagnostic continuation: Running checkpoint 00.05."); + AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); + return; + } + + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; + const auto& segments = ALL_SEGMENTS(); + + if (ENABLE_ADVANCED_MODE){ + size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); + + env.log("Starting AutoStory from checkpoint " + std::to_string(start) + + " to " + std::to_string(end)); + + const auto& checkpoints = ALL_CHECKPOINTS(); + + for (size_t i = start; i <= end && i < checkpoints.size(); i++){ + env.log("Running Checkpoint: " + std::to_string(i)); + checkpoints[i]->run_checkpoint(env, context, options, stats); + } + + }else{ + size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); + + for (size_t i = start; i <= end && i < segments.size(); i++){ + env.log("Running Segment: " + std::to_string(i)); + segments[i]->run_segment(env, context, options, stats); + } + } + }; + if (fake_saves){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + std::move(run_selected_range), true, CheckpointSaveMode::FAKE, + CheckpointRetryScope::SELECTED_RANGE); + }else{ + run_selected_range(0); + } + + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); +} + + + +} +} } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 4f244154d1..9557e802e1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1,1904 +1,2046 @@ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" -#include "CommonFramework/GlobalAutoPaths.h" -#include -/* BDSP AutoStory Tools - * - * From: https://github.com/PokemonAutomation/ - * - */ -#include -#include "CommonFramework/ImageTools/ImageDiff.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "CommonFramework/Globals.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/ImageTools/ImageStats.h" -#include "CommonFramework/Notifications/ProgramNotifications.h" -#include "CommonFramework/Tools/VideoStream.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" -#include "CommonTools/Images/SolidColorTest.h" -#include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" -#include "CommonTools/Images/ImageFilter.h" -#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" - -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" -#include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" -#include "Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "PokemonBDSP_AutoStoryTools.h" - -#include -#include - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - -const char* torterra_move_slug(TorterraMove move){ - switch (move){ - case TorterraMove::EARTHQUAKE: return "earthquake"; - case TorterraMove::WOOD_HAMMER: return "wood-hammer"; - case TorterraMove::RAZOR_LEAF: return "razor-leaf"; - case TorterraMove::CRUNCH: return "crunch"; - case TorterraMove::GIGA_DRAIN: return "giga-drain"; - case TorterraMove::IRON_TAIL: return "iron-tail"; - case TorterraMove::ROCK_TOMB: return "rock-tomb"; - case TorterraMove::FACADE: return "facade"; - case TorterraMove::STONE_EDGE: return "stone-edge"; - case TorterraMove::ROCK_SLIDE: return "rock-slide"; - case TorterraMove::BULLDOZE: return "bulldoze"; - case TorterraMove::STRENGTH: return "strength"; - case TorterraMove::ROCK_SMASH: return "rock-smash"; - case TorterraMove::ROCK_CLIMB: return "rock-climb"; - } - return "unknown"; -} - -const char* trainer_slug(Trainer t){ - switch (t){ - case Trainer::Veilstone_GalacticGrunts_2v2: return "Veilstone_GalacticGrunts_2v2"; - } - return "unknown"; -} - -bool fake_save_game( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -){ - pbf_mash_button(context, BUTTON_L, 1000ms); - - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_X, 80ms, 1000ms); - pbf_press_button(context, BUTTON_R, 80ms, 1000ms); - pbf_mash_button(context, BUTTON_B, 2000ms); - context.wait_for_all_requests(); - - return true; -} - -bool save_game( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -) { - static constexpr size_t MAX_RETRIES = 3; - - for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++) { - try { - env.console.log( - "Checkpoint save attempt: " + std::to_string(attempt + 1) - ); - - // Only mash B on retries - no need to on first attempt - // if already cleanly in the overworld. - if (attempt > 0) { - pbf_mash_button(context, BUTTON_B, 1000ms); - } - - // Reuse existing feedback-based save: opens menu with MenuWatcher - // + respects GameSettings::OVERWORLD_TO_MENU_DELAY0 internally. - save_game(env.console, context); - - // Return to overworld cleanly. - pbf_mash_button(context, BUTTON_B, 1000ms); - - // Drain R/confirm/B before detection; an earlier overworld frame - // must not report success while saving commands are still queued. - context.wait_for_all_requests(); - - // Verify overworld. Generous timeout: save animation + fade can be slow. - OverworldWatcher overworld; - int ret = wait_until( - env.console, - context, - std::chrono::seconds(15), - { overworld } - ); - if (ret < 0) { - throw OperationFailedExceptionWithScreenshot( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to detect overworld after save.", - env.console - ); - } - - env.console.log("Checkpoint save verified."); - return true; - - } - catch (OperationFailedException& e) { - env.console.log( - "Checkpoint save failed: " + e.message(), - COLOR_RED - ); - context.wait_for(1000ms); - } - } - - // Blind fallback: reuse the non-feedback save_game(context) from GameNavigation - // rather than duplicating the button sequence manually. - env.console.log( - "All save retries failed. Using blind fallback.", - COLOR_ORANGE - ); - pbf_mash_button(context, BUTTON_B, 1000ms); - save_game(context); // PokemonBDSP_GameNavigation.h - non-feedback overload - pbf_mash_button(context, BUTTON_B, 2000ms); - context.wait_for_all_requests(); - - return false; -} - - -void checkpoint_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - CheckpointSaveMode save_mode -){ - const bool fake = save_mode == CheckpointSaveMode::FAKE; - env.console.log(fake ? "[Checkpoint save] DIAGNOSTIC FAKE mode: save is not verified." - : "[Checkpoint save] REAL mode."); - bool success = fake ? fake_save_game(env, context) : save_game(env, context); - - stats.m_checkpoint++; - env.update_stats(); - - send_program_status_notification( - env, - notif_status_update, - fake ? "Diagnostic fake checkpoint save (not verified)." : success - ? "Saved at checkpoint (verified)." - : "Saved at checkpoint (blind fallback)." - ); -} - - -void reload_backup_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -){ - // TODO: implement backup save reload. - // This is called when a checkpoint with save=false fails. - // Load the game's automatic backup save rather than resetting from HOME. - (void)env; - (void)context; -} - - -void checkpoint_reattempt_loop( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - std::function&& action, - bool save, - CheckpointSaveMode save_mode -){ - static constexpr size_t MAX_ATTEMPTS = 20; - - for (size_t i = 0;; i++){ - try{ - if (i == 0){ - if (save || stats.m_first_run){ - checkpoint_save(env, context, notif_status_update, stats, save_mode); - } - stats.m_first_run = false; - } - - action(i); - return; // success - - }catch (OperationFailedException& e){ - env.console.log("checkpoint_reattempt_loop: attempt " + std::to_string(i) + " failed: " + e.message(), COLOR_RED); - stats.m_reset++; - env.update_stats(); - - if (i + 1 >= MAX_ATTEMPTS){ - throw; - } - - if (save){ - env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); - bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); - if (!ok){ - throw OperationFailedExceptionWithScreenshot( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to reset game from HOME during checkpoint retry.", - env.console - ); - } - } else { - env.console.log("Reloading from backup save before retry...", COLOR_ORANGE); - reload_backup_save(env, context); - } - } - } -} - - - -void repeat(std::initializer_list> actions, size_t times){ - for (size_t i = 0; i < times; i++){ - for (const auto& action : actions){ - action(); - } - } -} - - -void wait_for_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label, - Milliseconds timeout -){ - auto log_detected = [&]{ - if (!label.empty()){ - stream.log(label + " dialogue detected."); - } - }; - - ShortDialogWatcher watcher_cyan(COLOR_CYAN); - int ret = run_until( - stream, context, - [timeout](ProControllerContext& context){ - context.wait_for(timeout); - }, - {{watcher_cyan}} - ); - if (ret == 0){ log_detected(); return; } - - ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until( - stream, context, - [timeout](ProControllerContext& context){ - context.wait_for(timeout); - }, - {{watcher_red}} - ); - if (ret == 0){ log_detected(); return; } - - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", - stream - ); -} - - -void mash_until_dialogue_ends( - VideoStream& stream, - ProControllerContext& context, - Button button, - Milliseconds timeout -){ - OverworldWatcher overworld; - int ret = run_until( - stream, context, - [button, timeout](ProControllerContext& context){ - pbf_mash_button(context, button, timeout); - }, - {{overworld}} - ); - if (ret < 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "mash_until_dialogue_ends: overworld not reached within timeout.", - stream - ); - } -} - - -void repeat_dpad( - ProControllerContext& context, - DpadState& state, - DpadPosition dir, - Milliseconds press, - Milliseconds hold, - size_t times, - bool include_last_dir -){ - if (include_last_dir && state.last_dir != DPAD_NONE && state.last_dir != dir){ - times += 1; - } - state.last_dir = dir; - for (size_t i = 0; i < times; i++){ - pbf_press_dpad(context, dir, press, hold); - } -} - -static const char* shop_item_name(ShopItem item){ - switch (item){ - case ShopItem::FullRestore: return "Full restore"; - case ShopItem::MaxPotion: return "Max potion"; - case ShopItem::HyperPotion: return "Hyper potion"; - case ShopItem::SuperPotion: return "Super potion"; - case ShopItem::Potion: return "Potion"; - case ShopItem::Antidote: return "Antidote"; - case ShopItem::ParalyzHeal: return "Paralyz heal"; - case ShopItem::Awakening: return "Awakening"; - case ShopItem::Repel: return "Repel"; - case ShopItem::SuperRepel: return "Super repel"; - case ShopItem::MaxRepel: return "Max repel"; - case ShopItem::EscapeRope: return "Escape rope"; - default: return "Unknown"; - } -} - -// Returns the OCR slug used as the dictionary key in ShopItemNameOCR.json. -static const char* shop_item_slug(ShopItem item){ - switch (item){ - case ShopItem::FullRestore: return "full-restore"; - case ShopItem::MaxPotion: return "max-potion"; - case ShopItem::HyperPotion: return "hyper-potion"; - case ShopItem::SuperPotion: return "super-potion"; - case ShopItem::Potion: return "potion"; - case ShopItem::Antidote: return "antidote"; - case ShopItem::ParalyzHeal: return "paralyze-heal"; - case ShopItem::Awakening: return "awakening"; - case ShopItem::Repel: return "repel"; - case ShopItem::SuperRepel: return "super-repel"; - case ShopItem::MaxRepel: return "max-repel"; - case ShopItem::EscapeRope: return "escape-rope"; - default: return "unknown"; - } -} - -// {MaxRepel, 2, 4}, {FullRestore, 0, 3} → "4 Max repel, 3 Full restore" -static std::string make_buy_label(const std::vector& items){ - std::string label; - for (size_t i = 0; i < items.size(); i++){ - if (i > 0) label += ", "; - if (items[i].quantity == BUY_MAX){ - label += "max "; - } else { - label += std::to_string(items[i].quantity) + " "; - } - label += shop_item_name(items[i].item); - } - return label; -} - -bool buy_items( - VideoStream& stream, - ProControllerContext& context, - const std::vector& items, - Language language -){ - if (items.empty()){ - return true; - } - - const std::string label = make_buy_label(items); - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - context.wait_for_all_requests(); - - - // ── Coordinate constants ───────────────────────────────────────────────── - - // Area where the selection arrow appears at the vendor greeting menu (Buy/Sell/Leave). - static const ImageFloatBox VENDOR_ARROW_BOX{0.630000, 0.550000, 0.070000, 0.120000}; - - // Area where the selection arrow appears at the YES/NO confirmation dialog. - static const ImageFloatBox CONFIRM_ARROW_BOX{0.700000, 0.620000, 0.070000, 0.120000}; - - // Full area containing all item rows in the shop list. - // The hover detector scans this region to dynamically locate the red outline. - static const ImageFloatBox ITEM_SCAN_AREA{0.417000, 0.100000, 0.540000, 0.625000}; - - static constexpr size_t MAX_OUTER = 50; - static constexpr size_t MAX_ITEM_SEARCH = 50; // max iterations per item before giving up - - // ── Outer state machine ────────────────────────────────────────────────── - for (size_t outer = 0; outer < MAX_OUTER; outer++){ - SelectionArrowFinder arrow_vendor(stream.overlay(), VENDOR_ARROW_BOX, COLOR_GREEN); - ShopBuyingWindowWatcher buying_window; - ShortDialogWatcher dialog(COLOR_RED); - - // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. - stream.log("Starting to loop over items"); - int state = run_until( - stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 5; i++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); - } - }, - {{arrow_vendor}, {buying_window}, {dialog}} - ); - - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - - switch (state){ - - case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" - stream.log("Found the selection arrow, pressing A to enter the buying window...", COLOR_GREEN); - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - break; - - case 1: // case 2: buying window open → buy each requested item - stream.log("Found the buying window, searching for the right item...", COLOR_GREEN); - { - bool buying_failed = false; - - for (const ItemToBuy& item : items){ - stream.log("Buying " + std::to_string(item.quantity) + "x " + shop_item_slug(item.item)); - if (buying_failed) break; - - // Detector fires only when the red outline is found AND both - // name and price OCR match the expected item slug. - ShopItemHoverDetector hover(stream.logger(), ITEM_SCAN_AREA, - shop_item_slug(item.item), language, COLOR_CYAN); - ShopQuantityWindowWatcher qty_window; - SelectionArrowFinder confirm_arrow(stream.overlay(), CONFIRM_ARROW_BOX, COLOR_GREEN); - - bool item_bought = false; - bool hover_confirmed = false; - for (size_t inner = 0; inner < MAX_ITEM_SEARCH && !item_bought; inner++){ - context.wait_for_all_requests(); - - int sub = run_until( - stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(1000ms); - }, - {{confirm_arrow}, {qty_window}, {hover}} - ); - - context.wait_for_all_requests(); - - stream.log("buy_items: sub=" + std::to_string(sub), COLOR_WHITE); - - switch (sub){ - - case 0: // YES/NO confirmation arrow → confirm purchase - stream.log("Found the confirmation to buy arrow, pressing A to buy", COLOR_GREEN); - { - context.wait_for_all_requests(); - ShopBuyingWindowWatcher back_in_shop; - run_until( - stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 10; i++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); - } - }, - {{back_in_shop}} - ); - item_bought = true; - break; - } - - case 1: // quantity window - if (!hover_confirmed){ - // Quantity window appeared before we confirmed the right item — - // an earlier A press accidentally opened the wrong item's selector. - // Press B to go back to the list and continue searching. - stream.log("Unexpected quantity window (wrong item), pressing B...", COLOR_YELLOW); - pbf_press_button(context, BUTTON_B, 80ms, 800ms); - break; - } - stream.log("Found the quantity window", COLOR_GREEN); - if (item.quantity == BUY_MAX){ - stream.log("Buying max quantity"); - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } else { - stream.log("Buying " + std::to_string(item.quantity) + " of the item"); - for (int q = 1; q < item.quantity; q++){ - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - } - } - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 1000ms); - break; - - case 2: // correct item highlighted → press A to open quantity selector - stream.log("Found the right item, pressing A to buy a certain quantity...", COLOR_GREEN); - hover_confirmed = true; - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 1000ms); - break; - - default: // outline not found or wrong item → advance to next row - stream.log("Not on the right item, moving...", COLOR_GREEN); - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - pbf_wait(context, 300ms); - break; - } - } - - if (!item_bought){ - stream.log(label + ": buy_items: could not locate " + - shop_item_name(item.item) + " in shop list.", COLOR_RED); - buying_failed = true; - } - } - - if (!buying_failed){ - stream.log("All items bought, exiting shop...", COLOR_GREEN); - pbf_mash_button(context, BUTTON_B, 5000ms); - return true; - } - break; - } - - case 2: // case 3: unexpected ShortDialog → press B until back in buying window - { - ShopBuyingWindowWatcher back_in_shop; - run_until( - stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 5000ms); - }, - {{back_in_shop}} - ); - break; - } - - default: // outer default: unrecognised state → press B to recover - pbf_press_button(context, BUTTON_B, 80ms, 3000ms); - break; - } - } - - stream.log(label + ": buy_items: failed to complete after max iterations.", COLOR_RED); - return false; -} - - -bool heal_pokemon( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -){ - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - - pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 7+ - context.wait_for_all_requests(); - - pbf_mash_button(context, BUTTON_A, 5000ms); - context.wait_for_all_requests(); - pbf_wait(context, 3000ms); - wait_for_dialogue(stream, context, label + " - Pokemon healing"); - mash_until_dialogue_ends(stream, context, BUTTON_B); - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - - // Exiting the Pokemon Center - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); - }, - {{black_screen}} - ); - if (ret < 0){ - stream.log("heal_pokemon(" + label + "): black screen not detected", COLOR_RED); - return false; - } - - return true; -} - -std::string fly_point_name(FlyPoint place) { - switch (place) { - case FlyPoint::CanalavCity: return "canalave_city"; - case FlyPoint::CelesticTown: return "celestic_town"; - case FlyPoint::EternaCity: return "eterna_city"; - case FlyPoint::FloaromaTown: return "floaroma_town"; - case FlyPoint::HearthomeCity: return "hearthome_city"; - case FlyPoint::JubilifeCity: return "jubilife_city"; - case FlyPoint::OreburghCity: return "oreburgh_city"; - case FlyPoint::PastoriaCity: return "pastoria_city"; - case FlyPoint::PokemonLeagueLower: return "pokemon_league_lower"; - case FlyPoint::PokemonLeagueUpper: return "pokemon_league_upper"; - case FlyPoint::Route221: return "route_221"; - case FlyPoint::SandgemTown: return "sandgem_town"; - case FlyPoint::SolaceonTown: return "solaceon_town"; - case FlyPoint::SnowpointCity: return "snowpoint_city"; - case FlyPoint::SunyshoreCity: return "sunyshore_city"; - case FlyPoint::TwinleafTown: return "twinleaf_town"; - case FlyPoint::VeilstoneCity: return "veilstone_city"; - case FlyPoint::CurrentCity: return "current_city"; - } - return "unknown"; -} - -bool fly_to( - VideoStream& stream, - ProControllerContext& context, - FlyPoint place -){ - // Each city branch below should update this to the city icon's position on the fly map. - // Detection: when the cursor brackets overlap the icon, its normally uniform - // color becomes non-uniform (stddev rises). Box stays as a rough fallback - // until each city is calibrated. - ImageFloatBox city_icon_box{0.700000, 0.550000, 0.100000, 0.120000}; - - DpadState dpad; - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - open_menu(stream, context, MenuCursorPosition::MAP, 8); - - if (place == FlyPoint::CurrentCity) { - ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; - struct FlyAButtonDetector : VisualInferenceCallback { - ImageFloatBox m_box; - FlyAButtonDetector(const ImageFloatBox& box) - : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} - void make_overlays(VideoOverlaySet&) const override {} - bool process_frame(const ImageViewRGB32& frame, WallClock) override { - ImageStats s = image_stats(extract_box_reference(frame, m_box)); - return s.stddev.sum() > 30.0; - } - }; - FlyAButtonDetector watcher(button_a_box); - int ret = wait_until(stream, context, 3000ms, {{watcher}}); - if (ret < 0) { - stream.log("CurrentCity: A button not found.", COLOR_RED); - return false; - } - BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int fly_ret = run_until( - stream, context, - [](ProControllerContext& context){ pbf_mash_button(context, BUTTON_A, 200000ms); }, - {{flying_black_screen}} - ); - if (fly_ret < 0) { - stream.log("CurrentCity: black screen not detected!", COLOR_RED); - return false; - } - stream.log("CurrentCity: black screen detected, arrived.", COLOR_GREEN); - return true; - } - - switch (place) { - case FlyPoint::CelesticTown: - pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); - break; - case FlyPoint::HearthomeCity: - case FlyPoint::OreburghCity: - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); - break; - case FlyPoint::PastoriaCity: - case FlyPoint::PokemonLeagueLower: - case FlyPoint::PokemonLeagueUpper: - case FlyPoint::SolaceonTown: - case FlyPoint::SunyshoreCity: - case FlyPoint::VeilstoneCity: - pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); - break; - case FlyPoint::SnowpointCity: - pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); - break; - case FlyPoint::TwinleafTown: - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); - break; - default: - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); - break; - } - context.wait_for_all_requests(); - - ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; - - struct FlyAButtonDetector : VisualInferenceCallback { - ImageFloatBox m_box; - FlyAButtonDetector(const ImageFloatBox& box) - : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} - void make_overlays(VideoOverlaySet&) const override {} - bool process_frame(const ImageViewRGB32& frame, WallClock) override { - ImageStats s = image_stats(extract_box_reference(frame, m_box)); - // Blue stripe is uniform when no button shown; A button disrupts it - return s.stddev.sum() > 30.0; - } - }; - FlyAButtonDetector watcher_1(button_a_box); - - switch (place) { - case FlyPoint::CanalavCity: - break; - case FlyPoint::CelesticTown: - city_icon_box = {0.660000, 0.470000, 0.012000, 0.020000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 15, false); - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 13, false); - break; - case FlyPoint::EternaCity: - break; - case FlyPoint::FloaromaTown: - break; - case FlyPoint::HearthomeCity: - city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); - break; - case FlyPoint::JubilifeCity: - city_icon_box = {0.453000, 0.702000, 0.016000, 0.054000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); - break; - case FlyPoint::OreburghCity: - city_icon_box = {0.566000, 0.702000, 0.012000, 0.054000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 10, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); - break; - case FlyPoint::PastoriaCity: - break; - case FlyPoint::PokemonLeagueLower: - break; - case FlyPoint::PokemonLeagueUpper: - break; - case FlyPoint::Route221: - break; - case FlyPoint::SandgemTown: - break; - case FlyPoint::SolaceonTown: - city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); - break; - case FlyPoint::SnowpointCity: - city_icon_box = {0.602000, 0.070000, 0.013000, 0.054000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 12, false); - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, false); - break; - case FlyPoint::SunyshoreCity: - break; - case FlyPoint::TwinleafTown: - city_icon_box = {0.415000, 0.870000, 0.013000, 0.022000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, false); - break; - case FlyPoint::VeilstoneCity: - city_icon_box = {0.829000, 0.537000, 0.017000, 0.054000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 10, false); - break; - case FlyPoint::CurrentCity: - break; - } - - context.wait_for_all_requests(); - - struct CityIconDetector : VisualInferenceCallback { - ImageFloatBox m_box; - CityIconDetector(const ImageFloatBox& box) - : VisualInferenceCallback("CityIconDetector"), m_box(box) {} - void make_overlays(VideoOverlaySet&) const override {} - bool process_frame(const ImageViewRGB32& frame, WallClock) override { - ImageStats s = image_stats(extract_box_reference(frame, m_box)); - // City icon is normally a uniform color; cursor brackets disrupt it - return s.stddev.sum() > 30.0; - } - }; - CityIconDetector watcher_2(city_icon_box); - - const std::string name = fly_point_name(place); - int ret_1 = wait_until(stream, context, 3000ms, {{watcher_1}}); - int ret_2 = wait_until(stream, context, 4000ms, {{watcher_2}}); - if (ret_1 < 0) { - stream.log("A button not found, retrying...", COLOR_RED); - }; - if (ret_2 < 0) { - stream.log(name + " not found, retrying...", COLOR_RED); - }; - if (ret_1 == 0 and ret_2 == 0){ - stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); - WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{flying_white_screen}, {flying_black_screen}} - ); - if (ret < 0){ - stream.log("Flying to " + name + ": white / black screen not detected!", COLOR_RED); - return false; - } else if (ret >= 0) { - stream.log("Flying to " + name + ": white / black screen detected!", COLOR_GREEN); - return true; - } - } - return false; -} - -// --------------------------------------------------------------------------- -// Arrow - Based Potion Usage Helper - -// This version assumes : -// -// you are already inside the Bag -// Potion has already been selected -// the game is currently waiting for you to choose a Pokemon -// -// Flow : -// 1. Move LEFT once to Pokemon list -// 2. Press A on first Pokemon -// 3. Use SelectionArrow to detect arrow position -// 4. Move DOWN until arrow reaches slot 3 -// 5. Mash A for 3 seconds -// 6. Mash B for 10 seconds -// -// --------------------------------------------------------------------------- - -// Slot 1's colored HP bar in the Bag. Read fill position rather than OCR. -static double potion_hp_fraction(const ImageViewRGB32& frame) { - if (bag_pocket_position(frame) == 0) return -1; - const ImageViewRGB32 bar = extract_box_reference(frame, ImageFloatBox{0.137, 0.160, 0.202, 0.016}); - size_t filled = 0; - for (size_t x = 0; x < bar.width(); x++) { - size_t color = 0; - for (size_t y = 0; y < bar.height(); y++) { - uint32_t p = bar.pixel(x, y); - int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; - const bool green = g > r + 30 && g > b + 20; - const bool yellow = r > 150 && g > 120 && b < 100; - const bool red = r > 150 && r > g + 50 && r > b + 50; - color += green || yellow || red; - } - filled += color > bar.height() / 4; - } - return bar.width() ? (double)filled / bar.width() : -1; -} - -static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& context) { - double previous = -1; - size_t stable = 0; - for (size_t check = 0; check < 20; check++) { - context.wait_for_all_requests(); - auto frame = stream.video().snapshot(); - double hp = frame ? potion_hp_fraction(frame) : -1; - if (hp >= 0 && previous >= 0 && std::abs(hp - previous) <= 0.015) { - if (++stable >= 3) return hp; - } else stable = 0; - previous = hp; - pbf_wait(context, 150ms); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 HP bar could not be confirmed.", stream); -} - -void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context) { - OverlayBoxScope hp_overlay(stream.overlay(), COLOR_GREEN, {0.137, 0.160, 0.202, 0.016}, ""); - BagItemTemplate potion; - potion.reference_path = "PokemonBDSP/Autostory/Bag_Potion.png"; - potion.label = "Potion"; - potion.reference_row = 1; - bool healed = false; - for (size_t dose = 0; dose < 3; dose++) { - stream.log("[Potion] Finding Bag/Medicine/Potion; dose " + std::to_string(dose + 1) + "/3."); - if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Could not verify Potion in the Medicine pocket.", stream); - } - const double before = confirmed_potion_hp(stream, context); - stream.log("[Potion] Slot 1 HP bar before use=" + std::to_string(before * 100) + "%."); - if (before >= 0.98) { healed = true; break; } - if (before <= 0.005) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 appears fainted; a Potion cannot revive it.", stream); - } - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_mash_button(context, BUTTON_B, 2000ms); - context.wait_for_all_requests(); - OverworldWatcher overworld; - if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Did not return to the overworld after use.", stream); - } - // Reopen Bag to verify the result, including when the last Potion was used. - open_menu(stream, context, MenuCursorPosition::BAG, 5); - pbf_wait(context, 750ms); - context.wait_for_all_requests(); - const double after = confirmed_potion_hp(stream, context); - stream.log("[Potion] Slot 1 HP bar after use=" + std::to_string(after * 100) + "%."); - if (after <= before + 0.005) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 HP did not increase; refusing to continue movement.", stream); - } - if (after >= 0.98) { healed = true; break; } - pbf_mash_button(context, BUTTON_B, 2000ms); - context.wait_for_all_requests(); - } - if (!healed) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Full HP not confirmed after three uses.", stream); - pbf_mash_button(context, BUTTON_B, 2000ms); - context.wait_for_all_requests(); - OverworldWatcher overworld; - if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Final overworld return not confirmed.", stream); - stream.log("[Potion] Slot 1 HP bar full and overworld confirmed."); -} - - -namespace{ - -struct IconGrid { int row, col; }; - -IconGrid icon_to_grid(MenuCursorPosition pos, int num_icons){ - switch (pos){ - case MenuCursorPosition::POKEDEX: return {0, 0}; - case MenuCursorPosition::POKEMON: return {0, 1}; - case MenuCursorPosition::BAG: return {0, 2}; - case MenuCursorPosition::CARD: return {0, 3}; - case MenuCursorPosition::OPTIONS: return {1, num_icons <= 5 ? 0 : 2}; - case MenuCursorPosition::MAP: return {1, 0}; - case MenuCursorPosition::CAPSULES: return {1, 1}; - case MenuCursorPosition::MYSTERYGIFT: return {1, 3}; - default: return {-1, -1}; - } -} - -} // anonymous namespace - - -void open_menu( - VideoStream& stream, - ProControllerContext& context, - MenuCursorPosition target, - int num_icons -){ - using namespace std::chrono_literals; - - // Previous single opening attempt: - // context.wait_for_all_requests(); - // overworld_to_menu(stream, context); - // pbf_wait(context, 1000ms); - // context.wait_for_all_requests(); - context.wait_for_all_requests(); - - MenuCursorDetector detector; - { - static const char* ICON_NAMES[MENU_CURSOR_NUM_ICONS] = { - "POKEDEX", "POKEMON", "BAG", "CARD", - "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", - }; - // Previous optional template directory: PokemonBDSP/AutoStory. - std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/Autostory"; - for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ - std::string path = ref_dir + "/" + ICON_NAMES[i] + ".png"; - // These templates are optional. The dark-pixel cursor detector - // remains available when a template is not packaged. - std::error_code file_error; - if (!std::filesystem::is_regular_file(path, file_error)){ - continue; - } - // Previously constructed the image before checking its availability. - ImageRGB32 img(path); - if (img){ - detector.set_icon_reference( - static_cast(i), - std::move(img) - ); - } - } - } - // Previous single detection attempt retained for comparison. - // MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; - // for (int i = 0; i < 10 && cur == MenuCursorPosition::UNKNOWN; i++){ - // context.wait_for(50ms); - // VideoSnapshot snap = stream.video().snapshot(); - // if (snap) cur = detector.detect(snap); - // } - // if (cur == MenuCursorPosition::UNKNOWN){ - // OperationFailedExceptionWithScreenshot::fire( - // ErrorReportMode::SEND_ERROR_REPORT, - // "Menu cursor not detected after opening menu.", - // stream - // ); - // } - - MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; - for (size_t attempt = 1; attempt <= 3; attempt++){ - stream.log("[Open menu] Open and detect attempt " + std::to_string(attempt) + "/3."); - try { - // A late-opening menu may already be visible after a timeout. - // Check before pressing X again, since X would close that menu. - MenuWatcher already_open; - if (wait_until(stream, context, 100ms, {{already_open}}) < 0){ - overworld_to_menu(stream, context); - } - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - for (int frame = 0; frame < 10 && cur == MenuCursorPosition::UNKNOWN; frame++){ - context.wait_for(50ms); - const VideoSnapshot snapshot = stream.video().snapshot(); - if (snapshot){ - cur = detector.detect(snapshot); - } - } - if (cur != MenuCursorPosition::UNKNOWN){ - stream.log("[Open menu] Menu and cursor detected on attempt " + std::to_string(attempt) + ".", COLOR_GREEN); - break; - } - stream.log("[Open menu] Menu cursor not detected on attempt " + std::to_string(attempt) + ".", COLOR_ORANGE); - } - catch (OperationFailedException& error){ - stream.log("[Open menu] Attempt " + std::to_string(attempt) + " failed: " + error.message(), COLOR_ORANGE); - } - } - if (cur == MenuCursorPosition::UNKNOWN){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Menu or cursor detection failed after 3 opening attempts.", - stream - ); - } - - IconGrid cur_g = icon_to_grid(cur, num_icons); - IconGrid tgt_g = icon_to_grid(target, num_icons); - if (tgt_g.row == -1){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Unknown target menu icon.", - stream - ); - } - - if (cur_g.row != tgt_g.row){ - DpadPosition vert = (cur_g.row < tgt_g.row) ? DPAD_DOWN : DPAD_UP; - pbf_press_dpad(context, vert, 80ms, 300ms); - context.wait_for_all_requests(); - } - - int col_diff = tgt_g.col - cur_g.col; - if (col_diff != 0){ - DpadPosition horiz = (col_diff > 0) ? DPAD_RIGHT : DPAD_LEFT; - for (int i = 0; i < std::abs(col_diff); i++){ - pbf_press_dpad(context, horiz, 80ms, 300ms); - context.wait_for_all_requests(); - } - } - - pbf_press_button(context, BUTTON_A, 80ms, 200ms); -} - - -// Search the selected item's fixed description header rather than guessing -// which list row is selected. This remains in place while the list scrolls. -// A cursor parked at row 2 or 6 does not indicate a list end: only repeated -// unchanged item headers after movement are used to detect an end. -// Previous selected-item-only search, retained for reference. -// bool find_bag_item( -// VideoStream& stream, -// ProControllerContext& context, -// const BagItemTemplate& item, -// int num_icons, -// size_t max_scrolls_per_pocket, -// std::chrono::milliseconds search_timeout -// ){ -// auto reference = std::make_shared( -// RESOURCE_PATH() + item.reference_path -// ); -// ImageMatchDetector icon(reference, item.icon_box, item.max_icon_rmsd, true); -// ImageMatchDetector name(reference, item.name_box, item.max_name_rmsd, true); -// ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); -// const ImageFloatBox selected_header(0.428, 0.754, 0.382, 0.046); -// const auto deadline = std::chrono::steady_clock::now() + search_timeout; -// -// stream.log("[Bag search] Looking for " + item.label + "."); -// open_menu(stream, context, MenuCursorPosition::BAG, num_icons); -// pbf_wait(context, 1000ms); -// context.wait_for_all_requests(); -// -// for (size_t pocket = 0; pocket < 8; pocket++){ -// ImageRGB32 initial_header; -// ImageRGB32 previous_header; -// size_t unchanged = 0; -// size_t matching_frames = 0; -// size_t scrolls = 0; -// bool scanning_up = false; -// -// while (std::chrono::steady_clock::now() < deadline){ -// context.wait_for_all_requests(); -// const VideoSnapshot frame = stream.video().snapshot(); -// if (!frame || !bag.detect(frame)){ -// stream.log("[Bag search] Bag screen not detected.", COLOR_ORANGE); -// return false; -// } -// const double icon_score = icon.rmsd(frame); -// const double name_score = item.check_name ? name.rmsd(frame) : 0.0; -// stream.log( -// "[Bag search] Pocket " + std::to_string(pocket + 1) + -// ", scroll " + std::to_string(scrolls) + -// ": icon=" + std::to_string(icon_score) + -// ", name=" + std::to_string(name_score) -// ); -// if (icon_score <= item.max_icon_rmsd && -// name_score <= item.max_name_rmsd){ -// if (++matching_frames >= 3){ -// stream.log("[Bag search] " + item.label + " confirmed.", COLOR_GREEN); -// return true; -// } -// pbf_wait(context, 150ms); -// continue; -// } -// matching_frames = 0; -// -// const ImageRGB32 header = extract_box_reference(frame, selected_header).scale_to(382, 46); -// if (!initial_header){ -// initial_header = header.copy(); -// } -// else if (scrolls > 0){ -// const bool same_as_previous = -// ImageMatch::pixel_RMSD(previous_header, header) <= 3.0; -// const bool returned_to_start = -// ImageMatch::pixel_RMSD(initial_header, header) <= 3.0; -// if (returned_to_start && !same_as_previous){ -// break; -// } -// // Repeated unchanged rows mean an empty pocket or list end. -// // Returning to the initial row means the list wrapped. -// unchanged = same_as_previous || returned_to_start ? unchanged + 1 : 0; -// if (unchanged >= 3){ -// if (scanning_up){ -// break; -// } -// // A non-wrapping list may have opened in the middle. -// // Search upward as well before moving to another pocket. -// scanning_up = true; -// unchanged = 0; -// initial_header = header.copy(); -// } -// } -// previous_header = header.copy(); -// if (scrolls >= max_scrolls_per_pocket){ -// stream.log("[Bag search] Scroll limit reached; search incomplete.", COLOR_ORANGE); -// return false; -// } -// scrolls++; -// pbf_press_dpad(context, scanning_up ? DPAD_UP : DPAD_DOWN, 80ms, 350ms); -// } -// if (std::chrono::steady_clock::now() >= deadline){ -// stream.log("[Bag search] Search timed out; search incomplete.", COLOR_ORANGE); -// return false; -// } -// if (pocket < 7){ -// pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); -// context.wait_for_all_requests(); -// } -// } -// stream.log("[Bag search] Item not found in any pocket.", COLOR_ORANGE); -// return false; -// } -// - -// One narrow strip on the left side of the red selection border. Divide its -// height into the seven row bands; the item's art and NEW badge are outside it. -int bag_cursor_position(const ImageViewRGB32& frame){ - if (!frame){ - return 0; - } - const ImageViewRGB32 strip = extract_box_reference( - frame, ImageFloatBox(0.431, 0.229, 0.005, 0.470) - ); - double best = 0.0; - double second = 0.0; - int position = 0; - for (int row = 0; row < 7; row++){ - const size_t top = static_cast(strip.height() * (row * 0.075 / 0.470)); - const size_t bottom = static_cast(strip.height() * ((row * 0.075 + 0.020) / 0.470)); - size_t red = 0; - size_t total = 0; - for (size_t y = top; y < bottom && y < strip.height(); y++){ - for (size_t x = 0; x < strip.width(); x++){ - const uint32_t pixel = strip.pixel(x, y); - const unsigned r = (pixel >> 16) & 255; - const unsigned g = (pixel >> 8) & 255; - const unsigned b = pixel & 255; - red += r > 180 && g < 120 && b < 120; - total++; - } - } - const double score = total == 0 ? 0.0 : static_cast(red) / total; - if (score > best){ - second = best; - best = score; - position = row + 1; - }else if (score > second){ - second = score; - } - } - return best >= 0.15 && best - second >= 0.10 ? position : 0; -} - -static ImageFloatBox bag_list_icon_box(size_t row){ - return ImageFloatBox(0.449, 0.212 + (row - 1) * 0.075, 0.033, 0.050); -} - -static ImageFloatBox bag_list_name_box(size_t row){ - // Previous height included the faint lower selection border in rows 2/6. - // return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.052); - return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.050); -} - -static double bag_row_patch_score( - const ImageRGB32& reference, - const ImageViewRGB32& frame, - const ImageFloatBox& box -){ - const ImageRGB32 live = extract_box_reference(frame, box).scale_to( - reference.width(), reference.height() - ); - return ImageMatch::pixel_RMSD(reference, live); -} - -// Previous name-area empty-list check retained for reference. -// static bool bag_list_is_empty(const ImageViewRGB32& frame){ -// const ImageViewRGB32 names = extract_box_reference( -// frame, ImageFloatBox(0.492, 0.213, 0.335, 0.500) -// ); -// size_t dark = 0; -// for (size_t y = 0; y < names.height(); y++){ -// for (size_t x = 0; x < names.width(); x++){ -// const uint32_t pixel = names.pixel(x, y); -// dark += ((pixel >> 16) & 255) < 100 && -// ((pixel >> 8) & 255) < 100 && (pixel & 255) < 100; -// } -// } -// return dark < names.width() * names.height() * 0.001; -// } - -int bag_pocket_position(const ImageViewRGB32& frame){ - if (!frame) return 0; - int selected = 0; - for (int pocket = 1; pocket <= 8; pocket++){ - // The lower left edge of each tab is below the red NEW badge. - // This strip includes both the normal outline and enlarged selection. - const auto edge = extract_box_reference(frame, - ImageFloatBox(0.570 + (pocket - 1) * 0.0421, 0.098, 0.0045, 0.021)); - size_t colored = 0; - for (size_t y = 0; y < edge.height(); y++){ - for (size_t x = 0; x < edge.width(); x++){ - const uint32_t pixel = edge.pixel(x, y); - const int r = (pixel >> 16) & 255; - const int g = (pixel >> 8) & 255; - const int b = pixel & 255; - // Red when focused, blue when the item list has focus. - colored += (r > 160 && r > g + 60 && r > b + 60) || - (b > 160 && b > r + 40 && b > g + 20); - } - } - const double pixels = (double)edge.width() * edge.height(); - if (pixels > 0 && colored / pixels >= 0.18){ - if (selected != 0) return 0; - selected = pocket; - } - } - return selected; -} - -bool bag_list_is_empty(const ImageViewRGB32& frame){ - if (!frame || bag_pocket_position(frame) == 0 || bag_cursor_position(frame) != 0) return false; - // Empty pockets have no item icons. The central Empty label is outside - // this column; counting dark pixels in the name area included that label. - const auto icons = extract_box_reference(frame, ImageFloatBox(0.449, 0.213, 0.032, 0.500)); - const auto stats = image_stats(icons); - return stats.average.r > 220 && stats.average.g > 220 && stats.average.b > 220 && - stats.stddev.sum() < 20; -} - - -std::array match_bag_item_rows( - const ImageViewRGB32& frame, - const ImageViewRGB32& reference, - const BagItemTemplate& item -){ - std::array result; - if (!frame || !reference || item.reference_row < 1 || item.reference_row > 7){ - return result; - } - const ImageRGB32 icon = extract_box_reference(reference, bag_list_icon_box(item.reference_row)).copy(); - const ImageRGB32 name = extract_box_reference(reference, bag_list_name_box(item.reference_row)).copy(); - for (size_t row = 1; row <= 7; row++){ - auto& match = result[row - 1]; - match.icon_rmsd = bag_row_patch_score(icon, frame, bag_list_icon_box(row)); - match.name_rmsd = item.check_name - ? bag_row_patch_score(name, frame, bag_list_name_box(row)) : 0.0; - match.matched = match.icon_rmsd <= item.max_icon_rmsd && match.name_rmsd <= item.max_name_rmsd; - } - return result; -} - -BagMovePlan plan_bag_item_move(int cursor_row, int item_row){ - if (cursor_row < 1 || cursor_row > 7 || item_row < 0 || item_row > 7){ - return {}; - } - if (item_row != 0){ - if (item_row == cursor_row){ - return {BagMoveAction::CONFIRM, 0}; - } - return { - item_row > cursor_row ? BagMoveAction::DOWN : BagMoveAction::UP, - static_cast(std::abs(item_row - cursor_row)) - }; - } - if (cursor_row == 7){ - return {BagMoveAction::REWIND, 1}; - } - return {BagMoveAction::PAGE_DOWN, cursor_row == 1 ? 13 : static_cast(13 - cursor_row)}; -} - - -static int bag_confirm_pocket(VideoStream& stream, ProControllerContext& context){ - int previous = 0; - size_t matches = 0; - for (size_t check = 0; check < 10; check++){ - context.wait_for_all_requests(); - const auto frame = stream.video().snapshot(); - const int pocket = frame ? bag_pocket_position(frame) : 0; - matches = pocket != 0 && pocket == previous ? matches + 1 : pocket != 0 ? 1 : 0; - previous = pocket; - if (matches >= 3) return pocket; - pbf_wait(context, 100ms); - } - return 0; -} - -// static bool bag_advance_pocket(VideoStream& stream, ProControllerContext& context, int current){ -// const int expected = current % 8 + 1; -// for (size_t attempt = 0; attempt < 3; attempt++){ -// // Recheck before retrying: a delayed change must not cause a second step. -// const int observed = bag_confirm_pocket(stream, context); -// if (observed == expected) return true; -// if (observed != current){ -// stream.log("[Bag search] Pocket selection uncertain; stopping.", COLOR_ORANGE); -// return false; -// } -// pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); -// const int result = bag_confirm_pocket(stream, context); -// stream.log("[Bag search] Pocket change: " + std::to_string(current) + " -> " + -// std::to_string(result) + ", expected " + std::to_string(expected) + "."); -// if (result == expected) return true; -// } -// stream.log("[Bag search] Could not confirm pocket change after 3 attempts.", COLOR_ORANGE); -// return false; -// } - -BagPocketMovePlan plan_bag_pocket_move(int current, int target){ - if (current < 1 || current > 8 || target < 1 || target > 8) return {}; - const int right = (target - current + 8) % 8; - const int left = (current - target + 8) % 8; - if (right == 0) return {BagPocketAction::CONFIRM, 0}; - return right <= left ? BagPocketMovePlan{BagPocketAction::RIGHT, static_cast(right)} - : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; -} - -static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& context, int target){ - int current = bag_confirm_pocket(stream, context); - for (size_t attempt = 0; attempt < 3; attempt++){ - const auto plan = plan_bag_pocket_move(current, target); - if (plan.action == BagPocketAction::CONFIRM) return true; - if (plan.action == BagPocketAction::UNKNOWN){ - stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); - return false; - } - stream.log("[Bag search] Pocket " + std::to_string(current) + " to " + - std::to_string(target) + ": " + - (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + - std::to_string(plan.presses) + " presses, attempt " + std::to_string(attempt + 1) + "/3."); - for (size_t press = 0; press < plan.presses; press++){ - pbf_press_dpad(context, plan.action == BagPocketAction::RIGHT ? DPAD_RIGHT : DPAD_LEFT, - 80ms, 150ms); - } - pbf_wait(context, 350ms); - current = bag_confirm_pocket(stream, context); - stream.log("[Bag search] Final pocket=" + std::to_string(current) + - ", expected=" + std::to_string(target) + "."); - if (current == target) return true; - } - stream.log("[Bag search] Target pocket not reached after 3 movement batches.", COLOR_ORANGE); - return false; -} - - -bool find_bag_item( - VideoStream& stream, - ProControllerContext& context, - const BagItemTemplate& item, - int target_pocket, - int num_icons, - size_t max_scrolls_per_pocket, - std::chrono::milliseconds search_timeout -){ - if (target_pocket < 1 || target_pocket > 8 || item.reference_row < 1 || item.reference_row > 7){ - stream.log("[Bag search] Invalid template reference row.", COLOR_ORANGE); - return false; - } - auto reference = std::make_shared(RESOURCE_PATH() + item.reference_path); -// const ImageRGB32 reference_icon = extract_box_reference( -// *reference, bag_list_icon_box(item.reference_row) -// ).copy(); -// const ImageRGB32 reference_name = extract_box_reference( -// *reference, bag_list_name_box(item.reference_row) -// ).copy(); - ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); - ImageMatchDetector selected_icon(reference, item.icon_box, item.max_icon_rmsd, true); - ImageMatchDetector selected_name(reference, item.name_box, item.max_name_rmsd, true); - const auto deadline = std::chrono::steady_clock::now() + search_timeout; - - open_menu(stream, context, MenuCursorPosition::BAG, num_icons); - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - - if (!bag_navigate_to_pocket(stream, context, target_pocket)) return false; - // Previously searched all eight pockets. Only inspect the requested pocket now. - // for (size_t pocket = 0; pocket < 8; pocket++){ - { - const int selected_pocket = bag_confirm_pocket(stream, context); - if (selected_pocket == 0){ - stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); - return false; - } - size_t commands = 0; - bool empty = false; - auto move = [&](DpadPosition direction, size_t count, bool check_rows = false){ - for (size_t press = 0; press < count; press++){ - if (commands >= max_scrolls_per_pocket || - std::chrono::steady_clock::now() >= deadline){ - stream.log("[Bag search] Limit reached; search incomplete.", COLOR_ORANGE); - return false; - } - commands++; - pbf_press_dpad(context, direction, 80ms, 150ms); - if (check_rows){ - context.wait_for_all_requests(); - const VideoSnapshot page_frame = stream.video().snapshot(); - if (!page_frame || !bag.detect(page_frame)){ - return false; - } - const auto matches = match_bag_item_rows(page_frame, *reference, item); - for (const auto& match : matches){ - if (match.matched){ - // Stop paging; the main loop verifies and selects the row. - return true; - } - } - } - } - pbf_wait(context, 250ms); - context.wait_for_all_requests(); - return true; - }; - for (size_t pass = 0; pass < 3 && !empty; pass++){ - size_t matching_frames = 0; - size_t missing_cursor = 0; - size_t failed_confirmation = 0; - size_t unchanged_pages = 0; - ImageRGB32 previous_page; - stream.log( - "[Bag search] Looking for " + item.label + ": pocket " + - std::to_string(selected_pocket) + ", pass " + std::to_string(pass + 1) + "/3." - ); - while (std::chrono::steady_clock::now() < deadline){ - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame || !bag.detect(frame)){ - stream.log("[Bag search] Bag screen not detected.", COLOR_ORANGE); - return false; - } - const int cursor = bag_cursor_position(frame); - int target = 0; - double best = 1000.0; - bool ambiguous = false; - // The seven list ROIs move neither with the cursor nor with - // scrolling. Each contains an icon patch and a name patch. - // Previous per-row loop retained for reference. - // for (size_t row = 1; row <= 7; row++){ - // const double icon = bag_row_patch_score(reference_icon, frame, bag_list_icon_box(row)); - // const double name = item.check_name - // ? bag_row_patch_score(reference_name, frame, bag_list_name_box(row)) : 0.0; - // if (icon <= item.max_icon_rmsd && name <= item.max_name_rmsd){ - // if (target != 0){ - // ambiguous = true; - // } - // if (icon + name < best){ - // target = static_cast(row); - // best = icon + name; - // } - // } - // } - const auto matches = match_bag_item_rows(frame, *reference, item); - for (size_t row = 1; row <= 7; row++){ - const auto& match = matches[row - 1]; - if (match.matched){ - if (target != 0){ - ambiguous = true; - } - if (match.icon_rmsd + match.name_rmsd < best){ - target = static_cast(row); - best = match.icon_rmsd + match.name_rmsd; - } - } - } - stream.log( - "[Bag search] " + item.label + (target == 0 ? " is not detected" : " is detected") + - " on screen; item row=" + std::to_string(target) + - ", cursor row=" + std::to_string(cursor) + "." - ); - if (ambiguous){ - stream.log("[Bag search] Multiple rows match; selection is uncertain.", COLOR_ORANGE); - return false; - } - if (cursor == 0){ - if (bag_list_is_empty(frame)){ - empty = true; - break; - } - if (++missing_cursor >= 3){ - stream.log("[Bag search] Cursor location uncertain.", COLOR_ORANGE); - return false; - } - pbf_wait(context, 200ms); - context.wait_for_all_requests(); - continue; - } - missing_cursor = 0; - if (target != 0){ - previous_page = ImageRGB32(); - unchanged_pages = 0; - if (target == cursor && selected_icon.detect(frame) && - (!item.check_name || selected_name.detect(frame))){ - if (++matching_frames >= 3){ - stream.log("[Bag search] Item and cursor confirmed in row " + - std::to_string(cursor) + ".", COLOR_GREEN); - return true; - } - pbf_wait(context, 150ms); - context.wait_for_all_requests(); - continue; - } - matching_frames = 0; - if (target == cursor){ - if (++failed_confirmation < 3){ - pbf_wait(context, 150ms); - context.wait_for_all_requests(); - continue; - } - stream.log("[Bag search] Selected-item confirmation failed.", COLOR_ORANGE); - return false; - } - failed_confirmation = 0; - const size_t count = plan_bag_item_move(cursor, target).presses; - stream.log("[Bag search] Moving " + std::string(target > cursor ? "down " : "up ") + - std::to_string(count) + " time(s), then checking again."); - if (!move(target > cursor ? DPAD_DOWN : DPAD_UP, count)){ - return false; - } - continue; - } - matching_frames = 0; - const ImageRGB32 page = extract_box_reference( - frame, ImageFloatBox(0.449, 0.212, 0.378, 0.500) - ).scale_to(378, 500); - if (previous_page && ImageMatch::pixel_RMSD(previous_page, page) <= 3.0){ - unchanged_pages++; - }else{ - unchanged_pages = 0; - } - previous_page = page.copy(); - // Row 7 signals the bottom of a long list. Repeated pages - // also cover shorter lists whose final row is less than 7. - if (cursor == 7 || unchanged_pages >= 2){ - stream.log("[Bag search] List end; returning to row 1."); - while (true){ - const VideoSnapshot rewind_frame = stream.video().snapshot(); - if (!rewind_frame || !bag.detect(rewind_frame)){ - return false; - } - const int rewind_cursor = bag_cursor_position(rewind_frame); - if (rewind_cursor == 1){ - break; - } - if (rewind_cursor == 0 || !move(DPAD_UP, 1)){ - return false; - } - } - break; - } - const size_t count = plan_bag_item_move(cursor, 0).presses; - stream.log("[Bag search] Advancing " + std::to_string(count) + " presses to another page."); - if (!move(DPAD_DOWN, count, true)){ - return false; - } - } - if (std::chrono::steady_clock::now() >= deadline){ - stream.log("[Bag search] Timeout; search incomplete.", COLOR_ORANGE); - return false; - } - } -// if (pocket < 7){ -// // pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); -// // context.wait_for_all_requests(); -// if (!bag_advance_pocket(stream, context, selected_pocket)) return false; -// } - - } - stream.log("[Bag search] Item not found after three passes per nonempty pocket.", COLOR_ORANGE); - return false; -} - -bool use_escape_rope( - VideoStream& stream, - ProControllerContext& context, - int num_icons -){ - const BagItemTemplate rope{ - // "PokemonBDSP/AutoStory/Bag_EscapeRope.png", "escape-rope" - "PokemonBDSP/Autostory/Bag_EscapeRope.png", "escape-rope" - }; - if (!find_bag_item(stream, context, rope, 5, num_icons)){ - return false; - } - - pbf_press_button(context, BUTTON_A, 80ms, 350ms); - context.wait_for_all_requests(); - SelectionArrowFinder use_menu( - stream.overlay(), ImageFloatBox(0.660, 0.100, 0.310, 0.430), COLOR_GREEN - ); - if (wait_until(stream, context, 3s, {{use_menu}}) != 0){ - stream.log("[Escape Rope] Item action menu not detected.", COLOR_ORANGE); - return false; - } - - // Watch while selecting Use so the transition cannot happen before - // the watcher starts. Extra A presses handle a confirmation prompt. - BlackScreenWatcher transition; - // Escape Rope fades white in the supplied clips; support black transitions too. - WhiteScreenWatcher white_transition; - const int result = run_until( - stream, context, - [](ProControllerContext& ctx){ - for (size_t press = 0; press < 10; press++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); - } - ctx.wait_for_all_requests(); - pbf_wait(ctx, 10s); - ctx.wait_for_all_requests(); - }, - {transition, white_transition} - ); - if (result < 0){ - stream.log("[Escape Rope] No exit transition detected.", COLOR_ORANGE); - return false; - } - BlackScreenWatcher finished( - COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms - ); - WhiteScreenWatcher white_finished( - COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, - WhiteScreenWatcher::FinderType::GONE, 500ms - ); - stream.log(std::string("[Escape Rope] ") + - (result == 1 ? "White" : "Black") + " exit transition detected."); - // if (wait_until(stream, context, 15s, {{finished}}) != 0){ return false; } - const int finish_result = result == 1 - ? wait_until(stream, context, 15s, {{white_finished}}) - : wait_until(stream, context, 15s, {{finished}}); - if (finish_result != 0){ - stream.log("[Escape Rope] Exit transition did not finish.", COLOR_ORANGE); - return false; - } - OverworldWatcher overworld; - if (wait_until(stream, context, 10s, {{overworld}}) != 0){ - stream.log("[Escape Rope] Overworld not detected after transition.", COLOR_ORANGE); - return false; - } - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - stream.log("[Escape Rope] Exit transition and overworld confirmed.", COLOR_GREEN); - return true; -} - - -bool activate_repel( - VideoStream& stream, - ProControllerContext& context, - int num_icons -){ - stream.log("Activating repel"); - open_menu(stream, context, MenuCursorPosition::BAG, num_icons); - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - - auto repel_tab_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTab.png" - ); - // A smaller reference covers capture-resize sampling differences without - // loosening the match threshold or changing shared image processing. - auto repel_tab_small_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabSmall.png" - ); - const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; - const double repel_tab_rmsd = 50.0; - - // BDSP bag has 8 compartment tabs; check before each press so zero-press - // detection works when the bag already starts on the repel tab. - bool found = false; - for (int press = 0; press <= 8; press++){ - context.wait_for_all_requests(); - - ImageMatchWatcher watcher(repel_tab_ref, repel_tab_box, repel_tab_rmsd); - ImageMatchWatcher small_watcher(repel_tab_small_ref, repel_tab_box, repel_tab_rmsd); - int ret = wait_until(stream, context, 400ms, {{watcher}, {small_watcher}}); - if (ret >= 0){ - stream.log("[AutoStory] activate_repel: repel tab found after " - + std::to_string(press) + " press(es).", COLOR_GREEN); - found = true; - break; - } - - if (press < 8){ - stream.log("[AutoStory] activate_repel: tab not found, pressing right (" - + std::to_string(press + 1) + "/8)."); - pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); - } - } - - if (!found){ - stream.log("[AutoStory] activate_repel: failed to find repel compartment tab.", COLOR_RED); - return false; - } - - context.wait_for_all_requests(); - pbf_wait(context, 400ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - context.wait_for_all_requests(); - - const ImageFloatBox repel_item_box{0.720000, 0.100000, 0.070000, 0.150000}; - SelectionArrowFinder arrow_finder(stream.overlay(), repel_item_box, COLOR_GREEN); - int arrow_ret = wait_until(stream, context, 2000ms, {{arrow_finder}}); - if (arrow_ret != 0){ - stream.log("[AutoStory] activate_repel: selection arrow not found after pressing A.", COLOR_RED); - return false; - } - stream.log("[AutoStory] activate_repel: selection arrow confirmed.", COLOR_GREEN); - pbf_press_button(context, BUTTON_A, 80ms, 105ms); - pbf_mash_button(context, BUTTON_B, 2000ms); - return true; -} - - -void use_HM( - VideoStream& stream, - ProControllerContext& context, - const std::string& label - -){ - stream.log("Using " + label); - const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; - SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 30000ms); - }, - {{arrow}} - ); - if (ret != 0){ - stream.log("Using " + label + ": selection arrow not found.", COLOR_RED); - return; - } - - stream.log("Using " + label + ": selection arrow found, confirming.", COLOR_GREEN); - pbf_mash_button(context, BUTTON_A, 1000ms); - pbf_mash_button(context, BUTTON_B, 6000ms); -} - - -bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLevel /*gear*/){ - static const ImageFloatBox FRONT_BOX{0.440000, 0.470000, 0.035000, 0.054000}; - static const ImageFloatBox BACK_BOX {0.520000, 0.470000, 0.022000, 0.054000}; - // Spark color range: white (R=232,G=255,B=255) down to light green (R=163,G=217,B=181) - static const uint32_t SPARK_MIN = 0xFF000000u | (163u << 16) | (217u << 8) | 181u; - static const uint32_t SPARK_MAX = 0xFFFFFFFFu; - static const size_t SPARK_THRESHOLD = 13; - - stream.log("[AutoStory] get_on_bicycle: mounting bicycle.", COLOR_WHITE); - pbf_press_button(context, BUTTON_PLUS, 80ms, 800ms); - - // Press B, then poll up to 1 s for Fast-gear sparks. - // Logs the peak pixel counts in both boxes for threshold tuning. - // Returns true if sparks are detected (switched TO Fast gear). - auto toggle_and_check = [&]() -> bool { - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_B, 80ms, 800ms); - - size_t max_front = 0, max_back = 0; - auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000); - while (std::chrono::steady_clock::now() < deadline){ - context.wait_for(50ms); - VideoSnapshot screen = stream.video().snapshot(); - if (!screen) continue; - - size_t front_pixels = 0, back_pixels = 0; - filter_rgb32_range(front_pixels, extract_box_reference(screen, FRONT_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); - filter_rgb32_range(back_pixels, extract_box_reference(screen, BACK_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); - - if (front_pixels > max_front) max_front = front_pixels; - if (back_pixels > max_back) max_back = back_pixels; - - if (front_pixels >= SPARK_THRESHOLD || back_pixels >= SPARK_THRESHOLD){ - stream.log( - "[AutoStory] get_on_bicycle: sparks detected " - "(front=" + std::to_string(front_pixels) + - " back=" + std::to_string(back_pixels) + - " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", - COLOR_GREEN - ); - return true; - } - } - stream.log( - "[AutoStory] get_on_bicycle: no sparks detected " - "(peak front=" + std::to_string(max_front) + - " back=" + std::to_string(max_back) + - " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", - COLOR_ORANGE - ); - return false; - }; - - if (toggle_and_check()){ - stream.log("[AutoStory] get_on_bicycle: Fast gear confirmed.", COLOR_GREEN); - return true; - } - - // First toggle produced no sparks -> we switched to Slow gear; toggle back to Fast. - stream.log("[AutoStory] get_on_bicycle: Slow gear detected, toggling back to Fast.", COLOR_ORANGE); - if (toggle_and_check()){ - stream.log("[AutoStory] get_on_bicycle: Fast gear confirmed after correction.", COLOR_GREEN); - return true; - } - - stream.log("[AutoStory] get_on_bicycle: Failed to confirm Fast gear after two toggles.", COLOR_RED); - return false; -} - - -} -} +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include +/* BDSP AutoStory Tools + * + * From: https://github.com/PokemonAutomation/ + * + */ +#include +#include "CommonFramework/ImageTools/ImageDiff.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include +#include "CommonTools/Images/ImageFilter.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" +#include "Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "PokemonBDSP_AutoStoryTools.h" + +#include +#include + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +const char* torterra_move_slug(TorterraMove move){ + switch (move){ + case TorterraMove::EARTHQUAKE: return "earthquake"; + case TorterraMove::WOOD_HAMMER: return "wood-hammer"; + case TorterraMove::RAZOR_LEAF: return "razor-leaf"; + case TorterraMove::CRUNCH: return "crunch"; + case TorterraMove::GIGA_DRAIN: return "giga-drain"; + case TorterraMove::IRON_TAIL: return "iron-tail"; + case TorterraMove::ROCK_TOMB: return "rock-tomb"; + case TorterraMove::FACADE: return "facade"; + case TorterraMove::STONE_EDGE: return "stone-edge"; + case TorterraMove::ROCK_SLIDE: return "rock-slide"; + case TorterraMove::BULLDOZE: return "bulldoze"; + case TorterraMove::STRENGTH: return "strength"; + case TorterraMove::ROCK_SMASH: return "rock-smash"; + case TorterraMove::ROCK_CLIMB: return "rock-climb"; + } + return "unknown"; +} + +const char* trainer_slug(Trainer t){ + switch (t){ + case Trainer::Veilstone_GalacticGrunts_2v2: return "Veilstone_GalacticGrunts_2v2"; + } + return "unknown"; +} + +bool fake_save_game( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + pbf_mash_button(context, BUTTON_L, 1000ms); + + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + pbf_press_button(context, BUTTON_R, 80ms, 1000ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + + return true; +} + +bool save_game( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +) { + static constexpr size_t MAX_RETRIES = 3; + + for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++) { + try { + env.console.log( + "Checkpoint save attempt: " + std::to_string(attempt + 1) + ); + + // Only mash B on retries - no need to on first attempt + // if already cleanly in the overworld. + if (attempt > 0) { + pbf_mash_button(context, BUTTON_B, 1000ms); + } + + // Reuse existing feedback-based save: opens menu with MenuWatcher + // + respects GameSettings::OVERWORLD_TO_MENU_DELAY0 internally. + save_game(env.console, context); + + // Return to overworld cleanly. + pbf_mash_button(context, BUTTON_B, 1000ms); + + // Drain R/confirm/B before detection; an earlier overworld frame + // must not report success while saving commands are still queued. + context.wait_for_all_requests(); + + // Verify overworld. Generous timeout: save animation + fade can be slow. + OverworldWatcher overworld; + int ret = wait_until( + env.console, + context, + std::chrono::seconds(15), + { overworld } + ); + if (ret < 0) { + throw OperationFailedExceptionWithScreenshot( + ErrorReportMode::SEND_ERROR_REPORT, + "Failed to detect overworld after save.", + env.console + ); + } + + env.console.log("Checkpoint save verified."); + return true; + + } + catch (OperationFailedException& e) { + env.console.log( + "Checkpoint save failed: " + e.message(), + COLOR_RED + ); + context.wait_for(1000ms); + } + } + + // Blind fallback: reuse the non-feedback save_game(context) from GameNavigation + // rather than duplicating the button sequence manually. + env.console.log( + "All save retries failed. Using blind fallback.", + COLOR_ORANGE + ); + pbf_mash_button(context, BUTTON_B, 1000ms); + save_game(context); // PokemonBDSP_GameNavigation.h - non-feedback overload + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + + return false; +} + + +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + CheckpointSaveMode save_mode +){ + const bool fake = save_mode == CheckpointSaveMode::FAKE; + env.console.log(fake ? "[Checkpoint save] DIAGNOSTIC FAKE mode: save is not verified." + : "[Checkpoint save] REAL mode."); + bool success = fake ? fake_save_game(env, context) : save_game(env, context); + + stats.m_checkpoint++; + env.update_stats(); + + send_program_status_notification( + env, + notif_status_update, + fake ? "Diagnostic fake checkpoint save (not verified)." : success + ? "Saved at checkpoint (verified)." + : "Saved at checkpoint (blind fallback)." + ); +} + + +void reload_backup_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +){ + // TODO: implement backup save reload. + // This is called when a checkpoint with save=false fails. + // Load the game's automatic backup save rather than resetting from HOME. + (void)env; + (void)context; +} + + +void checkpoint_reattempt_loop( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + std::function&& action, + bool save, + CheckpointSaveMode save_mode, + CheckpointRetryScope retry_scope +){ + static constexpr size_t MAX_ATTEMPTS = 20; + + for (size_t i = 0;; i++){ + try{ + if (i == 0 && retry_scope != CheckpointRetryScope::SELECTED_RANGE){ + if (save || stats.m_first_run){ + checkpoint_save(env, context, notif_status_update, stats, save_mode); + } + stats.m_first_run = false; + } + + action(i); + return; // success + + }catch (OperationFailedException& e){ + if (retry_scope == CheckpointRetryScope::DEFER_TO_SELECTED_RANGE) throw; + env.console.log("checkpoint_reattempt_loop: attempt " + std::to_string(i) + " failed: " + e.message(), COLOR_RED); + stats.m_reset++; + env.update_stats(); + + if (i + 1 >= MAX_ATTEMPTS){ + throw; + } + + if (save || retry_scope == CheckpointRetryScope::SELECTED_RANGE){ + env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); + bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); + if (!ok){ + throw OperationFailedExceptionWithScreenshot( + ErrorReportMode::SEND_ERROR_REPORT, + "Failed to reset game from HOME during checkpoint retry.", + env.console + ); + } + } else { + env.console.log("Reloading from backup save before retry...", COLOR_ORANGE); + reload_backup_save(env, context); + } + } + } +} + + + +void repeat(std::initializer_list> actions, size_t times){ + for (size_t i = 0; i < times; i++){ + for (const auto& action : actions){ + action(); + } + } +} + + +void wait_for_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label, + Milliseconds timeout +){ + auto log_detected = [&]{ + if (!label.empty()){ + stream.log(label + " dialogue detected."); + } + }; + + ShortDialogWatcher watcher_cyan(COLOR_CYAN); + int ret = run_until( + stream, context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_cyan}} + ); + if (ret == 0){ log_detected(); return; } + + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until( + stream, context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_red}} + ); + if (ret == 0){ log_detected(); return; } + + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", + stream + ); +} + + +void mash_until_dialogue_ends( + VideoStream& stream, + ProControllerContext& context, + Button button, + Milliseconds timeout +){ + OverworldWatcher overworld; + int ret = run_until( + stream, context, + [button, timeout](ProControllerContext& context){ + pbf_mash_button(context, button, timeout); + }, + {{overworld}} + ); + if (ret < 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "mash_until_dialogue_ends: overworld not reached within timeout.", + stream + ); + } +} + + +void repeat_dpad( + ProControllerContext& context, + DpadState& state, + DpadPosition dir, + Milliseconds press, + Milliseconds hold, + size_t times, + bool include_last_dir +){ + if (include_last_dir && state.last_dir != DPAD_NONE && state.last_dir != dir){ + times += 1; + } + state.last_dir = dir; + for (size_t i = 0; i < times; i++){ + pbf_press_dpad(context, dir, press, hold); + } +} + +static const char* shop_item_name(ShopItem item){ + switch (item){ + case ShopItem::FullRestore: return "Full restore"; + case ShopItem::MaxPotion: return "Max potion"; + case ShopItem::HyperPotion: return "Hyper potion"; + case ShopItem::SuperPotion: return "Super potion"; + case ShopItem::Potion: return "Potion"; + case ShopItem::Antidote: return "Antidote"; + case ShopItem::ParalyzHeal: return "Paralyz heal"; + case ShopItem::Awakening: return "Awakening"; + case ShopItem::Repel: return "Repel"; + case ShopItem::SuperRepel: return "Super repel"; + case ShopItem::MaxRepel: return "Max repel"; + case ShopItem::EscapeRope: return "Escape rope"; + default: return "Unknown"; + } +} + +// Returns the OCR slug used as the dictionary key in ShopItemNameOCR.json. +static const char* shop_item_slug(ShopItem item){ + switch (item){ + case ShopItem::FullRestore: return "full-restore"; + case ShopItem::MaxPotion: return "max-potion"; + case ShopItem::HyperPotion: return "hyper-potion"; + case ShopItem::SuperPotion: return "super-potion"; + case ShopItem::Potion: return "potion"; + case ShopItem::Antidote: return "antidote"; + case ShopItem::ParalyzHeal: return "paralyze-heal"; + case ShopItem::Awakening: return "awakening"; + case ShopItem::Repel: return "repel"; + case ShopItem::SuperRepel: return "super-repel"; + case ShopItem::MaxRepel: return "max-repel"; + case ShopItem::EscapeRope: return "escape-rope"; + default: return "unknown"; + } +} + +// {MaxRepel, 2, 4}, {FullRestore, 0, 3} → "4 Max repel, 3 Full restore" +static std::string make_buy_label(const std::vector& items){ + std::string label; + for (size_t i = 0; i < items.size(); i++){ + if (i > 0) label += ", "; + if (items[i].quantity == BUY_MAX){ + label += "max "; + } else { + label += std::to_string(items[i].quantity) + " "; + } + label += shop_item_name(items[i].item); + } + return label; +} + +bool buy_items( + VideoStream& stream, + ProControllerContext& context, + const std::vector& items, + Language language +){ + if (items.empty()){ + return true; + } + + const std::string label = make_buy_label(items); + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + context.wait_for_all_requests(); + + + // ── Coordinate constants ───────────────────────────────────────────────── + + // Area where the selection arrow appears at the vendor greeting menu (Buy/Sell/Leave). + static const ImageFloatBox VENDOR_ARROW_BOX{0.630000, 0.550000, 0.070000, 0.120000}; + + // Area where the selection arrow appears at the YES/NO confirmation dialog. + static const ImageFloatBox CONFIRM_ARROW_BOX{0.700000, 0.620000, 0.070000, 0.120000}; + + // Full area containing all item rows in the shop list. + // The hover detector scans this region to dynamically locate the red outline. + static const ImageFloatBox ITEM_SCAN_AREA{0.417000, 0.100000, 0.540000, 0.625000}; + + static constexpr size_t MAX_OUTER = 50; + static constexpr size_t MAX_ITEM_SEARCH = 50; // max iterations per item before giving up + + // ── Outer state machine ────────────────────────────────────────────────── + for (size_t outer = 0; outer < MAX_OUTER; outer++){ + SelectionArrowFinder arrow_vendor(stream.overlay(), VENDOR_ARROW_BOX, COLOR_GREEN); + ShopBuyingWindowWatcher buying_window; + ShortDialogWatcher dialog(COLOR_RED); + + // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. + stream.log("Starting to loop over items"); + int state = run_until( + stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 5; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + } + }, + {{arrow_vendor}, {buying_window}, {dialog}} + ); + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + + switch (state){ + + case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" + stream.log("Found the selection arrow, pressing A to enter the buying window...", COLOR_GREEN); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + break; + + case 1: // case 2: buying window open → buy each requested item + stream.log("Found the buying window, searching for the right item...", COLOR_GREEN); + { + bool buying_failed = false; + + for (const ItemToBuy& item : items){ + stream.log("Buying " + std::to_string(item.quantity) + "x " + shop_item_slug(item.item)); + if (buying_failed) break; + + // Detector fires only when the red outline is found AND both + // name and price OCR match the expected item slug. + ShopItemHoverDetector hover(stream.logger(), ITEM_SCAN_AREA, + shop_item_slug(item.item), language, COLOR_CYAN); + ShopQuantityWindowWatcher qty_window; + SelectionArrowFinder confirm_arrow(stream.overlay(), CONFIRM_ARROW_BOX, COLOR_GREEN); + + bool item_bought = false; + bool hover_confirmed = false; + for (size_t inner = 0; inner < MAX_ITEM_SEARCH && !item_bought; inner++){ + context.wait_for_all_requests(); + + int sub = run_until( + stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(1000ms); + }, + {{confirm_arrow}, {qty_window}, {hover}} + ); + + context.wait_for_all_requests(); + + stream.log("buy_items: sub=" + std::to_string(sub), COLOR_WHITE); + + switch (sub){ + + case 0: // YES/NO confirmation arrow → confirm purchase + stream.log("Found the confirmation to buy arrow, pressing A to buy", COLOR_GREEN); + { + context.wait_for_all_requests(); + ShopBuyingWindowWatcher back_in_shop; + run_until( + stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 10; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); + } + }, + {{back_in_shop}} + ); + item_bought = true; + break; + } + + case 1: // quantity window + if (!hover_confirmed){ + // Quantity window appeared before we confirmed the right item — + // an earlier A press accidentally opened the wrong item's selector. + // Press B to go back to the list and continue searching. + stream.log("Unexpected quantity window (wrong item), pressing B...", COLOR_YELLOW); + pbf_press_button(context, BUTTON_B, 80ms, 800ms); + break; + } + stream.log("Found the quantity window", COLOR_GREEN); + if (item.quantity == BUY_MAX){ + stream.log("Buying max quantity"); + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } else { + stream.log("Buying " + std::to_string(item.quantity) + " of the item"); + for (int q = 1; q < item.quantity; q++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } + } + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 1000ms); + break; + + case 2: // correct item highlighted → press A to open quantity selector + stream.log("Found the right item, pressing A to buy a certain quantity...", COLOR_GREEN); + hover_confirmed = true; + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 1000ms); + break; + + default: // outline not found or wrong item → advance to next row + stream.log("Not on the right item, moving...", COLOR_GREEN); + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + pbf_wait(context, 300ms); + break; + } + } + + if (!item_bought){ + stream.log(label + ": buy_items: could not locate " + + shop_item_name(item.item) + " in shop list.", COLOR_RED); + buying_failed = true; + } + } + + if (!buying_failed){ + stream.log("All items bought, exiting shop...", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 5000ms); + return true; + } + break; + } + + case 2: // case 3: unexpected ShortDialog → press B until back in buying window + { + ShopBuyingWindowWatcher back_in_shop; + run_until( + stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 5000ms); + }, + {{back_in_shop}} + ); + break; + } + + default: // outer default: unrecognised state → press B to recover + pbf_press_button(context, BUTTON_B, 80ms, 3000ms); + break; + } + } + + stream.log(label + ": buy_items: failed to complete after max iterations.", COLOR_RED); + return false; +} + + +bool heal_pokemon( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +){ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + + pbf_move_left_joystick(context, {0, +1}, 1800ms, 100ms); // 7+ + context.wait_for_all_requests(); + + pbf_mash_button(context, BUTTON_A, 5000ms); + context.wait_for_all_requests(); + pbf_wait(context, 3000ms); + wait_for_dialogue(stream, context, label + " - Pokemon healing"); + mash_until_dialogue_ends(stream, context, BUTTON_B); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + // Exiting the Pokemon Center + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); + }, + {{black_screen}} + ); + if (ret < 0){ + stream.log("heal_pokemon(" + label + "): black screen not detected", COLOR_RED); + return false; + } + + return true; +} + +std::string fly_point_name(FlyPoint place) { + switch (place) { + case FlyPoint::CanalavCity: return "canalave_city"; + case FlyPoint::CelesticTown: return "celestic_town"; + case FlyPoint::EternaCity: return "eterna_city"; + case FlyPoint::FloaromaTown: return "floaroma_town"; + case FlyPoint::HearthomeCity: return "hearthome_city"; + case FlyPoint::JubilifeCity: return "jubilife_city"; + case FlyPoint::OreburghCity: return "oreburgh_city"; + case FlyPoint::PastoriaCity: return "pastoria_city"; + case FlyPoint::PokemonLeagueLower: return "pokemon_league_lower"; + case FlyPoint::PokemonLeagueUpper: return "pokemon_league_upper"; + case FlyPoint::Route221: return "route_221"; + case FlyPoint::SandgemTown: return "sandgem_town"; + case FlyPoint::SolaceonTown: return "solaceon_town"; + case FlyPoint::SnowpointCity: return "snowpoint_city"; + case FlyPoint::SunyshoreCity: return "sunyshore_city"; + case FlyPoint::TwinleafTown: return "twinleaf_town"; + case FlyPoint::VeilstoneCity: return "veilstone_city"; + case FlyPoint::CurrentCity: return "current_city"; + } + return "unknown"; +} + +bool fly_to( + VideoStream& stream, + ProControllerContext& context, + FlyPoint place +){ + // Each city branch below should update this to the city icon's position on the fly map. + // Detection: when the cursor brackets overlap the icon, its normally uniform + // color becomes non-uniform (stddev rises). Box stays as a rough fallback + // until each city is calibrated. + ImageFloatBox city_icon_box{0.700000, 0.550000, 0.100000, 0.120000}; + + DpadState dpad; + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + open_menu(stream, context, MenuCursorPosition::MAP, 8); + + if (place == FlyPoint::CurrentCity) { + ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; + struct FlyAButtonDetector : VisualInferenceCallback { + ImageFloatBox m_box; + FlyAButtonDetector(const ImageFloatBox& box) + : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} + void make_overlays(VideoOverlaySet&) const override {} + bool process_frame(const ImageViewRGB32& frame, WallClock) override { + ImageStats s = image_stats(extract_box_reference(frame, m_box)); + return s.stddev.sum() > 30.0; + } + }; + FlyAButtonDetector watcher(button_a_box); + int ret = wait_until(stream, context, 3000ms, {{watcher}}); + if (ret < 0) { + stream.log("CurrentCity: A button not found.", COLOR_RED); + return false; + } + BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int fly_ret = run_until( + stream, context, + [](ProControllerContext& context){ pbf_mash_button(context, BUTTON_A, 200000ms); }, + {{flying_black_screen}} + ); + if (fly_ret < 0) { + stream.log("CurrentCity: black screen not detected!", COLOR_RED); + return false; + } + stream.log("CurrentCity: black screen detected, arrived.", COLOR_GREEN); + return true; + } + + switch (place) { + case FlyPoint::CelesticTown: + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + break; + case FlyPoint::HearthomeCity: + case FlyPoint::OreburghCity: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; + case FlyPoint::PastoriaCity: + case FlyPoint::PokemonLeagueLower: + case FlyPoint::PokemonLeagueUpper: + case FlyPoint::SolaceonTown: + case FlyPoint::SunyshoreCity: + case FlyPoint::VeilstoneCity: + pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); + break; + case FlyPoint::SnowpointCity: + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + break; + case FlyPoint::TwinleafTown: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; + default: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; + } + context.wait_for_all_requests(); + + ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; + + struct FlyAButtonDetector : VisualInferenceCallback { + ImageFloatBox m_box; + FlyAButtonDetector(const ImageFloatBox& box) + : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} + void make_overlays(VideoOverlaySet&) const override {} + bool process_frame(const ImageViewRGB32& frame, WallClock) override { + ImageStats s = image_stats(extract_box_reference(frame, m_box)); + // Blue stripe is uniform when no button shown; A button disrupts it + return s.stddev.sum() > 30.0; + } + }; + FlyAButtonDetector watcher_1(button_a_box); + + switch (place) { + case FlyPoint::CanalavCity: + break; + case FlyPoint::CelesticTown: + city_icon_box = {0.660000, 0.470000, 0.012000, 0.020000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 15, false); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 13, false); + break; + case FlyPoint::EternaCity: + break; + case FlyPoint::FloaromaTown: + break; + case FlyPoint::HearthomeCity: + city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); + break; + case FlyPoint::JubilifeCity: + city_icon_box = {0.453000, 0.702000, 0.016000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); + break; + case FlyPoint::OreburghCity: + city_icon_box = {0.566000, 0.702000, 0.012000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 10, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); + break; + case FlyPoint::PastoriaCity: + break; + case FlyPoint::PokemonLeagueLower: + break; + case FlyPoint::PokemonLeagueUpper: + break; + case FlyPoint::Route221: + break; + case FlyPoint::SandgemTown: + break; + case FlyPoint::SolaceonTown: + city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); + break; + case FlyPoint::SnowpointCity: + city_icon_box = {0.602000, 0.070000, 0.013000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, false); + break; + case FlyPoint::SunyshoreCity: + break; + case FlyPoint::TwinleafTown: + city_icon_box = {0.415000, 0.870000, 0.013000, 0.022000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, false); + break; + case FlyPoint::VeilstoneCity: + city_icon_box = {0.829000, 0.537000, 0.017000, 0.054000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 10, false); + break; + case FlyPoint::CurrentCity: + break; + } + + context.wait_for_all_requests(); + + struct CityIconDetector : VisualInferenceCallback { + ImageFloatBox m_box; + CityIconDetector(const ImageFloatBox& box) + : VisualInferenceCallback("CityIconDetector"), m_box(box) {} + void make_overlays(VideoOverlaySet&) const override {} + bool process_frame(const ImageViewRGB32& frame, WallClock) override { + ImageStats s = image_stats(extract_box_reference(frame, m_box)); + // City icon is normally a uniform color; cursor brackets disrupt it + return s.stddev.sum() > 30.0; + } + }; + CityIconDetector watcher_2(city_icon_box); + + const std::string name = fly_point_name(place); + int ret_1 = wait_until(stream, context, 3000ms, {{watcher_1}}); + int ret_2 = wait_until(stream, context, 4000ms, {{watcher_2}}); + if (ret_1 < 0) { + stream.log("A button not found, retrying...", COLOR_RED); + }; + if (ret_2 < 0) { + stream.log(name + " not found, retrying...", COLOR_RED); + }; + if (ret_1 == 0 and ret_2 == 0){ + stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); + WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{flying_white_screen}, {flying_black_screen}} + ); + if (ret < 0){ + stream.log("Flying to " + name + ": white / black screen not detected!", COLOR_RED); + return false; + } else if (ret >= 0) { + stream.log("Flying to " + name + ": white / black screen detected!", COLOR_GREEN); + return true; + } + } + return false; +} + +// --------------------------------------------------------------------------- +// Arrow - Based Potion Usage Helper + +// This version assumes : +// +// you are already inside the Bag +// Potion has already been selected +// the game is currently waiting for you to choose a Pokemon +// +// Flow : +// 1. Move LEFT once to Pokemon list +// 2. Press A on first Pokemon +// 3. Use SelectionArrow to detect arrow position +// 4. Move DOWN until arrow reaches slot 3 +// 5. Mash A for 3 seconds +// 6. Mash B for 10 seconds +// +// --------------------------------------------------------------------------- + +// Slot 1's colored HP bar in the Bag. Read fill position rather than OCR. +static double potion_hp_fraction(const ImageViewRGB32& frame) { + if (bag_pocket_position(frame) == 0) return -1; + const ImageViewRGB32 bar = extract_box_reference(frame, ImageFloatBox{0.137, 0.160, 0.202, 0.016}); + size_t filled = 0; + for (size_t x = 0; x < bar.width(); x++) { + size_t color = 0; + for (size_t y = 0; y < bar.height(); y++) { + uint32_t p = bar.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + const bool green = g > r + 30 && g > b + 20; + const bool yellow = r > 150 && g > 120 && b < 100; + const bool red = r > 150 && r > g + 50 && r > b + 50; + color += green || yellow || red; + } + filled += color > bar.height() / 4; + } + return bar.width() ? (double)filled / bar.width() : -1; +} + +static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& context) { + double previous = -1; + size_t stable = 0; + for (size_t check = 0; check < 20; check++) { + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + double hp = frame ? potion_hp_fraction(frame) : -1; + if (hp >= 0 && previous >= 0 && std::abs(hp - previous) <= 0.015) { + if (++stable >= 3) return hp; + } else stable = 0; + previous = hp; + pbf_wait(context, 150ms); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Slot 1 HP bar could not be confirmed.", stream); +} + +void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context) { + OverlayBoxScope hp_overlay(stream.overlay(), COLOR_GREEN, {0.137, 0.160, 0.202, 0.016}, ""); + BagItemTemplate potion; + potion.reference_path = "PokemonBDSP/Autostory/Bag_Potion.png"; + potion.label = "Potion"; + potion.reference_row = 1; + bool healed = false; + for (size_t dose = 0; dose < 3; dose++) { + stream.log("[Potion] Finding Bag/Medicine/Potion; dose " + std::to_string(dose + 1) + "/3."); + if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Could not verify Potion in the Medicine pocket.", stream); + } + const double before = confirmed_potion_hp(stream, context); + stream.log("[Potion] Slot 1 HP bar before use=" + std::to_string(before * 100) + "%."); + if (before >= 0.98) { healed = true; break; } + if (before <= 0.005) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Slot 1 appears fainted; a Potion cannot revive it.", stream); + } + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + OverworldWatcher overworld; + if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Did not return to the overworld after use.", stream); + } + // Reopen Bag to verify the result, including when the last Potion was used. + open_menu(stream, context, MenuCursorPosition::BAG, 5); + pbf_wait(context, 750ms); + context.wait_for_all_requests(); + const double after = confirmed_potion_hp(stream, context); + stream.log("[Potion] Slot 1 HP bar after use=" + std::to_string(after * 100) + "%."); + if (after <= before + 0.005) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Slot 1 HP did not increase; refusing to continue movement.", stream); + } + if (after >= 0.98) { healed = true; break; } + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + } + if (!healed) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Full HP not confirmed after three uses.", stream); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + OverworldWatcher overworld; + if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Final overworld return not confirmed.", stream); + stream.log("[Potion] Slot 1 HP bar full and overworld confirmed."); +} + + +namespace{ + +struct IconGrid { int row, col; }; + +IconGrid icon_to_grid(MenuCursorPosition pos, int num_icons){ + switch (pos){ + case MenuCursorPosition::POKEDEX: return {0, 0}; + case MenuCursorPosition::POKEMON: return {0, 1}; + case MenuCursorPosition::BAG: return {0, 2}; + case MenuCursorPosition::CARD: return {0, 3}; + case MenuCursorPosition::OPTIONS: return {1, num_icons <= 5 ? 0 : 2}; + case MenuCursorPosition::MAP: return {1, 0}; + case MenuCursorPosition::CAPSULES: return {1, 1}; + case MenuCursorPosition::MYSTERYGIFT: return {1, 3}; + default: return {-1, -1}; + } +} + +} // anonymous namespace + + +void open_menu( + VideoStream& stream, + ProControllerContext& context, + MenuCursorPosition target, + int num_icons +){ + using namespace std::chrono_literals; + + // Previous single opening attempt: + // context.wait_for_all_requests(); + // overworld_to_menu(stream, context); + // pbf_wait(context, 1000ms); + // context.wait_for_all_requests(); + context.wait_for_all_requests(); + + MenuCursorDetector detector; + { + static const char* ICON_NAMES[MENU_CURSOR_NUM_ICONS] = { + "POKEDEX", "POKEMON", "BAG", "CARD", + "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", + }; + // Previous optional template directory: PokemonBDSP/AutoStory. + std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/Autostory"; + for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ + std::string path = ref_dir + "/" + ICON_NAMES[i] + ".png"; + // These templates are optional. The dark-pixel cursor detector + // remains available when a template is not packaged. + std::error_code file_error; + if (!std::filesystem::is_regular_file(path, file_error)){ + continue; + } + // Previously constructed the image before checking its availability. + ImageRGB32 img(path); + if (img){ + detector.set_icon_reference( + static_cast(i), + std::move(img) + ); + } + } + } + // Previous single detection attempt retained for comparison. + // MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; + // for (int i = 0; i < 10 && cur == MenuCursorPosition::UNKNOWN; i++){ + // context.wait_for(50ms); + // VideoSnapshot snap = stream.video().snapshot(); + // if (snap) cur = detector.detect(snap); + // } + // if (cur == MenuCursorPosition::UNKNOWN){ + // OperationFailedExceptionWithScreenshot::fire( + // ErrorReportMode::SEND_ERROR_REPORT, + // "Menu cursor not detected after opening menu.", + // stream + // ); + // } + + MenuCursorPosition cur = MenuCursorPosition::UNKNOWN; + bool menu_confirmed_open = false; + for (size_t attempt = 1; attempt <= 3; attempt++){ + stream.log("[Open menu] Open and detect attempt " + std::to_string(attempt) + "/3."); + try { + // A late-opening menu may already be visible after a timeout. + // Check before pressing X again, since X would close that menu. + if (!menu_confirmed_open){ + MenuWatcher already_open; + if (wait_until(stream, context, 300ms, {{already_open}}) < 0){ + overworld_to_menu(stream, context); + }else{ + stream.log("[Open menu] Menu already visible; skipping X."); + } + menu_confirmed_open = true; + }else{ + // Cursor detection retries send no input that could close the + // confirmed menu. Keep observing it instead of toggling X. + stream.log("[Open menu] Retrying cursor detection in the open menu."); + } + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + for (int frame = 0; frame < 10 && cur == MenuCursorPosition::UNKNOWN; frame++){ + context.wait_for(50ms); + const VideoSnapshot snapshot = stream.video().snapshot(); + if (snapshot){ + cur = detector.detect(snapshot); + } + } + if (cur != MenuCursorPosition::UNKNOWN){ + stream.log("[Open menu] Menu and cursor detected on attempt " + std::to_string(attempt) + ".", COLOR_GREEN); + break; + } + stream.log("[Open menu] Menu cursor not detected on attempt " + std::to_string(attempt) + ".", COLOR_ORANGE); + } + catch (OperationFailedException& error){ + stream.log("[Open menu] Attempt " + std::to_string(attempt) + " failed: " + error.message(), COLOR_ORANGE); + } + } + if (cur == MenuCursorPosition::UNKNOWN){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Menu or cursor detection failed after 3 opening attempts.", + stream + ); + } + + IconGrid cur_g = icon_to_grid(cur, num_icons); + IconGrid tgt_g = icon_to_grid(target, num_icons); + if (tgt_g.row == -1){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Unknown target menu icon.", + stream + ); + } + + if (cur_g.row != tgt_g.row){ + DpadPosition vert = (cur_g.row < tgt_g.row) ? DPAD_DOWN : DPAD_UP; + pbf_press_dpad(context, vert, 80ms, 300ms); + context.wait_for_all_requests(); + } + + int col_diff = tgt_g.col - cur_g.col; + if (col_diff != 0){ + DpadPosition horiz = (col_diff > 0) ? DPAD_RIGHT : DPAD_LEFT; + for (int i = 0; i < std::abs(col_diff); i++){ + pbf_press_dpad(context, horiz, 80ms, 300ms); + context.wait_for_all_requests(); + } + } + + pbf_press_button(context, BUTTON_A, 80ms, 200ms); +} + + +// Search the selected item's fixed description header rather than guessing +// which list row is selected. This remains in place while the list scrolls. +// A cursor parked at row 2 or 6 does not indicate a list end: only repeated +// unchanged item headers after movement are used to detect an end. +// Previous selected-item-only search, retained for reference. +// bool find_bag_item( +// VideoStream& stream, +// ProControllerContext& context, +// const BagItemTemplate& item, +// int num_icons, +// size_t max_scrolls_per_pocket, +// std::chrono::milliseconds search_timeout +// ){ +// auto reference = std::make_shared( +// RESOURCE_PATH() + item.reference_path +// ); +// ImageMatchDetector icon(reference, item.icon_box, item.max_icon_rmsd, true); +// ImageMatchDetector name(reference, item.name_box, item.max_name_rmsd, true); +// ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); +// const ImageFloatBox selected_header(0.428, 0.754, 0.382, 0.046); +// const auto deadline = std::chrono::steady_clock::now() + search_timeout; +// +// stream.log("[Bag search] Looking for " + item.label + "."); +// open_menu(stream, context, MenuCursorPosition::BAG, num_icons); +// pbf_wait(context, 1000ms); +// context.wait_for_all_requests(); +// +// for (size_t pocket = 0; pocket < 8; pocket++){ +// ImageRGB32 initial_header; +// ImageRGB32 previous_header; +// size_t unchanged = 0; +// size_t matching_frames = 0; +// size_t scrolls = 0; +// bool scanning_up = false; +// +// while (std::chrono::steady_clock::now() < deadline){ +// context.wait_for_all_requests(); +// const VideoSnapshot frame = stream.video().snapshot(); +// if (!frame || !bag.detect(frame)){ +// stream.log("[Bag search] Bag screen not detected.", COLOR_ORANGE); +// return false; +// } +// const double icon_score = icon.rmsd(frame); +// const double name_score = item.check_name ? name.rmsd(frame) : 0.0; +// stream.log( +// "[Bag search] Pocket " + std::to_string(pocket + 1) + +// ", scroll " + std::to_string(scrolls) + +// ": icon=" + std::to_string(icon_score) + +// ", name=" + std::to_string(name_score) +// ); +// if (icon_score <= item.max_icon_rmsd && +// name_score <= item.max_name_rmsd){ +// if (++matching_frames >= 3){ +// stream.log("[Bag search] " + item.label + " confirmed.", COLOR_GREEN); +// return true; +// } +// pbf_wait(context, 150ms); +// continue; +// } +// matching_frames = 0; +// +// const ImageRGB32 header = extract_box_reference(frame, selected_header).scale_to(382, 46); +// if (!initial_header){ +// initial_header = header.copy(); +// } +// else if (scrolls > 0){ +// const bool same_as_previous = +// ImageMatch::pixel_RMSD(previous_header, header) <= 3.0; +// const bool returned_to_start = +// ImageMatch::pixel_RMSD(initial_header, header) <= 3.0; +// if (returned_to_start && !same_as_previous){ +// break; +// } +// // Repeated unchanged rows mean an empty pocket or list end. +// // Returning to the initial row means the list wrapped. +// unchanged = same_as_previous || returned_to_start ? unchanged + 1 : 0; +// if (unchanged >= 3){ +// if (scanning_up){ +// break; +// } +// // A non-wrapping list may have opened in the middle. +// // Search upward as well before moving to another pocket. +// scanning_up = true; +// unchanged = 0; +// initial_header = header.copy(); +// } +// } +// previous_header = header.copy(); +// if (scrolls >= max_scrolls_per_pocket){ +// stream.log("[Bag search] Scroll limit reached; search incomplete.", COLOR_ORANGE); +// return false; +// } +// scrolls++; +// pbf_press_dpad(context, scanning_up ? DPAD_UP : DPAD_DOWN, 80ms, 350ms); +// } +// if (std::chrono::steady_clock::now() >= deadline){ +// stream.log("[Bag search] Search timed out; search incomplete.", COLOR_ORANGE); +// return false; +// } +// if (pocket < 7){ +// pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); +// context.wait_for_all_requests(); +// } +// } +// stream.log("[Bag search] Item not found in any pocket.", COLOR_ORANGE); +// return false; +// } +// + +// One narrow strip on the left side of the red selection border. Divide its +// height into the seven row bands; the item's art and NEW badge are outside it. +int bag_cursor_position(const ImageViewRGB32& frame){ + if (!frame){ + return 0; + } + const ImageViewRGB32 strip = extract_box_reference( + frame, ImageFloatBox(0.431, 0.229, 0.005, 0.470) + ); + double best = 0.0; + double second = 0.0; + int position = 0; + for (int row = 0; row < 7; row++){ + const size_t top = static_cast(strip.height() * (row * 0.075 / 0.470)); + const size_t bottom = static_cast(strip.height() * ((row * 0.075 + 0.020) / 0.470)); + size_t red = 0; + size_t total = 0; + for (size_t y = top; y < bottom && y < strip.height(); y++){ + for (size_t x = 0; x < strip.width(); x++){ + const uint32_t pixel = strip.pixel(x, y); + const unsigned r = (pixel >> 16) & 255; + const unsigned g = (pixel >> 8) & 255; + const unsigned b = pixel & 255; + red += r > 180 && g < 120 && b < 120; + total++; + } + } + const double score = total == 0 ? 0.0 : static_cast(red) / total; + if (score > best){ + second = best; + best = score; + position = row + 1; + }else if (score > second){ + second = score; + } + } + return best >= 0.15 && best - second >= 0.10 ? position : 0; +} + +static ImageFloatBox bag_list_icon_box(size_t row){ + return ImageFloatBox(0.449, 0.212 + (row - 1) * 0.075, 0.033, 0.050); +} + +static ImageFloatBox bag_list_name_box(size_t row){ + // Previous height included the faint lower selection border in rows 2/6. + // return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.052); + return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.050); +} + +static double bag_row_patch_score( + const ImageRGB32& reference, + const ImageViewRGB32& frame, + const ImageFloatBox& box +){ + const ImageRGB32 live = extract_box_reference(frame, box).scale_to( + reference.width(), reference.height() + ); + return ImageMatch::pixel_RMSD(reference, live); +} + +// Previous name-area empty-list check retained for reference. +// static bool bag_list_is_empty(const ImageViewRGB32& frame){ +// const ImageViewRGB32 names = extract_box_reference( +// frame, ImageFloatBox(0.492, 0.213, 0.335, 0.500) +// ); +// size_t dark = 0; +// for (size_t y = 0; y < names.height(); y++){ +// for (size_t x = 0; x < names.width(); x++){ +// const uint32_t pixel = names.pixel(x, y); +// dark += ((pixel >> 16) & 255) < 100 && +// ((pixel >> 8) & 255) < 100 && (pixel & 255) < 100; +// } +// } +// return dark < names.width() * names.height() * 0.001; +// } + +size_t bag_clear_tab_count(const ImageViewRGB32& frame){ + if (!frame || bag_pocket_position(frame)==0) return 0; + static const std::vector> references=[] { + std::vector> refs; + for (const char* name : {"Normal","NormalMedium","NormalSmall","Alternate","AlternateMedium","AlternateSmall","Selected","SelectedMedium","SelectedSmall","SelectedBlue","SelectedBlueMedium","SelectedBlueSmall"}) + refs.emplace_back(std::make_shared( + RESOURCE_PATH()+"PokemonBDSP/Autostory/BagTabs/"+name+".png")); + return refs; + }(); + size_t clear=0; + for (size_t tab=0;tab<8;tab++) { + const double x=.570+tab*.0421; + // A selected tab has a thin red outline. The NEW marker fills the + // interior of this upper-left patch, which the outline leaves white. + auto badge=extract_box_reference(frame,ImageFloatBox(x+.007,.074,.006,.014)); + size_t red=0; + for (size_t y=0;y>16)&255,gg=(pixel>>8)&255,bb=pixel&255; + red+=rr>150 && rr>gg+60 && rr>bb+60; + } + if (badge.width()*badge.height() && (double)red/(badge.width()*badge.height())>.45) continue; + const ImageFloatBox box{x,.06,.038,.072}; + for (const auto& reference : references) { + ImageMatchDetector detector(reference,box,50.0); + if (detector.detect(frame)) {clear++;break;} + } + } + return clear; +} + +bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons){ + stream.log("[Bag NEW flags] Opening bag to verify all eight tabs."); + open_menu(stream,context,MenuCursorPosition::BAG,num_icons); + pbf_wait(context,1000ms); + auto all_clear=[&]() { + context.wait_for_all_requests(); + size_t confirmed=0; + for (size_t check=0;check<3;check++) { + const auto frame=stream.video().snapshot(); + const size_t count=frame?bag_clear_tab_count(*frame.frame):0; + stream.log("[Bag NEW flags] Clear tabs: "+std::to_string(count)+"/8."); + if (count!=8) return false; + confirmed++; + pbf_wait(context,150ms); + context.wait_for_all_requests(); + } + return confirmed==3; + }; + bool cleared=all_clear(); + for (size_t pocket=0;pocket<32 && !cleared;pocket++) { + if (pocket) pbf_press_dpad(context,DPAD_RIGHT,80ms,300ms); + // Pause on each selection so its NEW banner has time to clear. + // Keep three cumulative seconds of Down input per pocket. + for (size_t tap=0;tap<12;tap++) pbf_press_dpad(context,DPAD_DOWN,250ms,100ms); + cleared=all_clear(); + } + pbf_press_dpad(context,DPAD_UP,3000ms,250ms); + pbf_mash_button(context,BUTTON_B,2000ms); + context.wait_for_all_requests(); + stream.log(cleared?"[Bag NEW flags] All eight tabs clear; bag closed.": + "[Bag NEW flags] Full pocket pass did not clear all eight tabs; bag closed.",cleared?COLOR_GREEN:COLOR_ORANGE); + return cleared; +} + +int bag_pocket_position(const ImageViewRGB32& frame){ + if (!frame) return 0; + int selected = 0; + for (int pocket = 1; pocket <= 8; pocket++){ + // The lower left edge of each tab is below the red NEW badge. + // This strip includes both the normal outline and enlarged selection. + const auto edge = extract_box_reference(frame, + ImageFloatBox(0.570 + (pocket - 1) * 0.0421, 0.098, 0.0045, 0.021)); + size_t colored = 0; + for (size_t y = 0; y < edge.height(); y++){ + for (size_t x = 0; x < edge.width(); x++){ + const uint32_t pixel = edge.pixel(x, y); + const int r = (pixel >> 16) & 255; + const int g = (pixel >> 8) & 255; + const int b = pixel & 255; + // Red when focused, blue when the item list has focus. + colored += (r > 160 && r > g + 60 && r > b + 60) || + (b > 160 && b > r + 40 && b > g + 20); + } + } + const double pixels = (double)edge.width() * edge.height(); + if (pixels > 0 && colored / pixels >= 0.18){ + if (selected != 0) return 0; + selected = pocket; + } + } + return selected; +} + +bool bag_list_is_empty(const ImageViewRGB32& frame){ + if (!frame || bag_pocket_position(frame) == 0 || bag_cursor_position(frame) != 0) return false; + // Empty pockets have no item icons. The central Empty label is outside + // this column; counting dark pixels in the name area included that label. + const auto icons = extract_box_reference(frame, ImageFloatBox(0.449, 0.213, 0.032, 0.500)); + const auto stats = image_stats(icons); + return stats.average.r > 220 && stats.average.g > 220 && stats.average.b > 220 && + stats.stddev.sum() < 20; +} + + +std::array match_bag_item_rows( + const ImageViewRGB32& frame, + const ImageViewRGB32& reference, + const BagItemTemplate& item +){ + std::array result; + if (!frame || !reference || item.reference_row < 1 || item.reference_row > 7){ + return result; + } + const ImageRGB32 icon = extract_box_reference(reference, bag_list_icon_box(item.reference_row)).copy(); + const ImageRGB32 name = extract_box_reference(reference, bag_list_name_box(item.reference_row)).copy(); + for (size_t row = 1; row <= 7; row++){ + auto& match = result[row - 1]; + match.icon_rmsd = bag_row_patch_score(icon, frame, bag_list_icon_box(row)); + match.name_rmsd = item.check_name + ? bag_row_patch_score(name, frame, bag_list_name_box(row)) : 0.0; + match.matched = match.icon_rmsd <= item.max_icon_rmsd && match.name_rmsd <= item.max_name_rmsd; + } + return result; +} + +BagMovePlan plan_bag_item_move(int cursor_row, int item_row){ + if (cursor_row < 1 || cursor_row > 7 || item_row < 0 || item_row > 7){ + return {}; + } + if (item_row != 0){ + if (item_row == cursor_row){ + return {BagMoveAction::CONFIRM, 0}; + } + return { + item_row > cursor_row ? BagMoveAction::DOWN : BagMoveAction::UP, + static_cast(std::abs(item_row - cursor_row)) + }; + } + if (cursor_row == 7){ + return {BagMoveAction::REWIND, 1}; + } + return {BagMoveAction::PAGE_DOWN, cursor_row == 1 ? 13 : static_cast(13 - cursor_row)}; +} + + +static int bag_confirm_pocket(VideoStream& stream, ProControllerContext& context){ + int previous = 0; + size_t matches = 0; + for (size_t check = 0; check < 10; check++){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + const int pocket = frame ? bag_pocket_position(frame) : 0; + matches = pocket != 0 && pocket == previous ? matches + 1 : pocket != 0 ? 1 : 0; + previous = pocket; + if (matches >= 3) return pocket; + pbf_wait(context, 100ms); + } + return 0; +} + +// static bool bag_advance_pocket(VideoStream& stream, ProControllerContext& context, int current){ +// const int expected = current % 8 + 1; +// for (size_t attempt = 0; attempt < 3; attempt++){ +// // Recheck before retrying: a delayed change must not cause a second step. +// const int observed = bag_confirm_pocket(stream, context); +// if (observed == expected) return true; +// if (observed != current){ +// stream.log("[Bag search] Pocket selection uncertain; stopping.", COLOR_ORANGE); +// return false; +// } +// pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); +// const int result = bag_confirm_pocket(stream, context); +// stream.log("[Bag search] Pocket change: " + std::to_string(current) + " -> " + +// std::to_string(result) + ", expected " + std::to_string(expected) + "."); +// if (result == expected) return true; +// } +// stream.log("[Bag search] Could not confirm pocket change after 3 attempts.", COLOR_ORANGE); +// return false; +// } + +BagPocketMovePlan plan_bag_pocket_move(int current, int target){ + if (current < 1 || current > 8 || target < 1 || target > 8) return {}; + const int right = (target - current + 8) % 8; + const int left = (current - target + 8) % 8; + if (right == 0) return {BagPocketAction::CONFIRM, 0}; + return right <= left ? BagPocketMovePlan{BagPocketAction::RIGHT, static_cast(right)} + : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; +} + +static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& context, int target){ + int current = bag_confirm_pocket(stream, context); + for (size_t attempt = 0; attempt < 3; attempt++){ + const auto plan = plan_bag_pocket_move(current, target); + if (plan.action == BagPocketAction::CONFIRM) return true; + if (plan.action == BagPocketAction::UNKNOWN){ + stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); + return false; + } + stream.log("[Bag search] Pocket " + std::to_string(current) + " to " + + std::to_string(target) + ": " + + (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + + std::to_string(plan.presses) + " presses, attempt " + std::to_string(attempt + 1) + "/3."); + for (size_t press = 0; press < plan.presses; press++){ + pbf_press_dpad(context, plan.action == BagPocketAction::RIGHT ? DPAD_RIGHT : DPAD_LEFT, + 80ms, 150ms); + } + pbf_wait(context, 350ms); + current = bag_confirm_pocket(stream, context); + stream.log("[Bag search] Final pocket=" + std::to_string(current) + + ", expected=" + std::to_string(target) + "."); + if (current == target) return true; + } + stream.log("[Bag search] Target pocket not reached after 3 movement batches.", COLOR_ORANGE); + return false; +} + + +bool find_bag_item( + VideoStream& stream, + ProControllerContext& context, + const BagItemTemplate& item, + int target_pocket, + int num_icons, + size_t max_scrolls_per_pocket, + std::chrono::milliseconds search_timeout +){ + if (target_pocket < 1 || target_pocket > 8 || item.reference_row < 1 || item.reference_row > 7){ + stream.log("[Bag search] Invalid template reference row.", COLOR_ORANGE); + return false; + } + auto reference = std::make_shared(RESOURCE_PATH() + item.reference_path); +// const ImageRGB32 reference_icon = extract_box_reference( +// *reference, bag_list_icon_box(item.reference_row) +// ).copy(); +// const ImageRGB32 reference_name = extract_box_reference( +// *reference, bag_list_name_box(item.reference_row) +// ).copy(); + ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); + ImageMatchDetector selected_icon(reference, item.icon_box, item.max_icon_rmsd, true); + ImageMatchDetector selected_name(reference, item.name_box, item.max_name_rmsd, true); + const auto deadline = std::chrono::steady_clock::now() + search_timeout; + + open_menu(stream, context, MenuCursorPosition::BAG, num_icons); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + + if (!bag_navigate_to_pocket(stream, context, target_pocket)) return false; + // Previously searched all eight pockets. Only inspect the requested pocket now. + // for (size_t pocket = 0; pocket < 8; pocket++){ + { + const int selected_pocket = bag_confirm_pocket(stream, context); + if (selected_pocket == 0){ + stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); + return false; + } + size_t commands = 0; + bool empty = false; + auto move = [&](DpadPosition direction, size_t count, bool check_rows = false){ + for (size_t press = 0; press < count; press++){ + if (commands >= max_scrolls_per_pocket || + std::chrono::steady_clock::now() >= deadline){ + stream.log("[Bag search] Limit reached; search incomplete.", COLOR_ORANGE); + return false; + } + commands++; + pbf_press_dpad(context, direction, 80ms, 150ms); + if (check_rows){ + context.wait_for_all_requests(); + const VideoSnapshot page_frame = stream.video().snapshot(); + if (!page_frame || !bag.detect(page_frame)){ + return false; + } + const auto matches = match_bag_item_rows(page_frame, *reference, item); + for (const auto& match : matches){ + if (match.matched){ + // Stop paging; the main loop verifies and selects the row. + return true; + } + } + } + } + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + return true; + }; + for (size_t pass = 0; pass < 3 && !empty; pass++){ + size_t matching_frames = 0; + size_t missing_cursor = 0; + size_t failed_confirmation = 0; + size_t unchanged_pages = 0; + ImageRGB32 previous_page; + stream.log( + "[Bag search] Looking for " + item.label + ": pocket " + + std::to_string(selected_pocket) + ", pass " + std::to_string(pass + 1) + "/3." + ); + while (std::chrono::steady_clock::now() < deadline){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame || !bag.detect(frame)){ + stream.log("[Bag search] Bag screen not detected.", COLOR_ORANGE); + return false; + } + const int cursor = bag_cursor_position(frame); + int target = 0; + double best = 1000.0; + bool ambiguous = false; + // The seven list ROIs move neither with the cursor nor with + // scrolling. Each contains an icon patch and a name patch. + // Previous per-row loop retained for reference. + // for (size_t row = 1; row <= 7; row++){ + // const double icon = bag_row_patch_score(reference_icon, frame, bag_list_icon_box(row)); + // const double name = item.check_name + // ? bag_row_patch_score(reference_name, frame, bag_list_name_box(row)) : 0.0; + // if (icon <= item.max_icon_rmsd && name <= item.max_name_rmsd){ + // if (target != 0){ + // ambiguous = true; + // } + // if (icon + name < best){ + // target = static_cast(row); + // best = icon + name; + // } + // } + // } + const auto matches = match_bag_item_rows(frame, *reference, item); + for (size_t row = 1; row <= 7; row++){ + const auto& match = matches[row - 1]; + if (match.matched){ + if (target != 0){ + ambiguous = true; + } + if (match.icon_rmsd + match.name_rmsd < best){ + target = static_cast(row); + best = match.icon_rmsd + match.name_rmsd; + } + } + } + stream.log( + "[Bag search] " + item.label + (target == 0 ? " is not detected" : " is detected") + + " on screen; item row=" + std::to_string(target) + + ", cursor row=" + std::to_string(cursor) + "." + ); + if (ambiguous){ + stream.log("[Bag search] Multiple rows match; selection is uncertain.", COLOR_ORANGE); + return false; + } + if (cursor == 0){ + if (bag_list_is_empty(frame)){ + empty = true; + break; + } + if (++missing_cursor >= 3){ + stream.log("[Bag search] Cursor location uncertain.", COLOR_ORANGE); + return false; + } + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + continue; + } + missing_cursor = 0; + if (target != 0){ + previous_page = ImageRGB32(); + unchanged_pages = 0; + if (target == cursor && selected_icon.detect(frame) && + (!item.check_name || selected_name.detect(frame))){ + if (++matching_frames >= 3){ + stream.log("[Bag search] Item and cursor confirmed in row " + + std::to_string(cursor) + ".", COLOR_GREEN); + return true; + } + pbf_wait(context, 150ms); + context.wait_for_all_requests(); + continue; + } + matching_frames = 0; + if (target == cursor){ + if (++failed_confirmation < 3){ + pbf_wait(context, 150ms); + context.wait_for_all_requests(); + continue; + } + stream.log("[Bag search] Selected-item confirmation failed.", COLOR_ORANGE); + return false; + } + failed_confirmation = 0; + const size_t count = plan_bag_item_move(cursor, target).presses; + stream.log("[Bag search] Moving " + std::string(target > cursor ? "down " : "up ") + + std::to_string(count) + " time(s), then checking again."); + if (!move(target > cursor ? DPAD_DOWN : DPAD_UP, count)){ + return false; + } + continue; + } + matching_frames = 0; + const ImageRGB32 page = extract_box_reference( + frame, ImageFloatBox(0.449, 0.212, 0.378, 0.500) + ).scale_to(378, 500); + if (previous_page && ImageMatch::pixel_RMSD(previous_page, page) <= 3.0){ + unchanged_pages++; + }else{ + unchanged_pages = 0; + } + previous_page = page.copy(); + // Row 7 signals the bottom of a long list. Repeated pages + // also cover shorter lists whose final row is less than 7. + if (cursor == 7 || unchanged_pages >= 2){ + stream.log("[Bag search] List end; returning to row 1."); + while (true){ + const VideoSnapshot rewind_frame = stream.video().snapshot(); + if (!rewind_frame || !bag.detect(rewind_frame)){ + return false; + } + const int rewind_cursor = bag_cursor_position(rewind_frame); + if (rewind_cursor == 1){ + break; + } + if (rewind_cursor == 0 || !move(DPAD_UP, 1)){ + return false; + } + } + break; + } + const size_t count = plan_bag_item_move(cursor, 0).presses; + stream.log("[Bag search] Advancing " + std::to_string(count) + " presses to another page."); + if (!move(DPAD_DOWN, count, true)){ + return false; + } + } + if (std::chrono::steady_clock::now() >= deadline){ + stream.log("[Bag search] Timeout; search incomplete.", COLOR_ORANGE); + return false; + } + } +// if (pocket < 7){ +// // pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); +// // context.wait_for_all_requests(); +// if (!bag_advance_pocket(stream, context, selected_pocket)) return false; +// } + + } + stream.log("[Bag search] Item not found after three passes per nonempty pocket.", COLOR_ORANGE); + return false; +} + +bool use_escape_rope( + VideoStream& stream, + ProControllerContext& context, + int num_icons +){ + const BagItemTemplate rope{ + // "PokemonBDSP/AutoStory/Bag_EscapeRope.png", "escape-rope" + "PokemonBDSP/Autostory/Bag_EscapeRope.png", "escape-rope" + }; + if (!find_bag_item(stream, context, rope, 5, num_icons)){ + return false; + } + + pbf_press_button(context, BUTTON_A, 80ms, 350ms); + context.wait_for_all_requests(); + SelectionArrowFinder use_menu( + stream.overlay(), ImageFloatBox(0.660, 0.100, 0.310, 0.430), COLOR_GREEN + ); + if (wait_until(stream, context, 3s, {{use_menu}}) != 0){ + stream.log("[Escape Rope] Item action menu not detected.", COLOR_ORANGE); + return false; + } + + // Watch while selecting Use so the transition cannot happen before + // the watcher starts. Extra A presses handle a confirmation prompt. + BlackScreenWatcher transition; + // Escape Rope fades white in the supplied clips; support black transitions too. + WhiteScreenWatcher white_transition; + const int result = run_until( + stream, context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 10; press++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); + } + ctx.wait_for_all_requests(); + pbf_wait(ctx, 10s); + ctx.wait_for_all_requests(); + }, + {transition, white_transition} + ); + if (result < 0){ + stream.log("[Escape Rope] No exit transition detected.", COLOR_ORANGE); + return false; + } + BlackScreenWatcher finished( + COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms + ); + WhiteScreenWatcher white_finished( + COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, + WhiteScreenWatcher::FinderType::GONE, 500ms + ); + stream.log(std::string("[Escape Rope] ") + + (result == 1 ? "White" : "Black") + " exit transition detected."); + // if (wait_until(stream, context, 15s, {{finished}}) != 0){ return false; } + const int finish_result = result == 1 + ? wait_until(stream, context, 15s, {{white_finished}}) + : wait_until(stream, context, 15s, {{finished}}); + if (finish_result != 0){ + stream.log("[Escape Rope] Exit transition did not finish.", COLOR_ORANGE); + return false; + } + OverworldWatcher overworld; + if (wait_until(stream, context, 10s, {{overworld}}) != 0){ + stream.log("[Escape Rope] Overworld not detected after transition.", COLOR_ORANGE); + return false; + } + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + stream.log("[Escape Rope] Exit transition and overworld confirmed.", COLOR_GREEN); + return true; +} + + +bool activate_repel( + VideoStream& stream, + ProControllerContext& context, + int num_icons, + int repel_type +){ + static const char* const slugs[]={"repel","super-repel","max-repel"}; + if (repel_type<0 || repel_type>2) { + stream.log("activate_repel: type must be 0 (Repel), 1 (Super Repel), or 2 (Max Repel).",COLOR_RED); + return false; + } + const std::string slug=slugs[repel_type]; + stream.log("Activating "+slug); + open_menu(stream, context, MenuCursorPosition::BAG, num_icons); + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + auto repel_tab_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTab.png" + ); + // A smaller reference covers capture-resize sampling differences without + // loosening the match threshold or changing shared image processing. + auto repel_tab_small_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabSmall.png" + ); + // Obtaining Honey adds a NEW badge to the same selected Other Items tab. + // Keep the original references and threshold for the earlier route stages. + auto repel_tab_honey_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabHoney.png" + ); + auto repel_tab_honey_small_ref = std::make_shared( + RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabHoneySmall.png" + ); + const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; + const double repel_tab_rmsd = 50.0; + + // BDSP bag has 8 compartment tabs; check before each press so zero-press + // detection works when the bag already starts on the repel tab. + bool found = false; + for (int press = 0; press <= 8; press++){ + context.wait_for_all_requests(); + + ImageMatchWatcher watcher(repel_tab_ref, repel_tab_box, repel_tab_rmsd); + ImageMatchWatcher small_watcher(repel_tab_small_ref, repel_tab_box, repel_tab_rmsd); + ImageMatchWatcher honey_watcher(repel_tab_honey_ref, repel_tab_box, repel_tab_rmsd); + ImageMatchWatcher honey_small_watcher(repel_tab_honey_small_ref, repel_tab_box, repel_tab_rmsd); + int ret = wait_until(stream, context, 400ms, {{watcher}, {small_watcher}, {honey_watcher}, {honey_small_watcher}}); + if (ret >= 0){ + stream.log("[AutoStory] activate_repel: repel tab found after " + + std::to_string(press) + " press(es).", COLOR_GREEN); + found = true; + break; + } + + if (press < 8){ + stream.log("[AutoStory] activate_repel: tab not found, pressing right (" + + std::to_string(press + 1) + "/8)."); + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); + } + } + + if (!found){ + stream.log("[AutoStory] activate_repel: failed to find repel compartment tab.", COLOR_RED); + return false; + } + + // Reuse the Bag cursor/name geometry and existing Tesseract backend. A later + // save may contain all three Repels and unrelated Other Items in any order. + bool item_found=false; + size_t confirmed=0; + size_t unknown_cursor=0; + for (size_t scan=0;scan<256;scan++) { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if (!frame || bag_list_is_empty(*frame.frame)) break; + const int cursor=bag_cursor_position(*frame.frame); + if (!cursor) { + if (++unknown_cursor>=3) break; + pbf_wait(context,200ms); + continue; + } + unknown_cursor=0; + // Use the existing Tesseract entry point directly so item selection + // does not depend on the optional Paddle/ONNX runtime configuration. + const std::string text=OCR::tesseract_ocr_read(Language::English, + extract_box_reference(*frame.frame,bag_list_name_box(cursor))); + std::string normalized; + for (unsigned char ch:text) if (std::isalpha(ch)) normalized.push_back((char)std::tolower(ch)); + static const char* const names[]={"repel","superrepel","maxrepel"}; + if (normalized==names[repel_type]) { + if (++confirmed>=2) {item_found=true;break;} + pbf_wait(context,150ms); + } else { + confirmed=0; + // Search both directions so a non-wrapping list opened halfway + // down still reaches items above the initial cursor position. + pbf_press_dpad(context,scan<128 ? DPAD_DOWN : DPAD_UP,80ms,300ms); + } + } + if (!item_found) { + stream.log("activate_repel: requested item not confirmed: "+slug,COLOR_RED); + pbf_mash_button(context,BUTTON_B,2000ms); + context.wait_for_all_requests(); + return false; + } + stream.log("[AutoStory] activate_repel: selected item confirmed: "+slug,COLOR_GREEN); + + context.wait_for_all_requests(); + pbf_wait(context, 400ms); + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + context.wait_for_all_requests(); + + // The Use-item popup follows the selected row, which may be anywhere + // in a later save's list. Exclude the Bag list cursor on the left. + const ImageFloatBox repel_item_box{0.700000, 0.100000, 0.280000, 0.650000}; + SelectionArrowFinder arrow_finder(stream.overlay(), repel_item_box, COLOR_GREEN); + int arrow_ret = wait_until(stream, context, 2000ms, {{arrow_finder}}); + if (arrow_ret != 0){ + stream.log("[AutoStory] activate_repel: selection arrow not found after pressing A.", COLOR_RED); + return false; + } + stream.log("[AutoStory] activate_repel: selection arrow confirmed.", COLOR_GREEN); + pbf_press_button(context, BUTTON_A, 80ms, 105ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + return true; +} + + +void use_HM( + VideoStream& stream, + ProControllerContext& context, + const std::string& label + +){ + stream.log("Using " + label); + const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; + SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); + + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 30000ms); + }, + {{arrow}} + ); + if (ret != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Using " + label + ": HM confirmation arrow not found; stopping before further movement.", + stream + ); + } + + stream.log("Using " + label + ": selection arrow found, confirming.", COLOR_GREEN); + pbf_mash_button(context, BUTTON_A, 1000ms); + pbf_mash_button(context, BUTTON_B, 6000ms); +} + + +bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLevel /*gear*/){ + static const ImageFloatBox FRONT_BOX{0.440000, 0.470000, 0.035000, 0.054000}; + static const ImageFloatBox BACK_BOX {0.520000, 0.470000, 0.022000, 0.054000}; + // Spark color range: white (R=232,G=255,B=255) down to light green (R=163,G=217,B=181) + static const uint32_t SPARK_MIN = 0xFF000000u | (163u << 16) | (217u << 8) | 181u; + static const uint32_t SPARK_MAX = 0xFFFFFFFFu; + static const size_t SPARK_THRESHOLD = 13; + + stream.log("[AutoStory] get_on_bicycle: mounting bicycle.", COLOR_WHITE); + pbf_press_button(context, BUTTON_PLUS, 80ms, 800ms); + + // Press B, then poll up to 1 s for Fast-gear sparks. + // Logs the peak pixel counts in both boxes for threshold tuning. + // Returns true if sparks are detected (switched TO Fast gear). + auto toggle_and_check = [&]() -> bool { + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_B, 80ms, 800ms); + + size_t max_front = 0, max_back = 0; + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000); + while (std::chrono::steady_clock::now() < deadline){ + context.wait_for(50ms); + VideoSnapshot screen = stream.video().snapshot(); + if (!screen) continue; + + size_t front_pixels = 0, back_pixels = 0; + filter_rgb32_range(front_pixels, extract_box_reference(screen, FRONT_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); + filter_rgb32_range(back_pixels, extract_box_reference(screen, BACK_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); + + if (front_pixels > max_front) max_front = front_pixels; + if (back_pixels > max_back) max_back = back_pixels; + + if (front_pixels >= SPARK_THRESHOLD || back_pixels >= SPARK_THRESHOLD){ + stream.log( + "[AutoStory] get_on_bicycle: sparks detected " + "(front=" + std::to_string(front_pixels) + + " back=" + std::to_string(back_pixels) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_GREEN + ); + return true; + } + } + stream.log( + "[AutoStory] get_on_bicycle: no sparks detected " + "(peak front=" + std::to_string(max_front) + + " back=" + std::to_string(max_back) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_ORANGE + ); + return false; + }; + + if (toggle_and_check()){ + stream.log("[AutoStory] get_on_bicycle: Fast gear confirmed.", COLOR_GREEN); + return true; + } + + // First toggle produced no sparks -> we switched to Slow gear; toggle back to Fast. + stream.log("[AutoStory] get_on_bicycle: Slow gear detected, toggling back to Fast.", COLOR_ORANGE); + if (toggle_and_check()){ + stream.log("[AutoStory] get_on_bicycle: Fast gear confirmed after correction.", COLOR_GREEN); + return true; + } + + stream.log("[AutoStory] get_on_bicycle: Failed to confirm Fast gear after two toggles.", COLOR_RED); + return false; +} + + +} +} } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 636c836fd6..9144cde6e6 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -1,458 +1,489 @@ -/* BDSP AutoStory Tools - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonBDSP_AutoStoryTools_H -#define PokemonAutomation_PokemonBDSP_AutoStoryTools_H - -#include "CommonFramework/GlobalAutoPaths.h" -#include -#include -#include -#include -#include -#include "Common/Cpp/Options/EnumDropdownOption.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/Language.h" -#include "CommonFramework/Notifications/EventNotificationsTable.h" -#include "CommonFramework/ProgramStats/StatsTracking.h" -#include "CommonFramework/Tools/VideoStream.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonTools/VisualDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" -#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" -#include "Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - -// --------------------------------------------------------------------------- -// Stats -// --------------------------------------------------------------------------- - -struct AutoStoryStats : public StatsTracker{ - AutoStoryStats() - : m_checkpoint(m_stats["Checkpoint"]) - , m_segment(m_stats["Segment"]) - , m_reset(m_stats["Reset"]) - { - m_display_order.emplace_back("Checkpoint"); - m_display_order.emplace_back("Segment"); - m_display_order.emplace_back("Reset"); - } - std::atomic& m_checkpoint; - std::atomic& m_segment; - std::atomic& m_reset; - bool m_first_run = true; -}; - - -// --------------------------------------------------------------------------- -// Options passed to every segment/checkpoint -// --------------------------------------------------------------------------- - -enum class StarterChoice{ - TURTWIG, - CHIMCHAR, - PIPLUP, -}; - -enum class HelpMode{ - NO_OUTSIDE_HELP, - OUTSIDE_HELP, -}; - -enum class OutsideHelpPokemon{ - TORTERRA, -}; - -enum class TorterraMove{ - EARTHQUAKE, - WOOD_HAMMER, - RAZOR_LEAF, - CRUNCH, - GIGA_DRAIN, - IRON_TAIL, - ROCK_TOMB, - FACADE, - STONE_EDGE, - ROCK_SLIDE, - BULLDOZE, - STRENGTH, - ROCK_SMASH, - ROCK_CLIMB, -}; - -// Returns the JSON slug for a TorterraMove (e.g. "earthquake"). -const char* torterra_move_slug(TorterraMove move); - -enum class Trainer { - Veilstone_GalacticGrunts_2v2, - // Extend with new trainer battle IDs as the AutoStory adds them. -}; - -// Returns the trainers.json battle-ID string for a Trainer value. -const char* trainer_slug(Trainer t); - -struct OutsideHelpConfig{ - OutsideHelpPokemon pokemon; - TorterraMove attacks[4]; -}; - -enum class CheckpointSaveMode { REAL, FAKE }; - -struct AutoStoryOptions{ - Language language; - StarterChoice starter_choice; - EventNotificationOption& notif_status_update; - HelpMode help_mode; - OutsideHelpConfig outside_help; - CheckpointSaveMode checkpoint_save_mode = CheckpointSaveMode::REAL; -}; - - -// --------------------------------------------------------------------------- -// Base classes -// --------------------------------------------------------------------------- - -class AutoStory_Segment{ -public: - virtual ~AutoStory_Segment() = default; - virtual std::string name() const = 0; - virtual std::string start_text() const = 0; - virtual std::string end_text() const = 0; - virtual void run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const = 0; -}; - -class AutoStory_Checkpoint{ -public: - virtual ~AutoStory_Checkpoint() = default; - virtual std::string name() const = 0; - virtual std::string start_text() const = 0; - virtual std::string end_text() const = 0; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const = 0; -}; - - -// --------------------------------------------------------------------------- -// Shared utility functions (implementations in .cpp) -// --------------------------------------------------------------------------- - -// Save the game from the overworld with retries. Returns true if the save was -// verified via overworld detection, false if the blind fallback was used. -bool save_game( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -); - -// Fake save game to be replaced with save_game when program runs smoothly -void checkpoint_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -); - -// Save the game from the overworld, increment the checkpoint counter, -// update stats display, and send a status notification. -void checkpoint_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - CheckpointSaveMode save_mode = CheckpointSaveMode::REAL -); - -// Reload the game from the most recent backup save (auto-save). -// Called on retry when a checkpoint has no hard save (save=false). -// TODO: implement this function. -void reload_backup_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -); - -// If save=true (default): save the game before the first attempt, then run -// `action(attempt_number)`. On failure, reset from HOME and retry. -// If save=false: skip the hard save. On failure, reload from backup save and retry. -// Throws a fatal error after too many consecutive failures. -void checkpoint_reattempt_loop( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - std::function&& action, - bool save = true, - CheckpointSaveMode save_mode = CheckpointSaveMode::REAL -); - -// Forward the explicit checkpoint save mode selected by AutoStory's options. -inline void checkpoint_reattempt_loop( - SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats, - std::function&& action, bool save = true -){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - std::move(action), save, options.checkpoint_save_mode); -} - -// Execute every action in `actions` in order, repeated `times` times. -void repeat(std::initializer_list> actions, size_t times); - -// Tracks the last D-pad direction pressed. Pass one per navigation function -// so each function gets a clean slate on each attempt. -struct DpadState { - DpadPosition last_dir = DPAD_NONE; -}; - -// Press `dir` on the D-pad `times` times (plus one extra if the direction -// changed from the previous call, since the first press turns the character). -void repeat_dpad( - ProControllerContext& context, - DpadState& state, - DpadPosition dir, - Milliseconds press, - Milliseconds hold, - size_t times, - bool include_last_dir = true -); - -// Wait (without pressing any buttons) until a short dialogue box appears. -// Logs `label + " dialogue detected."` when found (omit label to skip logging). -// Throws OperationFailedException if no dialogue appears within `timeout`. -void wait_for_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label = "", - Milliseconds timeout = std::chrono::seconds(60) -); - -// Mash `button` (default B) until the game returns to the free-roaming overworld -// (no dialogue, menu, or battle UI visible). -// Throws OperationFailedException if the overworld is not reached within `timeout`. -void mash_until_dialogue_ends( - VideoStream& stream, - ProControllerContext& context, - Button button = BUTTON_B, - Milliseconds timeout = std::chrono::seconds(60) -); - -// A full Bag screenshot with the desired item highlighted. Paths are relative -// to RESOURCE_PATH(); quantities and selection borders are excluded. -struct BagItemTemplate { - std::string reference_path; - std::string label; - ImageFloatBox icon_box{0.428, 0.754, 0.029, 0.044}; - ImageFloatBox name_box{0.460, 0.755, 0.350, 0.045}; - double max_icon_rmsd = 25.0; - double max_name_rmsd = 20.0; - bool check_name = true; - // Selected item row in the reference screenshot (1-7). - size_t reference_row = 1; -}; - -// Screenshot-only helpers shared by live search and developer tests. -// Cursor 0 means unknown. Array index 0 corresponds to visible row 1. -struct BagItemRowMatch { - double icon_rmsd = 1000.0; - double name_rmsd = 1000.0; - bool matched = false; -}; - -// Pocket positions 1-8: Medicine, Balls, Battle Items, Berries, Other Items, -// TMs, Treasures, Key Items. Zero means the selected tab is uncertain. -int bag_pocket_position(const ImageViewRGB32& frame); -bool bag_list_is_empty(const ImageViewRGB32& frame); -int bag_cursor_position(const ImageViewRGB32& frame); -std::array match_bag_item_rows( - const ImageViewRGB32& frame, - const ImageViewRGB32& reference, - const BagItemTemplate& item -); - -enum class BagMoveAction { UNKNOWN, CONFIRM, UP, DOWN, PAGE_DOWN, REWIND }; -struct BagMovePlan { - BagMoveAction action = BagMoveAction::UNKNOWN; - size_t presses = 0; -}; - -// Describes the next input without sending it. REWIND means repeat Up and -// recheck until row 1; a screenshot cannot tell the total distance to the top. -BagMovePlan plan_bag_item_move(int cursor_row, int item_row); - -enum class BagPocketAction { UNKNOWN, CONFIRM, LEFT, RIGHT }; -struct BagPocketMovePlan { - BagPocketAction action = BagPocketAction::UNKNOWN; - size_t presses = 0; -}; -// Circular eight-pocket route; ties prefer Right. Invalid positions send no input. -BagPocketMovePlan plan_bag_pocket_move(int current, int target); - - -// Starts in the overworld, opens the Bag, and leaves the requested item -// highlighted. Navigates to the requested pocket, then searches its item rows. -// Checks all seven visible rows and the cursor, then confirms the selected -// item in the green header. Makes up to three passes per pocket. -// Limits report an incomplete search, not absence. -// Returns false on missing frames, timeout, or no match; does not use an item. -bool find_bag_item( - VideoStream& stream, - ProControllerContext& context, - const BagItemTemplate& item, - int target_pocket, - int num_icons = 8, - size_t max_scrolls_per_pocket = 512, - std::chrono::milliseconds search_timeout = std::chrono::minutes(10) -); - -// Find Escape Rope, choose Use, and verify a transition back to the overworld. -bool use_escape_rope( - VideoStream& stream, - ProControllerContext& context, - int num_icons = 8 -); - -// Reuse the tutorial's verified Jubilife Center alignment and entry. -void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context); - -// Open the bag, navigate to the repel compartment tab, and use a repel. -// Returns false if the repel compartment tab could not be found. -// num_icons: 5 (early-game layout) or 8 (late-game layout). -bool activate_repel( - VideoStream& stream, - ProControllerContext& context, - int num_icons = 8 -); - -// Press A until the any HM selection arrow appears, confirm with A, then mash B for 3 s. -void use_HM( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -); - -// Walk to the nurse, heal the party, and exit the Pokemon Center. -// `label` names the center for log messages (e.g. "Veilstone"). -// Returns false if the exit black screen was not detected. -bool heal_pokemon( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -); - -enum class FlyPoint { - CanalavCity, - CelesticTown, - EternaCity, - FloaromaTown, - HearthomeCity, - JubilifeCity, - OreburghCity, - PastoriaCity, - PokemonLeagueLower, - PokemonLeagueUpper, - Route221, - SandgemTown, - SolaceonTown, - SnowpointCity, - SunyshoreCity, - TwinleafTown, - VeilstoneCity, - CurrentCity, // cursor already starts on the city; skip all map navigation -}; - -std::string fly_point_name(FlyPoint place); - -// Open the map, move the cursor to either bottom left, bottom right or -// top left, then move the cursor a number of steps, then checks for -// correctness, and mashes A until a black screen is detected -bool fly_to( - VideoStream& stream, - ProControllerContext& context, - FlyPoint place -); - -// Opens the X-menu from the overworld, navigates the cursor to `target`, and presses A. -// num_icons: 5 (early-game layout) or 8 (late-game layout). -void open_menu( - VideoStream& stream, - ProControllerContext& context, - MenuCursorPosition target, - int num_icons -); - -void use_potion_first_pokemon( - VideoStream& stream, - ProControllerContext& context -); - -enum class GearLevel { - Fast, -}; - - -// --------------------------------------------------------------------------- -// Mart buying -// --------------------------------------------------------------------------- - -enum class ShopItem { - FullRestore, - MaxPotion, - HyperPotion, - SuperPotion, - Potion, - Antidote, - ParalyzHeal, - Awakening, - Repel, - SuperRepel, - MaxRepel, - EscapeRope, -}; - -// Pass as the `quantity` field to buy the maximum available amount. -static constexpr int BUY_MAX = -1; - -struct ItemToBuy { - ShopItem item; - int quantity; // positive count to buy, or BUY_MAX -}; - -// Walk up to a vendor (already talking to them), navigate through the Buy menu, -// and purchase each item in `items` in order. The function presses DPAD_DOWN -// until the matching item name is found via OCR, so items may appear in any -// list order. Returns true on success. -bool buy_items( - VideoStream& stream, - ProControllerContext& context, - const std::vector& items, - Language language = Language::English -); - - -// Mount the bicycle (press +) and confirm it is in the requested gear. -// Presses B to toggle gear and uses the Fast-gear spark visual effect to -// determine which gear was entered. If the wrong gear is entered, presses B -// again and re-checks. Player must be on foot when called. -// Returns false if gear state cannot be confirmed after two toggles. -bool get_on_bicycle( - VideoStream& stream, - ProControllerContext& context, - GearLevel gear -); - -} -} -} -#endif +/* BDSP AutoStory Tools + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStoryTools_H +#define PokemonAutomation_PokemonBDSP_AutoStoryTools_H + +#include "CommonFramework/GlobalAutoPaths.h" +#include +#include +#include +#include +#include +#include "Common/Cpp/Options/EnumDropdownOption.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// --------------------------------------------------------------------------- +// Stats +// --------------------------------------------------------------------------- + +struct AutoStoryStats : public StatsTracker{ + AutoStoryStats() + : m_checkpoint(m_stats["Checkpoint"]) + , m_segment(m_stats["Segment"]) + , m_reset(m_stats["Reset"]) + { + m_display_order.emplace_back("Checkpoint"); + m_display_order.emplace_back("Segment"); + m_display_order.emplace_back("Reset"); + } + std::atomic& m_checkpoint; + std::atomic& m_segment; + std::atomic& m_reset; + bool m_first_run = true; +}; + + +// --------------------------------------------------------------------------- +// Options passed to every segment/checkpoint +// --------------------------------------------------------------------------- + +enum class StarterChoice{ + TURTWIG, + CHIMCHAR, + PIPLUP, +}; + +enum class HelpMode{ + NO_OUTSIDE_HELP, + OUTSIDE_HELP, +}; + +enum class OutsideHelpPokemon{ + TORTERRA, +}; + +enum class TorterraMove{ + EARTHQUAKE, + WOOD_HAMMER, + RAZOR_LEAF, + CRUNCH, + GIGA_DRAIN, + IRON_TAIL, + ROCK_TOMB, + FACADE, + STONE_EDGE, + ROCK_SLIDE, + BULLDOZE, + STRENGTH, + ROCK_SMASH, + ROCK_CLIMB, +}; + +// Returns the JSON slug for a TorterraMove (e.g. "earthquake"). +const char* torterra_move_slug(TorterraMove move); + +enum class Trainer { + Veilstone_GalacticGrunts_2v2, + // Extend with new trainer battle IDs as the AutoStory adds them. +}; + +// Returns the trainers.json battle-ID string for a Trainer value. +const char* trainer_slug(Trainer t); + +struct OutsideHelpConfig{ + OutsideHelpPokemon pokemon; + TorterraMove attacks[4]; +}; + +enum class CheckpointSaveMode { REAL, FAKE }; +// Real saves retry one checkpoint. Fake-save runs own one retry loop around +// the selected range; inner checkpoints propagate failures to that owner. +enum class CheckpointRetryScope { CHECKPOINT, SELECTED_RANGE, DEFER_TO_SELECTED_RANGE }; + +struct AutoStoryOptions{ + Language language; + StarterChoice starter_choice; + EventNotificationOption& notif_status_update; + HelpMode help_mode; + OutsideHelpConfig outside_help; + CheckpointSaveMode checkpoint_save_mode = CheckpointSaveMode::REAL; + CheckpointRetryScope checkpoint_retry_scope = CheckpointRetryScope::CHECKPOINT; +}; + + +// --------------------------------------------------------------------------- +// Base classes +// --------------------------------------------------------------------------- + +class AutoStory_Segment{ +public: + virtual ~AutoStory_Segment() = default; + virtual std::string name() const = 0; + virtual std::string start_text() const = 0; + virtual std::string end_text() const = 0; + virtual void run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const = 0; +}; + +class AutoStory_Checkpoint{ +public: + virtual ~AutoStory_Checkpoint() = default; + virtual std::string name() const = 0; + virtual std::string start_text() const = 0; + virtual std::string end_text() const = 0; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const = 0; +}; + + +// --------------------------------------------------------------------------- +// Shared utility functions (implementations in .cpp) +// --------------------------------------------------------------------------- + +// Save the game from the overworld with retries. Returns true if the save was +// verified via overworld detection, false if the blind fallback was used. +bool save_game( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +); + +// Fake save game to be replaced with save_game when program runs smoothly +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +); + +// Save the game from the overworld, increment the checkpoint counter, +// update stats display, and send a status notification. +void checkpoint_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL +); + +// Reload the game from the most recent backup save (auto-save). +// Called on retry when a checkpoint has no hard save (save=false). +// TODO: implement this function. +void reload_backup_save( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context +); + +// If save=true (default): save the game before the first attempt, then run +// `action(attempt_number)`. On failure, reset from HOME and retry. +// If save=false: skip the hard save. On failure, reload from backup save and retry. +// SELECTED_RANGE skips the entry save and owns HOME resets for the range. +// DEFER_TO_SELECTED_RANGE lets an inner failure reach that outer retry loop. +// Throws a fatal error after too many consecutive failures. +void checkpoint_reattempt_loop( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + std::function&& action, + bool save = true, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL, + CheckpointRetryScope retry_scope = CheckpointRetryScope::CHECKPOINT +); + +// Forward the explicit checkpoint save mode selected by AutoStory's options. +inline void checkpoint_reattempt_loop( + SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats, + std::function&& action, bool save = true +){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, + std::move(action), save, options.checkpoint_save_mode, options.checkpoint_retry_scope); +} + +// Execute every action in `actions` in order, repeated `times` times. +void repeat(std::initializer_list> actions, size_t times); + +// Tracks the last D-pad direction pressed. Pass one per navigation function +// so each function gets a clean slate on each attempt. +struct DpadState { + DpadPosition last_dir = DPAD_NONE; +}; + +// Press `dir` on the D-pad `times` times (plus one extra if the direction +// changed from the previous call, since the first press turns the character). +void repeat_dpad( + ProControllerContext& context, + DpadState& state, + DpadPosition dir, + Milliseconds press, + Milliseconds hold, + size_t times, + bool include_last_dir = true +); + +// Wait (without pressing any buttons) until a short dialogue box appears. +// Logs `label + " dialogue detected."` when found (omit label to skip logging). +// Throws OperationFailedException if no dialogue appears within `timeout`. +void wait_for_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label = "", + Milliseconds timeout = std::chrono::seconds(60) +); + +// Mash `button` (default B) until the game returns to the free-roaming overworld +// (no dialogue, menu, or battle UI visible). +// Throws OperationFailedException if the overworld is not reached within `timeout`. +void mash_until_dialogue_ends( + VideoStream& stream, + ProControllerContext& context, + Button button = BUTTON_B, + Milliseconds timeout = std::chrono::seconds(60) +); + +// Scripted trainer battle: choose a 1-based move position each turn, then +// confirm the default target with A and clear narration with B. No target steering. +// A single position is reused for the whole battle. A list is consumed in +// order, then its final position is reused. Empty lists and slots outside 1-4 fail. +// post_battle_dialogue_cleanup must be chosen explicitly: false returns after +// the completed battle-exit transition; true also clears post-fight NPC dialogue. +// Examples: +// fight_trainer_using_moves(stream, context, 2, "Grunt", false); +// const std::vector moves{2, 1, 3}; +// fight_trainer_using_moves(stream, context, moves, "Gym leader", true); +void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, + size_t move, const std::string& label, bool post_battle_dialogue_cleanup); +void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, + const std::vector& moves, const std::string& label, bool post_battle_dialogue_cleanup); + +// A full Bag screenshot with the desired item highlighted. Paths are relative +// to RESOURCE_PATH(); quantities and selection borders are excluded. +struct BagItemTemplate { + std::string reference_path; + std::string label; + ImageFloatBox icon_box{0.428, 0.754, 0.029, 0.044}; + ImageFloatBox name_box{0.460, 0.755, 0.350, 0.045}; + double max_icon_rmsd = 25.0; + double max_name_rmsd = 20.0; + bool check_name = true; + // Selected item row in the reference screenshot (1-7). + size_t reference_row = 1; +}; + +// Screenshot-only helpers shared by live search and developer tests. +// Cursor 0 means unknown. Array index 0 corresponds to visible row 1. +struct BagItemRowMatch { + double icon_rmsd = 1000.0; + double name_rmsd = 1000.0; + bool matched = false; +}; + +// Pocket positions 1-8: Medicine, Balls, Battle Items, Berries, Other Items, +// TMs, Treasures, Key Items. Zero means the selected tab is uncertain. +int bag_pocket_position(const ImageViewRGB32& frame); +// Count normal, unobscured pocket icons; NEW badges prevent a tab match. +size_t bag_clear_tab_count(const ImageViewRGB32& frame); +// Open bag, scan with 250ms Down / 100ms rest (3s cumulative Down per pocket) +// until all 8 tabs match, +// then restore the top of the item list and close the bag. +// Returns false after four bounded pocket passes. +bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons=8); +bool bag_list_is_empty(const ImageViewRGB32& frame); +int bag_cursor_position(const ImageViewRGB32& frame); +std::array match_bag_item_rows( + const ImageViewRGB32& frame, + const ImageViewRGB32& reference, + const BagItemTemplate& item +); + +enum class BagMoveAction { UNKNOWN, CONFIRM, UP, DOWN, PAGE_DOWN, REWIND }; +struct BagMovePlan { + BagMoveAction action = BagMoveAction::UNKNOWN; + size_t presses = 0; +}; + +// Describes the next input without sending it. REWIND means repeat Up and +// recheck until row 1; a screenshot cannot tell the total distance to the top. +BagMovePlan plan_bag_item_move(int cursor_row, int item_row); + +enum class BagPocketAction { UNKNOWN, CONFIRM, LEFT, RIGHT }; +struct BagPocketMovePlan { + BagPocketAction action = BagPocketAction::UNKNOWN; + size_t presses = 0; +}; +// Circular eight-pocket route; ties prefer Right. Invalid positions send no input. +BagPocketMovePlan plan_bag_pocket_move(int current, int target); + + +// Starts in the overworld, opens the Bag, and leaves the requested item +// highlighted. Navigates to the requested pocket, then searches its item rows. +// Checks all seven visible rows and the cursor, then confirms the selected +// item in the green header. Makes up to three passes per pocket. +// Limits report an incomplete search, not absence. +// Returns false on missing frames, timeout, or no match; does not use an item. +bool find_bag_item( + VideoStream& stream, + ProControllerContext& context, + const BagItemTemplate& item, + int target_pocket, + int num_icons = 8, + size_t max_scrolls_per_pocket = 512, + std::chrono::milliseconds search_timeout = std::chrono::minutes(10) +); + +// Find Escape Rope, choose Use, and verify a transition back to the overworld. +bool use_escape_rope( + VideoStream& stream, + ProControllerContext& context, + int num_icons = 8 +); + +// Reuse the tutorial's verified Jubilife Center alignment and entry. +void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context); + +// Open the bag, navigate to the repel compartment tab, and use a repel. +// Returns false if the repel compartment tab could not be found. +// num_icons: early/late menu layout (preserved for existing callers). +// repel_type: 0=Repel, 1=Super Repel, 2=Max Repel. English item names. +bool activate_repel( + VideoStream& stream, + ProControllerContext& context, + int num_icons = 8, + int repel_type = 0 +); + +// Press A until the any HM selection arrow appears, confirm with A, then mash B for 3 s. +void use_HM( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +); + +// Walk to the nurse, heal the party, and exit the Pokemon Center. +// `label` names the center for log messages (e.g. "Veilstone"). +// Returns false if the exit black screen was not detected. +bool heal_pokemon( + VideoStream& stream, + ProControllerContext& context, + const std::string& label +); + +enum class FlyPoint { + CanalavCity, + CelesticTown, + EternaCity, + FloaromaTown, + HearthomeCity, + JubilifeCity, + OreburghCity, + PastoriaCity, + PokemonLeagueLower, + PokemonLeagueUpper, + Route221, + SandgemTown, + SolaceonTown, + SnowpointCity, + SunyshoreCity, + TwinleafTown, + VeilstoneCity, + CurrentCity, // cursor already starts on the city; skip all map navigation +}; + +std::string fly_point_name(FlyPoint place); + +// Open the map, move the cursor to either bottom left, bottom right or +// top left, then move the cursor a number of steps, then checks for +// correctness, and mashes A until a black screen is detected +bool fly_to( + VideoStream& stream, + ProControllerContext& context, + FlyPoint place +); + +// Opens the X-menu from the overworld, navigates the cursor to `target`, and presses A. +// num_icons: 5 (early-game layout) or 8 (late-game layout). +void open_menu( + VideoStream& stream, + ProControllerContext& context, + MenuCursorPosition target, + int num_icons +); + +void use_potion_first_pokemon( + VideoStream& stream, + ProControllerContext& context +); + +enum class GearLevel { + Fast, +}; + + +// --------------------------------------------------------------------------- +// Mart buying +// --------------------------------------------------------------------------- + +enum class ShopItem { + FullRestore, + MaxPotion, + HyperPotion, + SuperPotion, + Potion, + Antidote, + ParalyzHeal, + Awakening, + Repel, + SuperRepel, + MaxRepel, + EscapeRope, +}; + +// Pass as the `quantity` field to buy the maximum available amount. +static constexpr int BUY_MAX = -1; + +struct ItemToBuy { + ShopItem item; + int quantity; // positive count to buy, or BUY_MAX +}; + +// Walk up to a vendor (already talking to them), navigate through the Buy menu, +// and purchase each item in `items` in order. The function presses DPAD_DOWN +// until the matching item name is found via OCR, so items may appear in any +// list order. Returns true on success. +bool buy_items( + VideoStream& stream, + ProControllerContext& context, + const std::vector& items, + Language language = Language::English +); + + +// Mount the bicycle (press +) and confirm it is in the requested gear. +// Presses B to toggle gear and uses the Fast-gear spark visual effect to +// determine which gear was entered. If the wrong gear is entered, presses B +// again and re-checks. Player must be on foot when called. +// Returns false if gear state cannot be confirmed after two toggles. +bool get_on_bicycle( + VideoStream& stream, + ProControllerContext& context, + GearLevel gear +); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 1740c14a4a..d0ff57e596 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -1,142 +1,2157 @@ -/* BDSP AutoStory - Segment 01 - * From after choosing the starter Pokemon until after acquiring the first badge. - * From: https://github.com/PokemonAutomation/ - */ - -#include "PokemonBDSP_AutoStory_Segment_01.h" -#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" - -namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } - std::string AutoStory_Segment_01::start_text() const { return "Start: Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } - - void AutoStory_Segment_01::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_checkpoint_0101().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0102().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0103().run_checkpoint(env, context, options, stats); - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); - } - - - std::string AutoStory_checkpoint_0101::name() const { return "01.01 - Maxed Starter from Home"; } - std::string AutoStory_checkpoint_0101::start_text() const { return "Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_checkpoint_0101::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } - // ======================== - // Checkpoint 0101 Wrapper - // ======================== - void AutoStory_checkpoint_0101::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0101(env, context, options, stats); - } - - - void checkpoint_0101( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - jubilife_to_oreburgh_gate(env, env.console, context); - oreburgh_gate_nav(env, env.console, context); - }, - true - ); - } - - // ======================== - // Checkpoint 0102 Wrapper - // ======================== - - std::string AutoStory_checkpoint_0102::name() const { return "01.02 - Oreburgh Mine"; } - std::string AutoStory_checkpoint_0102::start_text() const { return AutoStory_checkpoint_0101().end_text(); } - std::string AutoStory_checkpoint_0102::end_text() const { return "Inside Oreburgh Gym, ready to navigate to Roark."; } - - void AutoStory_checkpoint_0102::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0102(env, context, options, stats); - } - - - void checkpoint_0102( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - oreburgh_gate_to_mine(env, env.console, context); - oreburgh_mine_nav(env.console, context); - // Standalone test entry points are not part of the checkpoint route. - // oreburgh_mine_first_leg_test(env.console, context); - // oreburgh_mine_route_and_escape_test(env.console, context); - oreburgh_mine_to_gym(env, env.console, context); - }, - - true - ); - } - - // ======================== - // Checkpoint 0103 Wrapper - // ======================== - - std::string AutoStory_checkpoint_0103::name() const { return "01.03 - Oreburgh Gym"; } - std::string AutoStory_checkpoint_0103::start_text() const { return AutoStory_checkpoint_0102().end_text(); } - std::string AutoStory_checkpoint_0103::end_text() const { return "Standing at Roark after defeating him and acquiring the first badge."; } - - void AutoStory_checkpoint_0103::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0103(env, context, options, stats); - } - - - void checkpoint_0103( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - //#. Oreburgh Gym navigation and battle - gym_Puzzle_01(env, env.console, context); - - }, - - true - ); - } - - - } - } -} +/* BDSP AutoStory - Segment 01 + * From after choosing the starter Pokemon until after acquiring the first badge. + * From: https://github.com/PokemonAutomation/ + */ + +#include "PokemonBDSP_AutoStory_Segment_01.h" +#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" + +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include +#include +#include +#include +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/ImageTools/ImageDiff.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +using namespace std::chrono_literals; + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + + // Prefer the source Packages resources when running a development build. + // Installed builds continue to use their bundled Resources directory. + static const std::string& mine_navigation_resource_path() { + static const std::string path = [] { + QDir directory(QCoreApplication::applicationDirPath()); + for (size_t level = 0; level < 5; level++) { + const QString candidate = directory.filePath( + "Packages/Resources/PokemonBDSP/Autostory/Oreburg_mine_nav/" + ); + if (QFileInfo(candidate).isDir()) { + return QDir::cleanPath(candidate).toStdString() + "/"; + } + if (!directory.cdUp()) { + break; + } + } + return RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; + }(); + return path; + } + + enum class MoveDirection { UP, DOWN, LEFT, RIGHT }; + + // Left joystick vector for a direction (matches the game's joystick convention used in this file). + static std::pair joystick_for(MoveDirection direction) { + switch (direction) { + case MoveDirection::UP: return { 0.0, 1.0 }; + case MoveDirection::DOWN: return { 0.0, -1.0 }; + case MoveDirection::LEFT: return { -1.0, 0.0 }; + case MoveDirection::RIGHT: return { 1.0, 0.0 }; + } + return { 0.0, 0.0 }; + } + + // Runs from a wild battle that is already in progress (menu visible or about to be). + // Throws OperationFailedException if it cannot escape within `timeout`. + + // The main game menu is independent evidence that battle has ended. + + + + + + + + // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. + // Returns false (without throwing) only if every attempt times out, so callers can skip + // an optional interaction. Multiple attempts guard against a single missed detection. + static bool try_detect_dialogue( + VideoStream& stream, + ProControllerContext& context, + const std::string& label, + size_t attempts = 3, + std::chrono::milliseconds window = 5000ms + ) { + for (size_t attempt = 0; attempt < attempts; attempt++) { + ShortDialogWatcher watcher_cyan(COLOR_CYAN); + ShortDialogWatcher watcher_red(COLOR_RED); + if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0) { + stream.log(label + " dialogue detected."); + return true; + } + stream.log( + label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + std::to_string(attempts) + ").", + COLOR_ORANGE + ); + } + return false; + } + + struct DoorwayWalkOptions { + BattleType battle_type = BattleType::STANDARD; + // Active movement per step. Time spent in battles or waiting is never counted. + std::chrono::milliseconds step = 200ms; + // Extra walking allowed beyond `travel_time` before giving up. + std::chrono::milliseconds grace = 1600ms; + // How long to watch for a black screen after each step. + std::chrono::milliseconds black_screen_window = 1500ms; + // After a black screen, how long to wait for a battle menu before deciding it was a doorway. + std::chrono::milliseconds battle_menu_window = 8000ms; + size_t max_battles = 10; + // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). + bool detect_white_screen = false; + }; + + // Reusable: walk in `direction` for a cumulative `travel_time` of active movement until + // a black screen NOT followed by a battle menu (a doorway/area transition) is seen. + // Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are + // escaped and do not consume the time budget. + // Throws OperationFailedException if the doorway is not found within + // travel_time + options.grace, if more than options.max_battles occur, or if a battle + // cannot be escaped. + // Returns the total active walking time used. + static std::chrono::milliseconds walk_until_doorway( + VideoStream& stream, + ProControllerContext& context, + MoveDirection direction, + std::chrono::milliseconds travel_time, + const DoorwayWalkOptions& options = DoorwayWalkOptions() + ) { + enum class NavState { WALKING, BLACK_SCREEN, IN_BATTLE, DONE }; + + const auto [x, y] = joystick_for(direction); + const std::chrono::milliseconds max_travel = travel_time + options.grace; + + std::chrono::milliseconds walked = 0ms; + size_t battles = 0; + NavState state = NavState::WALKING; + + while (state != NavState::DONE) { + switch (state) { + case NavState::WALKING: { + if (walked >= max_travel) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "walk_until_doorway: Doorway not found within time budget.", + stream + ); + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {x, y}, options.step, 0ms); + context.wait_for_all_requests(); + walked += options.step; + + BlackScreenWatcher black_screen; + WhiteScreenWatcher white_screen; + if (options.detect_white_screen) { + int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); + if (ret == 0) { + state = NavState::BLACK_SCREEN; + } else if (ret == 1) { + stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); + state = NavState::DONE; + } + } else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0) { + state = NavState::BLACK_SCREEN; + } + break; + } + case NavState::BLACK_SCREEN: { + stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); + BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); + if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0) { + state = NavState::IN_BATTLE; + } else { + state = NavState::DONE; + } + break; + } + case NavState::IN_BATTLE: { + if (++battles > options.max_battles) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "walk_until_doorway: Too many battles while walking.", + stream + ); + } + stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + std::to_string(travel_time.count()) + "ms.", COLOR_ORANGE); + escape_wild_battle(stream, context, options.battle_type); + state = NavState::WALKING; + break; + } + case NavState::DONE: + break; + } + } + stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", COLOR_GREEN); + return walked; + } + + + + + + + + // ---------------------------------------------------------------- + // Segment 1 Part 1: Jubilife City to Oreburgh Gate + // Navigates the player from Jubilife City to the Oreburgh Gate, including the + // first Barry fight. + // --------------------------------------------------------------------------- + void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + // Wait for game to fully accept input after save/overworld return + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1465ms, 0ms); + pbf_wait(context, 127ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); + pbf_wait(context, 698ms); + leave_pokemon_center_before_barry_1(stream, context); + pbf_wait(context, 2340ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); + pbf_wait(context, 348ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2694ms, 0ms); + pbf_wait(context, 482ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3810ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); + pbf_wait(context, 1647ms); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + wait_for_dialogue(stream, context, "Dallas fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Dallas + context.wait_for_all_requests(); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); + walk_through_grass(stream, context, 1, 0, 10, "right through grass"); + //there is a small chance that the player dodges sSebastian, so this + //function addresses that edge case + if (try_detect_dialogue(stream, context, "Sebastian fight")) { + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Sebastian + context.wait_for_all_requests(); + } else { + stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); + } + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2000ms, 0ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { + BlackScreenWatcher black_screen; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + for (size_t i = 0; i < 10; i++) { + pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); + } + pbf_move_left_joystick(context, { 0, 1 }, 10000ms, 0ms); + }, + { black_screen } + ); + if (ret == 0) { + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + + if (!black_screen_found) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", + stream + ); + } + } + void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {1.000000, -0.000000 }, 879ms, 0ms); + wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); + pbf_mash_button(context, BUTTON_B, 15000ms); + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + //Similar to sebastian, an edge case exists where we run into too many + //wild battles and don't reach curtis or diana. This detect dialog function solves that. + while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)) { + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Curtis + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)) { + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context);//fighting Diana + // Walk right until the gate exit transition (white screen, or black screen without a battle menu). + // Wild battles are escaped without using up the budget. Throws on failure so the + // checkpoint is retried. + DoorwayWalkOptions gate_exit_options; + gate_exit_options.detect_white_screen = true; + walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); + context.wait_for_all_requests(); + } + // --------------------------------------------------------------------------- + // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine + // Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. + // Navigates to the mine. + // ---------------------------------------------------------------------------- + void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); + while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)) { + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); + } + pbf_mash_button(context, BUTTON_B, 12000ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); + pbf_wait(context, 23ms); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { + BlackScreenWatcher black_screen; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); + }, + { black_screen } + ); + if (ret == 0) { + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + + if (!black_screen_found) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "oreburgh_gate_to_mine: No black screen after 3 attempts.", + stream + ); + } + + // Let the mine entrance transition fully finish so the next step's black screen + // watcher can't pick up the tail of this one. + BlackScreenOverWatcher black_over; + wait_until(stream, context, 6000ms, {{black_over}}); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + } + + // Diagnostic only: identifies the entrance start without moving the player. + + + + struct MineCenterOffset { + bool valid; + double offset; + double score; + }; + + // Finds the sideways shift of stationary scenery relative to the center picture. + // Positive offset: scenery shifted right, so the player is left of center. + static MineCenterOffset measure_mine_center_offset( + const ImageViewRGB32& reference, + const ImageViewRGB32& live + ) { + if (!reference || !live) { + return { false, 0.0, 1000.0 }; + } + + // Use a fixed working size so the search works at different capture resolutions. + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const ImageRGB32 live_scaled = live.scale_to(960, 540); + + // Entrance beam and both posts. Avoid the player and shadow. + const std::array boxes = { + ImageFloatBox(0.35, 0.285, 0.30, 0.07), + ImageFloatBox(0.355, 0.37, 0.035, 0.23), + ImageFloatBox(0.615, 0.37, 0.035, 0.23) + }; + + const int search_radius = (int)std::lround(0.10*live_scaled.width()); + std::vector scores(2*search_radius+1); + std::vector worst_regions(2*search_radius+1); + + int best_shift = 0; + double best_score = 1000.0; + + // Search up to 10% of the screen width in either direction. + for (int shift = -search_radius; shift <= search_radius; shift++) { + double total = 0.0; + double worst = 0.0; + + for (const ImageFloatBox& box : boxes) { + const ImageViewRGB32 reference_crop = + extract_box_reference(reference_scaled, box); + + // Translate the same crop horizontally in the live image. + const ImageViewRGB32 live_crop = + extract_box_reference(live_scaled, box, shift, 0); + + const double score = + ImageMatch::pixel_RMSD(reference_crop, live_crop); + + total += score; + if (score > worst) { + worst = score; + } + } + + const double average = total / boxes.size(); + const size_t index = static_cast(shift + search_radius); + + scores[index] = average; + worst_regions[index] = worst; + + if (average < best_score) { + best_score = average; + best_shift = shift; + } + } + + // Reject another similarly good match well away from the chosen position. + double alternative_score = 1000.0; + + for (int shift = -search_radius; shift <= search_radius; shift++) { + if (std::abs(shift - best_shift) <= std::lround(0.0125*live_scaled.width())) { + continue; + } + + const double score = scores[static_cast(shift + search_radius)]; + if (score < alternative_score) { + alternative_score = score; + } + } + + const size_t best_index = static_cast(best_shift + search_radius); + + const bool valid = + best_score <= 20.0 && + worst_regions[best_index] <= 30.0 && + alternative_score - best_score >= 3.0 && + std::abs(best_shift) < search_radius; + + return { + valid, + static_cast(best_shift) / live_scaled.width(), + best_score + }; + } + // Test only: align with the entrance center, then stop. + static bool oreburgh_mine_alignment_test( + VideoStream& stream, + ProControllerContext& context + ) { + const ImageRGB32 reference( + mine_navigation_resource_path() + "mine_start_center.png" + ); + + constexpr size_t MAX_MOVEMENTS = 12; + constexpr double CENTER_TOLERANCE = 0.008; + + size_t movements = 0; + size_t center_streak = 0; + + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + while (true) { + const VideoSnapshot frame = stream.video().snapshot(); + + const MineCenterOffset measurement = + measure_mine_center_offset(reference, frame); + + stream.log( + "[Mine align] Offset=" + + std::to_string(measurement.offset) + + ", score=" + std::to_string(measurement.score) + + (measurement.valid ? ", trusted" : ", unsure") + ); + + if (!measurement.valid) { + stream.log( + "[Mine align] Entrance position unsure. Stopping.", + COLOR_ORANGE + ); + return false; + } + + if (std::abs(measurement.offset) <= CENTER_TOLERANCE) { + center_streak++; + + if (center_streak >= 3) { + stream.log( + "[Mine align] Center reached. Test complete.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; + } + + center_streak = 0; + + // Check the latest position before enforcing the movement limit. + if (movements >= MAX_MOVEMENTS) { + stream.log( + "[Mine align] Movement limit reached. Stopping.", + COLOR_ORANGE + ); + return false; + } + + const double x = measurement.offset > 0.0 ? 1.0 : -1.0; + + // Smaller movements when close to the center. + const auto duration = + std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; + + stream.log( + x > 0.0 + ? "[Mine align] Moving a little right." + : "[Mine align] Moving a little left." + ); + + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + + const int result = run_until( + stream, + context, + [x, duration](ProControllerContext& ctx) { + pbf_move_left_joystick( + ctx, { x, 0.0 }, duration, 0ms + ); + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + }, + { black, menu, dialog } + ); + + if (result >= 0) { + stream.log( + "[Mine align] Battle or transition detected. " + "Stopping the alignment test.", + COLOR_ORANGE + ); + return false; + } + + movements++; + } + } + // Confirm a battle before using the existing escape helper. + static bool mine_escape_and_settle( + VideoStream& stream, + ProControllerContext& context + ) { + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + + if (wait_until(stream, context, 12s, { {menu} }) != 0) { + stream.log( + "[Mine encounter] Battle menu not confirmed. Stopping.", + COLOR_ORANGE + ); + return false; + } + + stream.log("[Mine encounter] Battle confirmed. Escaping."); + escape_wild_battle(stream, context); + context.wait_for_all_requests(); + + // Check the return waits instead of ignoring their results. + BlackScreenWatcher visible( + COLOR_YELLOW, + ImageFloatBox(0.1, 0.1, 0.8, 0.8), + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms + ); + + if (wait_until(stream, context, 8s, { {visible} }) != 0) { + stream.log( + "[Mine encounter] Screen did not return. Stopping.", + COLOR_ORANGE + ); + return false; + } + + + pbf_wait(context, 800ms); + context.wait_for_all_requests(); + + stream.log( + "[Mine encounter] Escaped. Returning to the rock-walking loop.", + COLOR_GREEN + ); + return true; + } + + // Temporary test: wiggle along the corridor until a battle is detected. + static bool mine_force_encounter_test( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine encounter] Starting forced encounter wiggle."); + + const auto deadline = + std::chrono::steady_clock::now() + 60s; + + size_t pulses = 0; + + while (std::chrono::steady_clock::now() < deadline) { + // Alternate down and up. Do not move sideways. + const double y = pulses % 2 == 0 ? -1.0 : 1.0; + + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + + const int result = run_until( + stream, + context, + [y](ProControllerContext& ctx) { + pbf_move_left_joystick( + ctx, { 0.0, y }, 120ms, 0ms + ); + pbf_wait(ctx, 400ms); + ctx.wait_for_all_requests(); + }, + { black, menu, dialog } + ); + + if (result >= 0) { + stream.log( + "[Mine encounter] Wiggle interrupted. Checking for battle." + ); + return mine_escape_and_settle(stream, context); + } + + pulses++; + } + + stream.log( + "[Mine encounter] No encounter within 60 seconds. " + "Test incomplete; stopping.", + COLOR_ORANGE + ); + return false; + } + + // Watch throughout an action, including its movement and picture checks. + // After an encounter, restart the action using fresh watcher state. + template + static bool mine_run_with_encounters( + VideoStream& stream, + ProControllerContext& context, + Action action + ) { + for (size_t encounters = 0;;) { + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + bool completed = false; + + const int result = run_until( + stream, context, + [&](ProControllerContext& ctx) { + completed = action(ctx); + }, + { black, menu, dialog } + ); + + if (result < 0) { + return completed; + } + + if (++encounters > 10) { + stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); + return false; + } + + if (!mine_escape_and_settle(stream, context)) { + return false; + } + + stream.log("[Mine encounter] Escaped; resuming interrupted action."); + } + } + + static bool mine_timed_walk( + VideoStream& stream, + ProControllerContext& context, + double x, + double y, + std::chrono::milliseconds duration + ) { + auto remaining = duration; + + return mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + while (remaining > 0ms) { + const auto pulse = remaining < 100ms ? remaining : 100ms; + pbf_move_left_joystick(ctx, { x, y }, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + + // An interrupted pulse is retried after escaping. + // This is a timing budget, not exact distance tracking. + remaining -= pulse; + } + return true; + } + ); + } + + + + struct MinePictureScore { + double average = 1000.0; + double worst = 1000.0; + double horizontal = 0; + double vertical = 0; + }; + + // Supports four, five, or six regions. Existing four-region callers keep + // their original search ranges through the default arguments. + template + static MinePictureScore mine_score_picture( + const ImageRGB32& reference, + const ImageRGB32& live, + const std::array& boxes, + double horizontal_range = 1.0/60, + double vertical_range = 2.0/135 + ) { + MinePictureScore best; + const int radius_x = (int)std::lround(horizontal_range*live.width()); + const int radius_y = (int)std::lround(vertical_range*live.height()); + for (int y = -radius_y; y <= radius_y; y++) { + for (int x = -radius_x; x <= radius_x; x++) { + double total = 0.0; + double worst = 0.0; + for (const ImageFloatBox& box : boxes) { + const ImageViewRGB32 ref = extract_box_reference(reference, box); + const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); + const double score = ImageMatch::pixel_RMSD(ref, patch); + total += score; + if (score > worst) worst = score; + } + const double average = total / boxes.size(); + if (average < best.average) { + best.average = average; + best.worst = worst; + best.horizontal = (double)x/live.width(); + best.vertical = (double)y/live.height(); + } + } + } + return best; + } + + + static bool mine_check_picture( + VideoStream& stream, + ProControllerContext& context, + const char* filename, + const std::array& boxes, + const std::string& label, + size_t max_attempts = 8, + double max_average = 20.0, + double max_patch = 30.0 + ) { + const ImageRGB32 reference( + mine_navigation_resource_path() + filename + ); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + + const bool confirmed = mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + // The watcher stays active throughout settling and sampling. + // There is no separate 500ms watcher wait before each sample. + ctx.wait_for_all_requests(); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + + // Restart confirmation after a battle; do not reuse old matches. + size_t matching_frames = 0; + + for (size_t attempt = 0; attempt < max_attempts; attempt++) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + const MinePictureScore score = + mine_score_picture(reference_scaled, live, boxes); + + stream.log( + label + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + ); + + if (score.average <= max_average && score.worst <= max_patch) { + if (++matching_frames >= 3) { + return true; + } + } + else { + matching_frames = 0; + } + + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + } + + return false; + } + ); + + stream.log( + label + (confirmed ? ": confirmed." : ": not confirmed."), + confirmed ? COLOR_GREEN : COLOR_ORANGE + ); + return confirmed; + } + + struct MineWalkOptions { + double x = 0.0; + double y = 0.0; + std::chrono::milliseconds pulse = 40ms; + std::chrono::milliseconds settle = 500ms; + std::chrono::milliseconds initial_settle = 0ms; + size_t max_movements = 30; + double horizontal_range = 0.0125; + double vertical_range = 1.0/90; + double max_average = 20.0; + double max_patch = 30.0; + }; + + template + static bool mine_walk_to_endpoints( + VideoStream& stream, + ProControllerContext& context, + const std::array& files, + const std::array, Endpoints>& boxes, + const MineWalkOptions& options, + const std::string& label, + bool force_encounter = false + ) { + std::array, Endpoints> references; + for (size_t index = 0; index < Endpoints; index++) { + const ImageRGB32 image( + mine_navigation_resource_path() + files[index] + ); + references[index] = std::make_unique(image.scale_to(960, 540)); + } + + size_t movements = 0; + bool forced_completed = false; + + while (true) { + bool request_forced_test = false; + const bool reached = mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + if (options.initial_settle > 0ms) { + pbf_wait(ctx, options.initial_settle); + ctx.wait_for_all_requests(); + } + // Reset the match history whenever an encounter interrupts us. + size_t matching_frames = 0; + int previous_endpoint = -1; + + while (true) { + ctx.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + int accepted = -1; + double accepted_average = 1000.0; + + for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { + const MinePictureScore score = mine_score_picture( + *references[endpoint], live, boxes[endpoint], + options.horizontal_range, options.vertical_range + ); + stream.log( + label + " Endpoint " + std::to_string(endpoint + 1) + + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + + ", screen-relative offset=(" + std::to_string(score.horizontal) + + "," + std::to_string(score.vertical) + ")" + ); + + if (score.average <= options.max_average && + score.worst <= options.max_patch && + score.average < accepted_average) { + accepted = static_cast(endpoint); + accepted_average = score.average; + } + } + + if (accepted >= 0) { + matching_frames = accepted == previous_endpoint + ? matching_frames + 1 : 1; + previous_endpoint = accepted; + if (matching_frames >= 3) { + stream.log( + label + " Endpoint " + std::to_string(accepted + 1) + + " confirmed.", COLOR_GREEN + ); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + previous_endpoint = -1; + + // Exit the guard before invoking the diagnostic wiggle: + // it has its own encounter watcher and must not be nested. + if (force_encounter && !forced_completed && movements >= 4) { + request_forced_test = true; + return false; + } + + if (movements >= options.max_movements) { + stream.log(label + ": movement limit reached.", COLOR_ORANGE); + return false; + } + + // Count issued commands, including interrupted commands. + movements++; + pbf_move_left_joystick( + ctx, { options.x, options.y }, options.pulse, 0ms + ); + pbf_wait(ctx, options.settle); + ctx.wait_for_all_requests(); + } + } + ); + + if (reached) return true; + if (!request_forced_test) return false; + if (!mine_force_encounter_test(stream, context)) return false; + forced_completed = true; + stream.log(label + ": forced encounter completed; resuming."); + } + } + + static bool oreburgh_mine_walk_to_first_rock( + VideoStream& stream, + ProControllerContext& context, + bool force_encounter = false + ) { + const std::array files = { "mine_path_01.png" }; + const std::array, 1> boxes = {{ + {{ + ImageFloatBox(0.482, 0.585, 0.055, 0.055), + ImageFloatBox(0.335, 0.670, 0.050, 0.055), + ImageFloatBox(0.412, 0.670, 0.050, 0.055), + ImageFloatBox(0.570, 0.752, 0.045, 0.055), + ImageFloatBox(0.645, 0.752, 0.045, 0.055), + ImageFloatBox(0.570, 0.290, 0.080, 0.120) + }} + }}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 80ms; + options.max_movements = 40; + options.horizontal_range = 0.025; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine path 01]", force_encounter + ); + } + + static bool oreburgh_mine_move_right( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; + const std::array, 2> boxes = {{ + {{ + ImageFloatBox(0.400, 0.585, 0.055, 0.055), + ImageFloatBox(0.245, 0.670, 0.050, 0.055), + ImageFloatBox(0.322, 0.670, 0.050, 0.055), + ImageFloatBox(0.480, 0.752, 0.045, 0.055), + ImageFloatBox(0.555, 0.752, 0.045, 0.055), + ImageFloatBox(0.485, 0.290, 0.080, 0.090) + }}, + {{ + ImageFloatBox(0.325, 0.585, 0.055, 0.055), + ImageFloatBox(0.170, 0.670, 0.050, 0.055), + ImageFloatBox(0.244, 0.670, 0.050, 0.055), + ImageFloatBox(0.395, 0.752, 0.045, 0.055), + ImageFloatBox(0.470, 0.752, 0.045, 0.055), + ImageFloatBox(0.415, 0.290, 0.080, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = 1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 20; + options.horizontal_range = 0.0125; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine right]" + ); + } + + static bool oreburgh_mine_down_past_central_rock( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; + const std::array, 2> boxes = {{ + {{ + ImageFloatBox(0.345, 0.420, 0.040, 0.045), + ImageFloatBox(0.195, 0.500, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.410, 0.590, 0.040, 0.045), + ImageFloatBox(0.480, 0.590, 0.040, 0.045), + ImageFloatBox(0.445, 0.180, 0.060, 0.090) + }}, + {{ + ImageFloatBox(0.420, 0.420, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.345, 0.500, 0.040, 0.045), + ImageFloatBox(0.485, 0.590, 0.040, 0.045), + ImageFloatBox(0.555, 0.590, 0.040, 0.045), + ImageFloatBox(0.520, 0.180, 0.060, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 40ms; + options.max_movements = 30; + // options.horizontal_range = 0.0125; + // Path 02 can be shifted horizontally after the first right endpoint. + // The screenshot regression matches at about -2.08% of screen width. + options.horizontal_range = 0.025; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine down 02]" + ); + } + + static bool oreburgh_mine_move_left_to_gap( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array files = { "mine_path_03.png" }; + const std::array, 1> boxes = {{ + {{ + ImageFloatBox(0.340, 0.495, 0.040, 0.045), + ImageFloatBox(0.410, 0.495, 0.040, 0.045), + ImageFloatBox(0.555, 0.580, 0.040, 0.045), + ImageFloatBox(0.625, 0.580, 0.040, 0.045), + ImageFloatBox(0.580, 0.180, 0.060, 0.090) + }} + }}; + + MineWalkOptions options; + options.x = -1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 30; + options.horizontal_range = 0.0125; + return mine_walk_to_endpoints( + stream, context, files, boxes, options, "[Mine left 03]" + ); + } + + + + // Confirm we are far enough down before beginning the leftward section. + + + // Each picture stage owns its movement and matching settings. + struct MinePictureStep { + const char* file; + const char* label; + std::array boxes; + double x; + double y; + std::chrono::milliseconds pulse = 80ms; + size_t max_movements = 30; + double max_average = 20.0; + double max_patch = 30.0; + }; + + static bool mine_run_picture_step( + VideoStream& stream, ProControllerContext& context, + const MinePictureStep& step + ) { + MineWalkOptions options; + options.x = step.x; + options.y = step.y; + options.pulse = step.pulse; + options.max_movements = step.max_movements; + options.max_average = step.max_average; + options.max_patch = step.max_patch; + options.horizontal_range = 1.0/60; + options.vertical_range = 2.0/135; + options.initial_settle = 700ms; + options.settle = 700ms; + const std::array files = {step.file}; + const std::array, 1> boxes = {{step.boxes}}; + return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); + } + + static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_05.png", "[Mine endpoint 05]", + { + ImageFloatBox(0.240, 0.240, 0.055, 0.080), + ImageFloatBox(0.390, 0.220, 0.050, 0.090), + ImageFloatBox(0.360, 0.090, 0.030, 0.030), + ImageFloatBox(0.550, 0.140, 0.040, 0.040) + }, + 0.0, -1.0, 60ms, 10, 20.0, 30.0 + }; + return mine_run_picture_step(stream, context, step); + } + + // Timed movement through paths 6 and 7, with picture checks. + static bool oreburgh_mine_timed_to_07( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine 04-07] Walking down for 1500ms."); + + if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)) { + return false; + } + + if (!oreburgh_mine_confirm_05(stream, context)) { + return false; + } + + stream.log("[Mine 04-07] Walking left for 3000ms."); + + if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)) { + return false; + } + + // Path 6: crate, nearby small rock, large rock, upper-right small rock. + const std::array path6_boxes = { + ImageFloatBox(0.350, 0.250, 0.085, 0.075), + ImageFloatBox(0.405, 0.510, 0.035, 0.040), + ImageFloatBox(0.520, 0.300, 0.050, 0.080), + ImageFloatBox(0.620, 0.350, 0.030, 0.040) + }; + + if (!mine_check_picture( + stream, context, + "mine_path_06.png", path6_boxes, + "[Mine midpoint 06]" + )) { + return false; + } + + stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); + + if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)) { + return false; + } + + // Path 7: crate and three visible rock surfaces. + const std::array path7_boxes = { + ImageFloatBox(0.365, 0.025, 0.070, 0.055), + ImageFloatBox(0.420, 0.270, 0.035, 0.040), + ImageFloatBox(0.560, 0.415, 0.025, 0.040), + ImageFloatBox(0.530, 0.650, 0.060, 0.090) + }; + + return mine_check_picture( + stream, context, + "mine_path_07.png", path7_boxes, + "[Mine endpoint 07]" + ); + } + + // Move left from path 7 to the position shown in path 8. + static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_08.png", "[Mine endpoint 08]", + { + ImageFloatBox(0.495, 0.035, 0.070, 0.055), + ImageFloatBox(0.550, 0.270, 0.035, 0.040), + ImageFloatBox(0.695, 0.420, 0.030, 0.040), + ImageFloatBox(0.555, 0.585, 0.035, 0.040) + }, + -1.0, 0.0, 40ms, 20, 20.0, 30.0 + }; + return mine_run_picture_step(stream, context, step); + } + + // Walk down along the left wall to path 9. + + static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context) { + const MinePictureStep step = { + "mine_path_09.png", "[Mine endpoint 09]", + { + ImageFloatBox(0.550, 0.115, 0.030, 0.040), + ImageFloatBox(0.550, 0.390, 0.035, 0.040), + ImageFloatBox(0.620, 0.390, 0.025, 0.035), + ImageFloatBox(0.500, 0.720, 0.085, 0.080) + }, + 0.0, -1.0, 40ms, 25, 25.0, 35.0 + }; + return mine_run_picture_step(stream, context, step); + } + + + + // Require dialogue to appear first. + // Then finish only after it has been absent for one continuous second. + static bool mine_finish_item_dialogue( + VideoStream& stream, + ProControllerContext& context + ) { + ShortDialogDetector dialogue(COLOR_YELLOW); + + bool saw_dialogue = false; + auto absent_since = std::chrono::steady_clock::now(); + + const auto start = std::chrono::steady_clock::now(); + const auto deadline = start + 30s; + + stream.log("[Mine item] Starting interaction."); + + while (std::chrono::steady_clock::now() < deadline) { + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + + if (!frame) { + stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); + return false; + } + + const auto now = std::chrono::steady_clock::now(); + const bool visible = dialogue.detect(frame); + + if (visible) { + if (!saw_dialogue) { + stream.log("[Mine item] Dialogue detected."); + } + + saw_dialogue = true; + absent_since = now; + + // Short mash bursts allow frequent checks. + pbf_mash_button(context, BUTTON_A, 100ms); + } + else if (!saw_dialogue) { + if (now - start >= 10s) { + stream.log( + "[Mine item] No dialogue appeared. Stopping.", + COLOR_ORANGE + ); + return false; + } + + // Try to start the interaction. + pbf_press_button(context, BUTTON_A, 60ms, 100ms); + } + else if (now - absent_since >= 1s) { + stream.log( + "[Mine item] Dialogue absent for one second. Finished.", + COLOR_GREEN + ); + return true; + } + + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + + stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); + return false; + } + + + static bool oreburgh_mine_09_to_14( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array path10_boxes = { + ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate + ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock + ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock + ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock + }; + + const MinePictureStep path10_boxes_step = { + "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path10_boxes_step)) { + return false; + } + + const std::array path11_boxes = { + ImageFloatBox(0.405, 0.560, 0.070, 0.080), + ImageFloatBox(0.475, 0.270, 0.030, 0.040), + ImageFloatBox(0.575, 0.290, 0.045, 0.080), + ImageFloatBox(0.550, 0.575, 0.030, 0.040) + }; + + const MinePictureStep path11_boxes_step = { + "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path11_boxes_step)) { + return false; + } + + const std::array path12_boxes = { + ImageFloatBox(0.260, 0.560, 0.080, 0.080), + ImageFloatBox(0.355, 0.270, 0.025, 0.035), + ImageFloatBox(0.435, 0.290, 0.040, 0.070), + ImageFloatBox(0.415, 0.585, 0.025, 0.035) + }; + + const MinePictureStep path12_boxes_step = { + "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path12_boxes_step)) { + return false; + } + + const std::array path13_boxes = { + ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate + ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate + ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock + ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock + }; + + const MinePictureStep path13_boxes_step = { + "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path13_boxes_step)) { + return false; + } + + // Turn left, then verify position before starting the interaction. + pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + if (!mine_check_picture( + stream, context, + "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 + )) { + return false; + } + stream.log("[Mine item] Position confirmed. Pressing A."); + + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + context.wait_for_all_requests(); + + return mine_finish_item_dialogue(stream, context); + } + + // Compare a reference patch with a shifted live patch using the same + // brightness adjustment as the native ImageMatchDetector. + static double mine_offset_patch_score( + const ImageRGB32& reference, + const ImageRGB32& live, + const ImageFloatBox& box, + double horizontal, + double vertical + ) { + const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); + ImageFloatBox shifted = box; + shifted.x += horizontal; + shifted.y += vertical; + const ImageViewRGB32 live_crop = extract_box_reference(live, shifted); + ImageRGB32 adjusted = live_crop.scale_to( + reference_crop.width(), reference_crop.height() + ); + + const auto reference_average = + ImageMatch::pixel_average(reference_crop, reference_crop); + const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); + auto scale = reference_average / live_average; + if (std::isnan(scale.r)) scale.r = 1.0; + if (std::isnan(scale.g)) scale.g = 1.0; + if (std::isnan(scale.b)) scale.b = 1.0; + scale.bound(0.8, 1.2); + ImageMatch::scale_brightness(adjusted, scale); + + return ImageMatch::pixel_RMSD(reference_crop, adjusted); + } + + struct MineRoarkOffsetScore { + double left = 1000.0; + double right = 1000.0; + double helmet = 1000.0; + double horizontal = 0; + double vertical = 0; + double helmet_horizontal = 0; + double helmet_vertical = 0; + }; + + static MineRoarkOffsetScore mine_score_roark_offsets( + const ImageRGB32& reference, + const ImageRGB32& live + ) { + const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); + const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); + const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); + + MineRoarkOffsetScore best; + double best_worst_rock = 1000.0; + double best_total = 2000.0; + + // Both rocks must use the SAME offset. They cannot match independently. + // Search ranges are screen fractions; enumerate pixels only for scoring. + const int radius_x = (int)std::lround(live.width()/60.0); + const int radius_y = (int)std::lround(live.height()*2.0/135); + for (int y = -radius_y; y <= radius_y; y++) { + for (int x = -radius_x; x <= radius_x; x++) { + const double dx = (double)x/live.width(), dy = (double)y/live.height(); + const double left = mine_offset_patch_score(reference, live, left_box, dx, dy); + const double right = mine_offset_patch_score(reference, live, right_box, dx, dy); + const double worst = left > right ? left : right; + const double total = left + right; + + if (worst < best_worst_rock || + (worst == best_worst_rock && total < best_total)) { + best_worst_rock = worst; + best_total = total; + best.left = left; + best.right = right; + best.horizontal = (double)x/live.width(); + best.vertical = (double)y/live.height(); + } + } + } + + // Roark's helmet may move slightly relative to the stationary rocks. + // Keep this extra search small so another red object cannot match far away. + const int helmet_radius_x = (int)std::lround(live.width()*0.00625); + const int helmet_radius_y = (int)std::lround(live.height()/90.0); + for (int extra_y = -helmet_radius_y; extra_y <= helmet_radius_y; extra_y++) { + for (int extra_x = -helmet_radius_x; extra_x <= helmet_radius_x; extra_x++) { + const double score = mine_offset_patch_score( + reference, live, helmet_box, + best.horizontal + (double)extra_x/live.width(), + best.vertical + (double)extra_y/live.height() + ); + if (score < best.helmet) { + best.helmet = score; + best.helmet_horizontal = (double)extra_x/live.width(); + best.helmet_vertical = (double)extra_y/live.height(); + } + } + } + + return best; + } + + static bool oreburgh_mine_align_to_18( + VideoStream& stream, + ProControllerContext& context + ) { + const ImageRGB32 reference( + mine_navigation_resource_path() + "Mine_path_18.png" + ); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const double max_rmsd = 65.0; + size_t movements = 0; + + return mine_run_with_encounters( + stream, context, + [&](ProControllerContext& ctx) { + // Fresh confirmation after any encounter. + size_t matching_frames = 0; + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + + while (true) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); + return false; + } + + const ImageRGB32 live = frame->scale_to(960, 540); + const MineRoarkOffsetScore score = + mine_score_roark_offsets(reference_scaled, live); + + stream.log( + "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + + ", Right rock=" + std::to_string(score.right) + + ", Helmet=" + std::to_string(score.helmet) + + ", shared screen-relative offset=(" + std::to_string(score.horizontal) + + "," + std::to_string(score.vertical) + + "), helmet screen-relative extra=(" + std::to_string(score.helmet_horizontal) + + "," + std::to_string(score.helmet_vertical) + ")" + ); + + if (score.left <= max_rmsd && score.right <= max_rmsd && + score.helmet <= max_rmsd) { + if (++matching_frames >= 3) { + stream.log( + "[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN + ); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; + } + + matching_frames = 0; + if (movements >= 80) { + stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); + return false; + } + + const bool approaching = score.left <= 120.0 && + score.right <= 120.0 && score.helmet <= 150.0; + const bool near = score.left <= 100.0 && + score.right <= 100.0 && score.helmet <= 100.0; + const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; + + // Use the outer encounter watcher; do not nest mine_timed_walk. + // Count issued pulses, including any interrupted by a battle. + movements++; + pbf_move_left_joystick(ctx, { 1.0, 0.0 }, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + } + } + ); + } + + static bool oreburgh_mine_14_to_19( + VideoStream& stream, + ProControllerContext& context + ) { + const std::array path15_boxes = { + ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate + ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate + ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path15_boxes_step = { + "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path15_boxes_step)) { + return false; + } + + // Move below the worker before continuing right. + const std::array path16_boxes = { + ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate + ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate + ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path16_boxes_step = { + "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, + 80ms, 30, 20.0, 30.0 + }; + if (!mine_run_picture_step(stream, context, path16_boxes_step)) { + return false; + } + + // Skip 17 and find the actual stopping point at 18. + if (!oreburgh_mine_align_to_18(stream, context)) { + return false; + } + + // Position 19 is the upward turn toward Roark. + pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + + stream.log( + "[Mine endpoint 19] Position 18 confirmed; north turn sent.", + COLOR_GREEN + ); + return true; + } + + bool oreburgh_mine_route( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options + ) { + stream.log("[Mine resources] " + mine_navigation_resource_path()); + struct Step { + const char* name; + bool (*run)(VideoStream&, ProControllerContext&); + }; + + const std::array steps = { { + { + "Align entrance", + oreburgh_mine_alignment_test + }, + { + "Walk to first rock", + [](VideoStream& video, ProControllerContext& controller) { + return oreburgh_mine_walk_to_first_rock( + video, controller, false + ); + } + }, + { + "Move right of first rock", + oreburgh_mine_move_right + }, + { + "Move below first rock", + oreburgh_mine_down_past_central_rock + }, + { + "Move left into center gap", + oreburgh_mine_move_left_to_gap + }, + { + "Reach position 07", + oreburgh_mine_timed_to_07 + }, + { + "Reach position 08", + oreburgh_mine_left_to_08 + }, + { + "Reach position 09", + oreburgh_mine_down_to_09 + }, + { + "Reach 14 and collect item", + oreburgh_mine_09_to_14 + }, + { + "Reach 18 and turn north for 19", + oreburgh_mine_14_to_19 + } + } }; + + const size_t first = static_cast(options.first); + const size_t last = static_cast(options.last); + + if (first > last || last >= steps.size()) { + stream.log("[Mine route] Invalid stage range.", COLOR_ORANGE); + return false; + } + + for (size_t index = first; index <= last; index++) { + const Step& step = steps[index]; + + stream.log( + std::string("[Mine route] Starting: ") + step.name + ); + + bool succeeded = false; + + try { + succeeded = step.run(stream, context); + } + catch (OperationFailedException& error) { + stream.log( + std::string("[Mine route] Failed during ") + + step.name + ": " + error.message(), + COLOR_ORANGE + ); + + if (options.reset_on_failure) { + throw; + } + + stream.log( + "[Mine route] Test stopped without requesting a reset.", + COLOR_ORANGE + ); + return false; + } + + if (!succeeded) { + stream.log( + std::string("[Mine route] Stage failed: ") + step.name, + COLOR_ORANGE + ); + + if (options.reset_on_failure) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + std::string("Mine route failed during: ") + step.name, + stream + ); + } + + return false; + } + + stream.log( + std::string("[Mine route] Completed: ") + step.name, + COLOR_GREEN + ); + } + + stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); + return true; + } + + // Start Roark's conversation once, then advance it with B. + // Require dialogue to be seen before accepting its disappearance. + + static bool mine_talk_to_roark( + VideoStream& stream, + ProControllerContext& context + ) { + stream.log("[Mine Roark] Pressing A to start conversation."); + pbf_press_button(context, BUTTON_A, 80ms, 100ms); + context.wait_for_all_requests(); + + // The opening cutscene precedes the first dialogue box. + // Do not mash B or measure dialogue absence during that cutscene. + wait_for_dialogue(stream, context, "[Mine Roark] First box", 30s); + + ShortDialogDetector dialogue(COLOR_YELLOW); + const auto deadline = std::chrono::steady_clock::now() + 60s; + bool timing_absence = false; + auto absent_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); + return false; + } + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(frame)) { + timing_absence = false; + } + else { + if (!timing_absence) { + timing_absence = true; + absent_since = now; + } + if (now - absent_since >= 4s) { + stream.log( + "[Mine Roark] Dialogue absent for 4 seconds. Finished.", + COLOR_GREEN + ); + return true; + } + } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); + return false; + } + + // Retain the developer test entry point; both callers share the same route. + bool oreburgh_mine_first_leg_test( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options + ) { + return oreburgh_mine_route(stream, context, options); + } + + // Shared final step for the live route and the entrance-start test. + static bool mine_talk_and_escape(VideoStream& stream, ProControllerContext& context) { + if (!mine_talk_to_roark(stream, context)) { + return false; + } + stream.log("[Mine exit] Finding Escape Rope in the Bag."); + if (!use_escape_rope(stream, context, 8)) { + return false; + } + stream.log("[Mine exit] Roark conversation and Escape Rope exit succeeded.", COLOR_GREEN); + return true; + } + + bool oreburgh_mine_route_and_escape_test( + VideoStream& stream, + ProControllerContext& context + ) { + MineRouteTestOptions route; + route.reset_on_failure = false; + if (!oreburgh_mine_first_leg_test(stream, context, route)) { + return false; + } + try { + if (!mine_talk_and_escape(stream, context)) { + stream.log("[Mine exit test] Failed. Stopping without resetting.", COLOR_ORANGE); + return false; + } + } + catch (OperationFailedException& error) { + stream.log("[Mine exit test] " + error.message(), COLOR_ORANGE); + stream.log("[Mine exit test] Stopped without requesting a reset.", COLOR_ORANGE); + return false; + } + return true; + } + + void oreburgh_mine_nav( + VideoStream& stream, ProControllerContext& context) { + walk_through_grass(stream, context, 0, -1, 1, "down into mine"); + context.wait_for_all_requests(); + walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); + context.wait_for_all_requests(); + pbf_wait(context, 1824ms); + + MineRouteTestOptions route; + route.reset_on_failure = true; + + if (!oreburgh_mine_route(stream, context, route)) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Mine route did not complete.", stream + ); + } + + + if (!mine_talk_and_escape(stream, context)) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Mine route: Roark conversation or Escape Rope exit failed.", + stream + ); + } + + } + // --------------------------------------------------------------------------- + // Segment 1 Part 3: Oreburgh Mine to Gym + // Assumes that escape rope was picked up and used to exit the mine. + // Follows the validated blind route into the gym. + // ---------------------------------------------------------------------------- + void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + + stream.log("[Mine to gym] Following recorded route from the mine exit."); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 3995ms, 0ms); + pbf_wait(context, 672ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 681ms, 0ms); + pbf_wait(context, 468ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1081ms, 0ms); + pbf_wait(context, 420ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2445ms, 0ms); + pbf_wait(context, 1056ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 95ms, 0ms); + pbf_wait(context, 4787ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1309ms, 0ms); + + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4450ms, 0ms); + //pbf_wait(context, 3635ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); + //pbf_wait(context, 3795ms); + context.wait_for_all_requests(); + + //stream.log("[Mine to gym] Staying west of the hiker; moving north along the left fence."); + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); + //pbf_wait(context, 602ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); + //pbf_wait(context, 5069ms); + //pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); + //pbf_wait(context, 605ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); + //pbf_wait(context, 1081ms); + //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); + //pbf_wait(context, 2642ms); + //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); + //pbf_wait(context, 5340ms); + //context.wait_for_all_requests(); + + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); + //pbf_wait(context, 140ms); + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); + //context.wait_for_all_requests(); + //stream.log("[Mine to gym] Recorded movement finished; waiting for gym entry to settle."); + // walk_until_doorway(stream, context, MoveDirection::UP, 5s); + // Finish entering the doorway with a bounded blind input. + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); + //context.wait_for_all_requests(); + //pbf_wait(context, 1500ms); + //context.wait_for_all_requests(); + stream.log("[Mine to gym] Blind mine-to-gym sequence completed.", COLOR_GREEN); + } + + // --------------------------------------------------------------------------- + // Segment 1 Part 4: Gym navigation + // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. + // --------------------------------------------------------------------------- + + void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + + //Upon entering the Gym. + // The entry camera sequence lasts about 7.5 seconds in the reference + // video. Allow another 500ms before sending puzzle movement inputs. + context.wait_for_all_requests(); + env.log("[Gym puzzle] Waiting for the entry cutscene to finish."); + pbf_wait(context, 8000ms); + context.wait_for_all_requests(); + + struct Move { + DpadPosition dir; + int count; + }; + const Move sequence[] = { + {DPAD_UP, 3}, + {DPAD_LEFT, 4}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 8}, + {DPAD_UP, 11}, + {DPAD_LEFT, 5}, + {DPAD_UP, 4}, + }; + + for (const auto& move : sequence) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate the Gym battle. + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + //Barry 1 battle logic can be used here since the moves don't matter this early. + Barry_1_fight(env, stream, context); + + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); + ShortDialogDetector post_battle_dialogue(COLOR_YELLOW); + const auto dialogue_deadline = std::chrono::steady_clock::now() + 180s; + auto dialogue_absent_since = std::chrono::steady_clock::now(); + bool dialogue_finished = false; + stream.log("[Gym post battle] Clearing dialogue until absent for 4 seconds."); + while (std::chrono::steady_clock::now() < dialogue_deadline) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Gym post battle: No video frame while clearing dialogue.", stream + ); + } + const auto now = std::chrono::steady_clock::now(); + if (post_battle_dialogue.detect(frame)) { + dialogue_absent_since = now; + } + else if (now - dialogue_absent_since >= 4s) { + dialogue_finished = true; + break; + } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + if (!dialogue_finished) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Gym post battle: Dialogue did not finish within 180 seconds.", stream + ); + } + stream.log("[Gym post battle] Dialogue absent for 4 seconds. Walking down."); + + for (int i = 0; i < 21; i++) { + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + // Exit the building. + pbf_wait(context, 1000ms); + } + + + std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } + std::string AutoStory_Segment_01::start_text() const { return "Start: Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } + + void AutoStory_Segment_01::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_0101().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0102().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0103().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); + } + + + std::string AutoStory_checkpoint_0101::name() const { return "01.01 - Maxed Starter from Home"; } + std::string AutoStory_checkpoint_0101::start_text() const { return "Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } + std::string AutoStory_checkpoint_0101::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } + // ======================== + // Checkpoint 0101 Wrapper + // ======================== + void AutoStory_checkpoint_0101::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0101(env, context, options, stats); + } + + + void checkpoint_0101( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + jubilife_to_oreburgh_gate(env, env.console, context); + oreburgh_gate_nav(env, env.console, context); + }, + true + ); + } + + // ======================== + // Checkpoint 0102 Wrapper + // ======================== + + std::string AutoStory_checkpoint_0102::name() const { return "01.02 - Oreburgh Mine"; } + std::string AutoStory_checkpoint_0102::start_text() const { return AutoStory_checkpoint_0101().end_text(); } + std::string AutoStory_checkpoint_0102::end_text() const { return "Inside Oreburgh Gym, ready to navigate to Roark."; } + + void AutoStory_checkpoint_0102::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0102(env, context, options, stats); + } + + + void checkpoint_0102( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + oreburgh_gate_to_mine(env, env.console, context); + oreburgh_mine_nav(env.console, context); + // Standalone test entry points are not part of the checkpoint route. + // oreburgh_mine_first_leg_test(env.console, context); + // oreburgh_mine_route_and_escape_test(env.console, context); + oreburgh_mine_to_gym(env, env.console, context); + }, + + true + ); + } + + // ======================== + // Checkpoint 0103 Wrapper + // ======================== + + std::string AutoStory_checkpoint_0103::name() const { return "01.03 - Oreburgh Gym"; } + std::string AutoStory_checkpoint_0103::start_text() const { return AutoStory_checkpoint_0102().end_text(); } + std::string AutoStory_checkpoint_0103::end_text() const { return "Standing at Roark after defeating him and acquiring the first badge."; } + + void AutoStory_checkpoint_0103::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0103(env, context, options, stats); + } + + + void checkpoint_0103( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + //#. Oreburgh Gym navigation and battle + gym_Puzzle_01(env, env.console, context); + + }, + + true + ); + } + + + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h index c560945c77..d72ceed662 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -1,99 +1,160 @@ -/* BDSP AutoStory - Segment 01 - * From after choosing the starter Pokemon until after acquiring the first badge. - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H -#define PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H - -#include "../PokemonBDSP_AutoStoryTools.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - -class AutoStory_Segment_01 : 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint{ -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle -// end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0101( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); -class AutoStory_checkpoint_0102 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle -// end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0102( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - -class AutoStory_checkpoint_0103 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle -// end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0103( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - -} -} -} -#endif +/* BDSP AutoStory - Segment 01 + * From after choosing the starter Pokemon until after acquiring the first badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// Route ownership: Oreburgh navigation and isolated route tests belong to Segment 01. + enum class MineRouteStage { + ALIGN_ENTRANCE, + FIRST_ROCK, + RIGHT_OF_FIRST_ROCK, + BELOW_FIRST_ROCK, + CENTER_GAP, + POSITION_07, + POSITION_08, + POSITION_09, + ITEM_AT_14, + ROARK_AT_19 + }; + + struct MineRouteTestOptions { + MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; + MineRouteStage last = MineRouteStage::ROARK_AT_19; + + // False: stop the test on failure. + // True: let the checkpoint retry handler reset the game. + bool reset_on_failure = false; + }; + +void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void oreburgh_mine_nav( + VideoStream& stream, ProControllerContext& context); + + void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); + + // Starts inside the mine at an entrance position; stops outside after + // talking to Roark and using Escape Rope. Does not reset on failure. + bool oreburgh_mine_route_and_escape_test( + VideoStream& stream, + ProControllerContext& context + ); + + // Shared stage runner for production navigation and isolated route tests. + bool oreburgh_mine_route( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options + ); + + bool oreburgh_mine_first_leg_test( + VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options = MineRouteTestOptions() + ); + +class AutoStory_Segment_01 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle +// end: first Gym Badge acquired (Oreburgh City) +void checkpoint_0101( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); +class AutoStory_checkpoint_0102 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle +// end: first Gym Badge acquired (Oreburgh City) +void checkpoint_0102( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_checkpoint_0103 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: starter Pokemon taken out of PC at level 100 and ready to be used in battle +// end: first Gym Badge acquired (Oreburgh City) +void checkpoint_0103( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 751441dc8e..ab544d04f0 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -1,472 +1,2379 @@ -/* BDSP AutoStory - Segment 02 - * From after acquiring the first badge until after acquiring the second badge. - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "CommonFramework/GlobalAutoPaths.h" -#include -#include -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "CommonFramework/ImageTypes/ImageRGB32.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "PokemonBDSP_AutoStory_Segment_02.h" - -using namespace std::chrono_literals; - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - // Match only the mart emblem, avoiding moving NPCs and changing ground shadows. - static cv::Mat mart_grayscale(const ImageRGB32& image) { - cv::Mat pixels((int)image.height(), (int)image.width(), CV_8UC4, - const_cast(image.data()), image.bytes_per_row()); - cv::Mat gray; - cv::cvtColor(pixels, gray, cv::COLOR_BGRA2GRAY); - return gray; - } - - static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& context) { - const ImageRGB32 reference(RESOURCE_PATH() + - "PokemonBDSP/Autostory/OreburghMart/MartEmblem.png"); - const ImageRGB32 small_reference = reference.scale_to(54, 43); - const cv::Mat emblem = mart_grayscale(small_reference); - context.wait_for_all_requests(); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - size_t confirmations = 0; - for (size_t attempt = 0; attempt < 40; attempt++) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: No video frame.", stream); - } - const ImageRGB32 small_frame = frame->scale_to(960, 540); - const cv::Mat gray = mart_grayscale(small_frame); - cv::Mat scores; - cv::matchTemplate(gray(cv::Rect(0, 0, 960, 360)), emblem, scores, cv::TM_CCOEFF_NORMED); - double score; - cv::Point location; - cv::minMaxLoc(scores, nullptr, &score, nullptr, &location); - // Reject an ambiguous landmark rather than steering toward another building. - cv::Mat competitors = scores.clone(); - const cv::Rect exclusion = cv::Rect(location.x - 15, location.y - 15, 31, 31) - & cv::Rect(0, 0, scores.cols, scores.rows); - competitors(exclusion).setTo(-1); - double runner_up; - cv::minMaxLoc(competitors, nullptr, &runner_up); - if (score < 0.93 || score - runner_up < 0.10) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: Emblem match missing or ambiguous.", stream); - } - // Reference emblem top-left at half resolution. The reference player - // stands directly outside the door, 64 full-resolution pixels left - // of the emblem center; do not steer to the emblem center itself. - const int dx = location.x - 482; - const int dy = location.y - 127; - stream.log("[Mart alignment] score=" + std::to_string(score) + - ", offset=(" + std::to_string(dx * 2) + "," + std::to_string(dy * 2) + ")"); - if (std::abs(dx) <= 3 && std::abs(dy) <= 3) { - if (++confirmations >= 3) { - stream.log("[Mart alignment] Door position confirmed."); - return; - } - pbf_wait(context, 250ms); - } else { - confirmations = 0; - const bool horizontal = std::abs(dx) >= std::abs(dy); - const int error = horizontal ? dx : dy; - const auto duration = std::abs(error) > 15 ? 80ms : 40ms; - pbf_move_left_joystick(context, - {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, - horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, duration, 0ms); - pbf_wait(context, 350ms); - } - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: Could not reach the doorway reference position.", stream); - } - - void roark_to_mars(VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 3995ms, 0ms); - pbf_wait(context, 672ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 681ms, 0ms); - pbf_wait(context, 468ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1081ms, 0ms); - pbf_wait(context, 420ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2445ms, 0ms); - pbf_wait(context, 1056ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 95ms, 0ms); - align_to_oreburgh_mart(stream, context); - pbf_wait(context, 4787ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1309ms, 0ms); - - } - void mars_to_eterna_forest(VideoStream& stream, ProControllerContext& context) { - - } - void eterna_forest(VideoStream& stream, ProControllerContext& context) { - - } - void eterna_forest_exit_to_gym(VideoStream& stream, ProControllerContext& context) { - - } -static void gym_Puzzle_02(VideoStream& stream, ProControllerContext& context){ - - //Upon entering the Gym. - - struct Move { - DpadPosition dir; - int count; - }; - - const Move sequence1[] = { - {DPAD_UP, 5}, - {DPAD_LEFT, 2}, - {DPAD_UP, 9}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 7}, - {DPAD_LEFT, 3}, - {DPAD_UP, 2}, - {DPAD_LEFT, 5}, - {DPAD_DOWN, 4}, - }; - - const Move sequence2[] = { - {DPAD_UP, 6}, - {DPAD_LEFT, 6}, - {DPAD_DOWN, 9}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 5}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 6}, - {DPAD_RIGHT, 5}, - }; - - const Move sequence3[] = { - {DPAD_LEFT, 5}, - {DPAD_UP, 6}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 5}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 5}, - {DPAD_LEFT, 2}, - {DPAD_UP, 8}, - {DPAD_RIGHT, 7}, - {DPAD_DOWN, 2}, - {DPAD_RIGHT, 5}, - {DPAD_DOWN, 6}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 5}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, - {DPAD_RIGHT, 6}, - {DPAD_UP, 3}, - {DPAD_RIGHT, 2}, - }; - - const Move sequence4[] = { - {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, - {DPAD_LEFT, 6}, - {DPAD_DOWN, 6}, - {DPAD_RIGHT, 11}, - {DPAD_UP, 3}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 16}, - {DPAD_LEFT, 6}, - }; - - const Move sequence5[] = { - {DPAD_RIGHT, 6}, - {DPAD_DOWN, 17}, - {DPAD_LEFT, 5}, - {DPAD_DOWN, 2}, - {DPAD_LEFT, 8}, - {DPAD_UP, 11}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 7}, - {DPAD_LEFT, 2}, - {DPAD_UP, 7}, - }; - - const Move sequence6[] = { - {DPAD_DOWN, 10}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 4}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 13}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 2}, - }; - - for(int i=0; i < 7; i++){ - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - } - - pbf_mash_button(context, BUTTON_A, 2500ms); //Delay may need increasing depending on length of dialogue. - pbf_press_dpad(context, DPAD_UP, 80ms, 2000ms); - - for (const auto& move : sequence1){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate Trainer battle 01 (Lass Caroline). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. - - for (const auto& move : sequence2){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate Trainer battle 02 (Aroma Lady Jenna). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. - - for (const auto& move : sequence3){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate Trainer battle 03 (Aroma Lady Angela). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence4){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate Trainer battle 04 (Beauty Lindsay). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence5){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate Gym battle. - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence6){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - pbf_wait(context, 2000ms); - - for(int i=0; i < 9; i++){ - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } - - //Exits Buiilding - pbf_wait(context, 1000ms); -} - -std::string AutoStory_Segment_02::name() const{ return "02: First Badge to Second Badge"; } -std::string AutoStory_Segment_02::start_text() const{ return "Start: First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_Segment_02::end_text() const{ return "End: Second Gym Badge acquired (Eterna City)."; } - -void AutoStory_Segment_02::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_checkpoint_0201().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0202().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0203().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0204().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} -std::string AutoStory_checkpoint_0201::name() const { return "02.01 - Outside Gym 1"; } -std::string AutoStory_checkpoint_0201::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_0201::end_text() const { return "Standing in front of the scientist in Valley windworks after beating Mars."; } - -void AutoStory_checkpoint_0201::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - checkpoint_0201(env, context, options, stats); -} - - -void checkpoint_0201( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 02 gameplay logic - roark_to_mars(env.console, context); - - }, - - true - ); -} -std::string AutoStory_checkpoint_0202::name() const { return "02.02 - TBC"; } -std::string AutoStory_checkpoint_0202::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_0202::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } - -void AutoStory_checkpoint_0202::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - checkpoint_0202(env, context, options, stats); -} - - -void checkpoint_0202( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 02 gameplay logic - mars_to_eterna_forest(env.console, context); - - }, - - true - ); -} - -std::string AutoStory_checkpoint_0203::name() const{ return "02.03 - TBC"; } -std::string AutoStory_checkpoint_0203::start_text() const{ return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_0203::end_text() const{ return "Second Gym Badge acquired (Eterna City)."; } - -void AutoStory_checkpoint_0203::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - checkpoint_0203(env, context, options, stats); -} - - -void checkpoint_0203( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/){ - // TODO: implement Segment 02 gameplay logic - eterna_forest(env.console, context); - - }, - - true - ); -} - -std::string AutoStory_checkpoint_0204::name() const { return "02.04 - TBC"; } -std::string AutoStory_checkpoint_0204::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_0204::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } - -void AutoStory_checkpoint_0204::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - checkpoint_0204(env, context, options, stats); -} - - -void checkpoint_0204( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 02 gameplay logic - eterna_forest_exit_to_gym(env.console, context); - - }, - true - ); -} - -std::string AutoStory_checkpoint_0205::name() const { return "02.05 - Inside Gym"; } -std::string AutoStory_checkpoint_0205::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } -std::string AutoStory_checkpoint_0205::end_text() const { return "Second Gym Badge acquired (Eterna City)."; } - -void AutoStory_checkpoint_0205::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - checkpoint_0205(env, context, options, stats); -} - - -void checkpoint_0205( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - [&](size_t /*attempt*/) { - - //#. Eterna Gym navigation and battle - gym_Puzzle_02(env.console, context); - }, - true - ); -} - -} -} -} +/* BDSP AutoStory - Segment 02 + * From after acquiring the first badge until after acquiring the second badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/GlobalAutoPaths.h" +#include +#include +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "../Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "../Utils/PokemonBDSP_OverworldMatchers.h" +#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP_AutoStory_Segment_02.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ + namespace NintendoSwitch{ + namespace PokemonBDSP{ + static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmations = 0, missing = 0; + for (size_t attempt = 0; attempt < 40; attempt++) { + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mart alignment: No video frame.", stream); + const OverworldMatchResult result = match_oreburgh_mart(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Mart alignment] score=" + std::to_string(landmark.score) + + ", screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + if (!result.located) { + confirmations = 0; + if (++missing >= 8) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mart alignment: Emblem match missing or ambiguous.", stream); + // A passing shadow must not cause an immediate checkpoint reset. + // Stay still until a trusted match returns; never steer by a rejected candidate. + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; + } + missing = 0; + if (result.at_target) { + if (++confirmations >= 3) { stream.log("[Mart alignment] Door position confirmed."); return; } + pbf_wait(context, 500ms); + } else { + confirmations = 0; + const double dx = result.dx, dy = result.dy; + const bool horizontal = std::abs(dx) >= std::abs(dy)*9.0/16; + const double error = horizontal ? dx : dy*9.0/16; + const auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + pbf_move_left_joystick(context, + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, + horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, duration, 0ms); + pbf_wait(context, 700ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mart alignment: Could not reach the doorway reference position.", stream); + } + + static void enter_oreburgh_gate_from_city(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + stream.log("[Oreburgh Gate entry] Moving up, then searching left/down for the entrance."); + // Observe before the first move so entry during the upward + // alignment or either search direction cannot be missed. + BlackScreenWatcher black; + const int result = run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0.0, 1.0}, 800ms, 100ms); + for (size_t attempt = 0; attempt < 30; attempt++) { + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 80ms); + pbf_move_left_joystick(ctx, {0.0, -1.0}, 120ms, 80ms); + ctx.wait_for_all_requests(); + } + // Keep watching if the last pulse starts a delayed fade. + pbf_wait(ctx, 1500ms); + }, {{black}}); + if (result != 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate entry: No black transition during left/down search.", stream); + stream.log("[Oreburgh Gate entry] Black transition detected; movement stopped."); + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, + 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate entry: Transition did not finish.", stream); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + stream.log("[Oreburgh Gate entry] Cave visible; continuing the route.", COLOR_GREEN); + } + + enum class RepelRenewalChoice { YES, NO }; + + // Call at a safe walking point, away from trainer/NPC prompts. + static void force_repel_expiry(VideoStream& stream, ProControllerContext& context, + RepelRenewalChoice choice, bool north_south = false) { + context.wait_for_all_requests(); + stream.log(std::string("[Repel expiry] Wiggling ") + + (north_south ? "north/south" : "west/east") + " until expiry; renewal response=" + + (choice == RepelRenewalChoice::YES ? "Yes." : "No.")); + const Button response = choice == RepelRenewalChoice::YES ? BUTTON_A : BUTTON_B; + ShortDialogWatcher expired(COLOR_CYAN); + // 108 fresh Max Repels, two calibration runs: mean 41.244682s, + // sample SD 3.668059s. Round mean + SD up to the next millisecond. + // Match the calibration's 500ms movement / 100ms rest pulses. + const auto movement_deadline = std::chrono::steady_clock::now() + 44913ms; + const int result = run_until(stream, context, + [north_south, movement_deadline](ProControllerContext& ctx) { + while (std::chrono::steady_clock::now() + 1200ms <= movement_deadline) { + // Complete steps before reversing so the Repel counter advances. + if (north_south) { + pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 100ms); + pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 100ms); + } else { + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 500ms, 100ms); + pbf_move_left_joystick(ctx, {1.0, 0.0}, 500ms, 100ms); + } + ctx.wait_for_all_requests(); + } + // Finish the observation window without an unpaired move. + const auto remaining = std::chrono::duration_cast( + movement_deadline - std::chrono::steady_clock::now()); + if (remaining > 0ms) pbf_wait(ctx, remaining); + }, {{expired}}); + if (result != 0) { + stream.log("[Repel expiry] Calibrated 44.913s limit reached without an expiry prompt; checking clear overworld before continuing.", COLOR_ORANGE); + } + // Renewal has a gap before the final used-Repel message. A single + // overworld frame is insufficient: require three continuous clear seconds. + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BlackScreenDetector black; + OverworldDetector overworld; + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + black.make_overlays(overlays); + overworld.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 30s; + auto quiet_since = std::chrono::steady_clock::now(); + bool quiet = false; + while (std::chrono::steady_clock::now() < deadline) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: No video frame during cleanup.", stream); + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(*frame.frame)) { + quiet = false; + pbf_mash_button(context, response, 100ms); + } else if (black.detect(*frame.frame) || !overworld.detect(*frame.frame)) { + quiet = false; + pbf_wait(context, 100ms); + } else { + if (!quiet) { quiet = true; quiet_since = now; } + if (now - quiet_since >= 3s) { + context.wait_for_all_requests(); + if (result == 0) { + stream.log(std::string("[Repel expiry] Renewal ") + + (choice == RepelRenewalChoice::YES ? "accepted" : "declined") + + "; three continuous seconds of overworld confirmed."); + } else { + stream.log("[Repel expiry] No expiry observed within the calibrated limit; three continuous seconds of clear overworld confirmed. Continuing the route."); + } + return; + } + // Do not send confirmation/cancel buttons while the overworld is clear. + pbf_wait(context, 100ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: Three clear seconds not confirmed after renewal.", stream); + } + + static void wait_for_trainer_battle_menu(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector menu(BattleType::TRAINER, COLOR_GREEN); + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + menu.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 120s; + size_t confirmed = 0; + bool previous_dialogue = false; + while (std::chrono::steady_clock::now() < deadline) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Trainer approach: No video frame.", stream); + const bool dialog = dialogue.detect(*frame.frame); + if (dialog && !previous_dialogue) stream.log("[Trainer approach] Clearing dialogue with B."); + previous_dialogue = dialog; + if (menu.detect(*frame.frame)) { + if (++confirmed >= 3) { + stream.log("[Trainer approach] Battle menu confirmed; starting combat."); + return; + } + pbf_wait(context, 100ms); + } else { + confirmed = 0; + pbf_mash_button(context, BUTTON_B, 100ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Trainer approach: Battle menu did not appear after dialogue cleanup.", stream); + } + + static void exit_oreburgh_gate_to_route203(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + stream.log("[Oreburgh Gate exit] Moving west until the white transition starts."); + WhiteScreenWatcher white; + const int result = run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 6000ms, 0ms); + pbf_wait(ctx, 1000ms); + }, {{white}}); + if (result != 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate exit: White transition did not start.", stream); + WhiteScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, + 500, 10, WhiteScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate exit: White transition did not finish.", stream); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + } + + static void approach_galactic_dialogue(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic approach: No video frame.", stream); + // Alignment may already have handed off after the story started. + if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)) return; + stream.log("[Galactic approach] Plaza position confirmed; sidestepping left around the sign, then moving up until story dialogue."); + ShortDialogWatcher started(COLOR_YELLOW); + BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); + const int result = run_until(stream, context, + [](ProControllerContext& ctx) { + // The reference position faces the sign, which blocks a + // straight upward approach and consumes A as a sign read. + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 150ms); + ctx.wait_for_all_requests(); + for (size_t pulse = 0; pulse < 20; pulse++) { + pbf_move_left_joystick(ctx, {0.0, 1.0}, 100ms, 100ms); + ctx.wait_for_all_requests(); + } + // If an NPC blocks the final approach, initiate interaction. + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + pbf_wait(ctx, 1500ms); + }, {{started}, {menu}}); + if (result < 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic approach: Story dialogue/battle did not start during the bounded upward approach.", stream); + stream.log("[Galactic approach] Story dialogue/battle detected; movement stopped."); + } + + // Dialogue indicates the trainer interaction started; combat still waits + // for a confirmed battle menu before invoking the battle handler. + static bool watch_route204_trainer_event(VideoStream& stream, ProControllerContext& context, + const std::function& action, const std::string& label) { + context.wait_for_all_requests(); + ShortDialogWatcher dialogue(COLOR_YELLOW); + BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); + const int result = run_until(stream, context, + [&action](ProControllerContext& ctx) { action(ctx); }, {{dialogue}, {menu}}); + const bool detected = result >= 0; + stream.log("[" + label + "] Trainer dialogue/battle detected=" + std::to_string(detected)); + return detected; + } + + static bool approach_route204_female_trainer(VideoStream& stream, ProControllerContext& context) { + return watch_route204_trainer_event(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0.0, 1.0}, 8000ms, 0ms); + // Replaces the 2374ms settle plus the unbounded-looking + // generic dialogue wait with one explicit detection window. + pbf_wait(ctx, 2500ms); + }, "Route 204 female trainer approach"); + } + + static void recover_route204_female_trainer(VideoStream& stream, ProControllerContext& context) { + stream.log("[Route 204 female trainer] Missed initial trigger; crossing her sight line down/up."); + const bool recovered = watch_route204_trainer_event(stream, context, + [](ProControllerContext& ctx) { + for (size_t pair = 0; pair < 8; pair++) { + // We have passed her: go back down first, then up. + pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 150ms); + pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 150ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx, 2500ms); + }, "Route 204 female trainer recovery"); + if (!recovered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 female trainer: No dialogue/battle after bounded down/up recovery.", stream); + } + + static void recover_route204_youngster(VideoStream& stream, ProControllerContext& context) { + // The recording ends above the youngster after passing left. + // Cross his row down/up instead of resetting after one miss. + const bool recovered = watch_route204_trainer_event(stream, context, + [](ProControllerContext& ctx) { + for (size_t pair = 0; pair < 6; pair++) { + pbf_move_left_joystick(ctx, {0, -1}, 500ms, 200ms); + // Body collision can leave us adjacent without a sightline trigger. + pbf_press_button(ctx, BUTTON_A, 100ms, 250ms); + pbf_move_left_joystick(ctx, {0, 1}, 500ms, 200ms); + pbf_press_button(ctx, BUTTON_A, 100ms, 250ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx, 2500ms); + }, "Route 204 youngster sightline recovery"); + if (!recovered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 youngster: No trigger after bounded down/up recovery.", stream); + } + + static void floaroma_house_transition(VideoStream& stream, ProControllerContext& context, double y) { + context.wait_for_all_requests(); + BlackScreenWatcher black; + const int result=run_until(stream,context, + [y](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,y},2500ms,0ms); + pbf_wait(ctx,1000ms); + },{{black}}); + if (result!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma house: Door transition not detected.",stream); + context.wait_for_all_requests(); + BlackScreenDetector black_frame; + const auto deadline=std::chrono::steady_clock::now()+15s; + auto clear_since=std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now()=500ms) { + pbf_wait(context,700ms); + context.wait_for_all_requests(); + return; + } + pbf_wait(context,100ms); + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma house: Door transition did not finish.",stream); + } + + static void establish_floaroma_house_exit(VideoStream& stream, ProControllerContext& context) { + stream.log("[Floaroma checkpoint] Blind northward house entry, then immediate exit."); + floaroma_house_transition(stream,context,1); + floaroma_house_transition(stream,context,-1); + stream.log("[Floaroma checkpoint] House entry/exit completed; ready for checkpoint save.",COLOR_GREEN); + } + + static void recover_teighler_trigger(VideoStream& stream, ProControllerContext& context) { + // The first pass ended east of the corner. Return left, then + // cross her sightline along the same bottom ledge for five seconds. + const bool recovered=watch_route204_trainer_event(stream,context, + [](ProControllerContext& ctx) { + // The old 500ms sweeps stopped beneath/west of Teighler. + // Cross the full bottom ledge in both directions, still + // limiting the total recovery motion to five seconds. + for (size_t pair=0; pair<3; pair++) { + const Milliseconds sweep = pair<2 ? 1000ms : 500ms; + pbf_move_left_joystick(ctx,{-1,0},sweep,0ms); + pbf_move_left_joystick(ctx,{1,0},sweep,0ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx,2500ms); + },"Teighler five-second ledge recovery"); + if (!recovered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Teighler: No battle after five-second left/right ledge recovery.",stream); + } + + // True means the next trainer triggered before the position was reached. + // False means the sign position was confirmed. Both outcomes are explicit. + // Route 204 arrival accepts a small standing area, rather than one + // exact pose. Preserve strong observations through passing shadows + // only while stationary; every movement invalidates this history. + static bool align_to_route204_floaroma_turn(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector battle_menu(BattleType::TRAINER, COLOR_GREEN); + size_t confirmed=0, missing=0, blocked_left=0; + double previous_dx=1; + bool previously_left=false; + stream.log("[Route 204 north turn] Locate left to the sign; correct height after horizontal alignment."); + for (size_t attempt=0; attempt<60; attempt++) { + const auto frame=stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: No video frame.", stream); + if (dialogue.detect(*frame.frame) || battle_menu.detect(*frame.frame)) return true; + const auto result=match_route204_floaroma_turn(*frame.frame); + if (result.at_target) { + if (++confirmed>=3) { + stream.log("[Route 204 north turn] Sign position confirmed; ready to continue west.", COLOR_GREEN); + return false; + } + pbf_wait(context,150ms); + context.wait_for_all_requests(); + continue; + } + confirmed=0; + double x=-1, y=0; + Milliseconds duration=100ms; + if (result.located) { + missing=0; + stream.log("[Route 204 north turn] Sign offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); + const bool horizontal=std::abs(result.dx)>1.0/50; + if (previously_left && std::abs(result.dx-previous_dx)<1.0/384) blocked_left++; + else blocked_left=0; + // Primarily locate left. If a fence/NPC blocks progress, + // correct height briefly before resuming measured travel. + const bool correct_height=!horizontal || + (blocked_left>=3 && std::abs(result.dy)>1.0/54); + x=correct_height ? 0.0 : (result.dx<0 ? -1.0 : 1.0); + y=correct_height ? (result.dy>0 ? -1.0 : 1.0) : 0.0; + const double error=correct_height ? std::abs(result.dy) : std::abs(result.dx); + duration=error<.08 ? 40ms : 100ms; + previous_dx=result.dx; + previously_left=x<0; + if (correct_height) blocked_left=0; + } else { + previously_left=false; + blocked_left=0; + if (++missing>12) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Sign unavailable during bounded leftward acquisition.", stream); + } + if (watch_route204_trainer_event(stream,context, + [x,y,duration](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{x,y},duration,0ms); + pbf_wait(ctx,250ms); + }, "Route 204 north sign alignment")) return true; + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Sign position did not converge.", stream); + } + + static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + size_t confirmed=0, missing=0; + bool previously_located=false; + stream.log("[Windworks approach] Locate beside the sign before the blind wall/NPC approach."); + for (size_t attempt=0; attempt<80; attempt++) { + const auto frame=stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: No video frame.",stream); + const auto result=match_windworks_sign_approach(*frame.frame); + if (result.at_target) { + if (++confirmed>=3) return; + pbf_wait(context,150ms); + } else { + confirmed=0; + double x=0,y=1; + Milliseconds duration=100ms; + if (result.located) { + missing=0; + previously_located=true; + // Correct large height errors first, including coming + // south from the wall, then clear the grass horizontally. + const bool vertical=result.dy>.08 || std::abs(result.dx)<=1.0/50; + const double error=vertical?std::abs(result.dy):std::abs(result.dx); + x=vertical?0:(result.dx<0?-1:1); + y=vertical?(result.dy>0?-1:1):0; + duration=error<.08?40ms:100ms; + stream.log("[Windworks approach] Sign offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); + } else if (previously_located && ++missing<=12) { + pbf_wait(context,250ms); + context.wait_for_all_requests(); + continue; + } else if (previously_located || ++missing>12) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: Sign unavailable during bounded northward acquisition.",stream); + } + pbf_move_left_joystick(context,{x,y},duration,0ms); + pbf_wait(context,250ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: Sign position did not converge.",stream); + } + + struct Route204ArrivalTracker { + using Clock = std::chrono::steady_clock; + static constexpr double TOLERANCE_X = 1.0/32; + static constexpr double TOLERANCE_Y = 1.0/36; + struct Anchor { + bool seen = false; + double dx = 0, dy = 0; + Clock::time_point timestamp{}; + }; + Anchor anchors[2]; + bool holding = false; + Clock::time_point hold_started{}; + void reset() { + anchors[0].seen = anchors[1].seen = false; + holding = false; + } + bool observe(const OverworldMatchResult& result, Clock::time_point now) { + if (result.landmarks.size() != 2) { reset(); return false; } + for (const auto& landmark : result.landmarks) { + if (landmark.accepted && + (std::abs(landmark.dx)>TOLERANCE_X || std::abs(landmark.dy)>TOLERANCE_Y)) { + reset(); + return false; + } + } + for (size_t i = 0; i < 2; i++) { + const auto& landmark = result.landmarks[i]; + if (!landmark.accepted) continue; + anchors[i] = {true, landmark.dx, landmark.dy, now}; + if (!holding) { holding = true; hold_started = now; } + } + for (auto& anchor : anchors) { + if (anchor.seen && now-anchor.timestamp>10s) anchor.seen = false; + } + return anchors[0].seen && anchors[1].seen && + std::abs(anchors[0].dx-anchors[1].dx)<=1.0/64 && + std::abs(anchors[0].dy-anchors[1].dy)<=1.0/54; + } + bool wait_for_shadow(Clock::time_point now) const { + // Bound the stationary wait even if one anchor keeps matching. + return holding && now-hold_started<10s; + } + }; + + static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& context, bool northbound = false) { + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector menu(BattleType::TRAINER, COLOR_GREEN); + BlackScreenDetector black; + WhiteScreenDetector white; + size_t confirmations = 0, missing = 0, blocked_east = 0, detours = 0; + double previous_dx = 1; + bool previous_east = false; + Route204ArrivalTracker arrival; + for (size_t attempt = 0; attempt < 80; attempt++) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: No video frame.", stream); + if (dialogue.detect(*frame.frame) || menu.detect(*frame.frame)) { + stream.log("[Route 204 sign] Next trainer triggered; stopping alignment."); + return true; + } + double x = 0, y = 0; + bool detour = false, measured = false; + double measured_dx = 0; + Milliseconds duration = 250ms; + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + menu.make_overlays(overlays); + if (black.detect(*frame.frame) || white.detect(*frame.frame)) { + confirmations = 0; + arrival.reset(); + } else { + const auto result = northbound ? match_route204_north_approach(*frame.frame) : match_route204_sign(*frame.frame); + measured=result.located;measured_dx=result.dx; + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Route 204 sign] " + landmark.name + " score=" + std::to_string(landmark.score) + + ", margin=" + std::to_string(landmark.margin) + ", accepted=" + std::to_string(landmark.accepted) + + ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + const auto now = Route204ArrivalTracker::Clock::now(); + // The broad pre-battle cache tolerance can accept the sign + // while still inside the fence's collision column. + bool clear_column=true; + if (northbound) for (const auto& landmark:result.landmarks) + if (landmark.accepted && std::abs(landmark.dx)>1.0/128) clear_column=false; + if (!clear_column) arrival.reset(); + const bool arrived = clear_column && arrival.observe(result, now); + if (arrived) { + if (++confirmations >= 3) { + stream.log(northbound ? "[Route 204 sign] Left-of-sign northbound position confirmed." : "[Route 204 sign] Position confirmed; ready for the next trainer approach."); + return false; + } + } else if (arrival.wait_for_shadow(now)) { + // One strong nearby anchor is enough to hold still, + // but both are still required to confirm arrival. + confirmations = 0; + missing = 0; + duration = 250ms; + stream.log("[Route 204 sign] Holding near target while waiting for shadowed landmark."); + } else if (result.located) { + confirmations = 0; + missing = 0; + stream.log(std::string("[Route 204 sign] ") + (result.coarse ? "Sign-guided acquisition." : "Both landmarks located.")); + // At the wrong height, leftward movement can hit the + // grass boundary. Acquire the sign's height first. + const bool horizontal = (!result.coarse || std::abs(result.dy)<=1.0/54) && + std::abs(result.dx)>=std::abs(result.dy)*9.0/16; + const double error = horizontal ? result.dx : result.dy*9.0/16; + x = horizontal ? (result.dx>0 ? 1.0 : -1.0) : 0.0; + y = horizontal ? 0.0 : (result.dy<0 ? 1.0 : -1.0); + duration = std::abs(error)>1.0/24 ? 80ms : std::abs(error)>1.0/64 ? 40ms : 16ms; + if (result.coarse && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/72) { + // Resume fine acquisition after the bounded shadow wait. + // The bounded shadow wait has ended. Make a small + // correction instead of holding indefinitely. + duration = 40ms; + } + } else { + confirmations = 0; + if (++missing > 24) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: Landmarks missing during bounded acquisition.", stream); + // The sign is north of the defeated female trainer. + // Search that height first, then return below it. + if (northbound) { + // Tyler leaves us west of the sign. Search east + // along the road instead of climbing into grass. + x=1.0;y=0.0; + } else y = missing<=8 ? 1.0 : -1.0; + duration = 100ms; + } + } + if (northbound && measured && x>0 && previous_east && std::abs(previous_dx-measured_dx)<1.0/384) blocked_east++; + else blocked_east=0; + previous_east=northbound && measured && x>0; + // Reuse this snapshot; no movement has been sent since it. + if (previous_east) previous_dx=measured_dx; + if (blocked_east>=3) { + if (++detours>2) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north approach: Tyler obstruction did not clear.",stream); + stream.log("[Route 204 sign] Eastward movement blocked; passing below defeated Tyler."); + detour=true;blocked_east=0;previous_east=false; + } + if (x != 0 || y != 0 || detour) arrival.reset(); + // Observe throughout each correction and its settle period, + // so an early next-trainer trigger cancels remaining movement. + if (watch_route204_trainer_event(stream, context, + [x,y,duration,detour](ProControllerContext& ctx) { + if (detour) { + pbf_move_left_joystick(ctx,{0,-1},180ms,0ms); + pbf_move_left_joystick(ctx,{1,0},400ms,0ms); + pbf_move_left_joystick(ctx,{0,1},180ms,0ms); + pbf_wait(ctx,500ms); + } else if (x!=0 || y!=0) { + pbf_move_left_joystick(ctx, {x,y}, duration, 0ms); + pbf_wait(ctx, 500ms); + } else pbf_wait(ctx, duration); + }, "Route 204 sign alignment")) return true; + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: Position could not be confirmed.", stream); + } + + static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + // The cave's brown corridor and black map void distinguish an + // indoor return from a battle fade or an outdoor scene. All ROIs + // are normalized and visible without labels in the overlays. + const ImageFloatBox floor_left{0.42, 0.35, 0.08, 0.10}; + const ImageFloatBox floor_right{0.52, 0.35, 0.08, 0.10}; + const ImageFloatBox map_void{0.15, 0.85, 0.10, 0.08}; + auto cave_visible = [&](const ImageRGB32& frame) { + for (const auto& box : {floor_left, floor_right}) { + const auto stats = image_stats(extract_box_reference(frame, box)); + if (stats.average.r < stats.average.g + 12 || + stats.average.g < stats.average.b + 12) return false; + } + const auto empty = image_stats(extract_box_reference(frame, map_void)); + return empty.average.r < 20 && empty.average.g < 20 && + empty.average.b < 20 && empty.stddev.sum() < 30; + }; + auto handle_trainer = [&] { + stream.log("[Ravaged Path approach] Lass Samantha triggered; completing battle before continuing."); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + Route204ArrivalTracker arrival; + size_t position_confirmations = 0; + auto missing_since = std::chrono::steady_clock::now(); + bool initial_approach = true; + size_t acquisition_pulses = 0; + size_t blocked_north=0; + double previous_north_dy=1; + bool previous_coarse_north=false; + for (size_t attempt = 0; attempt < 120; attempt++) { + ShortDialogWatcher dialogue(COLOR_YELLOW); + BattleMenuWatcher battle(BattleType::TRAINER, COLOR_GREEN); + BlackScreenWatcher entrance; + VideoOverlaySet overlays(stream.overlay()); + overlays.add(COLOR_CYAN, floor_left); + overlays.add(COLOR_CYAN, floor_right); + overlays.add(COLOR_CYAN, map_void); + const auto before = stream.video().snapshot(); + if (!before) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path entrance: No video frame.", stream); + ShortDialogDetector current_dialogue; + BattleMenuDetector current_battle(BattleType::TRAINER); + if (current_dialogue.detect(*before.frame) || current_battle.detect(*before.frame)) { + handle_trainer(); + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + continue; + } + double x = 0, y = 0; + Milliseconds duration = 250ms; + if (initial_approach) { + // Retain the northbound approach from Tyler. Subsequent + // movement is landmark-guided, without the old right sweep. + initial_approach = false; + y = 1; + duration = 2400ms; + } else { + auto result = match_ravaged_path_south_entrance(*before.frame); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[Ravaged Path locator] " + landmark.name + + " score=" + std::to_string(landmark.score) + + ", margin=" + std::to_string(landmark.margin) + + ", accepted=" + std::to_string(landmark.accepted) + + ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + const auto now = std::chrono::steady_clock::now(); + auto fixed = result; + fixed.landmarks.resize(2); + // Cave entry needs tighter horizontal alignment than the + // standing area beside the Route 204 sign. + bool horizontal_ready = true; + for (const auto& anchor : fixed.landmarks) + if (anchor.accepted && std::abs(anchor.dx)>1.0/64) horizontal_ready = false; + if (!horizontal_ready) arrival.reset(); + const bool arrived = horizontal_ready && arrival.observe(fixed, now); + if (arrived) { + if (++position_confirmations >= 3) { + stream.log("[Ravaged Path locator] Entrance position confirmed; moving north into cave.", COLOR_GREEN); + y = 1; + duration = 1200ms; + } + } else if (arrival.wait_for_shadow(now)) { + position_confirmations = 0; + stream.log("[Ravaged Path locator] Holding still for shadowed landmark."); + } else if (result.located) { + missing_since = now; + position_confirmations = 0; + // Escape the upper-right alcove vertically before + // moving left. Near the doorway, correct horizontal first. + if (std::abs(result.dy)>1.0/36) { + y = result.dy>0 ? -1.0 : 1.0; + } else if (std::abs(result.dx)>1.0/64) { + x = result.dx>0 ? 1.0 : -1.0; + } + duration = 80ms; + if (result.coarse && y>0 && previous_coarse_north && + std::abs(result.dy-previous_north_dy)<1.0/384) blocked_north++; + else blocked_north=0; + previous_north_dy=result.dy; + previous_coarse_north=result.coarse && y>0; + if (blocked_north>=4) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Northward coarse acquisition blocked; refusing repeated movement.",stream); + if (result.coarse && x==0 && y==0) { + // After the stationary shadow wait, correct a + // small height error that may still change the + // sign's perspective enough to prevent matching. + if (std::abs(result.dy)<=1.0/72) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Near entrance, but sign remained shadowed; refusing blind correction.", stream); + y = result.dy<0 ? 1.0 : -1.0; + duration = 40ms; + } + if (y<0) { + // Fixed entrance geometry predicts the lass's + // feet when she is shadowed/off-screen. A visible + // lass may tighten this limit, never relax it. + double boundary = 930.0/1080 + result.dy; + if (result.landmarks.size()>2 && result.landmarks[2].accepted) + boundary = std::min(boundary, + result.landmarks[2].found.y + result.landmarks[2].found.height); + // Leave >12% screen height above her before any + // short down pulse. No blind southward search. + if (boundary - 0.55 <= 0.12) { + y = 0; + stream.log("[Ravaged Path locator] Downward correction blocked by lass boundary.", COLOR_RED); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Southern safety boundary reached.", stream); + } + } + } else { + position_confirmations = 0; + if (now-missing_since>10s) { + // Failed recording: still beside the Route 204 + // sign, blocked below the northbound approach. + // Only this recognizable route authorizes search. + const auto route = match_route204_sign(*before.frame); + const bool known_route = !route.landmarks.empty() && + route.landmarks[0].accepted && route.landmarks[0].score>=.94 && + route.landmarks[0].margin>=.30; + if (known_route && acquisition_pulses<8) { + const bool sidestep = acquisition_pulses++ % 2 == 0; + x = sidestep ? -1.0 : 0.0; + y = sidestep ? 0.0 : 1.0; + duration = sidestep ? 150ms : 500ms; + stream.log("[Ravaged Path locator] Recognized Route 204 sign; bounded left/north acquisition."); + } else OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Landmarks unavailable after bounded acquisition.", stream); + } + } + } + if (x!=0 || y!=0) { + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + } + const int ret = run_until(stream, context, + [x,y,duration](ProControllerContext& ctx) { + if (x!=0 || y!=0) { + pbf_move_left_joystick(ctx, {x,y}, duration, 0ms); + pbf_wait(ctx, 500ms); + } else pbf_wait(ctx, duration); + }, {{entrance}, {dialogue}, {battle}}); + context.wait_for_all_requests(); + if (ret == 1 || ret == 2) { + handle_trainer(); + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + continue; + } + if (ret != 0) continue; + // A trainer transition can also go black. Require a stable + // cave scene, or route the detected battle through combat. + const auto deadline = std::chrono::steady_clock::now() + 15s; + size_t confirmed = 0; + bool trainer_seen = false; + while (std::chrono::steady_clock::now() < deadline) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path entrance: No frame after transition.", stream); + if (current_dialogue.detect(*frame.frame) || current_battle.detect(*frame.frame)) { + trainer_seen = true; + break; + } + if (cave_visible(*frame.frame)) { + if (++confirmed >= 5) { + stream.log("[Ravaged Path entrance] Transition and indoor corridor confirmed.", COLOR_GREEN); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + return; + } + } else confirmed = 0; + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + if (trainer_seen) handle_trainer(); + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path entrance: Could not confirm entry after bounded approach.", stream); + } + + static void exit_ravaged_path_to_route204(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + stream.log("[Ravaged Path north exit] Right wall, lower wall, then left/down search."); + // Observe the entire approach: reaching the exit during either + // wall movement must cancel the remaining queued commands. + WhiteScreenWatcher white; + const int result=run_until(stream,context, + [](ProControllerContext& ctx) { + // Deliberate overtravel establishes the walls despite + // variation in the position after Rock Smash. + pbf_move_left_joystick(ctx,{1,0},3000ms,100ms); + pbf_move_left_joystick(ctx,{0,-1},1800ms,100ms); + for (size_t attempt=0; attempt<30; attempt++) { + pbf_move_left_joystick(ctx,{-1,0},160ms,80ms); + pbf_move_left_joystick(ctx,{0,-1},120ms,80ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx,1500ms); + },{{white}}); + if (result!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path north exit: No white transition during left/down search.",stream); + context.wait_for_all_requests(); + stream.log("[Ravaged Path north exit] White transition detected; movement stopped."); + WhiteScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8},500,10, + WhiteScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,15s,{{visible}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path north exit: White transition did not finish.",stream); + pbf_wait(context,700ms); + context.wait_for_all_requests(); + } + + void roark_to_floaroma(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 550ms, 0ms); + pbf_wait(context, 1046ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); + pbf_wait(context, 288ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 250ms, 0ms); + pbf_wait(context, 1009ms); + align_to_oreburgh_mart(stream, context); + pbf_wait(context, 1009ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1128ms, 0ms); + pbf_wait(context, 2924ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 885ms, 0ms); + buy_items(stream, context, { {ShopItem::Repel, BUY_MAX} }); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1193ms, 0ms); + pbf_wait(context, 2226ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2544ms, 0ms); + pbf_wait(context, 753ms); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 464ms, 0ms); + pbf_wait(context, 519ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1467ms, 0ms); + pbf_wait(context, 2816ms); + // Barry conversation + wait_for_dialogue(stream, context, "Barry conversation"); + context.wait_for_all_requests(); + + ShortDialogDetector dialogue(COLOR_YELLOW); + const auto started = std::chrono::steady_clock::now(); + auto absent_since = started; + + while (true) { + const auto frame = stream.video().snapshot(); + if (!frame) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Barry conversation: No video frame.", stream + ); + } + + const auto now = std::chrono::steady_clock::now(); + if (now - started >= 180s) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Barry conversation: Dialogue did not finish.", stream + ); + } + + if (dialogue.detect(*frame.frame)) { + absent_since = now; + } + else if (now - absent_since >= 2s) { + break; + } + + // Keep mashing through pauses between dialogue boxes. + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 282ms, 0ms); + pbf_wait(context, 483ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 218ms, 0ms); + pbf_wait(context, 482ms); + enter_oreburgh_gate_from_city(stream, context); + activate_repel(stream, context, 5); + exit_oreburgh_gate_to_route203(stream, context); + // Align north, then go only far enough west to jump the ledge. + // Avoid descending the upper stairs and climbing straight back up. + pbf_move_left_joystick(context, {0.0, 1.0}, 600ms, 0ms); + pbf_wait(context, 400ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 2100ms, 0ms); + pbf_wait(context, 300ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 1700ms, 0ms); + pbf_wait(context, 500ms); + // Cross the lower stairs at the offset needed for the next north leg. + // Combine the old left/right correction and the two upward moves. + pbf_move_left_joystick(context, {-1.0, 0.0}, 850ms, 0ms); + pbf_wait(context, 150ms); + pbf_move_left_joystick(context, {0.0, 1.0}, 1800ms, 0ms); + pbf_wait(context, 150ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 1400ms, 0ms); + pbf_wait(context, 100ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 1000ms, 0ms); + pbf_wait(context, 100ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 1600ms, 0ms); + pbf_wait(context, 600ms); + // Exhaust the active Repel and decline renewal before the safe + // Jubilife approach, story battle, and Pokemon Center sequence. + force_repel_expiry(stream, context, RepelRenewalChoice::NO); + // One clock setup covers this whole new locator strip: street, + // Galactic plaza approach, and the post-battle return. Existing + // locators elsewhere keep their current time-independent behavior. + stream.log("[Jubilife locator setup] Setting 13:00 once for the street/plaza locator strip; preserving the date."); + DateTime locator_time; + locator_time.hour = 13; + locator_time.minute = 0; + locator_time.second = 0; + set_time_fast(env, context, locator_time, true); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + // Stop east of the TV-station overshoot seen in the recording. + // Acquire/correct the street lane before moving north; the locator + // now supplies vertical travel instead of the blind 2579ms up move. + pbf_move_left_joystick(context, {-1.0, 0.0}, 3700ms, 0ms); + pbf_wait(context, 850ms); + align_to_overworld_landmarks(stream, context, match_jubilife_street_approach, + "Jubilife street alignment", false, true); + // Stop the upward approach if the story dialogue starts early. + ShortDialogWatcher plaza_story(COLOR_YELLOW); + run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0.0, 1.0}, 2600ms, 0ms); + pbf_wait(ctx, 250ms); + }, {{plaza_story}}); + align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, "Jubilife plaza alignment", true); + approach_galactic_dialogue(stream, context); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + pbf_wait(context, 2000ms); + clear_post_battle_dialogue(stream, context); + // Rowan and the Poketch president leave the player north of the + // reference. Bring the plaza landmarks back into view first. + stream.log("[Galactic return] Dialogue cleared; short downward return before reacquiring the plaza."); + pbf_move_left_joystick(context, {0.0, -1.0}, 600ms, 0ms); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, + "Jubilife post-battle plaza alignment"); + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 5141ms, 0ms); + pbf_wait(context, 749ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 868ms, 0ms); + pbf_wait(context, 1073ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 431ms, 0ms); + pbf_wait(context, 3099ms); + // Stop outside: the old additional 1028ms up move sometimes + // entered the building before exterior alignment even started. + // Repel is inactive here; no expiry search is needed before the Center. + align_and_enter_jubilife_center(stream, context); + if (!heal_pokemon(stream, context, "Jubilife")) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Jubilife Center: Healing/exit did not complete.", stream); + + pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1440ms, 0ms); + pbf_wait(context, 2259ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 765ms, 0ms); + pbf_wait(context, 439ms); + const bool missed_trainer = !approach_route204_female_trainer(stream, context); + stream.log("[Route 204 female trainer] missed_trainer=" + std::to_string(missed_trainer)); + if (missed_trainer) recover_route204_female_trainer(stream, context); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + // Resume encounter protection after the Center and the following battle. + if (!activate_repel(stream, context, 6)) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Post-Center battle: Could not activate a new Repel from the Bag.", stream); + } + context.wait_for_all_requests(); + const bool next_trainer_started = align_to_route204_sign(stream, context); + if (!next_trainer_started) { + const bool triggered = watch_route204_trainer_event(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 1200ms, 0ms); + pbf_wait(ctx, 2500ms); + }, "Route 204 next trainer approach"); + if (!triggered) recover_route204_youngster(stream, context); + } + // An early locator interruption still has to complete combat; + // it must never fall through into overworld movement mid-battle. + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + // Keep the recently activated Repel through Ravaged Path. + // Reacquire west of the sign after Tyler before moving north; + // do not spend its remaining steps on an early expiry search. + bool northbound_aligned=false; + for (size_t recovery=0; recovery<3; recovery++) { + if (!align_to_route204_sign(stream,context,true)) {northbound_aligned=true;break;} + wait_for_trainer_battle_menu(stream,context); + Barry_1_fight(env,stream,context); + clear_post_battle_dialogue(stream,context); + } + if (!northbound_aligned) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north approach: Trainer interruptions exhausted.",stream); + enter_ravaged_path_from_route204(env, stream, context); + //cave navigation + + // Ravaged Path -> Floaroma -> Valley Windworks -> Mars. + // First-pass route from ravagedpath.txt and the 2026-10-13 + // 19-48-08 recording. Timings need live route validation. + auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms) { + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, settle); + context.wait_for_all_requests(); + }; + auto battle = [&](const std::string& label) { + stream.log("[Segment 02 battle] " + label); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + // Break the blocking rock using the existing HM confirmation helper. + move(0, 1, 1100ms, 900ms); + move(1, 0, 600ms); + use_HM(stream, context, "Rock Smash in Ravaged Path"); + context.wait_for_all_requests(); + clear_post_battle_dialogue(stream, context); + // Failed frames stop against the remaining center rocks. Pass + // above their collision row before the eastward exit approach. + move(0,1,200ms); + exit_ravaged_path_to_route204(stream,context); + move(0, -1, 350ms, 500ms); + + // Resolve the current Repel before searching for Teighler so + // its expiry prompt cannot interrupt trainer acquisition. + // Renew here to protect the remaining route to Floaroma. + force_repel_expiry(stream, context, RepelRenewalChoice::YES); + + // Route 204 north: go around the cliff to Aroma Lady Teighler. + move(-1, 0, 1300ms); + move(0, 1, 1300ms, 650ms); + // Timed approach: get onto the alcove's left edge, descend + // to its bottom ledge, then cross right through her sightline. + // Watch the entire approach so an early trigger stops movement. + if (!watch_route204_trainer_event(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {1, 0}, 600ms, 100ms); + pbf_move_left_joystick(ctx, {0, -1}, 600ms, 100ms); + pbf_move_left_joystick(ctx, {1, 0}, 1000ms, 0ms); + pbf_wait(ctx, 3000ms); + }, "Aroma Lady Teighler timed ledge approach")) { + recover_teighler_trigger(stream, context); + } + stream.log("[Segment 02 battle] Aroma Lady Teighler: Flamethrower (move 2)."); + wait_for_trainer_battle_menu(stream,context); + fight_trainer_using_move_slot(stream,context,2,"Aroma Lady Teighler (Flamethrower)"); + clear_post_battle_dialogue(stream,context); + + // Omit the Awakening. The grass north of Teighler can trigger + // the Bug Catcher: finish that battle before any Center locator. + // Small watched steps preserve the remaining route if he interrupts. + size_t northbound_battles = 0; + ShortDialogDetector northbound_dialogue(COLOR_YELLOW); + BattleMenuDetector northbound_menu(BattleType::TRAINER, COLOR_GREEN); + auto northbound_move = [&](double x, double y, Milliseconds remaining, bool stop_after_battle = false) { + while (remaining > 0ms) { + const Milliseconds step = remaining > 200ms ? 200ms : remaining; + // A dialogue can appear at the end of the previous + // short step. Check it directly before restarting the + // watcher's 250ms confirmation window or sending input. + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 northbound: No video frame.", stream); + const bool interrupted = northbound_dialogue.detect(*frame.frame) || + northbound_menu.detect(*frame.frame) || + watch_route204_trainer_event(stream, context, + [x,y,step](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {x,y}, step, 0ms); + }, "Route 204 northbound movement"); + if (interrupted) { + if (++northbound_battles > 2) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 northbound: Too many trainer interruptions.", stream); + battle("Route 204 north Bug Catcher"); + // The sign locator establishes the next turn after this + // battle; do not finish an obsolete northward timer. + if (stop_after_battle) return; + // Replay only this possibly interrupted short step, + // never the complete pre-battle travel duration. + } else remaining -= step; + } + }; + northbound_move(1, 0, 2000ms); // Reach the right wall. + northbound_move(0, 1, 450ms); // Clear the corner above it. + northbound_move(1, 0, 1200ms); // Move into the northbound lane. + // Restore the north/west turns from the successful manual + // recording. The former 3200ms up stopped at the Bug Catcher, + // well short of town, and omitted the flower-bed detour. + northbound_move(0, 1, 2200ms, true); + // Locate left to the sign after the Bug Catcher, or proceed + // directly if skipped. Do not seek out a missed trainer. + for (size_t recovery=0; align_to_route204_floaroma_turn(stream,context); recovery++) { + if (recovery>=2) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Too many trainer interruptions.", stream); + battle("Route 204 north Bug Catcher"); + } + northbound_move(-1, 0, 1100ms); + northbound_move(0, 1, 150ms); + northbound_move(-1, 0, 1200ms); + northbound_move(-1, 0, 800ms); + // Continue through Floaroma's southern fence opening before + // turning east. The shorter approach stopped against the fence. + stream.log("[Floaroma approach] Moving farther north before Center approach."); + northbound_move(0, 1, 2700ms); + // Expire the Repel in the town approach lane before moving + // toward the Center. Alignment corrects any partial final step. + force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); + establish_floaroma_house_exit(stream,context); + context.wait_for_all_requests(); + stream.log("[Segment 02] Floaroma house exit established; ready for town checkpoint.", COLOR_GREEN); + } + + static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context) { + auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms) { + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, settle); + context.wait_for_all_requests(); + }; + auto transition = [&](double x, double y, Milliseconds duration, const std::string& label) { + context.wait_for_all_requests(); + stream.log("[Route transition] " + label); + BlackScreenWatcher black; + WhiteScreenWatcher white; + const int ret = run_until(stream, context, + [x, y, duration](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 1500ms); + // Bounded doorway correction if the recorded move stops short. + for (size_t pulse = 0; pulse < 10; pulse++) { + pbf_move_left_joystick(ctx, {x, y}, 100ms, 100ms); + } + }, {{black}, {white}}); + if (ret < 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Entrance/exit transition not detected.", stream); + context.wait_for_all_requests(); + BlackScreenDetector black_frame; + WhiteScreenDetector white_frame; + // Accept either fade type, then require a stable return before moving. + const auto deadline = std::chrono::steady_clock::now() + 15s; + auto clear_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame during transition.", stream); + const auto now = std::chrono::steady_clock::now(); + if (black_frame.detect(*frame.frame) || white_frame.detect(*frame.frame)) clear_since = now; + else if (now - clear_since >= 500ms) { + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + return; + } + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Transition did not finish.", stream); + }; + auto battle = [&](const std::string& label) { + stream.log("[Segment 02 battle] " + label); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + auto talk = [&](const std::string& label, Button response = BUTTON_B) { + stream.log("[Segment 02 dialogue] " + label); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, label, 5000ms); + clear_post_battle_dialogue(stream, context, response); + }; + + // The house exit fixes the start pose. Use the median of the + // twenty successful recorded approaches: 24 south taps, then + // nine east taps. Retain the recorded tap cadence; replacing + // it with one continuous hold changes acceleration/step timing. + stream.log("[Floaroma Center] Blind south/east approach from the house exit."); + for (size_t step=0;step<24;step++) move(0,-1,80ms,250ms); + for (size_t step=0;step<9;step++) move(1,0,80ms,500ms); + transition(0, 1, 950ms, "Floaroma Pokemon Center entrance"); + // heal_pokemon includes a watched exit; do not exit a second time. + if (!heal_pokemon(stream, context, "Floaroma")) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma Center: Healing/exit did not complete.", stream); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + // Clear the Center doorway to the west before turning north, + // so we cannot walk back through the door. Repel is already off. + move(-1, 0, 1500ms); + + // Meet the girl on Route 205, then return for the Works Key. + move(0, 1, 1000ms); + // Include the westward clearance and the former 650 ms east leg. + move(1, 0, 6550ms, 900ms); + talk("Route 205 girl requesting help for her father"); + move(-1, 0, 4500ms, 500ms); + // Repel is already inactive; omit the recorded renewal A presses. + move(-1, 0, 3000ms); + move(0, 1, 2300ms); + // The westward overtravel establishes the tree wall. The + // recording then stops west of the Meadow gap: move right + // into that opening instead of pushing farther left. + move(1, 0, 300ms); + transition(0, 1, 1000ms, "Floaroma Meadow entrance"); + move(0, 1, 1100ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + stream.log("[Segment 02 battle] Floaroma Meadow first Galactic grunt."); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + // The second grunt starts another battle automatically. Do not + // wait for quiet overworld or send movement between the fights. + battle("Floaroma Meadow second Galactic grunt"); + talk("Floaroma Meadow honey seller and Works Key"); + if (!clear_new_item_flags(stream,context,6)) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Floaroma Meadow: Bag NEW flags did not clear.",stream); + transition(0, -1, 1000ms, "Floaroma Meadow exit"); + move(0, -1, 2600ms); + + // No Repel is needed on the town/road route to Valley Windworks. + move(1, 0, 4200ms); + move(0, -1, 200ms); + move(1, 0, 5500ms); + move(0, -1, 500ms); + move(1, 0, 1200ms); + align_to_windworks_sign(stream, context); + // From the sign-side pose, the building wall fixes north/south + // position and the grunt blocks the subsequent eastward travel. + move(0, 1, 2000ms); + move(1, 0, 1500ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + battle("Valley Windworks door Galactic grunt"); + + // Probe the door after the grunt retreats. Cancel all remaining + // movement as soon as dialogue appears, preserving doorway alignment. + bool door_dialogue=false; + for (size_t attempt=0; attempt<20 && !door_dialogue; attempt++) { + ShortDialogWatcher dialogue(COLOR_YELLOW); + door_dialogue=run_until(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},100ms,0ms); + pbf_press_button(ctx,BUTTON_A,150ms,400ms); + pbf_move_left_joystick(ctx,{1,0},40ms,0ms); + pbf_wait(ctx,250ms); + },{{dialogue}})>=0; + context.wait_for_all_requests(); + } + if (!door_dialogue) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Valley Windworks: Door dialogue not detected after bounded up/A/right probes.",stream); + stream.log("[Windworks door] Dialogue detected; confirm Works Key for two seconds."); + pbf_mash_button(context,BUTTON_A,2000ms); + context.wait_for_all_requests(); + // Interaction reaches the door from west of its entrance lane. + // One additional recorded nudge centers the subsequent northward entry. + move(1, 0, 40ms); + transition(0, 1, 1000ms, "Valley Windworks entrance"); + clear_post_battle_dialogue(stream, context); // Entrance warning from the grunt. + move(-1, 0, 2000ms); // Combined 1210ms + 817ms, separated by only 5ms. + bool fought_grunt=watch_route204_trainer_event(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0, 1}, 1250ms, 0ms); + pbf_wait(ctx, 2500ms); + }, "Valley Windworks interior grunt approach"); + if (fought_grunt) battle("Valley Windworks interior Galactic grunt"); + auto grunt_position=[&]() { + OverworldMatchResult position; + for (size_t attempt=0;attempt<12;attempt++) { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if (frame) position=match_windworks_interior_grunt(*frame.frame); + if (position.located) return position; + pbf_wait(context,250ms); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: Could not determine player position relative to the grunt.",stream); + return position; + }; + auto position=grunt_position(); + if (!fought_grunt && position.dy<=.025) { + stream.log("[Windworks interior] Below/level with grunt; recover north and interact."); + for (size_t attempt=0;attempt<20 && !fought_grunt;attempt++) { + fought_grunt=watch_route204_trainer_event(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},100ms,0ms); + pbf_press_button(ctx,BUTTON_A,150ms,500ms); + },"Windworks interior below-grunt recovery"); + } + if (!fought_grunt) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: North/A recovery did not trigger the grunt battle.",stream); + battle("Valley Windworks interior Galactic grunt (below recovery)"); + } + auto align_above_grunt=[&]() { + // Re-establish the west wall after a below-grunt interaction, + // then climb past his row before crossing the partition gap. + move(-1,0,1000ms); + bool above=false; + for (size_t attempt=0;attempt<20;attempt++) { + position=grunt_position(); + if (position.dy>.025) {above=true;break;} + move(0,1,80ms); + } + if (!above) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: Could not get above the defeated grunt.",stream); + }; + if (fought_grunt) align_above_grunt(); + else stream.log("[Windworks interior] Above grunt; try the rightward crossing for his battle."); + const bool crossing_event=watch_route204_trainer_event(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{1,0},750ms,0ms); + pbf_wait(ctx,2500ms); + },"Windworks interior upper crossing"); + if (crossing_event) { + if (!fought_grunt) battle("Valley Windworks interior Galactic grunt (upper crossing)"); + else clear_post_battle_dialogue(stream,context); + // The event cancelled travel at an unknown point. Reacquire + // the west wall and upper row, then cross from that known pose. + align_above_grunt(); + move(1,0,750ms,450ms); + } else if (!fought_grunt) { + stream.log("[Windworks interior] Rightward crossing completed without a battle; grunt missed, continuing to Mars."); + } + + // The upper crossing above replaces the old blind north/right + // pair. Retain the remaining turns between the machines. + move(0, -1, 300ms, 0ms); + move(1, 0, 900ms); + move(0, 1, 250ms); + move(1, 0, 550ms, 400ms); + move(0, 1, 200ms); + move(1, 0, 750ms, 500ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + battle("Commander Mars"); + clear_post_battle_dialogue(stream, context); // Father/daughter reunion after Mars leaves. + stream.log("[Segment 02] Mars defeated; post-battle dialogue cleared.", COLOR_GREEN); + + } + static void align_to_route205_marker(VideoStream& stream, ProControllerContext& context, bool forest) { + context.wait_for_all_requests(); + const std::string label=forest?"Route 205 forest entrance":"Route 205 bridge"; + size_t confirmed=0,missing=0; + bool acquired=false; + for (size_t attempt=0;attempt<80;attempt++) { + const auto frame=stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": No video frame.",stream); + const auto result=forest?match_route205_forest_entrance(*frame.frame):match_route205_bridge(*frame.frame); + if (result.at_target) { + acquired=true;missing=0; + if (++confirmed>=3) {stream.log("["+label+"] Sign position confirmed.",COLOR_GREEN);return;} + pbf_wait(context,150ms); + } else { + confirmed=0; + double x=0,y=1; + Milliseconds duration=80ms; + if (result.located) { + acquired=true;missing=0; + stream.log("["+label+"] Sign offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); + const bool horizontal=std::abs(result.dx)>1.0/50; + const double error=horizontal?std::abs(result.dx):std::abs(result.dy); + x=horizontal?(result.dx<0?-1:1):0; + y=horizontal?0:(result.dy>0?-1:1); + duration=error<.08?40ms:80ms; + } else if (++missing>12) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Sign unavailable during bounded acquisition.",stream); + } else if (acquired) { + // Never walk past a sign after losing a trusted match. + pbf_wait(context,250ms); + context.wait_for_all_requests(); + continue; + } + pbf_move_left_joystick(context,{x,y},duration,0ms); + pbf_wait(context,250ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Sign position did not converge.",stream); + } + + static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + size_t confirmed=0,missing=0; + for (size_t attempt=0;attempt<80;attempt++) { + const auto frame=stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: No video frame.",stream); + const auto result=match_route205_bank_tree(*frame.frame); + if (!result.located) { + confirmed=0; + if (++missing>=12) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: Tree and blue-hat child unavailable; holding position.",stream); + pbf_wait(context,250ms); + } else if (result.at_target) { + missing=0; + if (++confirmed>=3) {stream.log("[Route 205 bank tree] Position confirmed.",COLOR_GREEN);return;} + pbf_wait(context,150ms); + } else { + confirmed=0;missing=0; + stream.log("[Route 205 bank tree] Offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); + // Clear the north bridge lip before attempting to step west. + // Once at bank height, keep horizontal priority beside the tree. + const bool clear_bank=result.dy < -1.0/54; + const bool horizontal=!clear_bank && std::abs(result.dx)>1.0/96; + const double error=horizontal?std::abs(result.dx):std::abs(result.dy); + const double x=horizontal?(result.dx<0?-1:1):0; + const double y=horizontal?0:(result.dy>0?-1:1); + pbf_move_left_joystick(context,{x,y},error<.08?40ms:80ms,0ms); + pbf_wait(context,250ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: Position did not converge.",stream); + } + + static void align_route205_patrol_position(VideoStream& stream, ProControllerContext& context, + const std::function& match, const std::string& label) { + size_t confirmed=0,missing=0; + for (size_t attempt=0;attempt<100;attempt++) { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": No video frame during patrol positioning.",stream); + const auto result=match(*frame.frame); + if (!result.located) { + confirmed=0; + if (++missing>=16) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Patrol landmarks unavailable; holding position.",stream); + pbf_wait(context,250ms); + } else if (result.at_target) { + missing=0; + if (++confirmed>=3) {stream.log("["+label+"] Patrol position confirmed.",COLOR_GREEN);return;} + pbf_wait(context,150ms); + } else { + confirmed=0;missing=0; + stream.log("["+label+"] Patrol offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); + // Correct height first so the subsequent horizontal step + // stays on the stair landing rather than hitting its cliff. + const bool vertical=std::abs(result.dy)>1.0/54; + const double error=vertical?std::abs(result.dy):std::abs(result.dx); + pbf_move_left_joystick(context, + {vertical?0:(result.dx<0?-1.0:1.0),vertical?(result.dy>0?-1.0:1.0):0}, + error<.08?40ms:80ms,0ms); + pbf_wait(context,250ms); + } + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Patrol position did not converge.",stream); + } + + void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + auto move=[&](double x,double y,Milliseconds duration,Milliseconds settle=250ms) { + pbf_move_left_joystick(context,{x,y},duration,0ms); + pbf_wait(context,settle); + context.wait_for_all_requests(); + }; + auto battle=[&](const std::string& label) { + stream.log("[Route 205 battle] "+label); + wait_for_trainer_battle_menu(stream,context); + Barry_1_fight(env,stream,context); + clear_post_battle_dialogue(stream,context); + }; + auto patrol_battle=[&](const std::string& label, + const std::function& match, + const std::function& approach) { + // One watcher covers every blind step, locator correction and + // stationary wait. An early encounter cancels queued movement. + if (!watch_route204_trainer_event(stream,context, + [&](ProControllerContext& ctx) { + approach(ctx); + ctx.wait_for_all_requests(); + align_route205_patrol_position(stream,ctx,match,label); + pbf_wait(ctx,45000ms); + },label)) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,label+": Trainer did not approach the confirmed patrol position.",stream); + battle(label); + // Either trainer can interrupt the approach. After the fight, + // continue the route without returning to a patrol waypoint + // or replaying the canceled approach movement. + }; + + // Recorded path around the Windworks machines from the exact + // reunion position at the end of 02.02. Keep directional turns. + move(-1,0,1335ms,847ms); + move(0,1,880ms,209ms); + move(-1,0,906ms,471ms); + move(0,-1,427ms,728ms); + move(-1,0,1213ms,936ms); + move(-1,0,991ms,175ms); + move(0,-1,1714ms,456ms); + move(1,-1,6809ms,1862ms); + move(0,-1,922ms,554ms); + move(-1,0,813ms,1370ms); + move(0,-1,1862ms,145ms); + move(-1,0,3478ms,500ms); + // Replace the manual left/right/up sign adjustments with the + // supplied repeatable position south of the bridge. + align_to_route205_marker(stream,context,false); + // Stop on the north bank, before Jacob's sight line. The longer + // hold reached his patrol and interrupted the westward crossing. + move(0,1,800ms,500ms); + if (!activate_repel(stream,context,6)) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Route 205: Could not activate Repel before the grass.",stream); + + // Cross beside the golden tree first, then travel through the + // grass west of Jacob. Clamp north against the plateau cliff + // before completing the westward approach to its south stairs. + // Keep the recorded total westward travel, split around the turn. + move(-1,0,600ms,268ms); + align_to_route205_bank_tree(stream,context); + move(0,1,2500ms,250ms); + move(-1,0,2212ms,268ms); + // Route 204's missed-fight pattern: one continuous approach, + // then stand still and decide before sending the next movement. + ShortDialogDetector hiker_dialogue(COLOR_YELLOW); + BattleDialogDetector hiker_intro(COLOR_YELLOW); + BattleMenuDetector hiker_menu(BattleType::TRAINER,COLOR_GREEN); + BlackScreenDetector hiker_transition; + const auto hiker_started=[&]() { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Daniel north pass: No frame while checking for a dodged battle.",stream); + return hiker_dialogue.detect(*frame.frame) || hiker_intro.detect(*frame.frame) || + hiker_menu.detect(*frame.frame) || hiker_transition.detect(*frame.frame); + }; + ShortDialogWatcher hiker_dialogue_watch(COLOR_YELLOW); + DetectorToFinder hiker_intro_watch("Daniel battle intro",250ms,COLOR_YELLOW); + BattleMenuWatcher hiker_menu_watch(BattleType::TRAINER,COLOR_GREEN); + BlackScreenWatcher hiker_transition_watch; + const bool hiker_encounter=hiker_started() || run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},1200ms,0ms); + ctx.wait_for_all_requests(); + stream.log("[Daniel dodge check] North movement stopped; observing without input."); + // Battle intro uses a long dialogue box before the menu. + // Keep one uninterrupted watcher through the approach and + // this stationary window; no wiggle and no A press. + pbf_wait(ctx,10000ms); + },{{hiker_dialogue_watch},{hiker_intro_watch},{hiker_menu_watch},{hiker_transition_watch}})>=0 || hiker_started(); + stream.log("[Daniel dodge check] missed_trainer="+std::to_string(!hiker_encounter)); + if (hiker_encounter) { + battle("Hiker Daniel"); + stream.log("[Route 205] Daniel defeated; moving north from his battle landing to Zackary."); + } else { + stream.log("[Route 205] Daniel dodged; continuing north up the stairs before turning right to Zackary.",COLOR_GREEN); + } + // Daniel stops us at his row when he engages. A skipped pass + // has already carried us farther north. Resume from each pose + // with its own remaining climb, then confirm the visual waypoint. + // Stop at the bottom of the upper landing: the previous extra + // 300 ms north put us in Zackary's walking lane before the turn. + const Milliseconds north_to_zackary=hiker_encounter?1000ms:400ms; + patrol_battle("Camper Zackary",match_route205_zackary, + [north_to_zackary](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},north_to_zackary,250ms); + // Clamp against the right cliff at the landing's bottom + // corner before visual confirmation and stationary wait. + pbf_move_left_joystick(ctx,{1,0},900ms,250ms); + }); + // Restore the west side for the recorded bridge/stair turns. + move(-1,0,1800ms,250ms); + patrol_battle("Battle Girl Kelsey",match_route205_kelsey, + [](ProControllerContext& ctx) { + const auto step=[&](double x,double y,Milliseconds duration,Milliseconds settle) { + pbf_move_left_joystick(ctx,{x,y},duration,0ms); + pbf_wait(ctx,settle); + }; + step(0,1,638ms,673ms); + step(1,0,476ms,508ms); + step(0,1,717ms,254ms); + step(-1,0,415ms,430ms); + step(0,1,2517ms,57ms); + step(1,0,410ms,299ms); + step(0,1,1343ms,500ms); + step(-1,0,294ms,250ms); + }); + stream.log("[Route 205] Kelsey defeated; taking the left/north/right/north forest approach."); + // Original recorded turns around the ledge and northern outcrop. + // This exit applies even if Kelsey interrupted an earlier step. + move(-1,0,415ms,430ms); + // Renew in the cleared lane so expiry cannot interrupt the climb. + // Keep both recorded northward movement durations unchanged. + force_repel_expiry(stream,context,RepelRenewalChoice::YES,true); + move(0,1,2517ms,57ms); + move(1,0,410ms,299ms); + move(0,1,1343ms,500ms); + align_to_route205_marker(stream,context,true); + move(0,1,20ms); + stream.log("[Route 205] Forest entrance sign confirmed; checkpoint complete.",COLOR_GREEN); + } + // Cheryl controls her own Pokemon. Select Flamethrower visually, + // then A advances target selection and battle text, as in the recording. + void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + (void)env; + // Second recorded run, beginning at the northern Route 205 sign. + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1466ms, 0ms); + pbf_wait(context, 1588ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 531ms, 0ms); + pbf_wait(context, 3398ms); + wait_for_dialogue(stream,context,"Cheryl introduction"); + // Reuse the Galactic plaza cutscene cleanup, including a second + // pass after the animation gap before route movement resumes. + clear_post_battle_dialogue(stream,context); + pbf_wait(context,2000ms); + clear_post_battle_dialogue(stream,context); + auto encounter=[&](const std::string& label,const std::function& approach) { + if (!watch_route204_trainer_event(stream,context,approach,label)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": No trainer encounter on the recorded approach.",stream); + stream.log("[Eterna Forest battle] "+label); + wait_for_trainer_battle_menu(stream,context); + // Ordinary trainers return after the confirmed battle-exit transition. + fight_trainer_using_moves(stream,context,2,label,false); + }; + encounter("Lass Briana and Bug Catcher Jack",[&](ProControllerContext& context) { + pbf_move_left_joystick(context, {1.000000, -0.000000}, 579ms, 0ms); + pbf_wait(context, 404ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 494ms, 0ms); + pbf_wait(context, 358ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1011ms, 0ms); + pbf_wait(context, 205ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1980ms, 0ms); + pbf_wait(context, 125ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1522ms, 0ms); + pbf_wait(context, 38ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1295ms, 0ms); + pbf_wait(context, 1811ms); + pbf_wait(context,10000ms); + }); + encounter("Psychics Lindsey and Elijah",[&](ProControllerContext& context) { + pbf_move_left_joystick(context, {0.000000, 1.000000}, 707ms, 0ms); + pbf_wait(context, 437ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1211ms, 0ms); + pbf_wait(context, 39ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1440ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1426ms, 0ms); + pbf_wait(context, 54ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1788ms, 0ms); + pbf_wait(context, 3269ms); + pbf_wait(context,10000ms); + }); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2844ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 12ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1358ms, 0ms); + pbf_wait(context, 223ms); + // The recording renews here before walking into the last pair. + force_repel_expiry(stream,context,RepelRenewalChoice::YES,true); + encounter("Bug Catchers Donald and Phillip",[&](ProControllerContext& context) { + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2335ms, 0ms); + pbf_wait(context, 1960ms); + pbf_wait(context,10000ms); + }); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3878ms, 0ms); + pbf_wait(context, 33ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 6451ms, 0ms); + pbf_wait(context, 117ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1727ms, 0ms); + pbf_wait(context, 207ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1693ms, 0ms); + pbf_wait(context, 109ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1613ms, 0ms); + pbf_wait(context, 538ms); + // Clear the recorded expiry prompt before Cheryl's farewell. + clear_post_battle_dialogue(stream,context,BUTTON_A); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 928ms, 0ms); + pbf_wait(context, 1487ms); + wait_for_dialogue(stream,context,"Cheryl farewell"); + // Reuse the Galactic plaza cutscene cleanup, including a second + // pass after the animation gap before route movement resumes. + clear_post_battle_dialogue(stream,context); + pbf_wait(context,2000ms); + clear_post_battle_dialogue(stream,context); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1238ms, 0ms); + pbf_wait(context, 2157ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 510ms, 0ms); + pbf_wait(context, 544ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 864ms, 0ms); + pbf_wait(context, 289ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1694ms, 0ms); + pbf_wait(context, 383ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 643ms, 0ms); + pbf_wait(context, 346ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 844ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 678ms, 0ms); + pbf_wait(context, 151ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 4143ms, 0ms); + context.wait_for_all_requests(); + stream.log("[Eterna Forest] East exit and Route 205 bridges complete; at Eterna City approach.",COLOR_GREEN); + } + void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + (void)env; + // Clear Repel on the open street before locator corrections. + // Reuse the helper's horizontal wiggles and decline renewal. + force_repel_expiry(stream,context,RepelRenewalChoice::NO,false); + // Reuse landmark alignment to clear the blue post east of the + // door before entering. Healing starts only after map transition. + align_to_overworld_landmarks(stream,context,match_eterna_center_entrance, + "Eterna Center doorway",false,true); + BlackScreenOverWatcher center_entry(COLOR_RED,{0.1,0.1,0.8,0.8}); + int entry=run_until(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); + pbf_wait(ctx,3000ms); + },{{center_entry}}); + if (entry!=0) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center doorway: Entry transition not confirmed.",stream); + context.wait_for_all_requests(); + if (!heal_pokemon(stream,context,"Eterna")) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit was not confirmed.",stream); + pbf_wait(context, 500ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 500ms, 0ms); + pbf_wait(context, 433ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 580ms, 0ms); + pbf_wait(context, 537ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 144ms, 0ms); + pbf_wait(context, 508ms); + bool entered_house=false; + align_to_overworld_landmarks(stream,context,match_eterna_underground_house, + "Underground Man house doorway",false,true,false,match_eterna_underground_man,&entered_house); + BlackScreenOverWatcher house_entry(COLOR_RED,{0.1,0.1,0.8,0.8}); + if (!entered_house && run_until(stream,context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); + pbf_wait(ctx,3000ms); + },{{house_entry}})!=0) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Underground Man house: Entry not confirmed.",stream); + pbf_wait(context,500ms); + // Retain the recorded northward approach from the fixed entrance. + pbf_move_left_joystick(context,{0,1},290ms,0ms); + align_to_overworld_landmarks(stream,context,match_eterna_underground_man, + "Underground Man interaction",false,true); + pbf_move_left_joystick(context,{0,1},80ms,200ms); + pbf_press_button(context,BUTTON_A,80ms,200ms); + wait_for_dialogue(stream,context,"Underground Man introduction"); + // Recorded forced walk: 4.7s between house and street dialogue. + // Keep watching for that gap plus a 500ms hardware buffer. + clear_post_battle_dialogue(stream,context,BUTTON_A,5200ms, + "PokemonBDSP/Autostory/EternaUndergroundMan/ExplorerKitCard.png"); + // The first-use tutorial opens the Bag, uses the Kit, and takes + // us underground automatically. The dialogue helper also clears + // Roark's introduction. Do not reopen the Bag to enter again. + const auto underground_frame=stream.video().snapshot(); + const char* underground_reference=underground_frame && underground_frame.frame->width()>=1600 + ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrival.png" + : underground_frame && underground_frame.frame->width()>=1100 + ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalMedium.png" + : "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalSmall.png"; + ImageMatchDetector underground_map( + std::make_shared(RESOURCE_PATH()+underground_reference), + ImageFloatBox{.005,.110,.085,.130},30.0); + if (!underground_frame || !underground_map.detect(*underground_frame.frame)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Grand Underground tutorial: Underground arrival not confirmed.",stream); + stream.log("[Grand Underground] Tutorial dialogue complete; returning with Y."); + // User-requested full settling interval immediately before Y. + pbf_wait(context,2000ms); + pbf_press_button(context, BUTTON_Y, 129ms, 0ms); + pbf_wait(context, 2076ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 88ms, 0ms); + pbf_wait(context, 819ms); + pbf_press_button(context, BUTTON_A, 127ms, 0ms); + pbf_wait(context, 1972ms); + pbf_press_button(context, BUTTON_A, 127ms, 0ms); + pbf_wait(context, 5330ms); + // Keep the Y / Go up / Yes inputs above, then watch the + // Underground Man's reward dialogue through the surface return. + context.wait_for_all_requests(); + wait_for_dialogue(stream,context,"Underground Man return"); + clear_post_battle_dialogue(stream,context,BUTTON_A,3500ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 584ms, 0ms); + pbf_wait(context, 85ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1448ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1455ms, 0ms); + pbf_wait(context, 145ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1257ms, 0ms); + pbf_wait(context, 956ms); + // Reuse the plaza/Cheryl cutscene cleanup rather than a + // recorded number of A presses during Cynthia's camera changes. + context.wait_for_all_requests(); + wait_for_dialogue(stream,context,"Cynthia introduction"); + clear_post_battle_dialogue(stream,context,BUTTON_A,3500ms); + // Let Cynthia's departure/camera finish before any route input, + // then watch once more for delayed dialogue on slower hardware. + pbf_wait(context,2000ms); + context.wait_for_all_requests(); + clear_post_battle_dialogue(stream,context,BUTTON_A,1500ms); + pbf_wait(context,500ms); + stream.log("[Cynthia exit] Departure settled; moving west and south to reacquire the Center."); + pbf_move_left_joystick(context,{-1,0},1200ms,100ms); + pbf_move_left_joystick(context,{0,-1},1800ms,300ms); + pbf_move_left_joystick(context, { 1,0 }, 1200ms, 100ms); + pbf_move_left_joystick(context, { 0,-1 }, 250ms, 300ms); + // Clear rewards before the approach can cross the gym doorway. + context.wait_for_all_requests(); + if (!clear_new_item_flags(stream,context)) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"New reward flags could not be cleared before continuing.",stream); + // Keep a loose horizontal street lane, but normalize the row: + // the following fixed south leg requires a repeatable start. + align_to_overworld_landmarks(stream,context,match_eterna_center_entrance, + "Eterna Center gym departure",false,true,true,nullptr,nullptr,.03,.02); + // User's eternapctogym.txt with a shorter south leg; omit its final left127ms correction. + // Start acquiring north of the old fence collision, then let + // gym landmarks correct the final row and rightward approach. + pbf_move_left_joystick(context,{0,-1},4300ms,0ms); + pbf_wait(context,642ms); + pbf_move_left_joystick(context,{1,0},1125ms,0ms); + pbf_wait(context,939ms); + pbf_move_left_joystick(context,{0,1},56ms,0ms); + pbf_wait(context,467ms); + pbf_move_left_joystick(context,{0,1},39ms,0ms); + pbf_wait(context,1255ms); + // Configurable only for a watched gym entry attempt. The normal + // three-landmark exterior arrival gates remain unchanged. + constexpr double GYM_MODERATE_ENTRY_CONFIDENCE=.88; + constexpr size_t GYM_ENTRY_ATTEMPTS=3; + bool gym_confirmed=false,transition_seen=false; + for(size_t attempt=1;attempt<=GYM_ENTRY_ATTEMPTS && !gym_confirmed;attempt++){ + stream.log("[Eterna Gym entry] Attempt "+std::to_string(attempt)+"/"+std::to_string(GYM_ENTRY_ATTEMPTS)); + if(!transition_seen){ + bool already_inside=false; + // Watch throughout alignment, not just the final north + // hold: a correction can cross the doorway first. + BlackScreenWatcher entering(COLOR_RED,{0.1,0.1,0.8,0.8}); + const int outcome=run_until(stream,context, + [&](ProControllerContext& ctx){ + align_to_overworld_landmarks(stream,ctx,match_eterna_gym_entrance, + "Eterna Gym doorway",false,true,false,match_eterna_gym_statues,&already_inside, + 0,0,GYM_MODERATE_ENTRY_CONFIDENCE); + if(!already_inside){ + pbf_move_left_joystick(ctx,{0,1},3000ms,0ms); + pbf_wait(ctx,5000ms); + } + },{{entering}}); + transition_seen=outcome==0 || already_inside; + stream.log("[Eterna Gym entry] Black transition detected="+std::to_string(outcome==0)+ + ", interior seen during alignment="+std::to_string(already_inside)); + } + if(transition_seen){ + BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8},100,10, + BlackScreenWatcher::FinderType::GONE,500ms); + if(wait_until(stream,context,30s,{{visible}})!=0){ + stream.log("[Eterna Gym entry] Transition has not finished; retrying within limit."); + continue; + } + stream.log("[Eterna Gym entry] Transition finished; verifying both statues."); + } + // Black alone never proves entry. Stop navigation and retry + // verification if a transition occurred into an unknown room. + size_t stable=0; + for(size_t check=0;check<8;check++){ + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if(!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Gym: No interior verification frame.",stream); + const auto interior=match_eterna_gym_statues(*frame.frame); + for(const auto& statue:interior.landmarks) stream.log("[Eterna Gym statues] "+statue.name+ + " score="+std::to_string(statue.score)+", margin="+std::to_string(statue.margin)+ + ", offset=("+std::to_string(statue.dx)+","+std::to_string(statue.dy)+")"); + stable=interior.located ? stable+1 : 0; + stream.log("[Eterna Gym statues] Both in expected positions="+std::to_string(interior.located)+ + ", stable frames="+std::to_string(stable)+"/3"); + if(stable>=3){gym_confirmed=true;break;} + pbf_wait(context,250ms); + } + if(!gym_confirmed) stream.log("[Eterna Gym entry] Statues not verified; bounded retry."); + } + if(!gym_confirmed) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Gym: Both entrance statues were not verified after three attempts.",stream); + stream.log("[Eterna City] Watched Explorer Kit/Cynthia events and verified gym entry complete.",COLOR_GREEN); + } + static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + }; + + const Move sequence1[] = { + {DPAD_UP, 5}, + {DPAD_LEFT, 2}, + {DPAD_UP, 9}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 7}, + {DPAD_LEFT, 3}, + {DPAD_UP, 2}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 4}, + }; + + const Move sequence2[] = { + {DPAD_UP, 6}, + {DPAD_LEFT, 6}, + {DPAD_DOWN, 9}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 5}, + }; + + const Move sequence3[] = { + {DPAD_LEFT, 5}, + {DPAD_UP, 6}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, + {DPAD_LEFT, 2}, + {DPAD_UP, 8}, + {DPAD_RIGHT, 7}, + {DPAD_DOWN, 2}, + {DPAD_RIGHT, 5}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_RIGHT, 6}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, + }; + + const Move sequence4[] = { + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 6}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 11}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 16}, + {DPAD_LEFT, 6}, + }; + + const Move sequence5[] = { + {DPAD_RIGHT, 6}, + {DPAD_DOWN, 17}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 2}, + {DPAD_LEFT, 8}, + {DPAD_UP, 11}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 7}, + {DPAD_LEFT, 2}, + {DPAD_UP, 7}, + }; + + const Move sequence6[] = { + {DPAD_DOWN, 10}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 13}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 2}, + }; + + for(int i=0; i < 7; i++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } + + // Reuse the Roark/Cynthia dialogue watcher so Gardenia's + // introduction finishes before the doorway movement starts. + pbf_press_button(context,BUTTON_A,150ms,300ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream,context,"Gardenia introduction"); + clear_post_battle_dialogue(stream,context,BUTTON_A,3500ms); + stream.log("[Eterna Gym puzzle] Gardenia introduction dialogue cleared."); + // Door transition and gym camera introduction take about 7.5s + // in 2026-10-18 20-01-01; include 500ms for hardware variation. + stream.log("[Eterna Gym puzzle] Entering Gardenia's doorway; waiting 8000ms for transition/cutscene (includes 500ms buffer)."); + pbf_press_dpad(context, DPAD_UP, 80ms, 8000ms); + + for (const auto& move : sequence1){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 01 (Lass Caroline). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + wait_for_trainer_battle_menu(stream,context); + fight_trainer_using_moves(stream,context,2,"Lass Caroline (Flamethrower)",false); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. + + for (const auto& move : sequence2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 02 (Aroma Lady Jenna). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + wait_for_trainer_battle_menu(stream,context); + fight_trainer_using_moves(stream,context,2,"Aroma Lady Jenna (Flamethrower)",false); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. + + for (const auto& move : sequence3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 03 (Aroma Lady Angela). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + wait_for_trainer_battle_menu(stream,context); + fight_trainer_using_moves(stream,context,2,"Aroma Lady Angela (Flamethrower)",false); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Trainer battle 04 (Beauty Lindsay). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + wait_for_trainer_battle_menu(stream,context); + fight_trainer_using_moves(stream,context,2,"Beauty Lindsay (Flamethrower)",false); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate Gym battle. + + pbf_press_button(context,BUTTON_A,150ms,300ms); + wait_for_trainer_battle_menu(stream,context); + fight_trainer_using_moves(stream,context,2,"Gardenia (Flamethrower)",true); + + // The shared battle handler already advances reward dialogue + // while confirming the overworld. Do not require the badge + // animation again after it may have finished or be underway. + // Continue the existing B watcher through remaining rewards; + // its clear confirmation spans the observed ~5s badge gap. + stream.log("[Eterna Gym puzzle] Shared battle cleanup returned; clearing remaining Gardenia rewards with B."); + clear_post_battle_dialogue(stream,context,BUTTON_B,6000ms); + stream.log("[Eterna Gym puzzle] Gardenia post-battle dialogue cleared; starting exit path."); + + for (const auto& move : sequence6){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + pbf_wait(context, 2000ms); + + for(int i=0; i < 9; i++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + //Exits Buiilding + pbf_wait(context, 1000ms); + } + + std::string AutoStory_Segment_02::name() const{ return "02: First Badge to Second Badge"; } + std::string AutoStory_Segment_02::start_text() const{ return "Start: First Gym Badge acquired (Oreburgh City)."; } + std::string AutoStory_Segment_02::end_text() const{ return "End: Second Gym Badge acquired (Eterna City)."; } + + void AutoStory_Segment_02::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_0201().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0202().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0203().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0204().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0205().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0206().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); + } + std::string AutoStory_checkpoint_0201::name() const { return "02.01 - Outside Gym 1"; } + std::string AutoStory_checkpoint_0201::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } + std::string AutoStory_checkpoint_0201::end_text() const { return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; } + + void AutoStory_checkpoint_0201::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0201(env, context, options, stats); + } + + + void checkpoint_0201( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + roark_to_floaroma(env, env.console, context); + + }, + + true + ); + } + std::string AutoStory_checkpoint_0202::name() const { return "02.02 - Floaroma Town"; } + std::string AutoStory_checkpoint_0202::start_text() const { + return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; + } + std::string AutoStory_checkpoint_0202::end_text() const { + return "Standing in Valley Windworks after defeating Commander Mars."; + } + void AutoStory_checkpoint_0202::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + floaroma_to_mars(env, env.console, context); + }, true); + } + + std::string AutoStory_checkpoint_0203::name() const { return "02.03 - Route 205 to Eterna Forest"; } + std::string AutoStory_checkpoint_0203::start_text() const { return "Standing in Valley Windworks after defeating Commander Mars."; } + std::string AutoStory_checkpoint_0203::end_text() const { return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; } + + void AutoStory_checkpoint_0203::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0203(env, context, options, stats); + } + + + void checkpoint_0203( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + mars_to_eterna_forest(env, env.console, context); + + }, + + true + ); + } + + std::string AutoStory_checkpoint_0204::name() const{ return "02.04 - Eterna Forest"; } + std::string AutoStory_checkpoint_0204::start_text() const{ return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; } + std::string AutoStory_checkpoint_0204::end_text() const{ return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; } + + void AutoStory_checkpoint_0204::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const{ + checkpoint_0204(env, context, options, stats); + } + + + void checkpoint_0204( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ){ + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 02 gameplay logic + eterna_forest(env, env.console, context); + + }, + + true + ); + } + + std::string AutoStory_checkpoint_0205::name() const { return "02.05 - Eterna City to Gym"; } + std::string AutoStory_checkpoint_0205::start_text() const { return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; } + std::string AutoStory_checkpoint_0205::end_text() const { return "Inside Eterna Gym at the entrance, facing north."; } + + void AutoStory_checkpoint_0205::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0205(env, context, options, stats); + } + + + void checkpoint_0205( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // TODO: implement Segment 02 gameplay logic + eterna_forest_exit_to_gym(env, env.console, context); + + }, + true + ); + } + + std::string AutoStory_checkpoint_0206::name() const { return "02.06 - Inside Gym"; } + std::string AutoStory_checkpoint_0206::start_text() const { return "Inside Eterna Gym at the entrance, facing north."; } + std::string AutoStory_checkpoint_0206::end_text() const { return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; } + + void AutoStory_checkpoint_0206::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const { + checkpoint_0206(env, context, options, stats); + } + + + void checkpoint_0206( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + + //#. Eterna Gym navigation and battle + gym_Puzzle_02(env, env.console, context); + }, + true + ); + } + + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h index a76d2774e8..26602b2ba1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h @@ -42,7 +42,7 @@ class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint{ }; // start: first badge acquired -// end: second Gym Badge acquired (Eterna City) +// end: Floaroma central house exit, facing south, Repel inactive void checkpoint_0201( SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -50,7 +50,21 @@ void checkpoint_0201( AutoStoryStats& stats ); +// Save/retry boundary at the central Floaroma house exit, matching 02.01 end. class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -65,13 +79,13 @@ class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0202( +void checkpoint_0203( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { +class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -86,14 +100,14 @@ class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0203( +void checkpoint_0204( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { +class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -108,14 +122,14 @@ class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0204( +void checkpoint_0205( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats ); -class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { +class AutoStory_checkpoint_0206 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; @@ -130,7 +144,7 @@ class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0205( +void checkpoint_0206( SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 864db1871d..d05af14398 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -1,503 +1,1952 @@ -/* BDSP AutoStory - Segment 03 - * From after acquiring the second badge until after acquiring the third badge. - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "CommonTools/Async/InferenceRoutines.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "PokemonBDSP_AutoStory_Segment_03.h" - -using namespace std::chrono_literals; - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context) { - - } - void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context) { - - } - void coronet1(VideoStream& stream, ProControllerContext& context) { - - } - void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context) { - - } - void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context) { - - } - static void lost_tower(VideoStream& stream, ProControllerContext& context) { - - } - void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context) { - - } - -static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ - - //Upon entering the Gym. - - struct Move { - DpadPosition dir; - int count; - std::chrono::milliseconds delay; - }; - - const Move sequence1[] = { - {DPAD_RIGHT, 8, 0ms}, - {DPAD_UP, 6, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_UP, 2, 0ms}, - }; - - const Move sequence2[] = { - {DPAD_UP, 5, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_DOWN, 8, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 5, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 2000ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_DOWN, 10, 0ms}, - {DPAD_RIGHT, 6, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 1000ms}, - {DPAD_UP, 6, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 1000ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_DOWN, 13, 0ms}, - {DPAD_LEFT, 3, 0ms}, - {DPAD_UP, 4, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 8, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 4, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_DOWN, 2, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 1000ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_DOWN, 6, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_DOWN, 7, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 5, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 5, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_DOWN, 3, 0ms}, - }; - - const Move sequence3[] = { - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 3000ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_UP, 5, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_LEFT, 8, 0ms}, - {DPAD_UP, 3, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 3000ms}, - {DPAD_UP, 11, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_DOWN, 13, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_UP, 3, 0ms}, - }; - - const Move sequence4[] = { - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 9, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 4, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_DOWN, 8, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_UP, 3, 0ms}, - }; - - const Move sequence5[] = { - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 8, 0ms}, - }; - - const Move sequence6[] = { - {DPAD_DOWN, 8, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 3000ms}, - {DPAD_DOWN, 6, 0ms}, - }; - - for (const auto& move : sequence1){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Button presses to initiate Trainer battle 01 (Black Belt Rafael). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence2){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Button presses to initiate Trainer battle 02 (Black Belt Jeffrey). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence3){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Button presses to initiate Trainer battle 03 (Black Belt Darren). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence4){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Button presses to initiate Trainer battle 04 (Black Belt Colby). - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence5){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Button presses to initiate the Gym battle. - - //Battle automation code. - - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - - for (const auto& move : sequence6){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Exits Buiilding - pbf_wait(context, 1000ms); -} - -std::string AutoStory_Segment_03::name() const{ return "03: Second Badge to Third Badge"; } -std::string AutoStory_Segment_03::start_text() const{ return "Start: Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Segment_03::end_text() const{ return "End: Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Segment_03::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0301().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0302().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0303().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0304().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0305().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0306().run_checkpoint(env, context, options, stats); - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} - - -std::string AutoStory_Checkpoint_0301::name() const{ return "03.01 - TBC"; } -std::string AutoStory_Checkpoint_0301::start_text() const{ return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0301::end_text() const{ return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0301::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ - Checkpoint_0301(env, context, options, stats); -} - - -void Checkpoint_0301( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/){ - // TODO: implement Segment 03 gameplay logic - eterna_gym_to_jupiter(env.console, context); - - }, - - true - ); -} -std::string AutoStory_Checkpoint_0302::name() const { return "03.02 - TBC"; } -std::string AutoStory_Checkpoint_0302::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0302::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0302::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - Checkpoint_0302(env, context, options, stats); -} - - -void Checkpoint_0302( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 03 gameplay logic - jupiter_to_coronet(env.console, context); - - }, - - true - ); -} -std::string AutoStory_Checkpoint_0303::name() const { return "03.03 - TBC"; } -std::string AutoStory_Checkpoint_0303::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0303::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0303::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - Checkpoint_0303(env, context, options, stats); -} - - -void Checkpoint_0303( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 03 gameplay logic - coronet1(env.console, context); - - }, - - true - ); -} -std::string AutoStory_Checkpoint_0304::name() const { return "03.04 - TBC"; } -std::string AutoStory_Checkpoint_0304::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0304::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0304::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - Checkpoint_0304(env, context, options, stats); -} - - -void Checkpoint_0304( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 03 gameplay logic - coronet_to_heartholme(env.console, context); - - }, - - true - ); -} -std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - TBC"; } -std::string AutoStory_Checkpoint_0305::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0305::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0305::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - Checkpoint_0305(env, context, options, stats); -} - - -void Checkpoint_0305( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 03 gameplay logic - heartholme_to_lost_tower(env.console, context); - lost_tower(env.console, context); - - }, - - true - ); -} - -std::string AutoStory_Checkpoint_0306::name() const { return "03.06 - TBC"; } -std::string AutoStory_Checkpoint_0306::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0306::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0306::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - Checkpoint_0306(env, context, options, stats); -} - - -void Checkpoint_0306( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 03 gameplay logic - lost_tower_to_veilstone_gym(env.console, context); - }, - true - ); -} - -std::string AutoStory_Checkpoint_0307::name() const { return "03.07 - Inside Gym 3"; } -std::string AutoStory_Checkpoint_0307::start_text() const { return "Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Checkpoint_0307::end_text() const { return "Third Gym Badge acquired (Hearthome City)."; } - -void AutoStory_Checkpoint_0307::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { - Checkpoint_0307(env, context, options, stats); -} - - -void Checkpoint_0307( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - //#. Veilstone Gym navigation and battle - gym_Puzzle_03(env.console, context); - }, - true - ); -} - -} -} -} +/* BDSP AutoStory - Segment 03 + * From after acquiring the second badge until after acquiring the third badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "../Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_03.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + static void segment03_route_battle(VideoStream& stream, ProControllerContext& context, + const std::string& label, bool post_battle_dialogue_cleanup = false) { + fight_trainer_using_moves(stream,context,2,label,post_battle_dialogue_cleanup); + } + + static void segment03_route_press(VideoStream& stream, ProControllerContext& context, + Button button, Milliseconds hold, Milliseconds release) { + context.wait_for_all_requests(); + auto frame=stream.video().snapshot(); + BattleMenuDetector menu(BattleType::TRAINER); + if (frame && menu.detect(*frame.frame)) { + segment03_route_battle(stream,context,"Recorded route encounter"); + return; + } + pbf_press_button(context,button,hold,release); + } + + // Stop at the completed doorway transition, rather than spending the + // remaining movement budget inside the next map. + static void segment03_doorway(VideoStream& stream, ProControllerContext& context, + bool north, std::chrono::milliseconds duration, const std::string& label, double horizontal = 0) { + context.wait_for_all_requests(); + // Horizontal HQ stairs must cancel at fade-out, before loading can + // swallow or carry movement onto the next floor. Vertical door callers + // retain their existing transition behavior. + if (horizontal != 0) { + BlackScreenWatcher entering; + if (run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{horizontal,0},duration,0ms); + pbf_wait(ctx,3000ms); + },{{entering}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Stair transition did not start.",stream); + BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{visible}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Stair transition did not finish.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + return; + } + BlackScreenOverWatcher transition(COLOR_RED,{0.1,0.1,0.8,0.8}); + int result=run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,north ? 1.0 : -1.0},duration,0ms); + pbf_wait(ctx,3000ms); + },{{transition}}); + if (result!=0) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,label+": Doorway transition not confirmed.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + } + + void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context) { + auto fight=[&](const std::string& label, bool post_battle_dialogue_cleanup = false) { + segment03_route_battle(stream,context,label+" (Flamethrower)",post_battle_dialogue_cleanup); + }; + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1060ms, 0ms); + pbf_wait(context, 225ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 4232ms, 0ms); + pbf_wait(context, 269ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 287ms, 0ms); + pbf_wait(context, 1777ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 367ms, 0ms); + pbf_wait(context, 237ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1140ms, 0ms); + pbf_wait(context, 2621ms); + if (!heal_pokemon(stream,context,"Eterna before Galactic HQ")) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit was not confirmed.",stream); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1895ms, 0ms); + pbf_wait(context, 130ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 710ms, 0ms); + pbf_wait(context, 921ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 129ms, 0ms); + pbf_wait(context, 354ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 813ms, 0ms); + pbf_wait(context, 565ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 618ms, 0ms); + pbf_wait(context, 483ms); + context.wait_for_all_requests(); + // Align with the sign and Center emblem before trying Cut. Never search blindly or + // keep pushing north against the hedge when the tree is east of us. + size_t hq_cut_confirmations=0,hq_cut_missing=0; + for (size_t correction=0;correction<20;correction++) { + const auto frame=stream.video().snapshot(); + const auto position=frame ? match_galactic_hq_cut(*frame.frame) : OverworldMatchResult{}; + for (const auto& anchor:position.landmarks) + stream.log("[Galactic HQ Cut] "+anchor.name+" score="+std::to_string(anchor.score)+ + ", margin="+std::to_string(anchor.margin)+", offset=("+ + std::to_string(anchor.dx)+","+std::to_string(anchor.dy)+")"); + if (!position.located) { + hq_cut_confirmations=0; + if (++hq_cut_missing>6) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush sign/Center alignment not confirmed.",stream); + // Let a passing shadow clear; never search by moving blindly. + pbf_wait(context,500ms); + context.wait_for_all_requests(); + continue; + } + hq_cut_missing=0; + if (std::abs(position.dx)>.20) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush alignment offset exceeds bounds.",stream); + if (position.at_target) { + if (++hq_cut_confirmations>=3) break; + } else { + hq_cut_confirmations=0; + pbf_move_left_joystick(context,{position.dx>0 ? 1.0 : -1.0,0}, + std::abs(position.dx)>1.0/24 ? 80ms : 40ms,0ms); + } + pbf_wait(context,250ms); + context.wait_for_all_requests(); + } + if (hq_cut_confirmations<3) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush column did not converge.",stream); + // Some attempts reach the bush before the interaction accepts input. + // Confirm its dialogue while stationary, then finish the Cut animation. + ShortDialogWatcher hq_cut_dialogue; + if (run_until(stream,context, + [](ProControllerContext& ctx) { + for (size_t press=0; press<30; press++) { + pbf_move_left_joystick(ctx,{0,1},80ms,100ms); + pbf_press_button(ctx,BUTTON_A,150ms,500ms); + } + },{{hq_cut_dialogue}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush interaction did not produce dialogue.",stream); + clear_post_battle_dialogue(stream,context,BUTTON_A,6000ms); + context.wait_for_all_requests(); + // Cut succeeds, but the Center exit can leave us one tile west of + // the HQ doorway. Do not replay the interior route against its wall. + BlackScreenWatcher hq_entering; + const int hq_entry = run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},1507ms,193ms); + ctx.wait_for_all_requests(); + for (size_t correction=0; correction<4; correction++) { + stream.log("[Galactic HQ entry] No transition; correcting east toward doorway, attempt "+ + std::to_string(correction+1)+"/4."); + pbf_move_left_joystick(ctx,{1,0},80ms,100ms); + pbf_move_left_joystick(ctx,{0,1},350ms,250ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx,1500ms); + },{{hq_entering}}); + if (hq_entry != 0) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Doorway transition not detected after bounded east correction.",stream); + stream.log("[Galactic HQ entry] Doorway transition detected; waiting for interior."); + BlackScreenWatcher hq_visible(COLOR_RED,{0.1,0.1,0.8,0.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{hq_visible}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Entry transition did not finish.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + // galacticHQ.txt starts at the ground-floor entrance mat. + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1247ms, 0ms); + pbf_wait(context, 237ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1246ms, 0ms); + pbf_wait(context, 2989ms); + pbf_press_button(context,BUTTON_A,150ms,250ms); + fight("Galactic HQ entrance grunts",false); + // With one usable Pokemon these grunts battle consecutively. The next + // intro can outlast the ordinary dialogue quiet period. Keep movement + // stopped and reuse the battle handler only if its command menu appears. + // A party that fought both together needs no second battle handler. + BattleMenuWatcher hq_next_grunt(BattleType::TRAINER); + const int hq_next_battle = run_until(stream,context, + [](ProControllerContext& ctx) { pbf_mash_button(ctx,BUTTON_B,30000ms); }, + {{hq_next_grunt}}); + if (hq_next_battle == 0) { + stream.log("[Galactic HQ] Second entrance grunt detected before stair movement."); + fight("Galactic HQ second entrance grunt",false); + } + pbf_move_left_joystick(context, {0.000000, 1.000000}, 580ms, 0ms); + pbf_wait(context, 220ms); + segment03_doorway(stream,context,true,1077ms,"Galactic HQ stairs 1F to 2F",-1.0); + pbf_wait(context, 1145ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 758ms, 0ms); + pbf_wait(context, 487ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1633ms, 0ms); + pbf_wait(context, 254ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 925ms, 0ms); + pbf_wait(context, 188ms); + segment03_doorway(stream,context,true,1145ms,"Galactic HQ stairs 2F to 3F",-1.0); + pbf_wait(context, 1455ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 410ms, 0ms); + pbf_wait(context, 679ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 744ms, 0ms); + pbf_wait(context, 2084ms); + pbf_press_button(context,BUTTON_A,150ms,250ms); + fight("Galactic HQ upper-floor grunt",false); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2119ms, 0ms); + pbf_wait(context, 903ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 263ms, 0ms); + pbf_wait(context, 984ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 342ms, 0ms); + pbf_wait(context, 1806ms); + pbf_press_button(context,BUTTON_A,150ms,250ms); + fight("Galactic HQ Scientist",false); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 425ms, 0ms); + pbf_wait(context, 161ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 723ms, 0ms); + pbf_wait(context, 1639ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 494ms, 0ms); + pbf_wait(context, 678ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 409ms, 0ms); + pbf_wait(context, 858ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 389ms, 0ms); + pbf_wait(context, 1189ms); + // Align directly above Jupiter, then turn south to interact without + // trying to enter the narrow gap between her and the rescued NPC. + align_to_overworld_landmarks(stream,context,match_galactic_hq_jupiter, + "Galactic HQ Jupiter",false,true); + // Use an explicit Down press: the 16ms stick pulse can leave the + // previous right-facing direction active and interact with Clefairy. + pbf_press_dpad(context,DPAD_DOWN,80ms,250ms); + context.wait_for_all_requests(); + // Advance the introduction before entering the battle handler's + // B-based waiting loop. A single press may only open the first text. + BattleMenuWatcher jupiter_menu(BattleType::TRAINER); + if (run_until(stream,context, + [](ProControllerContext& ctx) { + for (size_t press=0;press<30;press++) + pbf_press_button(ctx,BUTTON_A,150ms,500ms); + pbf_wait(ctx,15000ms); + },{{jupiter_menu}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Jupiter interaction did not reach the battle menu.",stream); + fight("Commander Jupiter",true); + // Jupiter's departure and the rescued family have chained dialogue. + clear_post_battle_dialogue(stream,context,BUTTON_A,6000ms); + stream.log("[Galactic HQ] Rescue dialogue cleared; following recorded exit.",COLOR_GREEN); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 691ms, 0ms); + pbf_wait(context, 575ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 330ms, 0ms); + pbf_wait(context, 2575ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 523ms, 0ms); + pbf_wait(context, 239ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 717ms, 0ms); + pbf_wait(context, 1721ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 306ms, 0ms); + pbf_wait(context, 294ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1041ms, 0ms); + pbf_wait(context, 307ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 431ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 999ms, 0ms); + pbf_wait(context, 573ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 343ms, 0ms); + pbf_wait(context, 175ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 999ms, 0ms); + pbf_wait(context, 456ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 277ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 654ms, 0ms); + pbf_wait(context, 1092ms); + context.wait_for_all_requests(); + BlackScreenOverWatcher hq_leaving(COLOR_RED,{0.1,0.1,0.8,0.8}); + if (run_until(stream,context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx,{0,-1},2350ms,0ms); + pbf_move_left_joystick(ctx,{-1,-1},2744ms,0ms); + pbf_wait(ctx,3000ms); + },{{hq_leaving}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Exit transition not confirmed.",stream); + // Clamp against the uncut front bush; checkpoint 03.02 starts here. + pbf_move_left_joystick(context,{0,-1},350ms,500ms); + context.wait_for_all_requests(); + stream.log("[Galactic HQ] Outside, facing the front Cut bush.",COLOR_GREEN); + } + static void eterna_mart_escape_ropes(VideoStream& stream, ProControllerContext& context) { + stream.log("[Eterna Mart] Buying exactly 3 Escape Ropes after the Jupiter heal."); + // Recorded Center-exit to Mart-door path (eternamart1.txt). + pbf_move_left_joystick(context,{0,-1},2713ms,0ms); + pbf_wait(context,444ms); + pbf_move_left_joystick(context,{1,0},587ms,0ms); + pbf_wait(context,705ms); + segment03_doorway(stream,context,true,896ms,"Eterna Mart entrance"); + // Standard Mart layout: walk north from the entrance to the clerk. + pbf_move_left_joystick(context,{0,1},885ms,300ms); + if (!buy_items(stream,context,{{ShopItem::EscapeRope,3}})) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Mart: Could not buy 3 Escape Ropes.",stream); + segment03_doorway(stream,context,false,5000ms,"Eterna Mart exit"); + clear_new_item_flags(stream,context); + + // Retrace to the Center column, then use its doorway to normalize the + // position. Do not reverse the time spent loading the Mart interior. + pbf_move_left_joystick(context,{0,-1},896ms,100ms); + pbf_move_left_joystick(context,{-1,0},587ms,100ms); + segment03_doorway(stream,context,true,5000ms,"Eterna Center return after Mart"); + segment03_doorway(stream,context,false,5000ms,"Eterna Center exit after Mart"); + } + + void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context) { + // Continue from 03.01 outside HQ, directly in front of the Cut bush. + segment03_route_press(stream,context, BUTTON_A, 232ms, 0ms); + pbf_wait(context, 314ms); + segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_wait(context, 129ms); + segment03_route_press(stream,context, BUTTON_A, 163ms, 0ms); + pbf_wait(context, 116ms); + segment03_route_press(stream,context, BUTTON_A, 174ms, 0ms); + pbf_wait(context, 173ms); + segment03_route_press(stream,context, BUTTON_A, 165ms, 0ms); + pbf_wait(context, 4174ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1413ms, 0ms); + pbf_wait(context, 182ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 712ms, 0ms); + pbf_wait(context, 91ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1126ms, 0ms); + pbf_wait(context, 27ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); + pbf_wait(context, 868ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 866ms, 0ms); + if (!heal_pokemon(stream,context,"Eterna after Jupiter")) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit not confirmed.",stream); + eterna_mart_escape_ropes(stream,context); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1514ms, 0ms); + pbf_wait(context, 177ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 587ms, 0ms); + pbf_wait(context, 522ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 144ms, 0ms); + pbf_wait(context, 439ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1078ms, 0ms); + pbf_wait(context, 3295ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 505ms, 0ms); + pbf_wait(context, 405ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 568ms, 0ms); + pbf_wait(context, 403ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 642ms, 0ms); + pbf_wait(context, 14ms); + segment03_route_press(stream,context, BUTTON_A, 186ms, 0ms); + pbf_wait(context, 612ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 190ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 376ms); + segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); + pbf_wait(context, 122ms); + segment03_route_press(stream,context, BUTTON_A, 158ms, 0ms); + pbf_wait(context, 242ms); + segment03_route_press(stream,context, BUTTON_A, 174ms, 0ms); + pbf_wait(context, 472ms); + segment03_route_press(stream,context, BUTTON_A, 126ms, 0ms); + pbf_wait(context, 8007ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 83ms); + segment03_route_press(stream,context, BUTTON_A, 143ms, 0ms); + pbf_wait(context, 1005ms); + segment03_route_press(stream,context, BUTTON_A, 101ms, 0ms); + pbf_wait(context, 268ms); + segment03_route_press(stream,context, BUTTON_A, 109ms, 0ms); + pbf_wait(context, 123ms); + segment03_route_press(stream,context, BUTTON_A, 112ms, 0ms); + pbf_wait(context, 539ms); + segment03_route_press(stream,context, BUTTON_A, 147ms, 0ms); + pbf_wait(context, 518ms); + segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 484ms); + segment03_route_press(stream,context, BUTTON_A, 185ms, 0ms); + pbf_wait(context, 613ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 526ms); + segment03_route_press(stream,context, BUTTON_A, 192ms, 0ms); + pbf_wait(context, 755ms); + segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); + pbf_wait(context, 962ms); + segment03_route_press(stream,context, BUTTON_A, 253ms, 0ms); + pbf_wait(context, 323ms); + segment03_route_press(stream,context, BUTTON_A, 277ms, 0ms); + pbf_wait(context, 506ms); + segment03_route_press(stream,context, BUTTON_A, 253ms, 0ms); + pbf_wait(context, 313ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 481ms); + segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); + pbf_wait(context, 737ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 589ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 296ms); + segment03_route_press(stream,context, BUTTON_A, 144ms, 0ms); + pbf_wait(context, 588ms); + segment03_route_press(stream,context, BUTTON_A, 189ms, 0ms); + pbf_wait(context, 425ms); + segment03_route_press(stream,context, BUTTON_A, 210ms, 0ms); + pbf_wait(context, 422ms); + segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_wait(context, 1007ms); + segment03_route_press(stream,context, BUTTON_A, 235ms, 0ms); + pbf_wait(context, 333ms); + segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); + pbf_wait(context, 5044ms); + segment03_route_press(stream,context, BUTTON_A, 208ms, 0ms); + pbf_wait(context, 2822ms); + segment03_route_press(stream,context, BUTTON_A, 210ms, 0ms); + pbf_wait(context, 4173ms); + segment03_route_press(stream,context, BUTTON_A, 119ms, 0ms); + pbf_wait(context, 482ms); + segment03_route_press(stream,context, BUTTON_B, 204ms, 0ms); + pbf_wait(context, 1087ms); + segment03_route_press(stream,context, BUTTON_X, 180ms, 0ms); + pbf_wait(context, 1306ms); + segment03_route_press(stream,context, BUTTON_A, 141ms, 0ms); + pbf_wait(context, 1456ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 285ms, 0ms); + pbf_wait(context, 741ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 206ms, 0ms); + pbf_wait(context, 209ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 194ms, 0ms); + pbf_wait(context, 869ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 181ms, 0ms); + pbf_wait(context, 306ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 188ms, 0ms); + pbf_wait(context, 361ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 154ms, 0ms); + pbf_wait(context, 1911ms); + segment03_route_press(stream,context, BUTTON_Y, 156ms, 0ms); + pbf_wait(context, 2929ms); + pbf_move_right_joystick(context, {1.000000, -0.000000}, 205ms, 0ms); + pbf_wait(context, 898ms); + pbf_move_right_joystick(context, {-1.000000, -0.000000}, 255ms, 0ms); + pbf_wait(context, 469ms); + segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); + pbf_wait(context, 508ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 177ms, 0ms); + pbf_wait(context, 274ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 1400ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 207ms, 0ms); + pbf_wait(context, 663ms); + segment03_route_press(stream,context, BUTTON_A, 163ms, 0ms); + pbf_wait(context, 1097ms); + segment03_route_press(stream,context, BUTTON_B, 178ms, 0ms); + pbf_wait(context, 742ms); + segment03_route_press(stream,context, BUTTON_B, 182ms, 0ms); + pbf_wait(context, 872ms); + segment03_route_press(stream,context, BUTTON_B, 171ms, 0ms); + pbf_wait(context, 774ms); + if (!clear_new_item_flags(stream,context)) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"New reward flags could not be cleared before continuing.",stream); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 733ms, 0ms); + pbf_wait(context, 316ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 3280ms, 0ms); + pbf_wait(context, 1944ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1789ms, 0ms); + pbf_wait(context, 1848ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 3291ms, 0ms); + pbf_wait(context, 2531ms); + segment03_route_battle(stream,context,"Cycling Road first trainer"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 676ms, 0ms); + pbf_wait(context, 2016ms); + segment03_route_battle(stream,context,"Cycling Road second trainer"); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 145ms, 0ms); + pbf_wait(context, 1136ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 424ms, 0ms); + pbf_wait(context, 374ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 160ms, 0ms); + pbf_wait(context, 895ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 224ms, 0ms); + pbf_wait(context, 643ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 838ms, 0ms); + pbf_wait(context, 1761ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1839ms, 0ms); + pbf_wait(context, 1407ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 3343ms, 0ms); + pbf_wait(context, 3457ms); + segment03_route_press(stream,context, BUTTON_A, 233ms, 0ms); + pbf_wait(context, 135ms); + segment03_route_press(stream,context, BUTTON_A, 175ms, 0ms); + pbf_wait(context, 159ms); + segment03_route_press(stream,context, BUTTON_A, 91ms, 0ms); + pbf_wait(context, 340ms); + segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 243ms); + segment03_route_press(stream,context, BUTTON_A, 223ms, 0ms); + pbf_wait(context, 375ms); + segment03_route_press(stream,context, BUTTON_A, 346ms, 0ms); + pbf_wait(context, 1168ms); + segment03_route_press(stream,context, BUTTON_A, 232ms, 0ms); + pbf_wait(context, 589ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 550ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 871ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 956ms); + segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_wait(context, 295ms); + segment03_route_press(stream,context, BUTTON_B, 172ms, 0ms); + pbf_wait(context, 206ms); + segment03_route_press(stream,context, BUTTON_B, 193ms, 0ms); + pbf_wait(context, 175ms); + segment03_route_press(stream,context, BUTTON_B, 175ms, 0ms); + pbf_wait(context, 307ms); + segment03_route_press(stream,context, BUTTON_B, 181ms, 0ms); + pbf_wait(context, 325ms); + segment03_route_press(stream,context, BUTTON_B, 144ms, 0ms); + pbf_wait(context, 417ms); + segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); + pbf_wait(context, 309ms); + segment03_route_press(stream,context, BUTTON_B, 173ms, 0ms); + pbf_wait(context, 827ms); + segment03_route_press(stream,context, BUTTON_B, 239ms, 0ms); + pbf_wait(context, 473ms); + segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); + pbf_wait(context, 321ms); + segment03_route_press(stream,context, BUTTON_B, 128ms, 0ms); + pbf_wait(context, 426ms); + segment03_route_press(stream,context, BUTTON_B, 158ms, 0ms); + pbf_wait(context, 488ms); + segment03_route_press(stream,context, BUTTON_B, 231ms, 0ms); + pbf_wait(context, 724ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 404ms); + segment03_route_press(stream,context, BUTTON_B, 109ms, 0ms); + pbf_wait(context, 357ms); + segment03_route_press(stream,context, BUTTON_B, 199ms, 0ms); + pbf_wait(context, 416ms); + segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); + pbf_wait(context, 175ms); + segment03_route_press(stream,context, BUTTON_B, 189ms, 0ms); + pbf_wait(context, 345ms); + segment03_route_press(stream,context, BUTTON_B, 189ms, 0ms); + pbf_wait(context, 277ms); + segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); + pbf_wait(context, 368ms); + segment03_route_press(stream,context, BUTTON_B, 232ms, 0ms); + pbf_wait(context, 189ms); + segment03_route_press(stream,context, BUTTON_B, 172ms, 0ms); + pbf_wait(context, 474ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 504ms); + segment03_route_press(stream,context, BUTTON_B, 228ms, 0ms); + pbf_wait(context, 372ms); + segment03_route_press(stream,context, BUTTON_B, 162ms, 0ms); + pbf_wait(context, 4324ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 360ms, 0ms); + pbf_wait(context, 188ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 944ms, 0ms); + pbf_wait(context, 161ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 388ms, 0ms); + pbf_wait(context, 39ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2236ms, 0ms); + pbf_wait(context, 1820ms); + segment03_route_battle(stream,context,"Route 207 Camper"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2758ms, 0ms); + pbf_wait(context, 94ms); + segment03_route_battle(stream,context,"Route 207 Hiker"); + context.wait_for_all_requests(); + } + void coronet1(VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1093ms, 0ms); + pbf_wait(context, 832ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 178ms, 0ms); + pbf_wait(context, 305ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); + pbf_wait(context, 2805ms); + segment03_route_press(stream,context, BUTTON_X, 163ms, 0ms); + pbf_wait(context, 837ms); + segment03_route_press(stream,context, BUTTON_A, 166ms, 0ms); + pbf_wait(context, 2708ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 171ms, 0ms); + pbf_wait(context, 308ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 185ms, 0ms); + pbf_wait(context, 967ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 196ms, 0ms); + pbf_wait(context, 52ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 182ms, 0ms); + pbf_wait(context, 507ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 138ms, 0ms); + pbf_wait(context, 1004ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 179ms, 0ms); + pbf_wait(context, 844ms); + segment03_route_press(stream,context, BUTTON_A, 192ms, 0ms); + pbf_wait(context, 487ms); + segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_wait(context, 677ms); + segment03_route_press(stream,context, BUTTON_A, 176ms, 0ms); + pbf_wait(context, 989ms); + segment03_route_press(stream,context, BUTTON_B, 179ms, 0ms); + pbf_wait(context, 736ms); + segment03_route_press(stream,context, BUTTON_B, 186ms, 0ms); + pbf_wait(context, 551ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); + pbf_wait(context, 104ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 852ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 37ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 102ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 78ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 983ms, 0ms); + pbf_wait(context, 1754ms); + segment03_route_press(stream,context, BUTTON_A, 173ms, 0ms); + pbf_wait(context, 1072ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 155ms); + segment03_route_press(stream,context, BUTTON_A, 142ms, 0ms); + pbf_wait(context, 136ms); + segment03_route_press(stream,context, BUTTON_A, 159ms, 0ms); + pbf_wait(context, 1598ms); + segment03_route_press(stream,context, BUTTON_A, 244ms, 0ms); + pbf_wait(context, 148ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 587ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 135ms); + segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 321ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 205ms); + segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 672ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 122ms); + segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_wait(context, 362ms); + segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_wait(context, 373ms); + segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_wait(context, 150ms); + segment03_route_press(stream,context, BUTTON_A, 183ms, 0ms); + pbf_wait(context, 190ms); + segment03_route_press(stream,context, BUTTON_A, 188ms, 0ms); + pbf_wait(context, 213ms); + segment03_route_press(stream,context, BUTTON_A, 143ms, 0ms); + pbf_wait(context, 7001ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 999ms, 0ms); + pbf_wait(context, 268ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 375ms, 0ms); + pbf_wait(context, 227ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 741ms, 0ms); + pbf_wait(context, 46ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 743ms, 0ms); + pbf_wait(context, 68ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 265ms, 0ms); + pbf_wait(context, 255ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 890ms, 0ms); + pbf_wait(context, 88ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 462ms, 0ms); + pbf_wait(context, 271ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 647ms, 0ms); + pbf_wait(context, 20ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1047ms, 0ms); + pbf_wait(context, 56ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 776ms, 0ms); + pbf_wait(context, 2038ms); + context.wait_for_all_requests(); + } + void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, {1.000000, -0.000000}, 4828ms, 0ms); + pbf_wait(context, 104ms); + segment03_route_battle(stream,context,"Route 208 Hiker"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 421ms, 0ms); + pbf_wait(context, 178ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1436ms, 0ms); + pbf_wait(context, 81ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 478ms, 0ms); + pbf_wait(context, 298ms); + segment03_route_press(stream,context, BUTTON_A, 218ms, 0ms); + pbf_wait(context, 274ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 611ms); + segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 359ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 189ms, 0ms); + pbf_wait(context, 98ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 719ms, 0ms); + pbf_wait(context, 55ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1300ms, 0ms); + pbf_wait(context, 432ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1453ms, 0ms); + pbf_wait(context, 522ms); + segment03_route_battle(stream,context,"Aroma Lady Hannah"); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1252ms, 0ms); + pbf_wait(context, 491ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2576ms, 0ms); + pbf_wait(context, 211ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 282ms, 0ms); + pbf_wait(context, 198ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 602ms, 0ms); + pbf_wait(context, 1732ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1878ms, 0ms); + pbf_wait(context, 1588ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2201ms, 0ms); + pbf_wait(context, 6189ms); + segment03_route_press(stream,context, BUTTON_A, 276ms, 0ms); + pbf_wait(context, 201ms); + segment03_route_press(stream,context, BUTTON_A, 111ms, 0ms); + pbf_wait(context, 1090ms); + segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); + pbf_wait(context, 218ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 255ms); + segment03_route_press(stream,context, BUTTON_A, 238ms, 0ms); + pbf_wait(context, 272ms); + segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_wait(context, 171ms); + segment03_route_press(stream,context, BUTTON_A, 202ms, 0ms); + pbf_wait(context, 451ms); + segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_wait(context, 267ms); + segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_wait(context, 482ms); + segment03_route_press(stream,context, BUTTON_A, 227ms, 0ms); + pbf_wait(context, 813ms); + segment03_route_press(stream,context, BUTTON_A, 186ms, 0ms); + pbf_wait(context, 427ms); + segment03_route_press(stream,context, BUTTON_A, 210ms, 0ms); + pbf_wait(context, 243ms); + segment03_route_press(stream,context, BUTTON_A, 187ms, 0ms); + pbf_wait(context, 191ms); + segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); + pbf_wait(context, 190ms); + segment03_route_press(stream,context, BUTTON_A, 212ms, 0ms); + pbf_wait(context, 3054ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3448ms, 0ms); + pbf_wait(context, 437ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2292ms, 0ms); + pbf_wait(context, 1582ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1125ms, 0ms); + pbf_wait(context, 334ms); + segment03_route_press(stream,context, BUTTON_B, 177ms, 0ms); + pbf_wait(context, 357ms); + segment03_route_press(stream,context, BUTTON_B, 216ms, 0ms); + pbf_wait(context, 400ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2228ms, 0ms); + pbf_wait(context, 304ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 279ms, 0ms); + pbf_wait(context, 174ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 498ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 76ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1452ms, 0ms); + pbf_wait(context, 21ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 648ms, 0ms); + pbf_wait(context, 221ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1161ms, 0ms); + pbf_wait(context, 59ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 533ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 96ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 81ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 37ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 941ms, 0ms); + pbf_wait(context, 2095ms); + if (!heal_pokemon(stream,context,"Hearthome")) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Hearthome Center exit not confirmed.",stream); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2092ms, 0ms); + pbf_wait(context, 185ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1004ms, 0ms); + pbf_wait(context, 62ms); + segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); + pbf_wait(context, 277ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 131ms, 0ms); + pbf_wait(context, 70ms); + segment03_route_press(stream,context, BUTTON_A, 190ms, 0ms); + pbf_wait(context, 512ms); + segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 169ms); + segment03_route_press(stream,context, BUTTON_A, 155ms, 0ms); + pbf_wait(context, 150ms); + segment03_route_press(stream,context, BUTTON_A, 167ms, 0ms); + pbf_wait(context, 129ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 179ms); + segment03_route_press(stream,context, BUTTON_A, 162ms, 0ms); + pbf_wait(context, 323ms); + segment03_route_press(stream,context, BUTTON_A, 207ms, 0ms); + pbf_wait(context, 160ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 287ms); + segment03_route_press(stream,context, BUTTON_A, 206ms, 0ms); + pbf_wait(context, 188ms); + segment03_route_press(stream,context, BUTTON_A, 176ms, 0ms); + pbf_wait(context, 233ms); + segment03_route_press(stream,context, BUTTON_A, 243ms, 0ms); + pbf_wait(context, 381ms); + segment03_route_press(stream,context, BUTTON_A, 220ms, 0ms); + pbf_wait(context, 287ms); + segment03_route_press(stream,context, BUTTON_A, 197ms, 0ms); + pbf_wait(context, 221ms); + segment03_route_press(stream,context, BUTTON_A, 205ms, 0ms); + pbf_wait(context, 191ms); + segment03_route_press(stream,context, BUTTON_A, 165ms, 0ms); + pbf_wait(context, 4201ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1179ms, 0ms); + pbf_wait(context, 4268ms); + segment03_route_press(stream,context, BUTTON_A, 181ms, 0ms); + pbf_wait(context, 219ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 123ms); + segment03_route_press(stream,context, BUTTON_A, 185ms, 0ms); + pbf_wait(context, 121ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 805ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 140ms); + segment03_route_press(stream,context, BUTTON_A, 102ms, 0ms); + pbf_wait(context, 218ms); + segment03_route_press(stream,context, BUTTON_B, 181ms, 0ms); + pbf_wait(context, 127ms); + segment03_route_press(stream,context, BUTTON_B, 167ms, 0ms); + pbf_wait(context, 539ms); + segment03_route_press(stream,context, BUTTON_B, 179ms, 0ms); + pbf_wait(context, 246ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 363ms); + segment03_route_press(stream,context, BUTTON_B, 182ms, 0ms); + pbf_wait(context, 176ms); + segment03_route_press(stream,context, BUTTON_B, 237ms, 0ms); + pbf_wait(context, 163ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 123ms); + segment03_route_press(stream,context, BUTTON_B, 186ms, 0ms); + pbf_wait(context, 173ms); + segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); + pbf_wait(context, 121ms); + segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); + pbf_wait(context, 337ms); + segment03_route_press(stream,context, BUTTON_B, 228ms, 0ms); + pbf_wait(context, 149ms); + segment03_route_press(stream,context, BUTTON_B, 236ms, 0ms); + pbf_wait(context, 138ms); + segment03_route_press(stream,context, BUTTON_B, 194ms, 0ms); + pbf_wait(context, 139ms); + segment03_route_press(stream,context, BUTTON_B, 195ms, 0ms); + pbf_wait(context, 162ms); + segment03_route_press(stream,context, BUTTON_B, 188ms, 0ms); + pbf_wait(context, 571ms); + segment03_route_press(stream,context, BUTTON_B, 229ms, 0ms); + pbf_wait(context, 122ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 122ms); + segment03_route_press(stream,context, BUTTON_B, 181ms, 0ms); + pbf_wait(context, 176ms); + segment03_route_press(stream,context, BUTTON_B, 170ms, 0ms); + pbf_wait(context, 175ms); + segment03_route_press(stream,context, BUTTON_B, 203ms, 0ms); + pbf_wait(context, 212ms); + segment03_route_press(stream,context, BUTTON_B, 209ms, 0ms); + pbf_wait(context, 274ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 214ms); + segment03_route_press(stream,context, BUTTON_B, 186ms, 0ms); + pbf_wait(context, 547ms); + segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); + pbf_wait(context, 1964ms); + segment03_route_press(stream,context, BUTTON_B, 195ms, 0ms); + pbf_wait(context, 139ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 279ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 350ms); + segment03_route_press(stream,context, BUTTON_B, 250ms, 0ms); + pbf_wait(context, 150ms); + segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); + pbf_wait(context, 488ms); + segment03_route_press(stream,context, BUTTON_B, 209ms, 0ms); + pbf_wait(context, 225ms); + segment03_route_press(stream,context, BUTTON_B, 242ms, 0ms); + pbf_wait(context, 347ms); + segment03_route_press(stream,context, BUTTON_B, 237ms, 0ms); + pbf_wait(context, 232ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 400ms); + segment03_route_press(stream,context, BUTTON_B, 226ms, 0ms); + pbf_wait(context, 162ms); + segment03_route_press(stream,context, BUTTON_B, 145ms, 0ms); + pbf_wait(context, 365ms); + segment03_route_press(stream,context, BUTTON_B, 209ms, 0ms); + pbf_wait(context, 560ms); + segment03_route_press(stream,context, BUTTON_B, 224ms, 0ms); + pbf_wait(context, 540ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 625ms); + segment03_route_press(stream,context, BUTTON_B, 198ms, 0ms); + pbf_wait(context, 218ms); + segment03_route_press(stream,context, BUTTON_B, 183ms, 0ms); + pbf_wait(context, 432ms); + segment03_route_press(stream,context, BUTTON_B, 235ms, 0ms); + pbf_wait(context, 323ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 189ms); + segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); + pbf_wait(context, 365ms); + segment03_route_press(stream,context, BUTTON_B, 236ms, 0ms); + pbf_wait(context, 189ms); + segment03_route_press(stream,context, BUTTON_B, 170ms, 0ms); + pbf_wait(context, 3365ms); + context.wait_for_all_requests(); + } + void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, {0.000000, -1.000000}, 903ms, 0ms); + pbf_wait(context, 1455ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1830ms, 0ms); + pbf_wait(context, 144ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1880ms, 0ms); + pbf_wait(context, 81ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 609ms, 0ms); + pbf_wait(context, 73ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2014ms, 0ms); + pbf_wait(context, 186ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1744ms, 0ms); + pbf_wait(context, 14ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 411ms, 0ms); + pbf_wait(context, 164ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1944ms, 0ms); + pbf_wait(context, 226ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1142ms, 0ms); + pbf_wait(context, 539ms); + segment03_route_battle(stream,context,"Barry in Hearthome"); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 877ms, 0ms); + pbf_wait(context, 55ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2561ms, 0ms); + pbf_wait(context, 25ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 344ms, 0ms); + pbf_wait(context, 203ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 518ms, 0ms); + pbf_wait(context, 1655ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1906ms, 0ms); + pbf_wait(context, 1761ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 878ms, 0ms); + pbf_wait(context, 1967ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1366ms, 0ms); + pbf_wait(context, 167ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 450ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 250ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 244ms, 0ms); + pbf_wait(context, 77ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 143ms, 0ms); + pbf_wait(context, 71ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 232ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 261ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 206ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 142ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 278ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 225ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3015ms, 0ms); + pbf_wait(context, 95ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1122ms, 0ms); + pbf_wait(context, 9ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1523ms, 0ms); + pbf_wait(context, 14ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 626ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 28ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 300ms, 0ms); + pbf_wait(context, 110ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1079ms, 0ms); + pbf_wait(context, 189ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 322ms, 0ms); + pbf_wait(context, 344ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 90ms, 0ms); + pbf_wait(context, 777ms); + context.wait_for_all_requests(); + } + static void lost_tower(VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, {0.000000, 1.000000}, 745ms, 0ms); + pbf_wait(context, 100ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 994ms, 0ms); + pbf_wait(context, 94ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1179ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 193ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 884ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1382ms, 0ms); + pbf_wait(context, 524ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1750ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 145ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 116ms, 0ms); + pbf_wait(context, 373ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 321ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 212ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 202ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 132ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 212ms, 0ms); + pbf_wait(context, 1507ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 403ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 210ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 244ms, 0ms); + pbf_wait(context, 56ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 567ms, 0ms); + pbf_wait(context, 855ms); + segment03_route_press(stream,context, BUTTON_X, 212ms, 0ms); + pbf_wait(context, 683ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 184ms, 0ms); + pbf_wait(context, 415ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 129ms, 0ms); + pbf_wait(context, 190ms); + segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); + pbf_wait(context, 1326ms); + segment03_route_press(stream,context, BUTTON_A, 144ms, 0ms); + pbf_wait(context, 591ms); + segment03_route_press(stream,context, BUTTON_A, 142ms, 0ms); + pbf_wait(context, 1017ms); + segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 411ms); + segment03_route_press(stream,context, BUTTON_B, 190ms, 0ms); + pbf_wait(context, 594ms); + segment03_route_press(stream,context, BUTTON_B, 172ms, 0ms); + pbf_wait(context, 190ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 317ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 266ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 76ms, 0ms); + pbf_wait(context, 424ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 265ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 61ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 280ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 144ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 814ms, 0ms); + pbf_wait(context, 1386ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 944ms, 0ms); + pbf_wait(context, 390ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 347ms, 0ms); + pbf_wait(context, 236ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 450ms, 0ms); + pbf_wait(context, 400ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 581ms, 0ms); + pbf_wait(context, 8ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 964ms, 0ms); + pbf_wait(context, 129ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 215ms, 0ms); + pbf_wait(context, 390ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1092ms, 0ms); + pbf_wait(context, 129ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1353ms, 0ms); + pbf_wait(context, 276ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 193ms, 0ms); + pbf_wait(context, 535ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 711ms, 0ms); + pbf_wait(context, 123ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 746ms, 0ms); + pbf_wait(context, 1670ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1114ms, 0ms); + pbf_wait(context, 474ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 175ms, 0ms); + pbf_wait(context, 377ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 828ms, 0ms); + pbf_wait(context, 366ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 165ms, 0ms); + pbf_wait(context, 274ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1012ms, 0ms); + pbf_wait(context, 1860ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 900ms, 0ms); + pbf_wait(context, 1078ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1089ms, 0ms); + pbf_wait(context, 711ms); + segment03_route_battle(stream,context,"Lost Tower trainers"); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 245ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 167ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 228ms, 0ms); + pbf_wait(context, 455ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 817ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 47ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 853ms, 0ms); + pbf_wait(context, 13884ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1010ms, 0ms); + pbf_wait(context, 2229ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 261ms, 0ms); + pbf_wait(context, 887ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 60ms, 0ms); + pbf_wait(context, 420ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1567ms, 0ms); + pbf_wait(context, 173ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 578ms, 0ms); + pbf_wait(context, 53ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 308ms, 0ms); + pbf_wait(context, 54ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 451ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 126ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 33ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 85ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 883ms, 0ms); + pbf_wait(context, 1309ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 419ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -1.000000}, 44ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 300ms, 0ms); + pbf_wait(context, 371ms); + segment03_route_press(stream,context, BUTTON_A, 181ms, 0ms); + pbf_wait(context, 136ms); + segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_wait(context, 137ms); + segment03_route_press(stream,context, BUTTON_A, 146ms, 0ms); + pbf_wait(context, 286ms); + segment03_route_press(stream,context, BUTTON_A, 150ms, 0ms); + pbf_wait(context, 175ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 310ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -1.000000}, 124ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 377ms, 0ms); + pbf_wait(context, 320ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 327ms, 0ms); + pbf_wait(context, 118ms); + segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_wait(context, 173ms); + segment03_route_press(stream,context, BUTTON_A, 205ms, 0ms); + pbf_wait(context, 106ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 115ms); + segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_wait(context, 145ms); + segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); + pbf_wait(context, 334ms); + segment03_route_press(stream,context, BUTTON_A, 217ms, 0ms); + pbf_wait(context, 174ms); + segment03_route_press(stream,context, BUTTON_A, 190ms, 0ms); + pbf_wait(context, 234ms); + segment03_route_press(stream,context, BUTTON_A, 233ms, 0ms); + pbf_wait(context, 119ms); + segment03_route_press(stream,context, BUTTON_A, 201ms, 0ms); + pbf_wait(context, 133ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 123ms); + segment03_route_press(stream,context, BUTTON_A, 214ms, 0ms); + pbf_wait(context, 109ms); + segment03_route_press(stream,context, BUTTON_B, 217ms, 0ms); + pbf_wait(context, 157ms); + segment03_route_press(stream,context, BUTTON_B, 162ms, 0ms); + pbf_wait(context, 523ms); + segment03_route_press(stream,context, BUTTON_B, 192ms, 0ms); + pbf_wait(context, 79ms); + segment03_route_press(stream,context, BUTTON_B, 132ms, 0ms); + pbf_wait(context, 115ms); + segment03_route_press(stream,context, BUTTON_B, 126ms, 0ms); + pbf_wait(context, 211ms); + segment03_route_press(stream,context, BUTTON_B, 156ms, 0ms); + pbf_wait(context, 528ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 156ms, 0ms); + pbf_wait(context, 559ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 171ms, 0ms); + pbf_wait(context, 65ms); + segment03_route_press(stream,context, BUTTON_A, 204ms, 0ms); + pbf_wait(context, 172ms); + segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_wait(context, 113ms); + segment03_route_press(stream,context, BUTTON_A, 173ms, 0ms); + pbf_wait(context, 92ms); + segment03_route_press(stream,context, BUTTON_A, 158ms, 0ms); + pbf_wait(context, 102ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 104ms); + segment03_route_press(stream,context, BUTTON_A, 179ms, 0ms); + pbf_wait(context, 106ms); + segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); + pbf_wait(context, 402ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 390ms); + segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_wait(context, 571ms); + segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_wait(context, 2265ms); + segment03_route_press(stream,context, BUTTON_A, 218ms, 0ms); + pbf_wait(context, 934ms); + segment03_route_press(stream,context, BUTTON_A, 232ms, 0ms); + pbf_wait(context, 171ms); + segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 488ms); + segment03_route_press(stream,context, BUTTON_A, 229ms, 0ms); + pbf_wait(context, 154ms); + segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_wait(context, 287ms); + segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_wait(context, 855ms); + segment03_route_press(stream,context, BUTTON_A, 249ms, 0ms); + pbf_wait(context, 594ms); + segment03_route_press(stream,context, BUTTON_A, 218ms, 0ms); + pbf_wait(context, 172ms); + segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_wait(context, 363ms); + segment03_route_press(stream,context, BUTTON_A, 207ms, 0ms); + pbf_wait(context, 1485ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 400ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 49ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 652ms, 0ms); + pbf_wait(context, 69ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 89ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 126ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 149ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 101ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 104ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 24ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 152ms, 0ms); + context.wait_for_all_requests(); + } + void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context) { + pbf_move_left_joystick(context, {0.000000, -1.000000}, 501ms, 0ms); + pbf_wait(context, 303ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 527ms, 0ms); + pbf_wait(context, 243ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 221ms, 0ms); + pbf_wait(context, 546ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1208ms, 0ms); + pbf_wait(context, 177ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 171ms, 0ms); + pbf_wait(context, 177ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2245ms, 0ms); + pbf_wait(context, 487ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 523ms, 0ms); + pbf_wait(context, 196ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 854ms, 0ms); + pbf_wait(context, 378ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 205ms, 0ms); + pbf_wait(context, 273ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 792ms, 0ms); + pbf_wait(context, 2437ms); + if (!heal_pokemon(stream,context,"Solaceon")) OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT,"Solaceon Center exit not confirmed.",stream); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 181ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -1.000000}, 192ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 448ms, 0ms); + pbf_wait(context, 13ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 811ms, 0ms); + pbf_wait(context, 49ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 549ms, 0ms); + pbf_wait(context, 391ms); + segment03_route_press(stream,context, BUTTON_B, 175ms, 0ms); + pbf_wait(context, 222ms); + segment03_route_press(stream,context, BUTTON_B, 141ms, 0ms); + pbf_wait(context, 781ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 180ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 38ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1070ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 17ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 186ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 35ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 191ms, 0ms); + pbf_wait(context, 31ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 323ms, 0ms); + pbf_wait(context, 3187ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 795ms, 0ms); + pbf_wait(context, 72ms); + segment03_route_press(stream,context, BUTTON_A, 178ms, 0ms); + pbf_wait(context, 540ms); + segment03_route_press(stream,context, BUTTON_A, 159ms, 0ms); + pbf_wait(context, 655ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 1683ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 163ms, 0ms); + pbf_wait(context, 2372ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 160ms, 0ms); + pbf_wait(context, 454ms); + segment03_route_press(stream,context, BUTTON_A, 165ms, 0ms); + pbf_wait(context, 1591ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 147ms, 0ms); + pbf_wait(context, 853ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 91ms, 0ms); + pbf_wait(context, 389ms); + segment03_route_press(stream,context, BUTTON_A, 162ms, 0ms); + pbf_wait(context, 438ms); + segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 106ms); + segment03_route_press(stream,context, BUTTON_A, 144ms, 0ms); + pbf_wait(context, 287ms); + segment03_route_press(stream,context, BUTTON_A, 169ms, 0ms); + pbf_wait(context, 534ms); + segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 655ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 473ms); + segment03_route_press(stream,context, BUTTON_B, 168ms, 0ms); + pbf_wait(context, 401ms); + segment03_route_press(stream,context, BUTTON_B, 175ms, 0ms); + pbf_wait(context, 439ms); + segment03_route_press(stream,context, BUTTON_B, 78ms, 0ms); + pbf_wait(context, 723ms); + segment03_route_press(stream,context, BUTTON_B, 159ms, 0ms); + pbf_wait(context, 213ms); + segment03_route_press(stream,context, BUTTON_B, 122ms, 0ms); + pbf_wait(context, 544ms); + segment03_route_press(stream,context, BUTTON_B, 144ms, 0ms); + pbf_wait(context, 567ms); + segment03_route_press(stream,context, BUTTON_B, 167ms, 0ms); + pbf_wait(context, 216ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1117ms, 0ms); + pbf_wait(context, 1924ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1610ms, 0ms); + pbf_wait(context, 189ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 5877ms, 0ms); + pbf_wait(context, 2351ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 383ms, 0ms); + pbf_wait(context, 321ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 745ms, 0ms); + pbf_wait(context, 3127ms); + segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_wait(context, 144ms); + segment03_route_press(stream,context, BUTTON_A, 184ms, 0ms); + pbf_wait(context, 550ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 122ms); + segment03_route_press(stream,context, BUTTON_A, 188ms, 0ms); + pbf_wait(context, 130ms); + segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 272ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 5334ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 159ms, 0ms); + pbf_wait(context, 204ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 698ms); + segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_wait(context, 230ms); + segment03_route_press(stream,context, BUTTON_A, 169ms, 0ms); + pbf_wait(context, 773ms); + segment03_route_press(stream,context, BUTTON_X, 212ms, 0ms); + pbf_wait(context, 1555ms); + segment03_route_press(stream,context, BUTTON_X, 233ms, 0ms); + pbf_wait(context, 1255ms); + segment03_route_press(stream,context, BUTTON_A, 173ms, 0ms); + pbf_wait(context, 1640ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 177ms, 0ms); + pbf_wait(context, 278ms); + segment03_route_press(stream,context, BUTTON_A, 189ms, 0ms); + pbf_wait(context, 506ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 700ms); + segment03_route_press(stream,context, BUTTON_A, 167ms, 0ms); + pbf_wait(context, 869ms); + segment03_route_press(stream,context, BUTTON_B, 176ms, 0ms); + pbf_wait(context, 356ms); + segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_wait(context, 257ms); + segment03_route_press(stream,context, BUTTON_B, 158ms, 0ms); + pbf_wait(context, 496ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1677ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 14ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2254ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2190ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 1ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 3839ms, 0ms); + pbf_wait(context, 98ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1963ms, 0ms); + pbf_wait(context, 13ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1099ms, 0ms); + pbf_wait(context, 235ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2766ms, 0ms); + pbf_wait(context, 40ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1652ms, 0ms); + pbf_wait(context, 30ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 839ms, 0ms); + pbf_wait(context, 98ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 725ms, 0ms); + pbf_wait(context, 77ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 824ms, 0ms); + pbf_wait(context, 356ms); + segment03_route_battle(stream,context,"Route 215 first trainer"); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2350ms, 0ms); + pbf_wait(context, 56ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2095ms, 0ms); + pbf_wait(context, 69ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 747ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 10ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1434ms, 0ms); + pbf_wait(context, 41ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1993ms, 0ms); + pbf_wait(context, 55ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1542ms, 0ms); + pbf_wait(context, 192ms); + segment03_route_press(stream,context, BUTTON_A, 174ms, 0ms); + pbf_wait(context, 841ms); + segment03_route_press(stream,context, BUTTON_A, 184ms, 0ms); + pbf_wait(context, 549ms); + segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_wait(context, 261ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 541ms, 0ms); + pbf_wait(context, 6ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 562ms, 0ms); + pbf_wait(context, 42ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 520ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 77ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 166ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 37ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 647ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 15ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1062ms, 0ms); + pbf_wait(context, 197ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1426ms, 0ms); + pbf_wait(context, 5ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 676ms, 0ms); + pbf_wait(context, 9ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2523ms, 0ms); + pbf_wait(context, 471ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 378ms, 0ms); + pbf_wait(context, 488ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 943ms, 0ms); + pbf_wait(context, 272ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 127ms, 0ms); + pbf_wait(context, 380ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 920ms, 0ms); + pbf_wait(context, 92ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1115ms, 0ms); + pbf_wait(context, 16ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 144ms, 0ms); + pbf_wait(context, 480ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 613ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 10ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 320ms, 0ms); + pbf_wait(context, 56ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 778ms, 0ms); + pbf_wait(context, 476ms); + segment03_route_battle(stream,context,"Route 215 Ace Trainers"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 4051ms, 0ms); + pbf_wait(context, 1256ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1047ms, 0ms); + pbf_wait(context, 503ms); + segment03_route_press(stream,context, BUTTON_B, 189ms, 0ms); + pbf_wait(context, 222ms); + segment03_route_press(stream,context, BUTTON_B, 183ms, 0ms); + pbf_wait(context, 637ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1824ms, 0ms); + pbf_wait(context, 1256ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2226ms, 0ms); + pbf_wait(context, 891ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1678ms, 0ms); + pbf_wait(context, 70ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1496ms, 0ms); + pbf_wait(context, 54ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1179ms, 0ms); + pbf_wait(context, 21ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2448ms, 0ms); + pbf_wait(context, 54ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1574ms, 0ms); + pbf_wait(context, 58ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 3675ms, 0ms); + pbf_wait(context, 93ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 666ms, 0ms); + pbf_wait(context, 69ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1757ms, 0ms); + pbf_wait(context, 57ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 3227ms, 0ms); + pbf_wait(context, 4788ms); + segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 388ms); + segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 176ms); + segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_wait(context, 509ms); + segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); + pbf_wait(context, 255ms); + segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 408ms); + segment03_route_press(stream,context, BUTTON_A, 212ms, 0ms); + pbf_wait(context, 189ms); + segment03_route_press(stream,context, BUTTON_A, 166ms, 0ms); + pbf_wait(context, 458ms); + segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 3630ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 744ms, 0ms); + // The recording ends outside the gym door, facing north. + segment03_doorway(stream,context,true,1200ms,"Veilstone Gym entrance"); + context.wait_for_all_requests(); + } + +static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + std::chrono::milliseconds delay; + }; + + const Move sequence1[] = { + {DPAD_RIGHT, 8, 0ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_UP, 2, 0ms}, + }; + + const Move sequence2[] = { + {DPAD_UP, 5, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_DOWN, 10, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_DOWN, 13, 0ms}, + {DPAD_LEFT, 3, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 6, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 7, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 3, 0ms}, + }; + + const Move sequence3[] = { + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 3000ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_LEFT, 8, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 3000ms}, + {DPAD_UP, 11, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_DOWN, 13, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 3, 0ms}, + }; + + const Move sequence4[] = { + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 3, 0ms}, + }; + + const Move sequence5[] = { + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 8, 0ms}, + }; + + const Move sequence6[] = { + {DPAD_DOWN, 8, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 3000ms}, + {DPAD_DOWN, 6, 0ms}, + }; + + for (const auto& move : sequence1){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 01 (Black Belt Rafael). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + segment03_route_battle(stream,context,"Black Belt Rafael (Flamethrower)"); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 02 (Black Belt Jeffrey). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + segment03_route_battle(stream,context,"Black Belt Jeffrey (Flamethrower)"); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 03 (Black Belt Darren). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + segment03_route_battle(stream,context,"Black Belt Darren (Flamethrower)"); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate Trainer battle 04 (Black Belt Colby). + + pbf_press_button(context,BUTTON_A,150ms,300ms); + segment03_route_battle(stream,context,"Black Belt Colby (Flamethrower)"); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Button presses to initiate the Gym battle. + + pbf_press_button(context,BUTTON_A,150ms,300ms); + segment03_route_battle(stream,context,"Maylene (Flamethrower)",true); + + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + + for (const auto& move : sequence6){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay.count() > 0){ + pbf_wait(context, move.delay); + } + } + } + + //Exits Buiilding + pbf_wait(context, 1000ms); +} + +std::string AutoStory_Segment_03::name() const{ return "03: Second Badge to Third Badge"; } +std::string AutoStory_Segment_03::start_text() const{ return "Start: Second Gym Badge acquired (Eterna City)."; } +std::string AutoStory_Segment_03::end_text() const{ return "End: Third Gym Badge acquired (Veilstone City)."; } + +void AutoStory_Segment_03::run_segment( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_0301().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0302().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0303().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0304().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0305().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0306().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0307().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_Checkpoint_0301::name() const{ return "03.01 - Eterna Gym to Jupiter"; } +std::string AutoStory_Checkpoint_0301::start_text() const{ return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; } +std::string AutoStory_Checkpoint_0301::end_text() const{ return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; } + +void AutoStory_Checkpoint_0301::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const{ + Checkpoint_0301(env, context, options, stats); +} + + +void Checkpoint_0301( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +){ + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/){ + // Recorded route with feedback battle handling. + eterna_gym_to_jupiter(env.console, context); + + }, + + true + ); +} +std::string AutoStory_Checkpoint_0302::name() const { return "03.02 - Jupiter to Mount Coronet"; } +std::string AutoStory_Checkpoint_0302::start_text() const { return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; } +std::string AutoStory_Checkpoint_0302::end_text() const { return "On Route 207 after the Hiker battle, before the west entrance of Mount Coronet."; } + +void AutoStory_Checkpoint_0302::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0302(env, context, options, stats); +} + + +void Checkpoint_0302( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // Recorded route with feedback battle handling. + jupiter_to_coronet(env.console, context); + + }, + + true + ); +} +std::string AutoStory_Checkpoint_0303::name() const { return "03.03 - Mount Coronet"; } +std::string AutoStory_Checkpoint_0303::start_text() const { return "On Route 207 after the Hiker battle, before the west entrance of Mount Coronet."; } +std::string AutoStory_Checkpoint_0303::end_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } + +void AutoStory_Checkpoint_0303::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0303(env, context, options, stats); +} + + +void Checkpoint_0303( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // Recorded route with feedback battle handling. + coronet1(env.console, context); + + }, + + true + ); +} +std::string AutoStory_Checkpoint_0304::name() const { return "03.04 - Coronet to Hearthome"; } +std::string AutoStory_Checkpoint_0304::start_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } +std::string AutoStory_Checkpoint_0304::end_text() const { return "Outside the Hearthome Contest Hall after the Keira/Johanna introduction."; } + +void AutoStory_Checkpoint_0304::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0304(env, context, options, stats); +} + + +void Checkpoint_0304( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // Recorded route with feedback battle handling. + coronet_to_heartholme(env.console, context); + + }, + + true + ); +} +std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - Hearthome and Lost Tower"; } +std::string AutoStory_Checkpoint_0305::start_text() const { return "Outside the Hearthome Contest Hall after the Keira/Johanna introduction."; } +std::string AutoStory_Checkpoint_0305::end_text() const { return "At the Lost Tower departure position after the tower gifts and recorded return path."; } + +void AutoStory_Checkpoint_0305::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0305(env, context, options, stats); +} + + +void Checkpoint_0305( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // Recorded route with feedback battle handling. + heartholme_to_lost_tower(env.console, context); + lost_tower(env.console, context); + + }, + + true + ); +} + +std::string AutoStory_Checkpoint_0306::name() const { return "03.06 - Lost Tower to Veilstone"; } +std::string AutoStory_Checkpoint_0306::start_text() const { return "At the Lost Tower departure position after the tower gifts and recorded return path."; } +std::string AutoStory_Checkpoint_0306::end_text() const { return "Inside Veilstone Gym at the entrance, facing north."; } + +void AutoStory_Checkpoint_0306::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0306(env, context, options, stats); +} + + +void Checkpoint_0306( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // Recorded route with feedback battle handling. + lost_tower_to_veilstone_gym(env.console, context); + }, + true + ); +} + +std::string AutoStory_Checkpoint_0307::name() const { return "03.07 - Inside Gym 3"; } +std::string AutoStory_Checkpoint_0307::start_text() const { return "Inside Veilstone Gym at the entrance, facing north."; } +std::string AutoStory_Checkpoint_0307::end_text() const { return "Third Gym Badge acquired (Veilstone City)."; } + +void AutoStory_Checkpoint_0307::run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) const { + Checkpoint_0307(env, context, options, stats); +} + + +void Checkpoint_0307( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +) { + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + //#. Veilstone Gym navigation and battle + gym_Puzzle_03(env.console, context); + }, + true + ); +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 5f9410e6af..d4c1737304 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -563,7 +563,8 @@ namespace PokemonAutomation { void set_time_fast( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - const DateTime& target + const DateTime& target, + bool preserve_date ) { env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); @@ -593,7 +594,25 @@ namespace PokemonAutomation { { VideoOverlaySet overlays(env.console.overlay()); date_reader.make_overlays(overlays); - date_reader.set_date(env.program_info(), env.console, context, target); + DateTime effective_target = target; + if (preserve_date) { + context.wait_for_all_requests(); + const auto snapshot = env.console.video().snapshot(); + if (!snapshot) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Clock-only setup: No date-editor frame.", env.console); + const DateTime current = date_reader.read_date(env.console, snapshot.frame).second; + if (current.year < 2000 || current.year > 2060 || current.month < 1 || current.month > 12 || + current.day < 1 || current.day > 31) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Clock-only setup: Could not reliably read the current console date.", env.console); + effective_target.year = current.year; + effective_target.month = current.month; + effective_target.day = current.day; + env.log("[AutoStory] Clock-only target=" + std::to_string((int)target.hour) + ":" + + std::to_string((int)target.minute) + "; preserving date=" + std::to_string(current.year) + "-" + + std::to_string((int)current.month) + "-" + std::to_string((int)current.day) + "."); + } + date_reader.set_date(env.program_info(), env.console, context, effective_target); } // 5) Commit date. @@ -615,11 +634,21 @@ namespace PokemonAutomation { // Natalie battle // --------------------------------------------------------------------------- static void select_battle_move_slot(VideoStream& stream, ProControllerContext& context, - BattleType battle_type, size_t target, const std::string& label) { + BattleType battle_type, size_t target, const std::string& label, + bool confirm_default_target = false, size_t current_move = 1) { BattleMenuDetector commands(battle_type); VideoOverlaySet overlays(stream.overlay()); commands.make_overlays(overlays); overlays.add(COLOR_GREEN, {0.90, 0.48, 0.075, 0.505}); + // Reenter Fight from an already-open list without assuming + // that this resets the remembered move cursor. + if (confirm_default_target) { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if (frame && detect_battle_move_menu(frame).move_count) { + pbf_press_button(context,BUTTON_B,80ms,500ms); + } + } bool fight = false; size_t confirmed = 0; for (size_t check = 0; check < 30; check++) { @@ -638,7 +667,30 @@ namespace PokemonAutomation { } if (!fight) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label + ": Could not confirm Battle command; A not pressed.", stream); - pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context,BUTTON_A,80ms,confirm_default_target ? 1000ms : 500ms); + if (confirm_default_target) { + // Reuse the farming move/target confirmation sequence, but + // navigate relative to this battle's retained move cursor. + // Reopening Fight does not reset it to move 1. + size_t available=0,stable=0; + for (size_t check=0;check<20 && stable<3;check++) { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + const auto moves=frame ? detect_battle_move_menu(frame) : BattleMoveMenuState{}; + if (moves.move_count && moves.move_count==available) stable++; + else {available=moves.move_count;stable=moves.move_count ? 1:0;} + if (stable<3) pbf_wait(context,100ms); + } + if (stable<3 || available "+std::to_string(target)+"."); + for (size_t slot=current_move;slottarget;slot--) pbf_press_dpad(context,DPAD_UP,80ms,400ms); + pbf_press_button(context,BUTTON_A,80ms,1000ms); + pbf_press_button(context,BUTTON_A,80ms,3000ms); + return; + } confirmed = 0; size_t previous_move_count = 0, stable_move_count = 0; for (size_t check = 0; check < 30; check++) { @@ -1010,17 +1062,29 @@ namespace PokemonAutomation { context.wait_for_all_requests(); } - static void fight_using_move_slot( + static void fight_using_move_list( VideoStream& stream, ProControllerContext& context, - BattleType battle_type, size_t move_slot, const std::string& label + BattleType battle_type, const std::vector& move_slots, + const std::string& label, bool confirm_default_target, bool post_battle_dialogue_cleanup ) { + if (move_slots.empty() || std::any_of(move_slots.begin(),move_slots.end(), + [](size_t slot) { return slot<1 || slot>4; })) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Supply a nonempty move list containing only positions 1 through 4.",stream); + } + size_t move_index=0; + // The first Fight menu starts at move 1. Later turns retain + // this Pokemon's previous selection; a new battle resets it. + size_t current_move=1; stream.log( "[AutoStory] Starting " + label + " battle...", COLOR_BLUE ); - // Wait for battle transition to fully finish. - pbf_wait(context, 5000ms); + // Scripted battles wait on the menu detector below, so no + // extra fixed delay is needed after a caller confirms it. + // Preserve the existing early-game transition delay. + if (!confirm_default_target) pbf_wait(context,5000ms); bool battle_menu_seen = false; EndBattleWatcher end_battle; @@ -1030,23 +1094,23 @@ namespace PokemonAutomation { context.wait_for_all_requests(); BattleMenuWatcher battle_menu(battle_type); + // Reuse the farming handlers' move-learning prompt recovery. + SelectionArrowFinder learn_move(stream.overlay(),{0.50,0.62,0.40,0.18},COLOR_YELLOW); int ret = run_until( stream, context, [](ProControllerContext& context) { - // Mash through battle text continuously. - pbf_mash_button( - context, - BUTTON_B, - 120000ms - ); + // Clear narration with B while watching for commands. + // A here can open Fight before selection begins. + pbf_mash_button(context,BUTTON_B,120000ms); }, { {battle_menu}, battle_menu_seen ? PeriodicInferenceCallback{end_battle} : PeriodicInferenceCallback{}, + confirm_default_target ? PeriodicInferenceCallback{learn_move} : PeriodicInferenceCallback{}, } ); @@ -1063,7 +1127,13 @@ namespace PokemonAutomation { COLOR_BLUE ); - select_battle_move_slot(stream, context, battle_type, move_slot, label); + stream.log("["+label+"] Move list position "+std::to_string(move_index+1)+ + ": use move "+std::to_string(move_slots[move_index])+"."); + select_battle_move_slot(stream, context, battle_type, move_slots[move_index], label, + confirm_default_target,current_move); + current_move=move_slots[move_index]; + // Advance once per move selection; retain the final entry. + if (move_index+1(stream, context, [](ProControllerContext& ctx) { @@ -1092,6 +1167,12 @@ namespace PokemonAutomation { return; } + case 2: + stream.log("["+label+"] Declining move learning; preserving scripted move positions."); + pbf_move_right_joystick(context,{0,-1},160ms,840ms); + pbf_press_button(context,BUTTON_ZL,160ms,840ms); + break; + default: stream.log( "[AutoStory] " + label + " battle timeout.", @@ -1104,6 +1185,253 @@ namespace PokemonAutomation { } } + // Preserve the existing early-game callers and their B text cleanup. + static void fight_using_move_slot(VideoStream& stream, ProControllerContext& context, + BattleType battle_type, size_t move_slot, const std::string& label) { + fight_using_move_list(stream,context,battle_type,{move_slot},label,false,true); + } + + void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, + Button button, Milliseconds quiet_period, + const char* instruction_card) { + context.wait_for_all_requests(); + std::unique_ptr card; + if (instruction_card) card = std::make_unique( + std::make_shared(RESOURCE_PATH()+instruction_card), + ImageFloatBox{.04,.32,.92,.34},30.0); + ShortDialogDetector dialogue(COLOR_YELLOW); + BlackScreenDetector black; + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + black.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 120s; + auto absent_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Post-battle dialogue: No video frame.", stream); + const auto now = std::chrono::steady_clock::now(); + const bool dialogue_visible = dialogue.detect(*frame.frame); + const bool card_visible = card && card->detect(*frame.frame); + if (dialogue_visible || card_visible || black.detect(*frame.frame)) absent_since = now; + else if (now - absent_since >= quiet_period) { + stream.log("[Post-battle dialogue] " + std::to_string(quiet_period.count()) + + "ms without dialogue confirmed."); + return; + } + // A can interact again once dialogue disappears. Only confirm + // visible prompts; B retains the existing post-battle behavior. + if (card_visible) pbf_press_button(context, BUTTON_B, 80ms, 100ms); + else if (button == BUTTON_A && !dialogue_visible) pbf_wait(context, 100ms); + else pbf_mash_button(context, button, 100ms); + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Post-battle dialogue: Three clear seconds not confirmed.", stream); + } + + void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, bool allow_story_start, bool horizontal_first, + bool search_down_first, + OverworldMatchResult (*entry_matcher)(const ImageRGB32&), + bool* entry_detected, + double arrival_tolerance_x, double arrival_tolerance_y, + double moderate_entry_confidence) { + if (entry_detected) *entry_detected=false; + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); + size_t confirmations = 0, missing = 0; + for (size_t attempt = 0; attempt < 80; attempt++) { + const auto frame = stream.video().snapshot(); + if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + if (entry_matcher){ + BlackScreenDetector transition; + if (entry_matcher(*frame.frame).located || transition.detect(*frame.frame)){ + BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{visible}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Building transition did not finish.",stream); + const auto inside=stream.video().snapshot(); + if (!inside || !entry_matcher(*inside.frame).located) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Expected interior not confirmed after transition.",stream); + if(entry_detected)*entry_detected=true; + stream.log("["+label+"] Expected interior confirmed; ending outdoor alignment.",COLOR_GREEN); + return; + } + } + // The story can start before the exact reference point. Hand off + // to battle preparation instead of steering during dialogue. + if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)) { + if (allow_story_start) { + stream.log("[" + label + "] Story dialogue/battle started; handing off without further movement."); + return; + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Unexpected dialogue/battle during alignment.", stream); + } + const auto result = matcher(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks) { + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); + stream.log("[" + label + "] " + landmark.name + " score=" + + std::to_string(landmark.score) + ", margin=" + std::to_string(landmark.margin) + + ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + stream.log("[" + label + "] located=" + std::to_string(result.located) + + ", coarse=" + std::to_string(result.coarse) + ", at_target=" + std::to_string(result.at_target) + + ", consensus offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); + const double entry_confidence=moderate_entry_confidence>0 ? overworld_entry_confidence(result) : 0; + const bool moderate_arrival=moderate_entry_confidence>0 && entry_confidence>=moderate_entry_confidence; + if(moderate_entry_confidence>0) stream.log("["+label+"] Exterior entry confidence="+ + std::to_string(entry_confidence)+", moderate threshold="+std::to_string(moderate_entry_confidence)+ + ", strict target="+std::to_string(result.at_target)); + // Explicit loose bounds are for non-interacting route handoffs. + // Default callers still require the matcher's precise arrival. + const bool rough_arrival=arrival_tolerance_x>0 && arrival_tolerance_y>0 && + result.located && std::abs(result.dx)<=arrival_tolerance_x && + std::abs(result.dy)<=arrival_tolerance_y; + if (result.at_target || rough_arrival || moderate_arrival) { + if (++confirmations >= 3) { stream.log("[" + label + "] Position confirmed."); return; } + pbf_wait(context, 250ms); + } else if (result.located) { + confirmations = 0; + missing = 0; + // A temporarily obscured third Jupiter anchor must not + // move us away from an otherwise aligned position. + if (matcher==match_galactic_hq_jupiter && result.coarse && + std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/54){ + pbf_wait(context,500ms); + context.wait_for_all_requests(); + continue; + } + // A coarse match cannot confirm position, but two accepted + // anchors can establish a safe horizontal direction when both + // offsets are well outside the uncertainty in their spacing. + bool coarse_horizontal = false; + if (horizontal_first && result.coarse && result.landmarks.size() == 2) { + const auto& a = result.landmarks[0]; + const auto& b = result.landmarks[1]; + const double nearest = std::min(std::abs(a.dx), std::abs(b.dx)); + coarse_horizontal = a.accepted && b.accepted && a.dx*b.dx > 0 && + nearest > 1.0/64 && nearest > 2*std::abs(a.dx-b.dx); + } + const bool plaza_anchor_horizontal = matcher == match_jubilife_plaza_approach && + result.coarse && std::abs(result.dx)>1.0/50 && std::abs(result.dy)<=1.0/54; + // A uniquely matched Eterna ball may correct the door + // column while the sign/bush enter the frame. Arrival + // still requires the matcher's full three-anchor check. + const bool eterna_anchor_horizontal = (matcher == match_eterna_center_entrance || + matcher == match_eterna_underground_house || matcher == match_eterna_underground_man) && + result.coarse && std::abs(result.dx)>1.0/128; + // Recover the gym's doorway row before moving right if + // the south approach overshot toward the fence/cliff. + const bool gym_anchor_horizontal=matcher==match_eterna_gym_entrance && + result.coarse && std::abs(result.dx)>1.0/128 && std::abs(result.dy)<=.06; + const bool gym_row_recovery=matcher==match_eterna_gym_entrance && + result.coarse && std::abs(result.dy)>.06; + // Reach the row north of Jupiter before entering her + // column. From above that row, align sideways before + // moving south so the NPC cannot block the correction. + const bool jupiter_row_recovery=matcher==match_galactic_hq_jupiter && + result.dy < -1.0/54; + const bool jupiter_anchor_horizontal=matcher==match_galactic_hq_jupiter && + !jupiter_row_recovery && result.coarse && std::abs(result.dx)>1.0/128; + const bool horizontal = !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && + ((horizontal_first && std::abs(result.dx)>1.0/128) || + std::abs(result.dx)>=std::abs(result.dy)*9.0/16))); + if (coarse_horizontal) stream.log("[" + label + + "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); + const double error = horizontal ? result.dx : result.dy*9.0/16; + auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 1.0/64 ? 40ms : 16ms; + // A 16ms turn may not complete a step. Use a bounded + // full correction while reacquiring the second plaza anchor. + if (matcher == match_jubilife_plaza_approach && result.coarse) + duration = std::max(duration, 60ms); + if (matcher==match_galactic_hq_jupiter){ + for (const auto& landmark:result.landmarks){ + // The player stands around y=.74. Do not steer + // sideways/north when the stair opening is close + // to that row; a southward correction moves away. + if (landmark.name=="StairBoundary" && landmark.accepted && + landmark.found.y+landmark.found.height>.60 && + (horizontal || result.dy<0)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Correction would approach the stair boundary.",stream); + } + } + pbf_move_left_joystick(context, + {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, + horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0)}, duration, 0ms); + pbf_wait(context, 500ms); + } else { + confirmations = 0; + if (matcher==match_galactic_hq_jupiter){ + // Blind room-wide sweeps can enter the nearby stairs. + // Reobserve in place, then fail if no anchors recover. + if (++missing>6) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Chair/stair anchors missing; no blind search near stairs.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + continue; + } + // Search right first, then across the starting point to the + // left and back. Each complete sweep returns to its origin. + // Try that sweep at the initial height, above it, and below it; + // no missing/weak landmark ever confirms the destination. + if (++missing > (search_down_first ? 78 : 74)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Landmarks missing during bounded bidirectional search.", stream); + if (search_down_first && missing <= 4) { + stream.log("[" + label + "] Post-battle acquisition: moving down before lateral search."); + pbf_move_left_joystick(context, {0.0, -1.0}, 100ms, 0ms); + } else { + const size_t search_step = missing - 1 - (search_down_first ? 4 : 0); + const size_t level = search_step / 25; + const size_t slot = search_step % 25; + const bool lateral = slot < 24; + const double direction = slot < 6 || slot >= 18 ? 1.0 : -1.0; + const auto search_duration = lateral ? 100ms : level == 0 ? 600ms : 1200ms; + stream.log("[" + label + "] Acquisition search step=" + std::to_string(missing) + + ", direction=" + (lateral ? (direction>0 ? "right" : "left") : (level==0 ? "up" : "down"))); + // On the street, a northward search from the east + // edge can stop at the building/NPC collision row. + // Step west around it before changing height, then + // return to the same lane for the next bounded sweep. + const bool street_height_search=!lateral && matcher==match_jubilife_street_approach; + if (street_height_search) pbf_move_left_joystick(context,{-1.0,0.0},150ms,0ms); + pbf_move_left_joystick(context, + {lateral ? direction : 0.0, lateral ? 0.0 : (level == 0 ? 1.0 : -1.0)}, search_duration, 0ms); + if (street_height_search) pbf_move_left_joystick(context,{1.0,0.0},150ms,0ms); + } + pbf_wait(context, 500ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Could not confirm the reference position.", stream); + } + + void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, + const std::vector& moves, const std::string& label, bool post_battle_dialogue_cleanup) { + fight_using_move_list(stream,context,BattleType::TRAINER,moves,label,true,post_battle_dialogue_cleanup); + } + + void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, + size_t move, const std::string& label, bool post_battle_dialogue_cleanup) { + fight_trainer_using_moves(stream,context,std::vector{move},label,post_battle_dialogue_cleanup); + } + + void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, + size_t move_slot, const std::string& label) { + fight_using_move_slot(stream,context,BattleType::TRAINER,move_slot,label); + } + void fight_tristan(VideoStream& stream, ProControllerContext& context) { // Chimchar learns Ember into slot 3 at level 6. Confirm the move // cursor visually; missing slot 3 fails instead of using Scratch. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index 2d31058029..f3574d296f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -27,12 +27,25 @@ namespace PokemonAutomation { // Time / setup helpers // --------------------------------------------------------------------------- + // Shared visual alignment and dialogue cleanup used by story routes. + void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, + Button button = BUTTON_B, Milliseconds quiet_period = Milliseconds(3000), + const char* instruction_card = nullptr); + void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, bool allow_story_start = false, bool horizontal_first = false, + bool search_down_first = false, + OverworldMatchResult (*entry_matcher)(const ImageRGB32&) = nullptr, + bool* entry_detected = nullptr, + double arrival_tolerance_x = 0, double arrival_tolerance_y = 0, + double moderate_entry_confidence = 0); + void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context); void set_time_fast( SingleSwitchProgramEnvironment& env, ProControllerContext& context, - const DateTime& target + const DateTime& target, + bool preserve_date = false // Keep current year/month/day; set only target time. ); // --------------------------------------------------------------------------- @@ -44,6 +57,10 @@ namespace PokemonAutomation { ProControllerContext& context ); + // Select the requested 1-based move slot using visual cursor confirmation. + void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, + size_t move_slot, const std::string& label); + void fight_tristan( VideoStream& stream, ProControllerContext& context diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 7ad7e071a1..182fd95efd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -1,2228 +1,249 @@ -/* BDSP AutoStory - Segment 01 - * From after choosing the starter Pokemon until after acquiring the first badge. - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" -#include "CommonFramework/GlobalAutoPaths.h" -#include -#include -#include -#include -#include -#include -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "CommonFramework/ImageTools/ImageDiff.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" -#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "PokemonBDSP_Autostory_Segment_01_tools.h" -#include "PokemonBDSP_AutoStory_Segment_00_tools.h" - -using namespace std::chrono_literals; - -namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - // Prefer the source Packages resources when running a development build. - // Installed builds continue to use their bundled Resources directory. - static const std::string& mine_navigation_resource_path() { - static const std::string path = [] { - QDir directory(QCoreApplication::applicationDirPath()); - for (size_t level = 0; level < 5; level++) { - const QString candidate = directory.filePath( - "Packages/Resources/PokemonBDSP/Autostory/Oreburg_mine_nav/" - ); - if (QFileInfo(candidate).isDir()) { - return QDir::cleanPath(candidate).toStdString() + "/"; - } - if (!directory.cdUp()) { - break; - } - } - return RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; - }(); - return path; - } - - enum class MoveDirection { UP, DOWN, LEFT, RIGHT }; - - // Left joystick vector for a direction (matches the game's joystick convention used in this file). - static std::pair joystick_for(MoveDirection direction) { - switch (direction) { - case MoveDirection::UP: return { 0.0, 1.0 }; - case MoveDirection::DOWN: return { 0.0, -1.0 }; - case MoveDirection::LEFT: return { -1.0, 0.0 }; - case MoveDirection::RIGHT: return { 1.0, 0.0 }; - } - return { 0.0, 0.0 }; - } - - // Runs from a wild battle that is already in progress (menu visible or about to be). - // Throws OperationFailedException if it cannot escape within `timeout`. - - // The main game menu is independent evidence that battle has ended. - static bool mine_confirm_escape_via_menu( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type - ) { - MenuWatcher game_menu; - OverworldWatcher overworld; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - for (size_t attempt = 1; attempt <= 3; attempt++) { - // Do not accept the absence-based overworld detector before menu proof. - const int state = wait_until(stream, context, 300ms, {{game_menu}, {battle_menu}}); - if (state == 1) { - stream.log("[Mine escape] Battle menu still present; retrying Run."); - return false; - } - int detected = state; - if (state != 0) { - stream.log("[Mine escape] Main menu fallback attempt " + std::to_string(attempt) + "/3."); - detected = run_until( - stream, context, - [](ProControllerContext& controller) { - pbf_press_button(controller, BUTTON_X, 160ms, 840ms); - pbf_wait(controller, 3s); - controller.wait_for_all_requests(); - }, - {game_menu, battle_menu} - ); - if (detected == 1) { - return false; - } - } - if (detected != 0) { - continue; - } - stream.log("[Mine escape] Main game menu confirmed; battle has ended.", COLOR_GREEN); - // Close the confirmed menu before returning control to navigation. - MenuDetector menu_visible; - for (size_t close = 0; close < 3; close++) { - pbf_press_button(context, BUTTON_B, 80ms, 500ms); - context.wait_for_all_requests(); - if (wait_until(stream, context, 1s, {{overworld}}) == 0) { - return true; - } - BlackScreenWatcher visible( - COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms - ); - if (wait_until(stream, context, 2s, {{visible}}) == 0) { - const VideoSnapshot frame = stream.video().snapshot(); - if (frame && !menu_visible.detect(frame)) { - stream.log("[Mine escape] Confirmed game menu closed; returning to navigation.", COLOR_GREEN); - return true; - } - } - } - stream.log("[Mine escape] Main menu detected but could not confirm it closed.", COLOR_ORANGE); - return false; - } - return false; - } - - - static bool mine_select_run( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type - ) { - BattleMenuDetector cursor(battle_type, COLOR_YELLOW); - size_t matching_frames = 0; - for (size_t check = 0; check < 16; check++) { - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - return false; - } - const BattleMenuOption selected = cursor.selected_option(frame); - stream.log("[Mine escape] Battle cursor row=" + - std::to_string(static_cast(selected) + 1) + "; target Run=4."); - if (selected == BattleMenuOption::RUN) { - if (++matching_frames >= 3) { - stream.log("[Mine escape] Run cursor confirmed.", COLOR_GREEN); - return true; - } - pbf_wait(context, 100ms); - } - else { - matching_frames = 0; - if (selected == BattleMenuOption::UNKNOWN) { - // Do not send a direction or A without a known cursor. - pbf_wait(context, 150ms); - } - else { - const DpadPosition direction = selected == BattleMenuOption::BATTLE - ? DPAD_UP : DPAD_DOWN; - pbf_press_dpad(context, direction, 80ms, 250ms); - } - } - } - stream.log("[Mine escape] Could not confirm Run selection; A was not pressed.", COLOR_ORANGE); - return false; - } - - void escape_wild_battle( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type, - std::chrono::seconds timeout - ) { - EndBattleWatcher battle_end; - bool exit_transition = false; - const auto deadline = std::chrono::steady_clock::now() + timeout; - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - const int ready = wait_until(stream, context, 3s, {{battle_end}, {menu_ready}}); - if (ready == 0) { - exit_transition = true; - break; - } - if (ready != 1) { - continue; - } - if (!mine_select_run(stream, context, battle_type)) { - continue; - } - stream.log("[Mine escape] Run input sent; watching for black-screen exit."); - const int result = run_until( - stream, context, - [](ProControllerContext& controller) { - pbf_press_button(controller, BUTTON_A, 60ms, 0ms); - pbf_wait(controller, 4s); - controller.wait_for_all_requests(); - }, - {battle_end} - ); - if (result == 0) { - exit_transition = true; - break; - } - } - if (!exit_transition && wait_until(stream, context, 10s, {{battle_end}}) == 0) { - exit_transition = true; - } - if (exit_transition) { - // EndBattleWatcher requires black followed by a visible screen. - stream.log("[Mine escape] Black-screen exit transition confirmed.", COLOR_GREEN); - pbf_wait(context, 800ms); - context.wait_for_all_requests(); - return; - } - // Only probe X after normal escape attempts and the final transition wait. - // Absence of battle UI by itself is not evidence of an overworld return. - stream.log("[Mine escape] Exit transition missed; trying late main-menu fallback."); - if (mine_confirm_escape_via_menu(stream, context, battle_type)) { - return; - } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", - stream - ); - } - - // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. - // Returns false (without throwing) only if every attempt times out, so callers can skip - // an optional interaction. Multiple attempts guard against a single missed detection. - static bool try_detect_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label, - size_t attempts = 3, - std::chrono::milliseconds window = 5000ms - ) { - for (size_t attempt = 0; attempt < attempts; attempt++) { - ShortDialogWatcher watcher_cyan(COLOR_CYAN); - ShortDialogWatcher watcher_red(COLOR_RED); - if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0) { - stream.log(label + " dialogue detected."); - return true; - } - stream.log( - label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + std::to_string(attempts) + ").", - COLOR_ORANGE - ); - } - return false; - } - - struct DoorwayWalkOptions { - BattleType battle_type = BattleType::STANDARD; - // Active movement per step. Time spent in battles or waiting is never counted. - std::chrono::milliseconds step = 200ms; - // Extra walking allowed beyond `travel_time` before giving up. - std::chrono::milliseconds grace = 1600ms; - // How long to watch for a black screen after each step. - std::chrono::milliseconds black_screen_window = 1500ms; - // After a black screen, how long to wait for a battle menu before deciding it was a doorway. - std::chrono::milliseconds battle_menu_window = 8000ms; - size_t max_battles = 10; - // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). - bool detect_white_screen = false; - }; - - // Reusable: walk in `direction` for a cumulative `travel_time` of active movement until - // a black screen NOT followed by a battle menu (a doorway/area transition) is seen. - // Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are - // escaped and do not consume the time budget. - // Throws OperationFailedException if the doorway is not found within - // travel_time + options.grace, if more than options.max_battles occur, or if a battle - // cannot be escaped. - // Returns the total active walking time used. - static std::chrono::milliseconds walk_until_doorway( - VideoStream& stream, - ProControllerContext& context, - MoveDirection direction, - std::chrono::milliseconds travel_time, - const DoorwayWalkOptions& options = DoorwayWalkOptions() - ) { - enum class NavState { WALKING, BLACK_SCREEN, IN_BATTLE, DONE }; - - const auto [x, y] = joystick_for(direction); - const std::chrono::milliseconds max_travel = travel_time + options.grace; - - std::chrono::milliseconds walked = 0ms; - size_t battles = 0; - NavState state = NavState::WALKING; - - while (state != NavState::DONE) { - switch (state) { - case NavState::WALKING: { - if (walked >= max_travel) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "walk_until_doorway: Doorway not found within time budget.", - stream - ); - } - context.wait_for_all_requests(); - pbf_move_left_joystick(context, {x, y}, options.step, 0ms); - context.wait_for_all_requests(); - walked += options.step; - - BlackScreenWatcher black_screen; - WhiteScreenWatcher white_screen; - if (options.detect_white_screen) { - int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); - if (ret == 0) { - state = NavState::BLACK_SCREEN; - } else if (ret == 1) { - stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); - state = NavState::DONE; - } - } else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0) { - state = NavState::BLACK_SCREEN; - } - break; - } - case NavState::BLACK_SCREEN: { - stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); - BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); - if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0) { - state = NavState::IN_BATTLE; - } else { - state = NavState::DONE; - } - break; - } - case NavState::IN_BATTLE: { - if (++battles > options.max_battles) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "walk_until_doorway: Too many battles while walking.", - stream - ); - } - stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + std::to_string(travel_time.count()) + "ms.", COLOR_ORANGE); - escape_wild_battle(stream, context, options.battle_type); - state = NavState::WALKING; - break; - } - case NavState::DONE: - break; - } - } - stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", COLOR_GREEN); - return walked; - } - - void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - stream.log("Starting Barry 1 battle..."); - - // State machine: loop until EndBattleWatcher fires - for (size_t c = 0; c < 20; c++) { - context.wait_for_all_requests(); - - BattleMenuWatcher battle_menu(BattleType::STANDARD); - EndBattleWatcher end_battle; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_mash_button(context, BUTTON_ZL, 120000ms); - }, - { - battle_menu, - end_battle, - } - ); - switch (ret) { - case 0: - stream.log("Battle menu detected - using first move."); - pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); - break; - case 1: - stream.log("Barry 1 battle complete."); - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - return; - default: - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Barry_1_fight: Timed out waiting for battle menu or end.", - stream - ); - } - } - - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Barry_1_fight: Battle did not end after 20 turns.", - stream - ); - } - - - - - - // ---------------------------------------------------------------- - // Segment 1 Part 1: Jubilife City to Oreburgh Gate - // Navigates the player from Jubilife City to the Oreburgh Gate, including the - // first Barry fight. - // --------------------------------------------------------------------------- - void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - // Wait for game to fully accept input after save/overworld return - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1465ms, 0ms); - pbf_wait(context, 127ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); - pbf_wait(context, 698ms); - leave_pokemon_center_before_barry_1(stream, context); - pbf_wait(context, 2340ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); - pbf_wait(context, 348ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2694ms, 0ms); - pbf_wait(context, 482ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3810ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); - pbf_wait(context, 1647ms); - walk_through_grass(stream, context, 1, 0, 5, "right through grass"); - wait_for_dialogue(stream, context, "Dallas fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Dallas - context.wait_for_all_requests(); - walk_through_grass(stream, context, 1, 0, 5, "right through grass"); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); - walk_through_grass(stream, context, 1, 0, 10, "right through grass"); - //there is a small chance that the player dodges sSebastian, so this - //function addresses that edge case - if (try_detect_dialogue(stream, context, "Sebastian fight")) { - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Sebastian - context.wait_for_all_requests(); - } else { - stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); - } - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2000ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); - pbf_wait(context, 200ms); - context.wait_for_all_requests(); - bool black_screen_found = false; - for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { - BlackScreenWatcher black_screen; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - for (size_t i = 0; i < 10; i++) { - pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - } - pbf_move_left_joystick(context, { 0, 1 }, 10000ms, 0ms); - }, - { black_screen } - ); - if (ret == 0) { - stream.log("Black screen detected."); - black_screen_found = true; - } - } - context.wait_for_all_requests(); - - if (!black_screen_found) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", - stream - ); - } - } - void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {1.000000, -0.000000 }, 879ms, 0ms); - wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); - pbf_mash_button(context, BUTTON_B, 15000ms); - walk_through_grass(stream, context, 1, 0, 10, "right through gate"); - //Similar to sebastian, an edge case exists where we run into too many - //wild battles and don't reach curtis or diana. This detect dialog function solves that. - while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)) { - walk_through_grass(stream, context, 1, 0, 1, "right through gate"); - } - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Curtis - walk_through_grass(stream, context, 1, 0, 10, "right through gate"); - while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)) { - walk_through_grass(stream, context, 1, 0, 1, "right through gate"); - } - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Diana - // Walk right until the gate exit transition (white screen, or black screen without a battle menu). - // Wild battles are escaped without using up the budget. Throws on failure so the - // checkpoint is retried. - DoorwayWalkOptions gate_exit_options; - gate_exit_options.detect_white_screen = true; - walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); - context.wait_for_all_requests(); - } - // --------------------------------------------------------------------------- - // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine - // Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. - // Navigates to the mine. - // ---------------------------------------------------------------------------- - void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)) { - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - } - pbf_mash_button(context, BUTTON_B, 12000ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); - pbf_wait(context, 23ms); - bool black_screen_found = false; - for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { - BlackScreenWatcher black_screen; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); - }, - { black_screen } - ); - if (ret == 0) { - stream.log("Black screen detected."); - black_screen_found = true; - } - } - context.wait_for_all_requests(); - - if (!black_screen_found) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "oreburgh_gate_to_mine: No black screen after 3 attempts.", - stream - ); - } - - // Let the mine entrance transition fully finish so the next step's black screen - // watcher can't pick up the tail of this one. - BlackScreenOverWatcher black_over; - wait_until(stream, context, 6000ms, {{black_over}}); - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - } - - // Diagnostic only: identifies the entrance start without moving the player. - - - - struct MineCenterOffset { - bool valid; - double offset; - double score; - }; - - // Finds the sideways shift of stationary scenery relative to the center picture. - // Positive offset: scenery shifted right, so the player is left of center. - static MineCenterOffset measure_mine_center_offset( - const ImageViewRGB32& reference, - const ImageViewRGB32& live - ) { - if (!reference || !live) { - return { false, 0.0, 1000.0 }; - } - - // Use a fixed working size so the search works at different capture resolutions. - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - const ImageRGB32 live_scaled = live.scale_to(960, 540); - - // Entrance beam and both posts. Avoid the player and shadow. - const std::array boxes = { - ImageFloatBox(0.35, 0.285, 0.30, 0.07), - ImageFloatBox(0.355, 0.37, 0.035, 0.23), - ImageFloatBox(0.615, 0.37, 0.035, 0.23) - }; - - const int search_radius = (int)std::lround(0.10*live_scaled.width()); - std::vector scores(2*search_radius+1); - std::vector worst_regions(2*search_radius+1); - - int best_shift = 0; - double best_score = 1000.0; - - // Search up to 10% of the screen width in either direction. - for (int shift = -search_radius; shift <= search_radius; shift++) { - double total = 0.0; - double worst = 0.0; - - for (const ImageFloatBox& box : boxes) { - const ImageViewRGB32 reference_crop = - extract_box_reference(reference_scaled, box); - - // Translate the same crop horizontally in the live image. - const ImageViewRGB32 live_crop = - extract_box_reference(live_scaled, box, shift, 0); - - const double score = - ImageMatch::pixel_RMSD(reference_crop, live_crop); - - total += score; - if (score > worst) { - worst = score; - } - } - - const double average = total / boxes.size(); - const size_t index = static_cast(shift + search_radius); - - scores[index] = average; - worst_regions[index] = worst; - - if (average < best_score) { - best_score = average; - best_shift = shift; - } - } - - // Reject another similarly good match well away from the chosen position. - double alternative_score = 1000.0; - - for (int shift = -search_radius; shift <= search_radius; shift++) { - if (std::abs(shift - best_shift) <= std::lround(0.0125*live_scaled.width())) { - continue; - } - - const double score = scores[static_cast(shift + search_radius)]; - if (score < alternative_score) { - alternative_score = score; - } - } - - const size_t best_index = static_cast(best_shift + search_radius); - - const bool valid = - best_score <= 20.0 && - worst_regions[best_index] <= 30.0 && - alternative_score - best_score >= 3.0 && - std::abs(best_shift) < search_radius; - - return { - valid, - static_cast(best_shift) / live_scaled.width(), - best_score - }; - } - // Test only: align with the entrance center, then stop. - static bool oreburgh_mine_alignment_test( - VideoStream& stream, - ProControllerContext& context - ) { - const ImageRGB32 reference( - mine_navigation_resource_path() + "mine_start_center.png" - ); - - constexpr size_t MAX_MOVEMENTS = 12; - constexpr double CENTER_TOLERANCE = 0.008; - - size_t movements = 0; - size_t center_streak = 0; - - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - while (true) { - const VideoSnapshot frame = stream.video().snapshot(); - - const MineCenterOffset measurement = - measure_mine_center_offset(reference, frame); - - stream.log( - "[Mine align] Offset=" + - std::to_string(measurement.offset) + - ", score=" + std::to_string(measurement.score) + - (measurement.valid ? ", trusted" : ", unsure") - ); - - if (!measurement.valid) { - stream.log( - "[Mine align] Entrance position unsure. Stopping.", - COLOR_ORANGE - ); - return false; - } - - if (std::abs(measurement.offset) <= CENTER_TOLERANCE) { - center_streak++; - - if (center_streak >= 3) { - stream.log( - "[Mine align] Center reached. Test complete.", - COLOR_GREEN - ); - return true; - } - - pbf_wait(context, 250ms); - context.wait_for_all_requests(); - continue; - } - - center_streak = 0; - - // Check the latest position before enforcing the movement limit. - if (movements >= MAX_MOVEMENTS) { - stream.log( - "[Mine align] Movement limit reached. Stopping.", - COLOR_ORANGE - ); - return false; - } - - const double x = measurement.offset > 0.0 ? 1.0 : -1.0; - - // Smaller movements when close to the center. - const auto duration = - std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; - - stream.log( - x > 0.0 - ? "[Mine align] Moving a little right." - : "[Mine align] Moving a little left." - ); - - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - - const int result = run_until( - stream, - context, - [x, duration](ProControllerContext& ctx) { - pbf_move_left_joystick( - ctx, { x, 0.0 }, duration, 0ms - ); - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - }, - { black, menu, dialog } - ); - - if (result >= 0) { - stream.log( - "[Mine align] Battle or transition detected. " - "Stopping the alignment test.", - COLOR_ORANGE - ); - return false; - } - - movements++; - } - } - // Confirm a battle before using the existing escape helper. - static bool mine_escape_and_settle( - VideoStream& stream, - ProControllerContext& context - ) { - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - - if (wait_until(stream, context, 12s, { {menu} }) != 0) { - stream.log( - "[Mine encounter] Battle menu not confirmed. Stopping.", - COLOR_ORANGE - ); - return false; - } - - stream.log("[Mine encounter] Battle confirmed. Escaping."); - escape_wild_battle(stream, context); - context.wait_for_all_requests(); - - // Check the return waits instead of ignoring their results. - BlackScreenWatcher visible( - COLOR_YELLOW, - ImageFloatBox(0.1, 0.1, 0.8, 0.8), - 100, - 10, - BlackScreenWatcher::FinderType::GONE, - 500ms - ); - - if (wait_until(stream, context, 8s, { {visible} }) != 0) { - stream.log( - "[Mine encounter] Screen did not return. Stopping.", - COLOR_ORANGE - ); - return false; - } - - - pbf_wait(context, 800ms); - context.wait_for_all_requests(); - - stream.log( - "[Mine encounter] Escaped. Returning to the rock-walking loop.", - COLOR_GREEN - ); - return true; - } - - // Temporary test: wiggle along the corridor until a battle is detected. - static bool mine_force_encounter_test( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine encounter] Starting forced encounter wiggle."); - - const auto deadline = - std::chrono::steady_clock::now() + 60s; - - size_t pulses = 0; - - while (std::chrono::steady_clock::now() < deadline) { - // Alternate down and up. Do not move sideways. - const double y = pulses % 2 == 0 ? -1.0 : 1.0; - - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - - const int result = run_until( - stream, - context, - [y](ProControllerContext& ctx) { - pbf_move_left_joystick( - ctx, { 0.0, y }, 120ms, 0ms - ); - pbf_wait(ctx, 400ms); - ctx.wait_for_all_requests(); - }, - { black, menu, dialog } - ); - - if (result >= 0) { - stream.log( - "[Mine encounter] Wiggle interrupted. Checking for battle." - ); - return mine_escape_and_settle(stream, context); - } - - pulses++; - } - - stream.log( - "[Mine encounter] No encounter within 60 seconds. " - "Test incomplete; stopping.", - COLOR_ORANGE - ); - return false; - } - - // Watch throughout an action, including its movement and picture checks. - // After an encounter, restart the action using fresh watcher state. - template - static bool mine_run_with_encounters( - VideoStream& stream, - ProControllerContext& context, - Action action - ) { - for (size_t encounters = 0;;) { - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - bool completed = false; - - const int result = run_until( - stream, context, - [&](ProControllerContext& ctx) { - completed = action(ctx); - }, - { black, menu, dialog } - ); - - if (result < 0) { - return completed; - } - - if (++encounters > 10) { - stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); - return false; - } - - if (!mine_escape_and_settle(stream, context)) { - return false; - } - - stream.log("[Mine encounter] Escaped; resuming interrupted action."); - } - } - - static bool mine_timed_walk( - VideoStream& stream, - ProControllerContext& context, - double x, - double y, - std::chrono::milliseconds duration - ) { - auto remaining = duration; - - return mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - while (remaining > 0ms) { - const auto pulse = remaining < 100ms ? remaining : 100ms; - pbf_move_left_joystick(ctx, { x, y }, pulse, 0ms); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - - // An interrupted pulse is retried after escaping. - // This is a timing budget, not exact distance tracking. - remaining -= pulse; - } - return true; - } - ); - } - - - - struct MinePictureScore { - double average = 1000.0; - double worst = 1000.0; - double horizontal = 0; - double vertical = 0; - }; - - // Supports four, five, or six regions. Existing four-region callers keep - // their original search ranges through the default arguments. - template - static MinePictureScore mine_score_picture( - const ImageRGB32& reference, - const ImageRGB32& live, - const std::array& boxes, - double horizontal_range = 1.0/60, - double vertical_range = 2.0/135 - ) { - MinePictureScore best; - const int radius_x = (int)std::lround(horizontal_range*live.width()); - const int radius_y = (int)std::lround(vertical_range*live.height()); - for (int y = -radius_y; y <= radius_y; y++) { - for (int x = -radius_x; x <= radius_x; x++) { - double total = 0.0; - double worst = 0.0; - for (const ImageFloatBox& box : boxes) { - const ImageViewRGB32 ref = extract_box_reference(reference, box); - const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); - const double score = ImageMatch::pixel_RMSD(ref, patch); - total += score; - if (score > worst) worst = score; - } - const double average = total / boxes.size(); - if (average < best.average) { - best.average = average; - best.worst = worst; - best.horizontal = (double)x/live.width(); - best.vertical = (double)y/live.height(); - } - } - } - return best; - } - - - static bool mine_check_picture( - VideoStream& stream, - ProControllerContext& context, - const char* filename, - const std::array& boxes, - const std::string& label, - size_t max_attempts = 8, - double max_average = 20.0, - double max_patch = 30.0 - ) { - const ImageRGB32 reference( - mine_navigation_resource_path() + filename - ); - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - - const bool confirmed = mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - // The watcher stays active throughout settling and sampling. - // There is no separate 500ms watcher wait before each sample. - ctx.wait_for_all_requests(); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - - // Restart confirmation after a battle; do not reuse old matches. - size_t matching_frames = 0; - - for (size_t attempt = 0; attempt < max_attempts; attempt++) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log(label + ": no video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - const MinePictureScore score = - mine_score_picture(reference_scaled, live, boxes); - - stream.log( - label + ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) - ); - - if (score.average <= max_average && score.worst <= max_patch) { - if (++matching_frames >= 3) { - return true; - } - } - else { - matching_frames = 0; - } - - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - } - - return false; - } - ); - - stream.log( - label + (confirmed ? ": confirmed." : ": not confirmed."), - confirmed ? COLOR_GREEN : COLOR_ORANGE - ); - return confirmed; - } - - struct MineWalkOptions { - double x = 0.0; - double y = 0.0; - std::chrono::milliseconds pulse = 40ms; - std::chrono::milliseconds settle = 500ms; - std::chrono::milliseconds initial_settle = 0ms; - size_t max_movements = 30; - double horizontal_range = 0.0125; - double vertical_range = 1.0/90; - double max_average = 20.0; - double max_patch = 30.0; - }; - - template - static bool mine_walk_to_endpoints( - VideoStream& stream, - ProControllerContext& context, - const std::array& files, - const std::array, Endpoints>& boxes, - const MineWalkOptions& options, - const std::string& label, - bool force_encounter = false - ) { - std::array, Endpoints> references; - for (size_t index = 0; index < Endpoints; index++) { - const ImageRGB32 image( - mine_navigation_resource_path() + files[index] - ); - references[index] = std::make_unique(image.scale_to(960, 540)); - } - - size_t movements = 0; - bool forced_completed = false; - - while (true) { - bool request_forced_test = false; - const bool reached = mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - if (options.initial_settle > 0ms) { - pbf_wait(ctx, options.initial_settle); - ctx.wait_for_all_requests(); - } - // Reset the match history whenever an encounter interrupts us. - size_t matching_frames = 0; - int previous_endpoint = -1; - - while (true) { - ctx.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log(label + ": no video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - int accepted = -1; - double accepted_average = 1000.0; - - for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { - const MinePictureScore score = mine_score_picture( - *references[endpoint], live, boxes[endpoint], - options.horizontal_range, options.vertical_range - ); - stream.log( - label + " Endpoint " + std::to_string(endpoint + 1) + - ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) + - ", screen-relative offset=(" + std::to_string(score.horizontal) + - "," + std::to_string(score.vertical) + ")" - ); - - if (score.average <= options.max_average && - score.worst <= options.max_patch && - score.average < accepted_average) { - accepted = static_cast(endpoint); - accepted_average = score.average; - } - } - - if (accepted >= 0) { - matching_frames = accepted == previous_endpoint - ? matching_frames + 1 : 1; - previous_endpoint = accepted; - if (matching_frames >= 3) { - stream.log( - label + " Endpoint " + std::to_string(accepted + 1) + - " confirmed.", COLOR_GREEN - ); - return true; - } - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - previous_endpoint = -1; - - // Exit the guard before invoking the diagnostic wiggle: - // it has its own encounter watcher and must not be nested. - if (force_encounter && !forced_completed && movements >= 4) { - request_forced_test = true; - return false; - } - - if (movements >= options.max_movements) { - stream.log(label + ": movement limit reached.", COLOR_ORANGE); - return false; - } - - // Count issued commands, including interrupted commands. - movements++; - pbf_move_left_joystick( - ctx, { options.x, options.y }, options.pulse, 0ms - ); - pbf_wait(ctx, options.settle); - ctx.wait_for_all_requests(); - } - } - ); - - if (reached) return true; - if (!request_forced_test) return false; - if (!mine_force_encounter_test(stream, context)) return false; - forced_completed = true; - stream.log(label + ": forced encounter completed; resuming."); - } - } - - static bool oreburgh_mine_walk_to_first_rock( - VideoStream& stream, - ProControllerContext& context, - bool force_encounter = false - ) { - const std::array files = { "mine_path_01.png" }; - const std::array, 1> boxes = {{ - {{ - ImageFloatBox(0.482, 0.585, 0.055, 0.055), - ImageFloatBox(0.335, 0.670, 0.050, 0.055), - ImageFloatBox(0.412, 0.670, 0.050, 0.055), - ImageFloatBox(0.570, 0.752, 0.045, 0.055), - ImageFloatBox(0.645, 0.752, 0.045, 0.055), - ImageFloatBox(0.570, 0.290, 0.080, 0.120) - }} - }}; - - MineWalkOptions options; - options.x = 0.0; - options.y = -1.0; - options.pulse = 80ms; - options.max_movements = 40; - options.horizontal_range = 0.025; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine path 01]", force_encounter - ); - } - - static bool oreburgh_mine_move_right( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; - const std::array, 2> boxes = {{ - {{ - ImageFloatBox(0.400, 0.585, 0.055, 0.055), - ImageFloatBox(0.245, 0.670, 0.050, 0.055), - ImageFloatBox(0.322, 0.670, 0.050, 0.055), - ImageFloatBox(0.480, 0.752, 0.045, 0.055), - ImageFloatBox(0.555, 0.752, 0.045, 0.055), - ImageFloatBox(0.485, 0.290, 0.080, 0.090) - }}, - {{ - ImageFloatBox(0.325, 0.585, 0.055, 0.055), - ImageFloatBox(0.170, 0.670, 0.050, 0.055), - ImageFloatBox(0.244, 0.670, 0.050, 0.055), - ImageFloatBox(0.395, 0.752, 0.045, 0.055), - ImageFloatBox(0.470, 0.752, 0.045, 0.055), - ImageFloatBox(0.415, 0.290, 0.080, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = 1.0; - options.y = 0.0; - options.pulse = 40ms; - options.max_movements = 20; - options.horizontal_range = 0.0125; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine right]" - ); - } - - static bool oreburgh_mine_down_past_central_rock( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; - const std::array, 2> boxes = {{ - {{ - ImageFloatBox(0.345, 0.420, 0.040, 0.045), - ImageFloatBox(0.195, 0.500, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.410, 0.590, 0.040, 0.045), - ImageFloatBox(0.480, 0.590, 0.040, 0.045), - ImageFloatBox(0.445, 0.180, 0.060, 0.090) - }}, - {{ - ImageFloatBox(0.420, 0.420, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.345, 0.500, 0.040, 0.045), - ImageFloatBox(0.485, 0.590, 0.040, 0.045), - ImageFloatBox(0.555, 0.590, 0.040, 0.045), - ImageFloatBox(0.520, 0.180, 0.060, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = 0.0; - options.y = -1.0; - options.pulse = 40ms; - options.max_movements = 30; - // options.horizontal_range = 0.0125; - // Path 02 can be shifted horizontally after the first right endpoint. - // The screenshot regression matches at about -2.08% of screen width. - options.horizontal_range = 0.025; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine down 02]" - ); - } - - static bool oreburgh_mine_move_left_to_gap( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array files = { "mine_path_03.png" }; - const std::array, 1> boxes = {{ - {{ - ImageFloatBox(0.340, 0.495, 0.040, 0.045), - ImageFloatBox(0.410, 0.495, 0.040, 0.045), - ImageFloatBox(0.555, 0.580, 0.040, 0.045), - ImageFloatBox(0.625, 0.580, 0.040, 0.045), - ImageFloatBox(0.580, 0.180, 0.060, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = -1.0; - options.y = 0.0; - options.pulse = 40ms; - options.max_movements = 30; - options.horizontal_range = 0.0125; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine left 03]" - ); - } - - - - // Confirm we are far enough down before beginning the leftward section. - - - // Each picture stage owns its movement and matching settings. - struct MinePictureStep { - const char* file; - const char* label; - std::array boxes; - double x; - double y; - std::chrono::milliseconds pulse = 80ms; - size_t max_movements = 30; - double max_average = 20.0; - double max_patch = 30.0; - }; - - static bool mine_run_picture_step( - VideoStream& stream, ProControllerContext& context, - const MinePictureStep& step - ) { - MineWalkOptions options; - options.x = step.x; - options.y = step.y; - options.pulse = step.pulse; - options.max_movements = step.max_movements; - options.max_average = step.max_average; - options.max_patch = step.max_patch; - options.horizontal_range = 1.0/60; - options.vertical_range = 2.0/135; - options.initial_settle = 700ms; - options.settle = 700ms; - const std::array files = {step.file}; - const std::array, 1> boxes = {{step.boxes}}; - return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); - } - - static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_05.png", "[Mine endpoint 05]", - { - ImageFloatBox(0.240, 0.240, 0.055, 0.080), - ImageFloatBox(0.390, 0.220, 0.050, 0.090), - ImageFloatBox(0.360, 0.090, 0.030, 0.030), - ImageFloatBox(0.550, 0.140, 0.040, 0.040) - }, - 0.0, -1.0, 60ms, 10, 20.0, 30.0 - }; - return mine_run_picture_step(stream, context, step); - } - - // Timed movement through paths 6 and 7, with picture checks. - static bool oreburgh_mine_timed_to_07( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine 04-07] Walking down for 1500ms."); - - if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)) { - return false; - } - - if (!oreburgh_mine_confirm_05(stream, context)) { - return false; - } - - stream.log("[Mine 04-07] Walking left for 3000ms."); - - if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)) { - return false; - } - - // Path 6: crate, nearby small rock, large rock, upper-right small rock. - const std::array path6_boxes = { - ImageFloatBox(0.350, 0.250, 0.085, 0.075), - ImageFloatBox(0.405, 0.510, 0.035, 0.040), - ImageFloatBox(0.520, 0.300, 0.050, 0.080), - ImageFloatBox(0.620, 0.350, 0.030, 0.040) - }; - - if (!mine_check_picture( - stream, context, - "mine_path_06.png", path6_boxes, - "[Mine midpoint 06]" - )) { - return false; - } - - stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); - - if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)) { - return false; - } - - // Path 7: crate and three visible rock surfaces. - const std::array path7_boxes = { - ImageFloatBox(0.365, 0.025, 0.070, 0.055), - ImageFloatBox(0.420, 0.270, 0.035, 0.040), - ImageFloatBox(0.560, 0.415, 0.025, 0.040), - ImageFloatBox(0.530, 0.650, 0.060, 0.090) - }; - - return mine_check_picture( - stream, context, - "mine_path_07.png", path7_boxes, - "[Mine endpoint 07]" - ); - } - - // Move left from path 7 to the position shown in path 8. - static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_08.png", "[Mine endpoint 08]", - { - ImageFloatBox(0.495, 0.035, 0.070, 0.055), - ImageFloatBox(0.550, 0.270, 0.035, 0.040), - ImageFloatBox(0.695, 0.420, 0.030, 0.040), - ImageFloatBox(0.555, 0.585, 0.035, 0.040) - }, - -1.0, 0.0, 40ms, 20, 20.0, 30.0 - }; - return mine_run_picture_step(stream, context, step); - } - - // Walk down along the left wall to path 9. - - static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_09.png", "[Mine endpoint 09]", - { - ImageFloatBox(0.550, 0.115, 0.030, 0.040), - ImageFloatBox(0.550, 0.390, 0.035, 0.040), - ImageFloatBox(0.620, 0.390, 0.025, 0.035), - ImageFloatBox(0.500, 0.720, 0.085, 0.080) - }, - 0.0, -1.0, 40ms, 25, 25.0, 35.0 - }; - return mine_run_picture_step(stream, context, step); - } - - - - // Require dialogue to appear first. - // Then finish only after it has been absent for one continuous second. - static bool mine_finish_item_dialogue( - VideoStream& stream, - ProControllerContext& context - ) { - ShortDialogDetector dialogue(COLOR_YELLOW); - - bool saw_dialogue = false; - auto absent_since = std::chrono::steady_clock::now(); - - const auto start = std::chrono::steady_clock::now(); - const auto deadline = start + 30s; - - stream.log("[Mine item] Starting interaction."); - - while (std::chrono::steady_clock::now() < deadline) { - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - - if (!frame) { - stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); - return false; - } - - const auto now = std::chrono::steady_clock::now(); - const bool visible = dialogue.detect(frame); - - if (visible) { - if (!saw_dialogue) { - stream.log("[Mine item] Dialogue detected."); - } - - saw_dialogue = true; - absent_since = now; - - // Short mash bursts allow frequent checks. - pbf_mash_button(context, BUTTON_A, 100ms); - } - else if (!saw_dialogue) { - if (now - start >= 10s) { - stream.log( - "[Mine item] No dialogue appeared. Stopping.", - COLOR_ORANGE - ); - return false; - } - - // Try to start the interaction. - pbf_press_button(context, BUTTON_A, 60ms, 100ms); - } - else if (now - absent_since >= 1s) { - stream.log( - "[Mine item] Dialogue absent for one second. Finished.", - COLOR_GREEN - ); - return true; - } - - pbf_wait(context, 100ms); - context.wait_for_all_requests(); - } - - stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); - return false; - } - - - static bool oreburgh_mine_09_to_14( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array path10_boxes = { - ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate - ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock - ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock - ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock - }; - - const MinePictureStep path10_boxes_step = { - "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path10_boxes_step)) { - return false; - } - - const std::array path11_boxes = { - ImageFloatBox(0.405, 0.560, 0.070, 0.080), - ImageFloatBox(0.475, 0.270, 0.030, 0.040), - ImageFloatBox(0.575, 0.290, 0.045, 0.080), - ImageFloatBox(0.550, 0.575, 0.030, 0.040) - }; - - const MinePictureStep path11_boxes_step = { - "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path11_boxes_step)) { - return false; - } - - const std::array path12_boxes = { - ImageFloatBox(0.260, 0.560, 0.080, 0.080), - ImageFloatBox(0.355, 0.270, 0.025, 0.035), - ImageFloatBox(0.435, 0.290, 0.040, 0.070), - ImageFloatBox(0.415, 0.585, 0.025, 0.035) - }; - - const MinePictureStep path12_boxes_step = { - "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path12_boxes_step)) { - return false; - } - - const std::array path13_boxes = { - ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate - ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate - ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock - ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock - }; - - const MinePictureStep path13_boxes_step = { - "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path13_boxes_step)) { - return false; - } - - // Turn left, then verify position before starting the interaction. - pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - if (!mine_check_picture( - stream, context, - "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 - )) { - return false; - } - stream.log("[Mine item] Position confirmed. Pressing A."); - - pbf_press_button(context, BUTTON_A, 80ms, 200ms); - context.wait_for_all_requests(); - - return mine_finish_item_dialogue(stream, context); - } - - // Compare a reference patch with a shifted live patch using the same - // brightness adjustment as the native ImageMatchDetector. - static double mine_offset_patch_score( - const ImageRGB32& reference, - const ImageRGB32& live, - const ImageFloatBox& box, - double horizontal, - double vertical - ) { - const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); - ImageFloatBox shifted = box; - shifted.x += horizontal; - shifted.y += vertical; - const ImageViewRGB32 live_crop = extract_box_reference(live, shifted); - ImageRGB32 adjusted = live_crop.scale_to( - reference_crop.width(), reference_crop.height() - ); - - const auto reference_average = - ImageMatch::pixel_average(reference_crop, reference_crop); - const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); - auto scale = reference_average / live_average; - if (std::isnan(scale.r)) scale.r = 1.0; - if (std::isnan(scale.g)) scale.g = 1.0; - if (std::isnan(scale.b)) scale.b = 1.0; - scale.bound(0.8, 1.2); - ImageMatch::scale_brightness(adjusted, scale); - - return ImageMatch::pixel_RMSD(reference_crop, adjusted); - } - - struct MineRoarkOffsetScore { - double left = 1000.0; - double right = 1000.0; - double helmet = 1000.0; - double horizontal = 0; - double vertical = 0; - double helmet_horizontal = 0; - double helmet_vertical = 0; - }; - - static MineRoarkOffsetScore mine_score_roark_offsets( - const ImageRGB32& reference, - const ImageRGB32& live - ) { - const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); - const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); - const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); - - MineRoarkOffsetScore best; - double best_worst_rock = 1000.0; - double best_total = 2000.0; - - // Both rocks must use the SAME offset. They cannot match independently. - // Search ranges are screen fractions; enumerate pixels only for scoring. - const int radius_x = (int)std::lround(live.width()/60.0); - const int radius_y = (int)std::lround(live.height()*2.0/135); - for (int y = -radius_y; y <= radius_y; y++) { - for (int x = -radius_x; x <= radius_x; x++) { - const double dx = (double)x/live.width(), dy = (double)y/live.height(); - const double left = mine_offset_patch_score(reference, live, left_box, dx, dy); - const double right = mine_offset_patch_score(reference, live, right_box, dx, dy); - const double worst = left > right ? left : right; - const double total = left + right; - - if (worst < best_worst_rock || - (worst == best_worst_rock && total < best_total)) { - best_worst_rock = worst; - best_total = total; - best.left = left; - best.right = right; - best.horizontal = (double)x/live.width(); - best.vertical = (double)y/live.height(); - } - } - } - - // Roark's helmet may move slightly relative to the stationary rocks. - // Keep this extra search small so another red object cannot match far away. - const int helmet_radius_x = (int)std::lround(live.width()*0.00625); - const int helmet_radius_y = (int)std::lround(live.height()/90.0); - for (int extra_y = -helmet_radius_y; extra_y <= helmet_radius_y; extra_y++) { - for (int extra_x = -helmet_radius_x; extra_x <= helmet_radius_x; extra_x++) { - const double score = mine_offset_patch_score( - reference, live, helmet_box, - best.horizontal + (double)extra_x/live.width(), - best.vertical + (double)extra_y/live.height() - ); - if (score < best.helmet) { - best.helmet = score; - best.helmet_horizontal = (double)extra_x/live.width(); - best.helmet_vertical = (double)extra_y/live.height(); - } - } - } - - return best; - } - - static bool oreburgh_mine_align_to_18( - VideoStream& stream, - ProControllerContext& context - ) { - const ImageRGB32 reference( - mine_navigation_resource_path() + "Mine_path_18.png" - ); - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - const double max_rmsd = 65.0; - size_t movements = 0; - - return mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - // Fresh confirmation after any encounter. - size_t matching_frames = 0; - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - - while (true) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - const MineRoarkOffsetScore score = - mine_score_roark_offsets(reference_scaled, live); - - stream.log( - "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + - ", Right rock=" + std::to_string(score.right) + - ", Helmet=" + std::to_string(score.helmet) + - ", shared screen-relative offset=(" + std::to_string(score.horizontal) + - "," + std::to_string(score.vertical) + - "), helmet screen-relative extra=(" + std::to_string(score.helmet_horizontal) + - "," + std::to_string(score.helmet_vertical) + ")" - ); - - if (score.left <= max_rmsd && score.right <= max_rmsd && - score.helmet <= max_rmsd) { - if (++matching_frames >= 3) { - stream.log( - "[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN - ); - return true; - } - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - if (movements >= 80) { - stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); - return false; - } - - const bool approaching = score.left <= 120.0 && - score.right <= 120.0 && score.helmet <= 150.0; - const bool near = score.left <= 100.0 && - score.right <= 100.0 && score.helmet <= 100.0; - const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; - - // Use the outer encounter watcher; do not nest mine_timed_walk. - // Count issued pulses, including any interrupted by a battle. - movements++; - pbf_move_left_joystick(ctx, { 1.0, 0.0 }, pulse, 0ms); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - } - } - ); - } - - static bool oreburgh_mine_14_to_19( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array path15_boxes = { - ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate - ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate - ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock - ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock - }; - - const MinePictureStep path15_boxes_step = { - "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path15_boxes_step)) { - return false; - } - - // Move below the worker before continuing right. - const std::array path16_boxes = { - ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate - ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate - ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock - ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock - }; - - const MinePictureStep path16_boxes_step = { - "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path16_boxes_step)) { - return false; - } - - // Skip 17 and find the actual stopping point at 18. - if (!oreburgh_mine_align_to_18(stream, context)) { - return false; - } - - // Position 19 is the upward turn toward Roark. - pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - stream.log( - "[Mine endpoint 19] Position 18 confirmed; north turn sent.", - COLOR_GREEN - ); - return true; - } - - bool oreburgh_mine_route( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options - ) { - stream.log("[Mine resources] " + mine_navigation_resource_path()); - struct Step { - const char* name; - bool (*run)(VideoStream&, ProControllerContext&); - }; - - const std::array steps = { { - { - "Align entrance", - oreburgh_mine_alignment_test - }, - { - "Walk to first rock", - [](VideoStream& video, ProControllerContext& controller) { - return oreburgh_mine_walk_to_first_rock( - video, controller, false - ); - } - }, - { - "Move right of first rock", - oreburgh_mine_move_right - }, - { - "Move below first rock", - oreburgh_mine_down_past_central_rock - }, - { - "Move left into center gap", - oreburgh_mine_move_left_to_gap - }, - { - "Reach position 07", - oreburgh_mine_timed_to_07 - }, - { - "Reach position 08", - oreburgh_mine_left_to_08 - }, - { - "Reach position 09", - oreburgh_mine_down_to_09 - }, - { - "Reach 14 and collect item", - oreburgh_mine_09_to_14 - }, - { - "Reach 18 and turn north for 19", - oreburgh_mine_14_to_19 - } - } }; - - const size_t first = static_cast(options.first); - const size_t last = static_cast(options.last); - - if (first > last || last >= steps.size()) { - stream.log("[Mine route] Invalid stage range.", COLOR_ORANGE); - return false; - } - - for (size_t index = first; index <= last; index++) { - const Step& step = steps[index]; - - stream.log( - std::string("[Mine route] Starting: ") + step.name - ); - - bool succeeded = false; - - try { - succeeded = step.run(stream, context); - } - catch (OperationFailedException& error) { - stream.log( - std::string("[Mine route] Failed during ") + - step.name + ": " + error.message(), - COLOR_ORANGE - ); - - if (options.reset_on_failure) { - throw; - } - - stream.log( - "[Mine route] Test stopped without requesting a reset.", - COLOR_ORANGE - ); - return false; - } - - if (!succeeded) { - stream.log( - std::string("[Mine route] Stage failed: ") + step.name, - COLOR_ORANGE - ); - - if (options.reset_on_failure) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - std::string("Mine route failed during: ") + step.name, - stream - ); - } - - return false; - } - - stream.log( - std::string("[Mine route] Completed: ") + step.name, - COLOR_GREEN - ); - } - - stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); - return true; - } - - // Start Roark's conversation once, then advance it with B. - // Require dialogue to be seen before accepting its disappearance. - - static bool mine_talk_to_roark( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine Roark] Pressing A to start conversation."); - pbf_press_button(context, BUTTON_A, 80ms, 100ms); - context.wait_for_all_requests(); - - // The opening cutscene precedes the first dialogue box. - // Do not mash B or measure dialogue absence during that cutscene. - wait_for_dialogue(stream, context, "[Mine Roark] First box", 30s); - - ShortDialogDetector dialogue(COLOR_YELLOW); - const auto deadline = std::chrono::steady_clock::now() + 60s; - bool timing_absence = false; - auto absent_since = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() < deadline) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); - return false; - } - const auto now = std::chrono::steady_clock::now(); - if (dialogue.detect(frame)) { - timing_absence = false; - } - else { - if (!timing_absence) { - timing_absence = true; - absent_since = now; - } - if (now - absent_since >= 4s) { - stream.log( - "[Mine Roark] Dialogue absent for 4 seconds. Finished.", - COLOR_GREEN - ); - return true; - } - } - pbf_mash_button(context, BUTTON_B, 100ms); - context.wait_for_all_requests(); - } - stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); - return false; - } - - // Retain the developer test entry point; both callers share the same route. - bool oreburgh_mine_first_leg_test( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options - ) { - return oreburgh_mine_route(stream, context, options); - } - - // Shared final step for the live route and the entrance-start test. - static bool mine_talk_and_escape(VideoStream& stream, ProControllerContext& context) { - if (!mine_talk_to_roark(stream, context)) { - return false; - } - stream.log("[Mine exit] Finding Escape Rope in the Bag."); - if (!use_escape_rope(stream, context, 8)) { - return false; - } - stream.log("[Mine exit] Roark conversation and Escape Rope exit succeeded.", COLOR_GREEN); - return true; - } - - bool oreburgh_mine_route_and_escape_test( - VideoStream& stream, - ProControllerContext& context - ) { - MineRouteTestOptions route; - route.reset_on_failure = false; - if (!oreburgh_mine_first_leg_test(stream, context, route)) { - return false; - } - try { - if (!mine_talk_and_escape(stream, context)) { - stream.log("[Mine exit test] Failed. Stopping without resetting.", COLOR_ORANGE); - return false; - } - } - catch (OperationFailedException& error) { - stream.log("[Mine exit test] " + error.message(), COLOR_ORANGE); - stream.log("[Mine exit test] Stopped without requesting a reset.", COLOR_ORANGE); - return false; - } - return true; - } - - void oreburgh_mine_nav( - VideoStream& stream, ProControllerContext& context) { - walk_through_grass(stream, context, 0, -1, 1, "down into mine"); - context.wait_for_all_requests(); - walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); - context.wait_for_all_requests(); - pbf_wait(context, 1824ms); - - MineRouteTestOptions route; - route.reset_on_failure = true; - - if (!oreburgh_mine_route(stream, context, route)) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Mine route did not complete.", stream - ); - } - - - if (!mine_talk_and_escape(stream, context)) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Mine route: Roark conversation or Escape Rope exit failed.", - stream - ); - } - - } - // --------------------------------------------------------------------------- - // Segment 1 Part 3: Oreburgh Mine to Gym - // Assumes that escape rope was picked up and used to exit the mine. - // Follows the validated blind route into the gym. - // ---------------------------------------------------------------------------- - void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - - stream.log("[Mine to gym] Following recorded route from the mine exit."); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 3995ms, 0ms); - pbf_wait(context, 672ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 681ms, 0ms); - pbf_wait(context, 468ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1081ms, 0ms); - pbf_wait(context, 420ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2445ms, 0ms); - pbf_wait(context, 1056ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 95ms, 0ms); - pbf_wait(context, 4787ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1309ms, 0ms); - - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4450ms, 0ms); - //pbf_wait(context, 3635ms); - //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); - //pbf_wait(context, 3795ms); - context.wait_for_all_requests(); - - //stream.log("[Mine to gym] Staying west of the hiker; moving north along the left fence."); - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); - //pbf_wait(context, 602ms); - //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); - //pbf_wait(context, 5069ms); - //pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); - //pbf_wait(context, 605ms); - //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); - //pbf_wait(context, 1081ms); - //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); - //pbf_wait(context, 2642ms); - //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); - //pbf_wait(context, 5340ms); - //context.wait_for_all_requests(); - - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); - //pbf_wait(context, 140ms); - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); - //context.wait_for_all_requests(); - //stream.log("[Mine to gym] Recorded movement finished; waiting for gym entry to settle."); - // walk_until_doorway(stream, context, MoveDirection::UP, 5s); - // Finish entering the doorway with a bounded blind input. - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); - //context.wait_for_all_requests(); - //pbf_wait(context, 1500ms); - //context.wait_for_all_requests(); - stream.log("[Mine to gym] Blind mine-to-gym sequence completed.", COLOR_GREEN); - } - - // --------------------------------------------------------------------------- - // Segment 1 Part 4: Gym navigation - // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. - // --------------------------------------------------------------------------- - - void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - - //Upon entering the Gym. - // The entry camera sequence lasts about 7.5 seconds in the reference - // video. Allow another 500ms before sending puzzle movement inputs. - context.wait_for_all_requests(); - env.log("[Gym puzzle] Waiting for the entry cutscene to finish."); - pbf_wait(context, 8000ms); - context.wait_for_all_requests(); - - struct Move { - DpadPosition dir; - int count; - }; - const Move sequence[] = { - {DPAD_UP, 3}, - {DPAD_LEFT, 4}, - {DPAD_UP, 5}, - {DPAD_RIGHT, 8}, - {DPAD_UP, 11}, - {DPAD_LEFT, 5}, - {DPAD_UP, 4}, - }; - - for (const auto& move : sequence) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - - //Button presses to initiate the Gym battle. - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - //Barry 1 battle logic can be used here since the moves don't matter this early. - Barry_1_fight(env, stream, context); - - context.wait_for_all_requests(); - wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); - ShortDialogDetector post_battle_dialogue(COLOR_YELLOW); - const auto dialogue_deadline = std::chrono::steady_clock::now() + 180s; - auto dialogue_absent_since = std::chrono::steady_clock::now(); - bool dialogue_finished = false; - stream.log("[Gym post battle] Clearing dialogue until absent for 4 seconds."); - while (std::chrono::steady_clock::now() < dialogue_deadline) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Gym post battle: No video frame while clearing dialogue.", stream - ); - } - const auto now = std::chrono::steady_clock::now(); - if (post_battle_dialogue.detect(frame)) { - dialogue_absent_since = now; - } - else if (now - dialogue_absent_since >= 4s) { - dialogue_finished = true; - break; - } - pbf_mash_button(context, BUTTON_B, 100ms); - context.wait_for_all_requests(); - } - if (!dialogue_finished) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Gym post battle: Dialogue did not finish within 180 seconds.", stream - ); - } - stream.log("[Gym post battle] Dialogue absent for 4 seconds. Walking down."); - - for (int i = 0; i < 21; i++) { - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } - - // Exit the building. - pbf_wait(context, 1000ms); - } - - } - } -} +/* BDSP AutoStory - Segment 01 + * From after choosing the starter Pokemon until after acquiring the first badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include +#include +#include +#include +#include +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/ImageTools/ImageDiff.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" + +using namespace std::chrono_literals; + +namespace PokemonAutomation { + namespace NintendoSwitch { + namespace PokemonBDSP { + + static bool mine_confirm_escape_via_menu( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type + ) { + MenuWatcher game_menu; + OverworldWatcher overworld; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + for (size_t attempt = 1; attempt <= 3; attempt++) { + // Do not accept the absence-based overworld detector before menu proof. + const int state = wait_until(stream, context, 300ms, {{game_menu}, {battle_menu}}); + if (state == 1) { + stream.log("[Mine escape] Battle menu still present; retrying Run."); + return false; + } + int detected = state; + if (state != 0) { + stream.log("[Mine escape] Main menu fallback attempt " + std::to_string(attempt) + "/3."); + detected = run_until( + stream, context, + [](ProControllerContext& controller) { + pbf_press_button(controller, BUTTON_X, 160ms, 840ms); + pbf_wait(controller, 3s); + controller.wait_for_all_requests(); + }, + {game_menu, battle_menu} + ); + if (detected == 1) { + return false; + } + } + if (detected != 0) { + continue; + } + stream.log("[Mine escape] Main game menu confirmed; battle has ended.", COLOR_GREEN); + // Close the confirmed menu before returning control to navigation. + MenuDetector menu_visible; + for (size_t close = 0; close < 3; close++) { + pbf_press_button(context, BUTTON_B, 80ms, 500ms); + context.wait_for_all_requests(); + if (wait_until(stream, context, 1s, {{overworld}}) == 0) { + return true; + } + BlackScreenWatcher visible( + COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms + ); + if (wait_until(stream, context, 2s, {{visible}}) == 0) { + const VideoSnapshot frame = stream.video().snapshot(); + if (frame && !menu_visible.detect(frame)) { + stream.log("[Mine escape] Confirmed game menu closed; returning to navigation.", COLOR_GREEN); + return true; + } + } + } + stream.log("[Mine escape] Main menu detected but could not confirm it closed.", COLOR_ORANGE); + return false; + } + return false; + } + + static bool mine_select_run( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type + ) { + BattleMenuDetector cursor(battle_type, COLOR_YELLOW); + size_t matching_frames = 0; + for (size_t check = 0; check < 16; check++) { + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame) { + return false; + } + const BattleMenuOption selected = cursor.selected_option(frame); + stream.log("[Mine escape] Battle cursor row=" + + std::to_string(static_cast(selected) + 1) + "; target Run=4."); + if (selected == BattleMenuOption::RUN) { + if (++matching_frames >= 3) { + stream.log("[Mine escape] Run cursor confirmed.", COLOR_GREEN); + return true; + } + pbf_wait(context, 100ms); + } + else { + matching_frames = 0; + if (selected == BattleMenuOption::UNKNOWN) { + // Do not send a direction or A without a known cursor. + pbf_wait(context, 150ms); + } + else { + const DpadPosition direction = selected == BattleMenuOption::BATTLE + ? DPAD_UP : DPAD_DOWN; + pbf_press_dpad(context, direction, 80ms, 250ms); + } + } + } + stream.log("[Mine escape] Could not confirm Run selection; A was not pressed.", COLOR_ORANGE); + return false; + } + + void escape_wild_battle( + VideoStream& stream, + ProControllerContext& context, + BattleType battle_type, + std::chrono::seconds timeout + ) { + EndBattleWatcher battle_end; + bool exit_transition = false; + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + const int ready = wait_until(stream, context, 3s, {{battle_end}, {menu_ready}}); + if (ready == 0) { + exit_transition = true; + break; + } + if (ready != 1) { + continue; + } + if (!mine_select_run(stream, context, battle_type)) { + continue; + } + stream.log("[Mine escape] Run input sent; watching for black-screen exit."); + const int result = run_until( + stream, context, + [](ProControllerContext& controller) { + pbf_press_button(controller, BUTTON_A, 60ms, 0ms); + pbf_wait(controller, 4s); + controller.wait_for_all_requests(); + }, + {battle_end} + ); + if (result == 0) { + exit_transition = true; + break; + } + } + if (!exit_transition && wait_until(stream, context, 10s, {{battle_end}}) == 0) { + exit_transition = true; + } + if (exit_transition) { + // EndBattleWatcher requires black followed by a visible screen. + stream.log("[Mine escape] Black-screen exit transition confirmed.", COLOR_GREEN); + pbf_wait(context, 800ms); + context.wait_for_all_requests(); + return; + } + // Only probe X after normal escape attempts and the final transition wait. + // Absence of battle UI by itself is not evidence of an overworld return. + stream.log("[Mine escape] Exit transition missed; trying late main-menu fallback."); + if (mine_confirm_escape_via_menu(stream, context, battle_type)) { + return; + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", + stream + ); + } + + void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { + stream.log("Starting Barry 1 battle..."); + + // State machine: loop until EndBattleWatcher fires + for (size_t c = 0; c < 20; c++) { + context.wait_for_all_requests(); + + BattleMenuWatcher battle_menu(BattleType::STANDARD); + EndBattleWatcher end_battle; + int ret = run_until( + stream, context, + [](ProControllerContext& context) { + pbf_mash_button(context, BUTTON_ZL, 120000ms); + }, + { + battle_menu, + end_battle, + } + ); + switch (ret) { + case 0: + stream.log("Battle menu detected - using first move."); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + break; + case 1: + stream.log("Barry 1 battle complete."); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + return; + default: + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Barry_1_fight: Timed out waiting for battle menu or end.", + stream + ); + } + } + + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Barry_1_fight: Battle did not end after 20 turns.", + stream + ); + } + + } + } +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h index 8ab2bb38dd..76cad0fe64 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h @@ -1,82 +1,16 @@ -// Navigation and battle helpers for AutoStory Segment 01. -#pragma once - -#include "../PokemonBDSP_AutoStoryTools.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" - -namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - // Shared confirmed-Run escape: verifies exit transition or main menu. - void escape_wild_battle( - VideoStream& stream, ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD, - std::chrono::seconds timeout = std::chrono::seconds(30) - ); - - enum class MineRouteStage { - ALIGN_ENTRANCE, - FIRST_ROCK, - RIGHT_OF_FIRST_ROCK, - BELOW_FIRST_ROCK, - CENTER_GAP, - POSITION_07, - POSITION_08, - POSITION_09, - ITEM_AT_14, - ROARK_AT_19 - }; - - struct MineRouteTestOptions { - MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; - MineRouteStage last = MineRouteStage::ROARK_AT_19; - - // False: stop the test on failure. - // True: let the checkpoint retry handler reset the game. - bool reset_on_failure = false; - }; - - // Shared battle routine: use the first move until the battle ends. - void Barry_1_fight(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void oreburgh_mine_nav( - VideoStream& stream, ProControllerContext& context); - - void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - // Starts inside the mine at an entrance position; stops outside after - // talking to Roark and using Escape Rope. Does not reset on failure. - bool oreburgh_mine_route_and_escape_test( - VideoStream& stream, - ProControllerContext& context - ); - - // Shared stage runner for production navigation and isolated route tests. - bool oreburgh_mine_route( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options - ); - - bool oreburgh_mine_first_leg_test( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options = MineRouteTestOptions() - ); - } - } -} +// Shared battle helpers used by AutoStory routes. +#pragma once + +#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" + +namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { +// Shared confirmed-Run escape: verifies exit transition or main menu. +void escape_wild_battle(VideoStream& stream, ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD, + std::chrono::seconds timeout = std::chrono::seconds(30)); + +// Shared legacy first-move battle routine; consolidation remains preliminary. +void Barry_1_fight(SingleSwitchProgramEnvironment& env, + VideoStream& stream, ProControllerContext& context); +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index d0a73d01bd..08a05a88ea 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -1,282 +1,1224 @@ -#include "CommonFramework/GlobalAutoPaths.h" -#include -#include -#include -#include "CommonFramework/Globals.h" -#include "PokemonBDSP_OverworldMatchers.h" -namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ -struct LandmarkConfig { const char* name; const char* path; ImageFloatBox target,search; double threshold,margin; }; -static cv::Mat gray_image(const ImageRGB32& image){ - cv::Mat pixels((int)image.height(),(int)image.width(),CV_8UC4, - const_cast(image.data()),image.bytes_per_row()); - cv::Mat gray; cv::cvtColor(pixels,gray,cv::COLOR_BGRA2GRAY); return gray; -} -static OverworldMatchResult match_landmarks(const ImageRGB32& frame, - const std::vector& configs, size_t required, double tolerance_x, double tolerance_y, bool smooth_resize=false){ - OverworldMatchResult result; - if (!frame) return result; - cv::Mat gray; - if (smooth_resize){ - // Center matching uses explicit area averaging for stable emblem edges. - // Other matchers retain their existing ImageRGB32 resize behavior. - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); - }else{ - ImageRGB32 scaled=frame.scale_to(960,540); - gray=gray_image(scaled); - } - for(const auto& config: configs){ - OverworldLandmarkResult landmark;landmark.name=config.name;landmark.search=config.search; - ImageRGB32 reference(RESOURCE_PATH()+config.path); - // Convert normalized geometry to pixels only at the image-processing boundary. - const int template_width = std::max(1, (int)std::lround(config.target.width*gray.cols)); - const int template_height = std::max(1, (int)std::lround(config.target.height*gray.rows)); - cv::Mat templ; - if (smooth_resize){ - cv::resize(gray_image(reference),templ,cv::Size(template_width,template_height),0,0,cv::INTER_AREA); - }else{ - ImageRGB32 small=reference.scale_to(template_width,template_height); - templ=gray_image(small); - } - cv::Rect search((int)(config.search.x*960),(int)(config.search.y*540), - (int)(config.search.width*960),(int)(config.search.height*540)); - search &= cv::Rect(0,0,960,540); - if(search.width=config.threshold && landmark.margin>=config.margin; - landmark.found={(double)location.x/960,(double)location.y/540,(double)templ.cols/960,(double)templ.rows/540}; - result.landmarks.push_back(landmark); - } - if(required==1 && result.landmarks.size()==1){ - result.located=result.landmarks[0].accepted;result.dx=result.landmarks[0].dx;result.dy=result.landmarks[0].dy; - }else{ - size_t best_support=0; bool ambiguous=false; - for(const auto& candidate:result.landmarks){ - if(!candidate.accepted)continue; - size_t support=0;double dx=0,dy=0; - for(const auto& other:result.landmarks){ - if(other.accepted && std::abs(candidate.dx-other.dx)<=1.0/192 && std::abs(candidate.dy-other.dy)<=1.0/108){ - support++;dx+=other.dx;dy+=other.dy; - } - } - if(supportbest_support){best_support=support;result.dx=dx;result.dy=dy;ambiguous=false;} - else if(support==best_support && (std::abs(dx-result.dx)>1.0/192 || std::abs(dy-result.dy)>1.0/108)) ambiguous=true; - } - result.located=best_support>=required && !ambiguous; - } - result.at_target=result.located && std::abs(result.dx)<=tolerance_x && std::abs(result.dy)<=tolerance_y; - return result; -} -OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ - // Same cropped reference emblems and local search windows as production. - OverworldMatchResult fine = match_landmarks(frame,{ - {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{598.0/960,0,356.0/960,212.0/540},.90,0}, - {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{724.0/960,264.0/540,236.0/960,276.0/540},.90,0}, - {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,268.0/540,240.0/960,272.0/540},.90,0} - },2,7.0/960,7.0/540); - if (fine.located) return fine; - // Coarse acquisition searches the whole frame. Agreement and a peak margin - // guard against confusing the Mart/Center emblems with another building. - OverworldMatchResult coarse = match_landmarks(frame,{ - {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{0,0,1,1},.93,.12}, - {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{0,0,1,1},.93,.12}, - {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,0,1,1},.92,.40} - },2,7.0/960,7.0/540); - if (!coarse.located) { - // Acquisition may expose just one building. School keeps its strong - // spatial margin; Mart/Center require a higher score than pair matching. - // A single anchor guides coarse movement only, never interaction. - const OverworldLandmarkResult* anchor = nullptr; - for (const auto& landmark : coarse.landmarks) { - const bool strong = landmark.accepted && (landmark.name == "School" || - (landmark.score >= .95 && landmark.margin >= .12)); - if (strong && (!anchor || landmark.score > anchor->score)) anchor = &landmark; - } - if (anchor) { - bool contradictory = false; - for (const auto& other : coarse.landmarks) - if (other.accepted && (std::abs(other.dx - anchor->dx) > 1.0/192 || std::abs(other.dy - anchor->dy) > 1.0/108)) - contradictory = true; - if (!contradictory) { - coarse.located = true; - coarse.dx = anchor->dx; - coarse.dy = anchor->dy; - } - } - } - coarse.coarse = true; - // A coarse match may guide movement, but never authorize interaction. - // Final position must pass the normal local two-landmark confirmation. - coarse.at_target = false; - return coarse; -} -OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame){ - return match_landmarks(frame,{{"Mart emblem","PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", - {0.502083333333,0.235185185185,0.056250000000,0.079629629630},{0,0,1,360.0/540},.93,.10}},1,1.0/128,1.0/72); -} -OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame){ - // Native lighting-test captures avoid resizing artifacts in the reference. - // The emblem is about 5.73% of screen width left of the player at approach: - // shift its target right to center the door, preserving the approach height. - OverworldMatchResult clear = match_landmarks(frame,{{"Center entrance", - "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", - {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,1.0/128,1.0/72,true); - OverworldMatchResult shadow = match_landmarks(frame,{{"Center entrance", - "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", - {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,1.0/128,1.0/72,true); - // Lighting alternatives must identify the same position when both pass. - if (clear.located && shadow.located && - (std::abs(clear.dx-shadow.dx)>1.0/192 || std::abs(clear.dy-shadow.dy)>1.0/108)){ - clear.located = false; - clear.at_target = false; - return clear; - } - if (shadow.located && (!clear.located || shadow.landmarks[0].score>clear.landmarks[0].score)) - return shadow; - return clear; -} -OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame){ - // The sign face is stable across lighting; its post and surrounding grass are not. - // Path shadows change shape, so validate the corner against four lighting states. - // Each alternative still requires the sign and path to agree on position. - const char* corners[]={ - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour0.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png" - }; - OverworldMatchResult best; - double best_score=-1; - for (const char* corner : corners){ - auto candidate=match_landmarks(frame,{ - {"Route sign face","PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", - {0.292708333333,0.470370370370,0.109375,0.072222222222},{0,0,1,1},.94,.20}, - {"Path corner",corner, - {0.202083333333,0.648148148148,0.133333333333,0.174074074074},{0,0,1,1},.90,.10} - },2,1.0/128,1.0/72,true); - if (best.located && candidate.located && - (std::abs(best.dx-candidate.dx)>1.0/192 || std::abs(best.dy-candidate.dy)>1.0/108)){ - best.located=false; - best.at_target=false; - return best; - } - if (candidate.landmarks.size()!=2) continue; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate); - best_score=score; - } - } - return best; -} -OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame){ - // Building emblems contain no localized words. Ignore NPCs and door lights. - auto result = match_landmarks(frame,{ - {"Center emblem","PokemonBDSP/Autostory/SandgemJunction/Center.png", - {0.171875,0.2,0.058333333333,0.092592592593},{0,0,1,1},.90,.08}, - {"Mart emblem","PokemonBDSP/Autostory/SandgemJunction/Mart.png", - {0.726041666667,0.222222222222,0.0625,0.088888888889},{0,0,1,1},.90,.08} - },2,1.0/128,1.0/72,true); - if (result.located || result.landmarks.size()!=2) return result; - const auto& center=result.landmarks[0]; - const auto& mart=result.landmarks[1]; - const double dy=(center.dy+mart.dy)/2; - const double horizontal_disagreement=center.dx-mart.dx; - // At a different approach height, perspective changes the buildings' spacing. - // Trust matching height offsets for vertical acquisition only. Bound the - // horizontal discrepancy and require its direction to agree with perspective: - // scenery above target contracts, scenery below target expands. - if (center.accepted && mart.accepted && - std::abs(center.dy-mart.dy)<=1.0/108 && - std::abs(dy)>1.0/72 && std::abs(dy)<=0.20 && - std::abs(horizontal_disagreement)<=0.04 && - std::abs(horizontal_disagreement)<=0.35*std::abs(dy)+1.0/192 && - horizontal_disagreement*dy<=0){ - result.located=true; - result.coarse=true; - result.dx=(center.dx+mart.dx)/2; - result.dy=dy; - // Acquisition may move vertically; it never confirms the route endpoint. - result.at_target=false; - } - return result; -} -OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ - // Route marker and upper path bend avoid the player, NPCs, and localized text. - // Morning tree shadows need a second corner reference. Each alternative - // must still agree with the sign; neither can confirm the destination alone. - const char* corners[]={ - "PokemonBDSP/Autostory/Route202GrassExit/PathCorner.png", - "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png" - }; - OverworldMatchResult best; - double best_score=-1; - for (const char* corner : corners){ - auto candidate=match_landmarks(frame,{ - {"Route 202 sign","PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", - {0.59375,0.194444444444,0.091666666667,0.059259259259},{0,0,1,1},.94,.20}, - {"Path corner",corner, - {0.405208333333,0.331481481481,0.1375,0.062962962963},{0,0,1,1},.90,.08} - },2,1.0/128,1.0/72,true); - if (best.located && candidate.located && - (std::abs(best.dx-candidate.dx)>1.0/192 || std::abs(best.dy-candidate.dy)>1.0/108)){ - best.located=false; - best.at_target=false; - return best; - } - if (candidate.landmarks.size()!=2) continue; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate); - best_score=score; - } - } - return best; -} -OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame){ - // Static building/fence geometry excludes people and localized signs. - return match_landmarks(frame,{ - {"Door","PokemonBDSP/Autostory/JubilifeStreet/Door.png", - {0.677083333333,0.439814814815,0.213541666667,0.162037037037},{0,0,1,1},.90,.04}, - {"Facade","PokemonBDSP/Autostory/JubilifeStreet/Facade.png", - {0.583333333333,0.138888888889,0.322916666667,0.250000000000},{0,0,1,1},.90,.04} - },2,1.0/128,1.0/72,true); -} -OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame){ - // Static building/fence geometry excludes people and localized signs. - return match_landmarks(frame,{ - {"Roof","PokemonBDSP/Autostory/JubilifePlaza/Roof.png", - {0.640625000000,0.069444444444,0.260416666667,0.189814814815},{0,0,1,1},.90,.04}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/Fence.png", - {0.041666666667,0.319444444444,0.169270833333,0.120370370370},{0,0,1,1},.90,.04} - },2,1.0/128,1.0/72,true); -} -const std::vector& overworld_matchers(){ - static const std::vector matchers={ - {"jubilife-street-approach","Jubilife street before Galactic plaza",match_jubilife_street_approach}, - {"jubilife-plaza-approach","Jubilife Galactic plaza approach",match_jubilife_plaza_approach}, - {"poketch-landmarks","Jubilife Poketch landmarks",match_poketch_landmarks}, - {"oreburgh-mart","Oreburgh Mart emblem",match_oreburgh_mart}, - {"jubilife-center","Jubilife Pokemon Center entrance",match_jubilife_center_entrance}, - {"route201-potion-exit","Route 201 after Potion pickup",match_route201_potion_exit}, - {"sandgem-junction","Sandgem Center/Mart junction",match_sandgem_junction}, - {"route202-grass-exit","Route 202 after catching: grass exit",match_route202_grass_exit}, - }; - return matchers; -} -}}} +#include "CommonFramework/GlobalAutoPaths.h" +#include +#include +#include +#include "CommonFramework/Globals.h" +#include "PokemonBDSP_OverworldMatchers.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +enum class LandmarkFeatures { GRAYSCALE, LOCAL_CONTRAST, BLUE_FRACTION }; +struct LandmarkConfig { const char* name; const char* path; ImageFloatBox target,search; double threshold,margin; LandmarkFeatures features=LandmarkFeatures::GRAYSCALE; }; +// Screen-relative half-widths of the accepted position box, and how closely +// two landmarks (or reference variants) must agree to be trusted together. +static constexpr double POSITION_TOLERANCE_X = 1.0/50; +static constexpr double POSITION_TOLERANCE_Y = 1.0/54; +static constexpr double AGREEMENT_X = 1.0/64; +static constexpr double AGREEMENT_Y = 1.0/54; +static cv::Mat gray_image(const ImageRGB32& image){ + cv::Mat pixels((int)image.height(),(int)image.width(),CV_8UC4, + const_cast(image.data()),image.bytes_per_row()); + cv::Mat gray; cv::cvtColor(pixels,gray,cv::COLOR_BGRA2GRAY); return gray; +} +// Opt-in only for the outdoor Cut anchors. Divide out slow local lighting +// changes so a moving cloud does not alter the printed sign/emblem structure. +static cv::Mat normalize_landmark_illumination(const cv::Mat& gray){ + cv::Mat pixels,background,normalized; + gray.convertTo(pixels,CV_32F); + cv::GaussianBlur(pixels,background,cv::Size(0,0),3.0); + cv::divide(pixels,background+1.0,normalized); + cv::Mat structure; + normalized.convertTo(structure,CV_8U,128.0); + return structure; +} +// White Pokeball versus yellow fascia is a color-ratio boundary. A cast +// shadow changes their brightness, so compare blue fraction rather than luma. +static cv::Mat landmark_blue_fraction(const ImageRGB32& image){ + cv::Mat pixels((int)image.height(),(int)image.width(),CV_8UC4, + const_cast(image.data()),image.bytes_per_row()); + cv::Mat small,floating; + cv::resize(pixels,small,cv::Size(960,540),0,0,cv::INTER_AREA); + small.convertTo(floating,CV_32F); + std::vector channels;cv::split(floating,channels); + cv::Mat ratio,structure; + cv::divide(channels[0],channels[0]+channels[1]+channels[2]+3.0,ratio); + ratio.convertTo(structure,CV_8U,768.0); + return structure; +} +static OverworldMatchResult match_landmarks(const ImageRGB32& frame, + const std::vector& configs, size_t required, double tolerance_x, double tolerance_y, bool smooth_resize=false){ + OverworldMatchResult result; + if (!frame) return result; + cv::Mat gray; + if (smooth_resize){ + // Center matching uses explicit area averaging for stable emblem edges. + // Other matchers retain their existing ImageRGB32 resize behavior. + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + }else{ + ImageRGB32 scaled=frame.scale_to(960,540); + gray=gray_image(scaled); + } + cv::Mat normalized_gray,chromaticity; + for(const auto& config: configs){ + OverworldLandmarkResult landmark;landmark.name=config.name;landmark.search=config.search; + ImageRGB32 reference(RESOURCE_PATH()+config.path); + // Convert normalized geometry to pixels only at the image-processing boundary. + const int template_width = std::max(1, (int)std::lround(config.target.width*gray.cols)); + const int template_height = std::max(1, (int)std::lround(config.target.height*gray.rows)); + cv::Mat templ; + if (smooth_resize){ + cv::resize(gray_image(reference),templ,cv::Size(template_width,template_height),0,0,cv::INTER_AREA); + }else{ + ImageRGB32 small=reference.scale_to(template_width,template_height); + templ=gray_image(small); + } + cv::Rect search((int)(config.search.x*960),(int)(config.search.y*540), + (int)(config.search.width*960),(int)(config.search.height*540)); + search &= cv::Rect(0,0,960,540); + if(search.width=config.threshold && landmark.margin>=config.margin; + landmark.found={(double)location.x/960,(double)location.y/540,(double)templ.cols/960,(double)templ.rows/540}; + result.landmarks.push_back(landmark); + } + if(required==1 && result.landmarks.size()==1){ + result.located=result.landmarks[0].accepted;result.dx=result.landmarks[0].dx;result.dy=result.landmarks[0].dy; + }else{ + size_t best_support=0; bool ambiguous=false; + for(const auto& candidate:result.landmarks){ + if(!candidate.accepted)continue; + size_t support=0;double dx=0,dy=0; + for(const auto& other:result.landmarks){ + if(other.accepted && std::abs(candidate.dx-other.dx)<=AGREEMENT_X && std::abs(candidate.dy-other.dy)<=AGREEMENT_Y){ + support++;dx+=other.dx;dy+=other.dy; + } + } + if(supportbest_support){best_support=support;result.dx=dx;result.dy=dy;ambiguous=false;} + else if(support==best_support && (std::abs(dx-result.dx)>AGREEMENT_X || std::abs(dy-result.dy)>AGREEMENT_Y)) ambiguous=true; + } + result.located=best_support>=required && !ambiguous; + } + result.at_target=result.located && std::abs(result.dx)<=tolerance_x && std::abs(result.dy)<=tolerance_y; + return result; +} +OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ + // Same cropped reference emblems and local search windows as production. + OverworldMatchResult fine = match_landmarks(frame,{ + {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{598.0/960,0,356.0/960,212.0/540},.90,0}, + {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{724.0/960,264.0/540,236.0/960,276.0/540},.90,0}, + {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,268.0/540,240.0/960,272.0/540},.90,0} + },2,7.0/960,7.0/540); + if (fine.located) return fine; + // Coarse acquisition searches the whole frame. Agreement and a peak margin + // guard against confusing the Mart/Center emblems with another building. + OverworldMatchResult coarse = match_landmarks(frame,{ + {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{0,0,1,1},.93,.12}, + {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{0,0,1,1},.93,.12}, + {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,0,1,1},.92,.40} + },2,7.0/960,7.0/540); + if (!coarse.located) { + // Acquisition may expose just one building. School keeps its strong + // spatial margin; Mart/Center require a higher score than pair matching. + // A single anchor guides coarse movement only, never interaction. + const OverworldLandmarkResult* anchor = nullptr; + for (const auto& landmark : coarse.landmarks) { + const bool strong = landmark.accepted && (landmark.name == "School" || + (landmark.score >= .95 && landmark.margin >= .12)); + if (strong && (!anchor || landmark.score > anchor->score)) anchor = &landmark; + } + if (anchor) { + bool contradictory = false; + for (const auto& other : coarse.landmarks) + if (other.accepted && (std::abs(other.dx - anchor->dx) > 1.0/192 || std::abs(other.dy - anchor->dy) > 1.0/108)) + contradictory = true; + if (!contradictory) { + coarse.located = true; + coarse.dx = anchor->dx; + coarse.dy = anchor->dy; + } + } + } + coarse.coarse = true; + // A coarse match may guide movement, but never authorize interaction. + // Final position must pass the normal local two-landmark confirmation. + coarse.at_target = false; + return coarse; +} +OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame){ + const ImageFloatBox target{0.502083333333,0.235185185185,0.056250000000,0.079629629630}; + auto original=match_landmarks(frame,{{"Mart emblem","PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", + target,{0,0,1,360.0/540},.93,.10}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y); + // The recorded daytime roof appearance narrowly missed the old template. + // Preserve both confidence gates and reject disagreement between variants. + auto recorded=match_landmarks(frame,{{"Mart emblem","PokemonBDSP/Autostory/OreburghMart/MartEmblemRecorded.png", + target,{0,0,1,360.0/540},.93,.10}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y); + if (original.located && recorded.located && + (std::abs(original.dx-recorded.dx)>AGREEMENT_X || std::abs(original.dy-recorded.dy)>AGREEMENT_Y)){ + original.located=false;original.at_target=false;return original; + } + if ((recorded.located && !original.located) || (recorded.located==original.located && + recorded.landmarks[0].score>original.landmarks[0].score)) return recorded; + return original; +} +OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame){ + // Native lighting-test captures avoid resizing artifacts in the reference. + // The emblem is about 5.73% of screen width left of the player at approach: + // shift its target right to center the door, preserving the approach height. + OverworldMatchResult clear = match_landmarks(frame,{{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", + {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + OverworldMatchResult shadow = match_landmarks(frame,{{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", + {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + // Lighting alternatives must identify the same position when both pass. + if (clear.located && shadow.located && + (std::abs(clear.dx-shadow.dx)>AGREEMENT_X || std::abs(clear.dy-shadow.dy)>AGREEMENT_Y)){ + clear.located = false; + clear.at_target = false; + return clear; + } + if (shadow.located && (!clear.located || shadow.landmarks[0].score>clear.landmarks[0].score)) + return shadow; + return clear; +} +OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame){ + // The sign face is stable across lighting; its post and surrounding grass are not. + // Path shadows change shape, so validate the corner against four lighting states. + // Each alternative still requires the sign and path to agree on position. + const char* corners[]={ + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour0.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png" + }; + OverworldMatchResult best; + double best_score=-1; + for (const char* corner : corners){ + auto candidate=match_landmarks(frame,{ + {"Route sign face","PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", + {0.292708333333,0.470370370370,0.109375,0.072222222222},{0,0,1,1},.94,.20}, + {"Path corner",corner, + {0.202083333333,0.648148148148,0.133333333333,0.174074074074},{0,0,1,1},.90,.10} + },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (best.located && candidate.located && + (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)){ + best.located=false; + best.at_target=false; + return best; + } + if (candidate.landmarks.size()!=2) continue; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate); + best_score=score; + } + } + return best; +} +OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame){ + // Building emblems contain no localized words. Ignore NPCs and door lights. + auto result = match_landmarks(frame,{ + {"Center emblem","PokemonBDSP/Autostory/SandgemJunction/Center.png", + {0.171875,0.2,0.058333333333,0.092592592593},{0,0,1,1},.90,.08}, + {"Mart emblem","PokemonBDSP/Autostory/SandgemJunction/Mart.png", + {0.726041666667,0.222222222222,0.0625,0.088888888889},{0,0,1,1},.90,.08} + },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (result.located || result.landmarks.size()!=2) return result; + const auto& center=result.landmarks[0]; + const auto& mart=result.landmarks[1]; + const double dy=(center.dy+mart.dy)/2; + const double horizontal_disagreement=center.dx-mart.dx; + // At a different approach height, perspective changes the buildings' spacing. + // Trust matching height offsets for vertical acquisition only. Bound the + // horizontal discrepancy and require its direction to agree with perspective: + // scenery above target contracts, scenery below target expands. + if (center.accepted && mart.accepted && + std::abs(center.dy-mart.dy)<=AGREEMENT_Y && + std::abs(dy)>1.0/72 && std::abs(dy)<=0.20 && + std::abs(horizontal_disagreement)<=0.04 && + std::abs(horizontal_disagreement)<=0.35*std::abs(dy)+1.0/192 && + horizontal_disagreement*dy<=0){ + result.located=true; + result.coarse=true; + result.dx=(center.dx+mart.dx)/2; + result.dy=dy; + // Acquisition may move vertically; it never confirms the route endpoint. + result.at_target=false; + } + return result; +} +OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ + // Route marker and upper path bend avoid the player, NPCs, and localized text. + // Morning tree shadows need a second corner reference. Each alternative + // must still agree with the sign; neither can confirm the destination alone. + const char* corners[]={ + "PokemonBDSP/Autostory/Route202GrassExit/PathCorner.png", + "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png" + }; + OverworldMatchResult best; + double best_score=-1; + for (const char* corner : corners){ + auto candidate=match_landmarks(frame,{ + {"Route 202 sign","PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", + {0.59375,0.194444444444,0.091666666667,0.059259259259},{0,0,1,1},.94,.20}, + {"Path corner",corner, + {0.405208333333,0.331481481481,0.1375,0.062962962963},{0,0,1,1},.90,.08} + },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (best.located && candidate.located && + (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)){ + best.located=false; + best.at_target=false; + return best; + } + if (candidate.landmarks.size()!=2) continue; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate); + best_score=score; + } + } + return best; +} +// Lighting alternatives describe one physical landmark, not extra votes. +// Select one reference per landmark before requiring the two positions to agree. +static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, + const std::vector& variants, const char* first, const char* second, + double tolerance_x=POSITION_TOLERANCE_X, bool allow_vertical_acquisition=false, double agreement_x=AGREEMENT_X){ + auto candidates=match_landmarks(frame,variants,variants.size()+1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + OverworldMatchResult result; + for (const char* name : {first,second}){ + const OverworldLandmarkResult* best=nullptr; + for (const auto& candidate : candidates.landmarks){ + if (candidate.name!=name) continue; + if (!best || (candidate.accepted && !best->accepted) || + (candidate.accepted==best->accepted && candidate.score>best->score)) best=&candidate; + } + if (!best) return result; + result.landmarks.push_back(*best); + auto& chosen=result.landmarks.back(); + // Similar strong references must not disagree about location. + for (const auto& candidate : candidates.landmarks){ + if (candidate.name==name && candidate.accepted && + candidate.score>=chosen.score-0.02 && + (std::abs(candidate.dx-chosen.dx)>AGREEMENT_X || + std::abs(candidate.dy-chosen.dy)>AGREEMENT_Y)) chosen.accepted=false; + } + } + const auto& a=result.landmarks[0];const auto& b=result.landmarks[1]; + result.located=a.accepted && b.accepted && + std::abs(a.dx-b.dx)<=agreement_x && std::abs(a.dy-b.dy)<=AGREEMENT_Y; + result.dx=(a.dx+b.dx)/2;result.dy=(a.dy+b.dy)/2; + result.at_target=result.located && + std::abs(a.dx-b.dx)<=AGREEMENT_X && std::abs(result.dx)<=tolerance_x && std::abs(result.dy)<=POSITION_TOLERANCE_Y; + const double spacing_difference=a.dx-b.dx; + // At the street approach height, perspective can change column spacing. + // Agreeing vertical offsets may guide an upward correction, but cannot + // confirm the destination or authorize a horizontal correction yet. + if (!result.located && allow_vertical_acquisition && a.accepted && b.accepted && + std::abs(a.dy-b.dy)<=AGREEMENT_Y && std::abs(result.dy)>POSITION_TOLERANCE_Y && std::abs(result.dy)<=0.20 && + std::abs(spacing_difference)<=0.04 && + std::abs(spacing_difference)<=0.35*std::abs(result.dy)+1.0/192 && spacing_difference*result.dy<=0){ + result.located=true; + result.coarse=true; + result.at_target=false; + } + return result; +} +OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame){ + // Native screenshot-20261016-214300767696 is the intended doorway pose. + // The Pokeball is primary. Sign and bush assist when visible, but cloud + // shadows must not prevent arrival from a strong, unique emblem match. + OverworldMatchResult best; + double best_score=-1; + bool best_primary_only=false; + // Recorded evening/daytime appearances retain the same destination and gates. + for (int appearance=0;appearance<5;appearance++){ + for (double scale : {.90,.95,1.0,1.05,1.10}){ + std::vector configs={ + {"Center Pokeball","PokemonBDSP/Autostory/EternaCenter/BallTarget.png", + {904.0/1920,144.0/1080,112.0/1920,104.0/1080},{0,0,1,.65},.93,.20}, + {"Right sign","PokemonBDSP/Autostory/EternaCenter/RightSignTarget.png", + {1762.0/1920,304.0/1080,128.0/1920,138.0/1080},{0,0,1,.85},.93,.20}, + {"Lower left bush","PokemonBDSP/Autostory/EternaCenter/BushTarget.png", + {248.0/1920,854.0/1080,180.0/1920,146.0/1080},{0,.35,1,.65},.93,.20} + }; + if (appearance==1 || appearance==2){ + configs={ + {"Center Pokeball",appearance==1 ? "PokemonBDSP/Autostory/EternaCenter/BallEvening1.png" : "PokemonBDSP/Autostory/EternaCenter/BallEvening2.png", + {920.0/1920,154.0/1080,82.0/1920,84.0/1080},{0,0,1,.65},.93,.20}, + {"Right sign",appearance==1 ? "PokemonBDSP/Autostory/EternaCenter/RightSignEvening1.png" : "PokemonBDSP/Autostory/EternaCenter/RightSignEvening2.png", + {1770.0/1920,310.0/1080,116.0/1920,63.0/1080},{0,0,1,.85},.93,.20}, + {"Lower left bush",appearance==1 ? "PokemonBDSP/Autostory/EternaCenter/BushEvening1.png" : "PokemonBDSP/Autostory/EternaCenter/BushEvening2.png", + {248.0/1920,932.0/1080,180.0/1920,68.0/1080},{0,.35,1,.65},.93,.20} + }; + } + if (appearance>=3){ + // Recorded daylight changes the bush/planter contrast. These + // crops retain the original doorway geometry and all three votes. + configs[0].path=appearance==3 ? "PokemonBDSP/Autostory/EternaCenter/BallDaytime1.png" : "PokemonBDSP/Autostory/EternaCenter/BallDaytime2.png"; + configs[1].path=appearance==3 ? "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime1.png" : "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime2.png"; + configs[2].path=appearance==3 ? "PokemonBDSP/Autostory/EternaCenter/BushDaytime1.png" : "PokemonBDSP/Autostory/EternaCenter/BushDaytime2.png"; + } + for (auto& config : configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); + if (candidate.located) { + double score=1; + for (const auto& landmark:candidate.landmarks) score=std::min(score,landmark.score); + if (!best.located || best_primary_only || score>best_score){best=candidate;best_score=score;best_primary_only=false;} + } else if (!best.located || best_primary_only) { + // Optional sign/bush can disappear under clouds or at frame edges. + // Retain the existing stricter primary-emblem confidence gate. + const auto& ball=candidate.landmarks[0]; + if (ball.accepted && ball.score>=.95 && + (!best.located || ball.score>best_score)){ + best=candidate;best.located=true;best.coarse=false; + best.dx=ball.dx;best.dy=ball.dy; + best.at_target=std::abs(best.dx)<=1.0/128 && std::abs(best.dy)<=POSITION_TOLERANCE_Y; + best_score=ball.score;best_primary_only=true; + } else if (best.landmarks.empty()) best=candidate; + } + } + } + return best; +} +OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ + // Recorded centered doorway pose. All three stationary landmarks must agree + // before entering; two can guide acquisition without confirming arrival. + OverworldMatchResult best; + double best_score=-1; + for (int appearance : {19,16}){ + for (double scale : {.90,.95,1.0,1.05,1.10}){ + std::vector configs={ + {"Left gym sign",appearance==19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" : "PokemonBDSP/Autostory/EternaGym/LeftSign16.png", + {458.0/1920,358.0/1080,140.0/1920,150.0/1080},{0,0,.65,.85},.93,.20}, + {"Gym Pokeball",appearance==19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" : "PokemonBDSP/Autostory/EternaGym/Ball16.png", + {880.0/1920,264.0/1080,150.0/1920,80.0/1080},{.15,0,.7,.70},.93,.20}, + {"Right bike post",appearance==19 ? "PokemonBDSP/Autostory/EternaGym/BikePost19.png" : "PokemonBDSP/Autostory/EternaGym/BikePost16.png", + {1468.0/1920,460.0/1080,130.0/1920,178.0/1080},{.35,.15,.65,.80},.93,.20} + }; + for (auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); + if (!candidate.located){ + candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + candidate.coarse=true;candidate.at_target=false; + } + double score=1; + for(const auto& landmark:candidate.landmarks)if(landmark.accepted)score=std::min(score,landmark.score); + if(candidate.located && (!best.located || (best.coarse && !candidate.coarse) || + (best.coarse==candidate.coarse && score>best_score))){best=candidate;best_score=score;} + else if(best.landmarks.empty())best=candidate; + } + } + if (best.located) return best; + // The gym first appears at the far right of the approach street. Search + // the whole frame for its left sign and emblem; the bike post may be offscreen. + // These broad matches guide travel only. Arrival still needs all three above. + double coarse_score=-1; + for (int appearance : {680,19,333,303,306,339}){ + for (double scale : {.85,.90,1.0,1.10,1.15}){ + std::vector configs={ + {"Left gym sign",appearance==680 ? "PokemonBDSP/Autostory/EternaGym/LeftSignApproach.png" : appearance==19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" : + appearance==333 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWest.png" : + appearance==306 ? "PokemonBDSP/Autostory/EternaGym/LeftSignSouthShadow.png" : + appearance==339 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWestShadow.png" : "PokemonBDSP/Autostory/EternaGym/LeftSignSouth.png", + {458.0/1920,358.0/1080,140.0/1920,150.0/1080},{0,0,1,.95},.93,.20}, + {"Gym Pokeball",appearance==680 ? "PokemonBDSP/Autostory/EternaGym/BallApproach.png" : appearance==19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" : "PokemonBDSP/Autostory/EternaGym/BallWest.png", + {880.0/1920,264.0/1080,150.0/1920,80.0/1080},{0,0,1,.90},.93,.20} + }; + if(appearance==680){ + configs[0].target.width=148.0/1920;configs[0].target.height=158.0/1080; + configs[1].target.width=190.0/1920; + } + for(auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2;config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + const auto& sign=candidate.landmarks[0];const auto& ball=candidate.landmarks[1]; + double score=std::min(sign.score,ball.score); + // Perspective changes their spacing while the gym is far right. + // Both accepted anchors must still agree on the rightward direction + // and vertical row before using that directional-only acquisition. + if(!candidate.located && sign.accepted && ball.accepted && + sign.dx>.08 && ball.dx>.08 && std::abs(sign.dy-ball.dy)<=.04 && + std::min(sign.dx,ball.dx)>2*std::abs(sign.dx-ball.dx)){ + candidate.located=true;candidate.dx=(sign.dx+ball.dx)/2;candidate.dy=(sign.dy+ball.dy)/2; + } + if(!candidate.located){ + // One fully visible unique landmark is enough to guide the + // approach when the other is clipped at the right edge. + for(const auto& anchor:candidate.landmarks){ + if(anchor.accepted && anchor.score>=.97 && anchor.margin>=.30 && + anchor.dx>.08 && std::abs(anchor.dy)<=.22 && anchor.score>score){ + candidate.located=true;candidate.dx=anchor.dx;candidate.dy=anchor.dy;score=anchor.score; + } + } + } + candidate.coarse=true;candidate.at_target=false; + if(candidate.located && score>coarse_score){best=candidate;coarse_score=score;} + } + } + if(best.located)return best; + // The yellow-roofed house opposite the gym remains visible when the + // gym itself is clipped offscreen. Reuse paired landmark agreement to + // recover the doorway row and guide east until gym anchors take over. + // Its virtual target is off the left edge: these approach anchors can + // never satisfy near-door moderate confidence or confirm gym arrival. + double house_score=-1; + for(int perspective:{720,734,708}){ + for(double scale:{.90,.95,1.0,1.05,1.10}){ + std::vector configs={ + {"Gym approach house door",perspective==708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorFarWest.png" : perspective==734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorWest.png" : "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoor.png", + {548.0/1920-.55,448.0/1080,128.0/1920,156.0/1080},{0,.15,.75,.70},.93,.20}, + {"Gym approach house window",perspective==708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowFarWest.png" : perspective==734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowWest.png" : "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindow.png", + {718.0/1920-.55,462.0/1080,132.0/1920,112.0/1080},{0,.15,.85,.70},.93,.20} + }; + if(perspective==734){ + configs[0].target={338.0/1920-.55,450.0/1080,132.0/1920,150.0/1080}; + configs[1].target={496.0/1920-.55,464.0/1080,122.0/1920,102.0/1080}; + } + if(perspective==708){ + configs[0].target={240.0/1920-.55,464.0/1080,150.0/1920,150.0/1080}; + configs[1].target={416.0/1920-.55,478.0/1080,118.0/1920,90.0/1080}; + } + for(auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2;config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + candidate.coarse=true;candidate.at_target=false; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if(candidate.located && candidate.dx>.08 && score>house_score){best=candidate;house_score=score;} + } + } + return best; +} +double overworld_entry_confidence(const OverworldMatchResult& result, + double tolerance_x, double tolerance_y, double minimum_margin){ + double confidence=0; + for(size_t i=0;itolerance_x || std::abs(a.dy)>tolerance_y)continue; + for(size_t j=i+1;jtolerance_x || std::abs(b.dy)>tolerance_y)continue; + if(std::abs(a.dx-b.dx)>AGREEMENT_X || std::abs(a.dy-b.dy)>AGREEMENT_Y)continue; + confidence=std::max(confidence,std::min(a.score,b.score)); + } + } + return confidence; +} +OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame){ + // The supplied reference is the interaction pose directly north of Jupiter. + // Confirm both red chair groups and the stairs before facing south to talk. + const std::vector configs{ + {"LeftChairs","PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairs.png", + {334.0/1920,756.0/1080,156.0/1920,228.0/1080},{0,.40,.45,.60},.93,.20}, + {"RightChairs","PokemonBDSP/Autostory/GalacticHQ/AboveRightChairs.png", + {1238.0/1920,756.0/1080,164.0/1920,230.0/1080},{.50,.40,.50,.60},.93,.20}, + {"StairBoundary","PokemonBDSP/Autostory/GalacticHQ/AboveStairs.png", + {424.0/1920,310.0/1080,214.0/1920,198.0/1080},{0,0,1,.85},.93,.20} + }; + auto result=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); + if (!result.located){ + // Two agreeing points guide the approach; all three still own arrival. + auto coarse=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + if (coarse.located){coarse.coarse=true;coarse.at_target=false;result=coarse;} + } + if (!result.located){ + // Existing recorded chair views can guide recovery from the old row. + // Their coordinates still point to the new north-side destination; + // only the three canonical above-Jupiter references confirm arrival. + auto approach=std::vector{configs[0],configs[1]}; + approach[0].path="PokemonBDSP/Autostory/GalacticHQ/LeftChairs.png"; + approach[1].path="PokemonBDSP/Autostory/GalacticHQ/RightChairs.png"; + for (auto& chair:approach){chair.threshold=.95;chair.margin=.25;} + auto coarse=match_landmarks(frame,approach,2,1.0/128,POSITION_TOLERANCE_Y,true); + if (coarse.located){ + result.located=true;result.coarse=true;result.at_target=false; + result.dx=coarse.dx;result.dy=coarse.dy; + result.landmarks[0]=coarse.landmarks[0]; + result.landmarks[1]=coarse.landmarks[1]; + } + } + return result; +} + +OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame){ + // The sign face and Center emblem identify the cuttable tree's column. + // Exclude the HQ door, whose open/night appearance changed in the failed run. + // Tight face/emblem crops avoid grass and moving cloud shadows on the roof. + // The stronger emblem needs a corroborating sign, with a lower identity + // gate for the smaller sign face. Both still must agree on the same pose. + auto result=match_lighting_pair(frame,{ + {"HQ sign","PokemonBDSP/Autostory/GalacticHQ/CutSignStructure.png", + {810.0/1920,500.0/1080,80.0/1920,58.0/1080},{0,.25,1,.45},.85,.10,LandmarkFeatures::LOCAL_CONTRAST}, + {"Center Pokeball","PokemonBDSP/Autostory/GalacticHQ/CutCenterBallStructure.png", + {1048.0/1920,1004.0/1080,136.0/1920,66.0/1080},{0,.65,1,.35},.93,.20,LandmarkFeatures::BLUE_FRACTION} + },"HQ sign","Center Pokeball",1.0/128); + if (!result.located && result.landmarks.size()==2){ + const auto& sign=result.landmarks[0];const auto& ball=result.landmarks[1]; + // In the recorded far-west misses, the low fascia emblem shifts 1.31 + // screen pixels per sign pixel. Calibrate that acquisition observation + // before applying the same position-agreement bound; never use it for arrival. + const double calibrated_ball_dx=ball.dx/1.31; + if (sign.accepted && ball.accepted && sign.dx*ball.dx>0 && + std::abs(sign.dx)>.03 && std::abs(sign.dx)<=.20 && + std::abs(sign.dx-calibrated_ball_dx)<=AGREEMENT_X && + std::abs(sign.dy-ball.dy)<=AGREEMENT_Y){ + result.located=true;result.coarse=true;result.at_target=false; + result.dx=(sign.dx+calibrated_ball_dx)/2; + } + } + // The recorded approach already clamps against the hedge. Correct only + // its column; the watched Up/A loop owns the final north interaction. + result.dy=0; + result.at_target=result.located && !result.coarse && std::abs(result.dx)<=1.0/128; + return result; +} + +OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame){ + // Actual crops from screenshot-20261018-171535781091. Separate left/right + // search regions prevent one identical statue from satisfying both votes. + OverworldMatchResult best;double best_score=-1; + if(!frame)return best; + // Normalized correlation also matches almost-black fade frames. Require + // visible statue regions before allowing those shapes to verify entry. + const cv::Mat gray=gray_image(frame); + const auto visible=[&](double x,double width){ + const cv::Rect box((int)(x*gray.cols),(int)(458.0/1080*gray.rows), + (int)(width*gray.cols),(int)(350.0/1080*gray.rows)); + return cv::mean(gray(box))[0]>=60; + }; + const bool statues_visible=visible(448.0/1920,146.0/1920) + &&visible(1322.0/1920,154.0/1920); + for(double scale:{.95,1.0,1.05}){ + std::vector configs={ + {"Left gym statue","PokemonBDSP/Autostory/EternaGym/LeftStatue.png", + {448.0/1920,458.0/1080,146.0/1920,350.0/1080},{.12,.30,.27,.53},.93,.20}, + {"Right gym statue","PokemonBDSP/Autostory/EternaGym/RightStatue.png", + {1322.0/1920,458.0/1080,154.0/1920,350.0/1080},{.60,.30,.28,.53},.93,.20} + }; + for(auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2;config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,.04,.06,true); + // Interior verification requires both statues near the expected entry + // positions, not merely anywhere in the room or during a fade. + candidate.at_target=candidate.at_target&&statues_visible; + candidate.located=candidate.at_target; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if((candidate.located&&!best.located) || (candidate.located==best.located&&score>best_score)){ + best=candidate;best_score=score; + } + } + return best; +} +OverworldMatchResult match_eterna_underground_house(const ImageRGB32& frame){ + // User's doorway pose: the Center ball and the same house sign, viewed + // from the east. The sign is optional when a strong unique ball confirms + // the doorway pose; cloud shadow on the sign must not block entry. + OverworldMatchResult best; + double best_score=-1; + bool best_primary_only=false; + for (double scale : {.90,.95,1.0,1.05,1.10}){ + std::vector configs{ + {"Center Pokeball","PokemonBDSP/Autostory/EternaUndergroundMan/HouseBall.png", + {332.0/1920,224.0/1080,112.0/1920,104.0/1080},{0,0,.65,.7},.93,.20}, + {"House sign","PokemonBDSP/Autostory/EternaUndergroundMan/HouseSign.png", + {1240.0/1920,396.0/1080,110.0/1920,106.0/1080},{.35,0,.65,.85},.93,.20} + }; + for (auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if(candidate.located){ + if(!best.located || best_primary_only || score>best_score){best=candidate;best_score=score;best_primary_only=false;} + }else if(!best.located || best_primary_only){ + // The optional door-side sign can change perspective or disappear in + // cloud shadow. Preserve the high uniqueness margin on the primary ball. + const auto& ball=candidate.landmarks[0]; + if(ball.accepted && ball.score>=.95 && ball.margin>=.30 && std::abs(ball.dx)<.20 && std::abs(ball.dy)<.20 && + (!best.located || ball.score>best_score)){ + best=candidate;best.located=true;best.coarse=false; + best.dx=ball.dx;best.dy=ball.dy; + best.at_target=std::abs(best.dx)<=1.0/128 && std::abs(best.dy)<=POSITION_TOLERANCE_Y; + best_score=ball.score;best_primary_only=true; + }else if(best.landmarks.empty())best=candidate; + } + } + return best; +} +OverworldMatchResult match_eterna_underground_man(const ImageRGB32& frame){ + // Reuse fixed furniture, excluding people and the animated TV picture. + // The right-window search excludes the identical window above the stool. + const std::vector configs{ + {"Stool","PokemonBDSP/Autostory/EternaUndergroundMan/Stool.png", + {380.0/1920,610.0/1080,86.0/1920,70.0/1080},{0,.3,.65,.5},.93,.20}, + {"Right tree top","PokemonBDSP/Autostory/EternaUndergroundMan/TreeTop.png", + {1372.0/1920,320.0/1080,102.0/1920,96.0/1080},{.5,0,.4,.7},.93,.20}, + {"Wall sign","PokemonBDSP/Autostory/EternaUndergroundMan/WallSign.png", + {642.0/1920,354.0/1080,114.0/1920,70.0/1080},{.2,0,.5,.6},.93,.20}, + {"Window beside TV","PokemonBDSP/Autostory/EternaUndergroundMan/RightWindow.png", + {906.0/1920,260.0/1080,162.0/1920,160.0/1080},{.35,0,.55,.55},.93,.20} + }; + auto result=match_landmarks(frame,configs,4,1.0/128,POSITION_TOLERANCE_Y,true); + if(!result.located){ + // Three agreeing furniture anchors identify the room during entry and + // guide acquisition when a fourth is clipped. Never authorize talking. + auto coarse=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); + if(coarse.located){coarse.coarse=true;coarse.at_target=false;return coarse;} + } + return result; +} +OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame){ + // The lighting-run pose was 1/108 screen height south of the original + // screenshot. Targets preserve the original destination height. + // Capture-derived references cover lamp, daylight, and moving tree shadows. + return match_lighting_pair(frame,{ + {"BuildingCorner","PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour3Frame4.png", + {0.588541666667,0.351851851852,0.088541666667,0.259259259259},{0,0,1,1},.93,.08}, + {"BuildingCorner","PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour14Frame2.png", + {0.588541666667,0.351851851852,0.088541666667,0.259259259259},{0,0,1,1},.93,.08}, + {"BuildingCorner","PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour17Frame4.png", + {0.588541666667,0.351851851852,0.088541666667,0.259259259259},{0,0,1,1},.93,.08}, + {"BuildingBase","PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour15Frame2.png", + {0.812500000000,0.388888888889,0.078125000000,0.203703703704},{0,0,1,1},.93,.08}, + {"BuildingBase","PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour17Frame5.png", + {0.812500000000,0.388888888889,0.078125000000,0.203703703704},{0,0,1,1},.93,.08}, + {"BuildingBase","PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour20Frame5.png", + {0.812500000000,0.388888888889,0.078125000000,0.203703703704},{0,0,1,1},.93,.08} + },"BuildingCorner","BuildingBase",POSITION_TOLERANCE_X,true); +} +OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame){ + // Capture-derived references cover lamp, daylight, and moving tree shadows. + auto result=match_lighting_pair(frame,{ + {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour2Frame2.png", + {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, + {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame1.png", + {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, + {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame2.png", + {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, + {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame5.png", + {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, + {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour16Frame5.png", + {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, + {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour23Frame2.png", + {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour3Frame1.png", + {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour5Frame4.png", + {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour11Frame3.png", + {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour14Frame1.png", + {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour19Frame1.png", + {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, + {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour21Frame4.png", + {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08} + // Small screen-relative quantization allowance avoids bouncing across the + // plaza target when one movement step spans both sides of the tolerance. + },"RoofEdge","Fence",POSITION_TOLERANCE_X,false,1.0/32); + if (!result.located && result.landmarks.size()==2){ + const auto& fence=result.landmarks[1]; + // A moving roof shadow must not trigger a blind lateral sweep when the + // distinctive fence still gives a strong, nearby position estimate. + // Fence-only guidance can reacquire the roof; it cannot confirm arrival. + if (fence.accepted && fence.score>=.95 && fence.margin>=.30 && + std::abs(fence.dx)<=.20 && std::abs(fence.dy)<=.20){ + result.located=true; + result.coarse=true; + result.at_target=false; + result.dx=fence.dx; + result.dy=fence.dy; + } + } + return result; +} +OverworldMatchResult match_route204_sign(const ImageRGB32& frame){ + // One vote per physical landmark. Lighting variants avoid treating the + // moving tree shadow over the fence as a missing route destination. + const ImageFloatBox sign_target{0.468750000000,0.376851851852,0.072916666667,0.052777777778}; + const ImageFloatBox fence_target{0.598958333333,0.421296296296,0.041666666667,0.152777777778}; + std::vector variants{ + {"Route sign","PokemonBDSP/Autostory/Route204Sign/Sign.png",sign_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCorner.png",fence_target,{0,0,1,1},.93,.10}, + {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour0Frame3.png",sign_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour0Frame3.png",fence_target,{0,0,1,1},.93,.10}, + {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour8Frame3.png",sign_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour8Frame3.png",fence_target,{0,0,1,1},.93,.10}, + {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png",sign_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour13Frame3.png",fence_target,{0,0,1,1},.93,.10}, + {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour17Frame3.png",sign_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour17Frame3.png",fence_target,{0,0,1,1},.93,.10}, + {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour2Frame1.png",sign_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour2Frame1.png",fence_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour10Frame3.png",fence_target,{0,0,1,1},.93,.10}, + {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerRecordedCloud.png",fence_target,{0,0,1,1},.93,.10}, + }; + auto result=match_lighting_pair(frame,variants,"Route sign","Fence corner"); + if (!result.located && result.landmarks.size()==2){ + const auto& sign=result.landmarks[0]; + // A distinctive high-confidence sign can guide bounded acquisition. + // It cannot confirm the endpoint without the fence agreeing as well. + if (sign.accepted && sign.score>=.95 && sign.margin>=.30 && + std::abs(sign.dx)<=.20 && std::abs(sign.dy)<=.25){ + result.located=true; + result.coarse=true; + result.at_target=false; + result.dx=sign.dx; + result.dy=sign.dy; + } + } + return result; +} +static OverworldMatchResult match_route205_marker(const ImageRGB32& frame, double cx, double cy){ + OverworldMatchResult best; + // Native sign faces at the two route positions. The post, moving NPCs, + // grass and water are excluded from the acquisition references. + for (int variant : {0,1,2,3,4,5,6}) { + for (double scale : {.9,1.0,1.1,1.2}) { + const double w=(variant==6?188.0:variant==5?184.0:variant==4?220.0:variant==3?211.0:variant==2?208.0:variant==1?206.0:205.0)/1920*scale; + const double h=(variant==6?74.0:variant>=3?72.0:variant?80.0:82.0)/1080*scale; + auto candidate=match_landmarks(frame,{{"Route 205 marker", + variant==6?"PokemonBDSP/Autostory/Route205/BridgeSignMidCloud.png": + variant==5?"PokemonBDSP/Autostory/Route205/BridgeSignCloud.png": + variant==4?"PokemonBDSP/Autostory/Route205/BridgeSignLow.png": + variant==3?"PokemonBDSP/Autostory/Route205/BridgeSignLive.png": + variant==2?"PokemonBDSP/Autostory/Route205/NorthSignRecorded.png": + variant==1?"PokemonBDSP/Autostory/Route205/NorthSign.png": + "PokemonBDSP/Autostory/Route205/BridgeSign.png", + {cx-w/2,cy-h/2,w,h},{0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + return best; +} +OverworldMatchResult match_route205_zackary(const ImageRGB32& frame){ + OverworldMatchResult best; + double best_score=-1; + for (int view : {0,1,2,3}) { + for (double scale : {.9,1.0,1.1}) { + const double sw=91.0/1920*scale,sh=65.0/1080*scale; + // Include the fixed cliff edge beside both pipes: the repeated blue caps + // alone produce a second strong peak in cloud shadow. + const double cw=300.0/1920*scale,ch=300.0/1080*scale; + auto candidate=match_landmarks(frame,{ + {"Zackary bridge sign",view==2?"PokemonBDSP/Autostory/Route205/PatrolSignCloud.png": + view>=1?"PokemonBDSP/Autostory/Route205/PatrolSignLive.png":"PokemonBDSP/Autostory/Route205/PatrolSign.png", + {959.5/1920-sw/2,67.5/1080-sh/2,sw,sh},{0,0,1,1},.93,.20}, + {"Zackary twin chimneys",view==3?"PokemonBDSP/Autostory/Route205/PatrolChimneysPartialCloud.png": + view==2?"PokemonBDSP/Autostory/Route205/PatrolChimneysCloud.png": + view==1?"PokemonBDSP/Autostory/Route205/PatrolChimneysLive.png":"PokemonBDSP/Autostory/Route205/PatrolChimneys.png", + {1210.0/1920-cw/2,290.0/1080-ch/2,cw,ch},{0,0,1,1},.93,.20} + },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if (best.located && candidate.located && + (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)) { + best.located=false;best.at_target=false;return best; + } + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)) {best=std::move(candidate);best_score=score;} + } + } + return best; +} +OverworldMatchResult match_route205_kelsey(const ImageRGB32& frame){ + OverworldMatchResult best; + // This item is retained on the route. Restrict acquisition to the left of + // the player, excluding the red cap and the northern bridge's item ball. + for (double scale : {.9,1.0,1.1}) { + const double w=56.0/1920*scale,h=53.0/1080*scale; + auto candidate=match_landmarks(frame,{{"Kelsey west item ball", + "PokemonBDSP/Autostory/Route205/KelseyItem.png", + {280.0/1920-w/2,574.5/1080-h/2,w,h},{0,.2,.48,.65},.93,.20}}, + 1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=std::move(candidate); + } + return best; +} +OverworldMatchResult match_route205_bank_tree(const ImageRGB32& frame){ + // Use the tree crown above the player's reach, plus the stationary child. + // Recorded cloud-edge views retain both anchor gates and agreement. + // Trunk crops can include the player's cap when he stands under the tree. + OverworldMatchResult best; + double best_score=-1; + for (int view : {0,1,2,3,4,5,6,7,8}) { + for (double scale : {.9,1.0,1.1}) { + const double tw=140.0/1920*scale,th=130.0/1080*scale; + const double nw=120.0/1920*scale,nh=170.0/1080*scale; + auto candidate=match_landmarks(frame,{ + {"Bank tree crown",view==8?"PokemonBDSP/Autostory/Route205/BankTreeCrownWest.png": + view==7?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud333.png": + view==6?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud76.png": + view==5?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud336.png": + view==4?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud328.png": + view==3?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud80.png": + view==2?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud72.png": + view==1?"PokemonBDSP/Autostory/Route205/BankTreeCrownLive.png": + "PokemonBDSP/Autostory/Route205/BankTreeCrown.png", + {770.0/1920-tw/2,235.0/1080-th/2,tw,th},{0,0,1,1},.93,.20}, + {"Bank blue-hat child",view==8?"PokemonBDSP/Autostory/Route205/BankBlueHatWest.png": + view==7?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud333.png": + view==6?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud76.png": + view==5?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud336.png": + view==4?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud328.png": + view==3?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud80.png": + view==2?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud72.png": + view==1?"PokemonBDSP/Autostory/Route205/BankBlueHatLive.png": + "PokemonBDSP/Autostory/Route205/BankBlueHat.png", + {700.0/1920-nw/2,525.0/1080-nh/2,nw,nh},{0,0,1,1},.93,.20} + },2,1.0/96,POSITION_TOLERANCE_Y,true); + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if (best.located && candidate.located && + (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)) { + best.located=false;best.at_target=false;return best; + } + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)) { + best=std::move(candidate);best_score=score; + } + } + } + return best; +} +OverworldMatchResult match_route205_bridge(const ImageRGB32& frame){ + return match_route205_marker(frame,722.5/1920,465.0/1080); +} +OverworldMatchResult match_route205_forest_entrance(const ImageRGB32& frame){ + return match_route205_marker(frame,1245.0/1920,508.0/1080); +} +OverworldMatchResult match_windworks_interior_grunt(const ImageRGB32& frame){ + OverworldMatchResult result; + cv::Mat pixels((int)frame.height(),(int)frame.width(),CV_8UC4, + (void*)frame.data(),frame.bytes_per_row()); + cv::Mat small,bgr,hsv; + cv::resize(pixels,small,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::cvtColor(small,bgr,cv::COLOR_BGRA2BGR); + cv::cvtColor(bgr,hsv,cv::COLOR_BGR2HSV); + // Grey indoor tiles distinguish this scene from the outdoor grunt, battle + // screens and fades. Pale turquoise partitions have too little saturation + // to qualify as the grunt's hair. + cv::Mat floor; + cv::inRange(hsv,cv::Scalar(0,0,100),cv::Scalar(179,65,255),floor); + const cv::Rect room(288,81,624,405); + if ((double)cv::countNonZero(floor(room))/room.area()<.30) return result; + auto head=[&](cv::Scalar low,cv::Scalar high,bool player,cv::Point2d& center) { + cv::Mat mask,labels,stats,centroids; + cv::inRange(hsv,low,high,mask); + const int count=cv::connectedComponentsWithStats(mask,labels,stats,centroids); + int best=-1,best_area=0,runner_area=0; + for (int i=1;i(i,cv::CC_STAT_AREA); + int w=stats.at(i,cv::CC_STAT_WIDTH),h=stats.at(i,cv::CC_STAT_HEIGHT); + double x=centroids.at(i,0),y=centroids.at(i,1); + if (area<80 || area>4000 || w<12 || w>110 || h<8 || h>90 || + (double)area/(w*h)<.35 || x<288 || x>(player?672:864) || + y<(player?135:15) || y>459) continue; + if (area>best_area) {runner_area=best_area;best_area=area;best=i;} + else runner_area=std::max(runner_area,area); + } + if (best<0 || runner_area>best_area*.70) return false; + center={centroids.at(best,0),centroids.at(best,1)}; + return true; + }; + cv::Point2d player,grunt; + if (!head({0,120,120},{22,255,255},true,player) || + !head({75,100,120},{105,255,255},false,grunt)) return result; + if (std::abs(player.x-grunt.x)>288 || std::abs(player.y-grunt.y)>350) return result; + result.located=true; + result.dx=(grunt.x-player.x)/960; + result.dy=(grunt.y-player.y)/540; + return result; +} +OverworldMatchResult match_windworks_sign_approach(const ImageRGB32& frame){ + // Sign beside the open lane west of the doorway turbine. Keep the player + // east of the sign/grass before the blind building-wall approach. + OverworldMatchResult best; + // The sign grows as it approaches the bottom of the camera view. Scale + // acquisition templates around the same target center, keeping all gates. + for (int view : {0,1,2}) { + for (double scale : {1.0,1.1,1.2,1.3}) { + const double w=(view==2?124.0:view==1?144.0:121.0)/1920*scale; + const double h=(view==1?140.0:122.0)/1080*scale; + auto candidate=match_landmarks(frame,{{"Windworks approach sign", + view==2?"PokemonBDSP/Autostory/WindworksApproach/SignNight.png": + view==1?"PokemonBDSP/Autostory/WindworksApproach/SignLow.png": + "PokemonBDSP/Autostory/WindworksApproach/Sign.png", + {836.5/1920-w/2,515.0/1080-h/2,w,h}, + {0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + // Face-only references cover the high/mid camera perspectives without + // relying on the moving grass that can cover the sign's lower border. + for (bool high_view : {false,true}) { + for (double scale : {.9,1.0,1.1}) { + const double w=(high_view?86.0:106.0)/1920*scale; + const double h=(high_view?72.0:94.0)/1080*scale; + auto candidate=match_landmarks(frame,{{"Windworks approach sign face", + high_view?"PokemonBDSP/Autostory/WindworksApproach/SignHighFace.png": + "PokemonBDSP/Autostory/WindworksApproach/SignMidFace.png", + {838.0/1920-w/2,516.0/1080-h/2,w,h}, + {0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if ((candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + return best; +} +OverworldMatchResult match_route204_floaroma_turn(const ImageRGB32& frame){ + OverworldMatchResult best; + // The northern 204 marker is wider in this camera view. Match its face, + // placing it above the player so the westward turn clears its post and + // the flower-bed fence. Align horizontally with the sign before the + // subsequent westward route, rather than staying in the eastern lane. + for (double scale_x : {1.0,1.05,1.10,1.15,1.20}) { + for (double scale_y : {.90,1.0,1.10}) { + const double w=140.0/1920*scale_x, h=57.0/1080*scale_y; + auto candidate=match_landmarks(frame,{{"Northern Route 204 sign", + "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", + {.5-w/2,420.0/1080-h/2,w,h},{0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + return best; +} +OverworldMatchResult match_route204_north_approach(const ImageRGB32& frame){ + auto result=match_route204_sign(frame); + // The pre-battle reference stands below the sign. After Tyler, place + // the player one body-width west of it before starting north. + constexpr double west_offset=.04; + for (auto& anchor:result.landmarks){anchor.dx-=west_offset;} + result.dx-=west_offset; + if (!result.located && !result.landmarks.empty()){ + const auto& sign=result.landmarks[0]; + if (sign.accepted && sign.score>=.93 && sign.margin>=.30 && + std::abs(sign.dx)<=.45 && std::abs(sign.dy)<=.25){ + result.located=true;result.coarse=true;result.dx=sign.dx;result.dy=sign.dy; + } + } + result.at_target=result.located && !result.coarse; + for (const auto& anchor:result.landmarks) + if (!anchor.accepted || std::abs(anchor.dx)>1.0/128 || + std::abs(anchor.dy)>POSITION_TOLERANCE_Y) result.at_target=false; + return result; +} +OverworldMatchResult match_floaroma_house_entrance(const ImageRGB32& frame){ + // Fixed facade details above/aside the player avoid door/player occlusion. + // Both must agree on the pose recorded just outside the central house. + return match_landmarks(frame,{ + {"House window","PokemonBDSP/Autostory/FloaromaHouse/Window.png", + {1060.0/1920,370.0/1080,128.0/1920,106.0/1080},{0,0,1,1},.93,.10}, + {"House gable","PokemonBDSP/Autostory/FloaromaHouse/Gable.png", + {796.0/1920,196.0/1080,460.0/1920,154.0/1080},{0,0,1,1},.93,.10} + },2,1.0/64,1.0/54,true); +} +OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame){ + OverworldMatchResult best; + // Perspective changes the emblem size as we approach the doorway gap. + for (double scale : {.90,.95,1.0,1.05,1.10}){ + const double w=105.0/1920*scale, h=86.0/1080*scale; + auto candidate=match_landmarks(frame,{{"Center emblem", + "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", + {.5-w/2,135.0/1080-h/2,w,h},{0,0,1,1},.93,.20}},1,1.0/64,1.0/36,true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + // At the west wall the camera makes the emblem nearly circular, unlike + // the vertically compressed doorway reference. Search that recorded + // perspective without lowering the score or uniqueness requirements. + if (!best.located) { + for (double scale_x : {1.0,1.05,1.10,1.15,1.20}) { + for (double scale_y : {1.15,1.25,1.35,1.45}) { + const double w=105.0/1920*scale_x, h=86.0/1080*scale_y; + auto candidate=match_landmarks(frame,{{"Center emblem west perspective", + "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", + {.5-w/2,135.0/1080-h/2,w,h},{0,0,1,1},.93,.20}},1,1.0/64,1.0/36,true); + candidate.coarse=true; + candidate.at_target=false; + if ((candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + } + // A lower-face reference excludes the tester's burned-in overlay line + // and covers the doorway-facing perspective, retaining score/margin gates. + auto lower=match_landmarks(frame,{{"Center emblem lower face", + "PokemonBDSP/Autostory/FloaromaCenter/EmblemLower.png", + {.5-52.0/1920,114.0/1080,104.0/1920,65.0/1080},{0,0,1,1},.93,.20}}, + 1,1.0/64,1.0/36,true); + if ((lower.located && (!best.located || best.coarse)) || (lower.located==best.located && + lower.landmarks[0].score>best.landmarks[0].score)) best=lower; + return best; +} +OverworldMatchResult match_route204_north_stairs(const ImageRGB32& frame){ + OverworldMatchResult best; + // Fixed rails/steps, with sunny and cloud-covered perspectives. + for (const char* path:{"PokemonBDSP/Autostory/Route204NorthStairs/Stairs.png", + "PokemonBDSP/Autostory/Route204NorthStairs/StairsCloud.png"}){ + // Camera pitch changes height and width by different amounts. + for (double scale_x:{.90,1.0,1.10}) for (double scale_y:{.80,.90,1.0,1.10}){ + const double w=298.0/1920*scale_x,h=282.0/1080*scale_y; + auto candidate=match_landmarks(frame,{{"North route stairs",path, + {.5-w/2,650.0/1080,w,h},{0,0,1,1},.93,.10}},1,1.0/64,1.0/54,true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + if (!best.located){ + // The lower steps distort as the camera moves north. The rail tops, + // grass edge and first steps retain a stable physical upper edge. + for (const auto& config:std::vector{ + {"North route stairs top","PokemonBDSP/Autostory/Route204NorthStairs/Top.png", + {.5-149.0/1920,640.0/1080,298.0/1920,70.0/1080},{0,0,1,1},.93,.10}, + {"North route stairs top","PokemonBDSP/Autostory/Route204NorthStairs/TopRaised.png", + {.5-157.0/1920,640.0/1080,314.0/1920,70.0/1080},{0,0,1,1},.93,.10} + }) for (double scale_x:{.95,1.0,1.05}) for (double scale_y:{.90,1.0,1.10}){ + auto variant=config; + variant.target.width*=scale_x;variant.target.height*=scale_y; + variant.target.x=.5-variant.target.width/2; + auto candidate=match_landmarks(frame,{variant},1,1.0/64,1.0/54,true); + if ((candidate.located && !best.located) || + (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + } + } + return best; +} +OverworldMatchResult match_teighler_alcove_corner(const ImageRGB32& frame){ + auto result=match_route204_north_stairs(frame); + // Reuse the fixed stairs only as an anchor. The destination is the + // bottom-left of Teighler's alcove, east of the stairs, not their center. + // Geometry comes from the recorded 1920x1080 corner reference. + constexpr double shift_x=393.0/1920, shift_y=182.0/1080; + result.dx+=shift_x;result.dy+=shift_y; + for (auto& anchor:result.landmarks){anchor.dx+=shift_x;anchor.dy+=shift_y;} + result.at_target=result.located && std::abs(result.dx)<=1.0/64 && + std::abs(result.dy)<=1.0/54; + return result; +} +OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame){ + const ImageFloatBox mouth{860.0/1920,220.0/1080,200.0/1920,155.0/1080}; + const ImageFloatBox sign{810.0/1920,380.0/1080,100.0/1920,110.0/1080}; + std::vector variants; + // A different approach height changes perspective. Compare a small range + // of sizes, keeping each physical anchor's reference center fixed. + for (double scale : {.90,1.0,1.10}){ + for (const auto& config : std::vector{ + {"Cave mouth","PokemonBDSP/Autostory/RavagedPathSouthEntrance/Mouth.png",mouth,{0,0,1,1},.93,.10}, + {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/Sign.png",sign,{0,0,1,1},.93,.10}, + {"Cave mouth","PokemonBDSP/Autostory/RavagedPathSouthEntrance/MouthShadow.png",mouth,{0,0,1,1},.93,.10}, + {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignShadow.png",sign,{0,0,1,1},.93,.10}, + {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignPartialShadow.png",sign,{0,0,1,1},.93,.10}, + {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFace.png",{828.0/1920,400.0/1080,64.0/1920,62.0/1080},{0,0,1,1},.93,.10}, + {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceLowApproach.png",{828.0/1920,400.0/1080,64.0/1920,62.0/1080},{0,0,1,1},.93,.10}, + {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceCloud.png",{828.0/1920,400.0/1080,64.0/1920,62.0/1080},{0,0,1,1},.93,.10} + }){ + auto variant=config; + variant.target.width*=scale; + variant.target.height*=scale; + variant.target.x+=(config.target.width-variant.target.width)/2; + variant.target.y+=(config.target.height-variant.target.height)/2; + variants.push_back(variant); + } + } + auto result=match_lighting_pair(frame,variants,"Cave mouth","Entrance sign",1.0/64,true); + if (!result.located && result.landmarks.size()==2){ + for (const auto& landmark : result.landmarks){ + if (landmark.accepted && landmark.score>=.95 && landmark.margin>=.20 && + std::abs(landmark.dx)<=.35 && std::abs(landmark.dy)<=.40){ + result.located=true;result.coarse=true;result.at_target=false; + result.dx=landmark.dx;result.dy=landmark.dy; + break; + } + } + } + // The moving blue-hatted boy is deliberately absent from the references. + // The lass is optional evidence for the southern movement boundary, never + // a replacement for the fixed mouth/sign arrival anchors. + auto boundary=match_landmarks(frame,{{"Lass south boundary", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Lass.png", + {910.0/1920,788.0/1080,100.0/1920,142.0/1080},{0,0,1,1},.88,.08 + }},1,1.0/64,1.0/36,true); + if (!boundary.landmarks.empty()) result.landmarks.push_back(boundary.landmarks[0]); + return result; +} +const std::vector& overworld_matchers(){ static const std::vector matchers={ + {"route204-north-approach","Route 204 left of sign after Tyler",match_route204_north_approach}, + {"teighler-alcove-corner","Teighler alcove bottom-left corner",match_teighler_alcove_corner}, + {"route204-north-stairs","Route 204 north stairs junction",match_route204_north_stairs}, + {"floaroma-center-entrance","Floaroma Pokemon Center entrance",match_floaroma_center_entrance}, + {"ravaged-path-south-entrance","Ravaged Path south entrance",match_ravaged_path_south_entrance}, + {"route204-sign","Route 204 sign before the next trainer",match_route204_sign}, + {"windworks-sign","Valley Windworks west of the door grunt",match_windworks_sign_approach}, + {"route205-bridge","Route 205 south of the bridge",match_route205_bridge}, + {"route205-bank-tree","Route 205 north bank beside the golden tree",match_route205_bank_tree}, + {"route205-zackary","Route 205 Zackary patrol intercept",match_route205_zackary}, + {"route205-kelsey","Route 205 Kelsey patrol intercept",match_route205_kelsey}, + {"route205-forest","Route 205 outside Eterna Forest",match_route205_forest_entrance}, + {"jubilife-street-approach","Jubilife street before Galactic plaza",match_jubilife_street_approach}, + {"jubilife-plaza-approach","Jubilife Galactic plaza approach",match_jubilife_plaza_approach}, + {"poketch-landmarks","Jubilife Poketch landmarks",match_poketch_landmarks}, + {"oreburgh-mart","Oreburgh Mart emblem",match_oreburgh_mart}, + {"jubilife-center","Jubilife Pokemon Center entrance",match_jubilife_center_entrance}, + {"eterna-center","Eterna Pokemon Center doorway",match_eterna_center_entrance}, + {"eterna-gym","Eterna Gym doorway",match_eterna_gym_entrance}, + {"galactic-hq-jupiter","Galactic HQ Jupiter interaction",match_galactic_hq_jupiter}, + {"eterna-gym-statues","Eterna Gym interior statues",match_eterna_gym_statues}, + {"eterna-underground-house","Eterna Underground Man house doorway",match_eterna_underground_house}, + {"eterna-underground-man","Eterna Underground Man interaction",match_eterna_underground_man}, + {"route201-potion-exit","Route 201 after Potion pickup",match_route201_potion_exit}, + {"sandgem-junction","Sandgem Center/Mart junction",match_sandgem_junction}, + {"route202-grass-exit","Route 202 after catching: grass exit",match_route202_grass_exit}, + }; + return matchers; +} +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 2e419a10ce..4fefc6f8fb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -1,37 +1,66 @@ -#pragma once -#include -#include -#include -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/ImageTypes/ImageRGB32.h" -namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ -struct OverworldLandmarkResult { - std::string name; - double score = 0, margin = 0; - // Signed fractions of screen width/height, independent of capture resolution. - double dx = 0, dy = 0; - bool accepted = false; - ImageFloatBox search, found; -}; -struct OverworldMatchResult { - std::vector landmarks; - bool located = false, at_target = false; - bool coarse = false; - // Signed fractions of screen width/height, independent of capture resolution. - double dx = 0, dy = 0; -}; -struct OverworldMatcher { - std::string slug, name; - std::function match; -}; -// Register additional functions here; each owns its landmarks and coordinate system. -const std::vector& overworld_matchers(); -OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame); -OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame); -OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame); -OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame); -OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame); -OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame); -OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame); -OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame); -}}} +#pragma once +#include +#include +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +struct OverworldLandmarkResult { + std::string name; + double score = 0, margin = 0; + // Signed fractions of screen width/height, independent of capture resolution. + double dx = 0, dy = 0; + bool accepted = false; + ImageFloatBox search, found; +}; +struct OverworldMatchResult { + std::vector landmarks; + bool located = false, at_target = false; + bool coarse = false; + // Signed fractions of screen width/height, independent of capture resolution. + double dx = 0, dy = 0; +}; +struct OverworldMatcher { + std::string slug, name; + std::function match; +}; +// Register additional functions here; each owns its landmarks and coordinate system. +const std::vector& overworld_matchers(); +OverworldMatchResult match_route204_north_approach(const ImageRGB32& frame); +OverworldMatchResult match_teighler_alcove_corner(const ImageRGB32& frame); +OverworldMatchResult match_route204_north_stairs(const ImageRGB32& frame); +OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame); +OverworldMatchResult match_floaroma_house_entrance(const ImageRGB32& frame); +OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame); +OverworldMatchResult match_route204_sign(const ImageRGB32& frame); +OverworldMatchResult match_windworks_sign_approach(const ImageRGB32& frame); +// Interior only: offsets from the player's red cap to the grunt's cyan hair. +// Positive dy > .025 means the player is above him; otherwise below/level. +OverworldMatchResult match_windworks_interior_grunt(const ImageRGB32& frame); +OverworldMatchResult match_route205_bridge(const ImageRGB32& frame); +OverworldMatchResult match_route205_bank_tree(const ImageRGB32& frame); +OverworldMatchResult match_route205_zackary(const ImageRGB32& frame); +OverworldMatchResult match_route205_kelsey(const ImageRGB32& frame); +OverworldMatchResult match_route205_forest_entrance(const ImageRGB32& frame); +// Northern sign: align primarily westward, then correct height below the flower bed. +OverworldMatchResult match_route204_floaroma_turn(const ImageRGB32& frame); +OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame); +OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame); +OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame); +OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame); +OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame); +OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame); +OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame); +OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame); +OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame); +OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame); +// Two unique, agreeing landmarks close to the doorway; this confidence only +// authorizes a watched entry attempt, never proves the interior location. +double overworld_entry_confidence(const OverworldMatchResult& result, + double tolerance_x=1.0/50, double tolerance_y=1.0/27, double minimum_margin=.15); +OverworldMatchResult match_eterna_underground_house(const ImageRGB32& frame); +OverworldMatchResult match_eterna_underground_man(const ImageRGB32& frame); +OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame); +OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame); +OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame); +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/PokemonBDSP_GameNavigation.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/PokemonBDSP_GameNavigation.cpp index 35d8083ce7..a9f7635dbe 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/PokemonBDSP_GameNavigation.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/PokemonBDSP_GameNavigation.cpp @@ -5,6 +5,8 @@ */ #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/PokemonBDSP_Settings.h" @@ -74,6 +76,12 @@ void overworld_to_menu(VideoStream& stream, ProControllerContext& context){ {{detector}} ); if (ret < 0){ + const VideoSnapshot snapshot = stream.video().snapshot(); + if (snapshot){ + stream.log(detector.debug_report(snapshot), COLOR_ORANGE); + }else{ + stream.log("MenuDetector report: no video frame available.", COLOR_ORANGE); + } OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, "Menu not detected after 10 seconds.", From 7cfa6a6ae5bb0048c413e89c49a2f27cd184b47b Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Wed, 21 Oct 2026 17:20:32 -0500 Subject: [PATCH 27/39] Harden BDSP segment 03 route automation Refactors AutoStory segment 03 to rely on visual confirmations and encounter watchers instead of long fixed-input sequences. Adds reusable helpers for transition completion, scene confirmation, expected trainer encounters, and bike registration flow, then rewires the Jupiter-to-Coronet path to use them. Also adds new overworld matchers (Cycle Shop entry/interior/counter/reward, Cycling Road north entry, Galactic HQ exterior, Eterna Center departure) and updates Jupiter-room landmark references to avoid NPC overlap, improving route reliability and recovery from timing variance. --- .../PokemonBDSP_AutoStory_Segment_03.cpp | 343 ++++++++++-------- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 82 ++++- .../Utils/PokemonBDSP_OverworldMatchers.h | 7 + 3 files changed, 273 insertions(+), 159 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index d05af14398..01e1c704d1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -8,6 +8,10 @@ #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include +#include +#include "CommonFramework/Globals.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" @@ -42,6 +46,99 @@ namespace PokemonBDSP{ pbf_press_button(context,button,hold,release); } + static void segment03_finish_transition(VideoStream& stream, ProControllerContext& context, + const std::string& label) { + BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{visible}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Transition did not finish.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + } + + // Stationary destination confirmation: do not steer during an NPC reward + // scene or declare arrival from a fade that has only just started. + static void segment03_confirm_location(VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, + size_t observations = 20, bool advance_dialogue = false, bool require_target = false) { + context.wait_for_all_requests(); + size_t confirmations=0; + for (size_t observation=0;observation=3) { stream.log("["+label+"] Scene confirmed.",COLOR_GREEN); return; } + pbf_wait(context,250ms); + } else { + confirmations=0; + if (advance_dialogue) pbf_press_button(context,BUTTON_A,150ms,500ms); + else pbf_wait(context,500ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Expected scene not confirmed.",stream); + } + + // Watch the approach itself, cancelling queued movement as soon as the + // trainer speaks or a battle starts. The existing battle engine owns combat. + static void segment03_expected_encounter(VideoStream& stream, ProControllerContext& context, + const std::string& label, const std::function& approach) { + context.wait_for_all_requests(); + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + if (run_until(stream,context, + [&](ProControllerContext& ctx) { approach(ctx); pbf_wait(ctx,45000ms); }, + {{dialogue},{starting},{menu}}) < 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Expected trainer encounter did not start.",stream); + stream.log("["+label+"] Encounter detected; route movement cancelled."); + segment03_route_battle(stream,context,label,false); + } + + static void segment03_wait_image(VideoStream& stream, ProControllerContext& context, + const std::string& resource, const ImageFloatBox& box, const std::string& label) { + auto reference=std::make_shared(RESOURCE_PATH()+resource); + ImageMatchWatcher ready(reference,box,25.0,false,250ms); + if (wait_until(stream,context,5s,{{ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Visual confirmation missing.",stream); + } + + static void segment03_register_bike(VideoStream& stream, ProControllerContext& context) { + if (!clear_new_item_flags(stream,context)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Bike: Could not clear reward flags.",stream); + BagItemTemplate bike; + bike.reference_path="PokemonBDSP/Autostory/EternaBikeShop/BikeBag.png"; + bike.label="Bike"; + bike.reference_row=4; + if (!find_bag_item(stream,context,bike,8)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Bike: Bike selection not confirmed.",stream); + pbf_press_button(context,BUTTON_A,150ms,500ms); + context.wait_for_all_requests(); + segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikePopup.png", + {.79,.44,.15,.05},"Bike Register menu"); + pbf_press_dpad(context,DPAD_DOWN,80ms,250ms); + pbf_press_button(context,BUTTON_A,150ms,500ms); + context.wait_for_all_requests(); + segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikeRegisterWheel.png", + {.475,.375,.05,.085},"Bike registration wheel"); + pbf_press_dpad(context,DPAD_RIGHT,80ms,250ms); + pbf_press_button(context,BUTTON_A,150ms,500ms); + context.wait_for_all_requests(); + segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikeRegistered.png", + {.605,.365,.075,.11},"Registered Bike icon"); + segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikeRegistered.png", + {.285,.835,.32,.048},"Bike registration confirmation"); + pbf_mash_button(context,BUTTON_B,2000ms); + context.wait_for_all_requests(); + stream.log("[Eterna Bike] Registration confirmed; preserving the reward's mounted state.",COLOR_GREEN); + } + // Stop at the completed doorway transition, rather than spending the // remaining movement budget inside the next map. static void segment03_doorway(VideoStream& stream, ProControllerContext& context, @@ -76,8 +173,7 @@ namespace PokemonBDSP{ },{{transition}}); if (result!=0) OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT,label+": Doorway transition not confirmed.",stream); - pbf_wait(context,500ms); - context.wait_for_all_requests(); + segment03_finish_transition(stream,context,label); } void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context) { @@ -297,6 +393,8 @@ namespace PokemonBDSP{ },{{hq_leaving}}) != 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Galactic HQ: Exit transition not confirmed.",stream); + segment03_finish_transition(stream,context,"Galactic HQ exit"); + segment03_confirm_location(stream,context,match_galactic_hq_exterior,"Galactic HQ exterior"); // Clamp against the uncut front bush; checkpoint 03.02 starts here. pbf_move_left_joystick(context,{0,-1},350ms,500ms); context.wait_for_all_requests(); @@ -316,7 +414,9 @@ namespace PokemonBDSP{ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Mart: Could not buy 3 Escape Ropes.",stream); segment03_doorway(stream,context,false,5000ms,"Eterna Mart exit"); - clear_new_item_flags(stream,context); + if (!clear_new_item_flags(stream,context)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Mart: Could not clear purchase flags.",stream); // Retrace to the Center column, then use its doorway to normalize the // position. Do not reverse the time spent loading the Mart interior. @@ -324,20 +424,51 @@ namespace PokemonBDSP{ pbf_move_left_joystick(context,{-1,0},587ms,100ms); segment03_doorway(stream,context,true,5000ms,"Eterna Center return after Mart"); segment03_doorway(stream,context,false,5000ms,"Eterna Center exit after Mart"); + align_to_overworld_landmarks(stream,context,match_eterna_center_departure, + "Eterna Center departure after Mart",false,true); + } + + static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + auto frame=stream.video().snapshot(); + if (frame && match_eterna_cycle_shop_interior(*frame.frame).located) { + segment03_confirm_location(stream,context,match_eterna_cycle_shop_interior, + "Eterna bicycle shop interior"); + return; + } + bool entered=false; + // Watch the entire approach AND the locator. A fade cancels all queued + // input, even if the doorway is crossed before alignment has finished. + BlackScreenWatcher entering; + const int result=run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,-1},1514ms,0ms); + pbf_wait(ctx,177ms); + pbf_move_left_joystick(ctx,{1,0},587ms,0ms); + pbf_wait(ctx,522ms); + pbf_move_left_joystick(ctx,{1,0},144ms,0ms); + pbf_wait(ctx,439ms); + align_to_overworld_landmarks(stream,ctx,match_eterna_cycle_shop_entrance, + "Eterna bicycle shop entrance",false,true,false, + match_eterna_cycle_shop_interior,&entered); + if (!entered) { + pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); + pbf_wait(ctx,3000ms); + } + },{{entering}}); + if (result!=0 && !entered) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna bicycle shop: Entry transition not detected.",stream); + segment03_finish_transition(stream,context,"Eterna bicycle shop entrance"); + segment03_confirm_location(stream,context,match_eterna_cycle_shop_interior, + "Eterna bicycle shop interior"); + stream.log("[Eterna bicycle shop] Interior confirmed; outdoor alignment stopped.",COLOR_GREEN); } void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context) { // Continue from 03.01 outside HQ, directly in front of the Cut bush. - segment03_route_press(stream,context, BUTTON_A, 232ms, 0ms); - pbf_wait(context, 314ms); - segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); - pbf_wait(context, 129ms); - segment03_route_press(stream,context, BUTTON_A, 163ms, 0ms); - pbf_wait(context, 116ms); - segment03_route_press(stream,context, BUTTON_A, 174ms, 0ms); - pbf_wait(context, 173ms); - segment03_route_press(stream,context, BUTTON_A, 165ms, 0ms); - pbf_wait(context, 4174ms); + use_HM(stream,context,"Galactic HQ exit Cut"); + clear_post_battle_dialogue(stream,context,BUTTON_A,6000ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1413ms, 0ms); pbf_wait(context, 182ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 712ms, 0ms); @@ -346,146 +477,46 @@ namespace PokemonBDSP{ pbf_wait(context, 27ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); pbf_wait(context, 868ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 866ms, 0ms); + align_to_overworld_landmarks(stream,context,match_eterna_center_entrance, + "Eterna Center after Jupiter",false,true); + segment03_doorway(stream,context,true,4000ms,"Eterna Center entrance after Jupiter"); if (!heal_pokemon(stream,context,"Eterna after Jupiter")) OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit not confirmed.",stream); + segment03_finish_transition(stream,context,"Eterna Center heal exit"); + align_to_overworld_landmarks(stream,context,match_eterna_center_departure, + "Eterna Center departure to Mart",false,true); eterna_mart_escape_ropes(stream,context); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1514ms, 0ms); - pbf_wait(context, 177ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 587ms, 0ms); - pbf_wait(context, 522ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 144ms, 0ms); - pbf_wait(context, 439ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1078ms, 0ms); - pbf_wait(context, 3295ms); + segment03_enter_cycle_shop(stream,context); pbf_move_left_joystick(context, {0.000000, 1.000000}, 505ms, 0ms); pbf_wait(context, 405ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 568ms, 0ms); pbf_wait(context, 403ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 642ms, 0ms); pbf_wait(context, 14ms); - segment03_route_press(stream,context, BUTTON_A, 186ms, 0ms); - pbf_wait(context, 612ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 190ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 376ms); - segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); - pbf_wait(context, 122ms); - segment03_route_press(stream,context, BUTTON_A, 158ms, 0ms); - pbf_wait(context, 242ms); - segment03_route_press(stream,context, BUTTON_A, 174ms, 0ms); - pbf_wait(context, 472ms); - segment03_route_press(stream,context, BUTTON_A, 126ms, 0ms); - pbf_wait(context, 8007ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 83ms); - segment03_route_press(stream,context, BUTTON_A, 143ms, 0ms); - pbf_wait(context, 1005ms); - segment03_route_press(stream,context, BUTTON_A, 101ms, 0ms); - pbf_wait(context, 268ms); - segment03_route_press(stream,context, BUTTON_A, 109ms, 0ms); - pbf_wait(context, 123ms); - segment03_route_press(stream,context, BUTTON_A, 112ms, 0ms); - pbf_wait(context, 539ms); - segment03_route_press(stream,context, BUTTON_A, 147ms, 0ms); - pbf_wait(context, 518ms); - segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); - pbf_wait(context, 484ms); - segment03_route_press(stream,context, BUTTON_A, 185ms, 0ms); - pbf_wait(context, 613ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 526ms); - segment03_route_press(stream,context, BUTTON_A, 192ms, 0ms); - pbf_wait(context, 755ms); - segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); - pbf_wait(context, 962ms); - segment03_route_press(stream,context, BUTTON_A, 253ms, 0ms); - pbf_wait(context, 323ms); - segment03_route_press(stream,context, BUTTON_A, 277ms, 0ms); - pbf_wait(context, 506ms); - segment03_route_press(stream,context, BUTTON_A, 253ms, 0ms); - pbf_wait(context, 313ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 481ms); - segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); - pbf_wait(context, 737ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 589ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 296ms); - segment03_route_press(stream,context, BUTTON_A, 144ms, 0ms); - pbf_wait(context, 588ms); - segment03_route_press(stream,context, BUTTON_A, 189ms, 0ms); - pbf_wait(context, 425ms); - segment03_route_press(stream,context, BUTTON_A, 210ms, 0ms); - pbf_wait(context, 422ms); - segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); - pbf_wait(context, 1007ms); - segment03_route_press(stream,context, BUTTON_A, 235ms, 0ms); - pbf_wait(context, 333ms); - segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); - pbf_wait(context, 5044ms); - segment03_route_press(stream,context, BUTTON_A, 208ms, 0ms); - pbf_wait(context, 2822ms); - segment03_route_press(stream,context, BUTTON_A, 210ms, 0ms); - pbf_wait(context, 4173ms); - segment03_route_press(stream,context, BUTTON_A, 119ms, 0ms); - pbf_wait(context, 482ms); - segment03_route_press(stream,context, BUTTON_B, 204ms, 0ms); - pbf_wait(context, 1087ms); - segment03_route_press(stream,context, BUTTON_X, 180ms, 0ms); - pbf_wait(context, 1306ms); - segment03_route_press(stream,context, BUTTON_A, 141ms, 0ms); - pbf_wait(context, 1456ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 285ms, 0ms); - pbf_wait(context, 741ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 206ms, 0ms); - pbf_wait(context, 209ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 194ms, 0ms); - pbf_wait(context, 869ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 181ms, 0ms); - pbf_wait(context, 306ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 188ms, 0ms); - pbf_wait(context, 361ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 154ms, 0ms); - pbf_wait(context, 1911ms); - segment03_route_press(stream,context, BUTTON_Y, 156ms, 0ms); - pbf_wait(context, 2929ms); - pbf_move_right_joystick(context, {1.000000, -0.000000}, 205ms, 0ms); - pbf_wait(context, 898ms); - pbf_move_right_joystick(context, {-1.000000, -0.000000}, 255ms, 0ms); - pbf_wait(context, 469ms); - segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); - pbf_wait(context, 508ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 177ms, 0ms); - pbf_wait(context, 274ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 1400ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 207ms, 0ms); - pbf_wait(context, 663ms); - segment03_route_press(stream,context, BUTTON_A, 163ms, 0ms); - pbf_wait(context, 1097ms); - segment03_route_press(stream,context, BUTTON_B, 178ms, 0ms); - pbf_wait(context, 742ms); - segment03_route_press(stream,context, BUTTON_B, 182ms, 0ms); - pbf_wait(context, 872ms); - segment03_route_press(stream,context, BUTTON_B, 171ms, 0ms); - pbf_wait(context, 774ms); - if (!clear_new_item_flags(stream,context)) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"New reward flags could not be cleared before continuing.",stream); + segment03_confirm_location(stream,context,match_eterna_cycle_shop_counter, + "Eterna bicycle shop counter",20,false,true); + pbf_press_button(context,BUTTON_A,150ms,500ms); + // The reward scene returns us outside already mounted. Wait for that + // positive destination instead of replaying a fixed dialogue burst. + segment03_confirm_location(stream,context,match_eterna_cycle_shop_reward, + "Eterna bicycle reward complete",180,true); + segment03_register_bike(stream,context); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 733ms, 0ms); pbf_wait(context, 316ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 3280ms, 0ms); - pbf_wait(context, 1944ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1789ms, 0ms); - pbf_wait(context, 1848ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 3291ms, 0ms); - pbf_wait(context, 2531ms); - segment03_route_battle(stream,context,"Cycling Road first trainer"); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 676ms, 0ms); - pbf_wait(context, 2016ms); - segment03_route_battle(stream,context,"Cycling Road second trainer"); + segment03_doorway(stream,context,false,3280ms,"Cycling Road north gate entrance"); + segment03_doorway(stream,context,false,1789ms,"Cycling Road north gate exit"); + align_to_overworld_landmarks(stream,context,match_cycling_road_north_entry, + "Cycling Road north entrance",false,true); + segment03_expected_encounter(stream,context,"Cycling Road Axel", + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,-1},3291ms,0ms); + pbf_wait(ctx,2531ms); + }); + segment03_expected_encounter(stream,context,"Cycling Road Nicole", + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{1,0},676ms,0ms); + pbf_wait(ctx,2016ms); + }); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 145ms, 0ms); pbf_wait(context, 1136ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 424ms, 0ms); @@ -570,18 +601,22 @@ namespace PokemonBDSP{ pbf_wait(context, 372ms); segment03_route_press(stream,context, BUTTON_B, 162ms, 0ms); pbf_wait(context, 4324ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 360ms, 0ms); - pbf_wait(context, 188ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 944ms, 0ms); - pbf_wait(context, 161ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 388ms, 0ms); - pbf_wait(context, 39ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2236ms, 0ms); - pbf_wait(context, 1820ms); - segment03_route_battle(stream,context,"Route 207 Camper"); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2758ms, 0ms); - pbf_wait(context, 94ms); - segment03_route_battle(stream,context,"Route 207 Hiker"); + segment03_expected_encounter(stream,context,"Route 207 Camper", + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 360ms, 0ms); + pbf_wait(ctx, 188ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 944ms, 0ms); + pbf_wait(ctx, 161ms); + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 388ms, 0ms); + pbf_wait(ctx, 39ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 2236ms, 0ms); + pbf_wait(ctx, 1820ms); + }); + segment03_expected_encounter(stream,context,"Route 207 Hiker", + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{1,0},2758ms,0ms); + pbf_wait(ctx,94ms); + }); context.wait_for_all_requests(); } void coronet1(VideoStream& stream, ProControllerContext& context) { diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index 08a05a88ea..bc1c487575 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -547,12 +547,13 @@ double overworld_entry_confidence(const OverworldMatchResult& result, } OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame){ // The supplied reference is the interaction pose directly north of Jupiter. - // Confirm both red chair groups and the stairs before facing south to talk. + // Confirm the table-facing chair sides, adjacent table edges, and stairs. + // Exclude the NPC-facing chair edges where Buneary can overlap the reference. const std::vector configs{ - {"LeftChairs","PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairs.png", - {334.0/1920,756.0/1080,156.0/1920,228.0/1080},{0,.40,.45,.60},.93,.20}, - {"RightChairs","PokemonBDSP/Autostory/GalacticHQ/AboveRightChairs.png", - {1238.0/1920,756.0/1080,164.0/1920,230.0/1080},{.50,.40,.50,.60},.93,.20}, + {"LeftChairs","PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairTableEdge.png", + {300.0/1920,780.0/1080,156.0/1920,206.0/1080},{0,.40,.45,.60},.93,.20}, + {"RightChairs","PokemonBDSP/Autostory/GalacticHQ/AboveRightChairTableEdge.png", + {1286.0/1920,780.0/1080,154.0/1920,206.0/1080},{.50,.40,.50,.60},.93,.20}, {"StairBoundary","PokemonBDSP/Autostory/GalacticHQ/AboveStairs.png", {424.0/1920,310.0/1080,214.0/1920,198.0/1080},{0,0,1,.85},.93,.20} }; @@ -569,6 +570,9 @@ OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame){ auto approach=std::vector{configs[0],configs[1]}; approach[0].path="PokemonBDSP/Autostory/GalacticHQ/LeftChairs.png"; approach[1].path="PokemonBDSP/Autostory/GalacticHQ/RightChairs.png"; + // Retain the original geometry for the old-row guide-only references. + approach[0].target={334.0/1920,756.0/1080,156.0/1920,228.0/1080}; + approach[1].target={1238.0/1920,756.0/1080,164.0/1920,230.0/1080}; for (auto& chair:approach){chair.threshold=.95;chair.margin=.25;} auto coarse=match_landmarks(frame,approach,2,1.0/128,POSITION_TOLERANCE_Y,true); if (coarse.located){ @@ -614,6 +618,74 @@ OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame){ return result; } +OverworldMatchResult match_eterna_cycle_shop_entrance(const ImageRGB32& frame){ + const std::vector configs{ + {"Shop sign","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_entrance-0.png",{555.0/1920,350.0/1080,100.0/1920,120.0/1080},{0,0.1,0.65,0.75},.93,.20}, + {"Shop roof braces","PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png",{680.0/1920,145.0/1080,470.0/1920,36.0/1080},{0,0,.95,.75},.93,.20} + }; + return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); +} + +OverworldMatchResult match_eterna_cycle_shop_interior(const ImageRGB32& frame){ + // Entry mat view: fixed wall tools and register, clear of NPCs/Pokemon. + // This identifies the room; it is not the counter interaction position. + const std::vector configs{ + {"Shop wall tools","PokemonBDSP/Autostory/EternaBikeShop/InteriorWallTools.png",{920.0/1920,20.0/1080,350.0/1920,150.0/1080},{.15,0,.60,.55},.93,.20}, + {"Shop register","PokemonBDSP/Autostory/EternaBikeShop/InteriorRegister.png",{760.0/1920,160.0/1080,120.0/1920,145.0/1080},{.20,0,.55,.65},.93,.20} + }; + auto result=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + if (result.located) return result; + const std::vector north_configs{ + {"Shop wall tools","PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthWallTools.png",{928.0/1920,84.0/1080,350.0/1920,150.0/1080},{.15,0,.60,.55},.93,.20}, + {"Shop register","PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthRegister.png",{754.0/1920,230.0/1080,120.0/1920,145.0/1080},{.20,0,.55,.65},.93,.20} + }; + return match_landmarks(frame,north_configs,2,1.0/128,POSITION_TOLERANCE_Y,true); +} + +OverworldMatchResult match_eterna_cycle_shop_reward(const ImageRGB32& frame){ + // Reward returns the player outside on the bicycle, at a separate pose. + const std::vector configs{ + {"Shop sign","PokemonBDSP/Autostory/EternaBikeShop/reward-sign.png",{580.0/1920,330.0/1080,100.0/1920,120.0/1080},{0,.1,.65,.75},.93,.20}, + {"Shop post","PokemonBDSP/Autostory/EternaBikeShop/reward-post.png",{1020.0/1920,350.0/1080,100.0/1920,106.0/1080},{.3,.1,.65,.75},.93,.20} + }; + return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); +} + +OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame){ + const std::vector configs{ + {"Red frame","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-0.png",{620.0/1920,65.0/1080,390.0/1920,240.0/1080},{0.1,0,0.65,0.55},.93,.20}, + {"Counter detail","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-1.png",{1110.0/1920,486.0/1080,86.0/1920,38.0/1080},{0.3,0.15,0.65,0.65},.93,.20} + }; + return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); +} + +OverworldMatchResult match_cycling_road_north_entry(const ImageRGB32& frame){ + const std::vector configs{ + {"Route sign","PokemonBDSP/Autostory/CyclingRoad/cycling_road_north_entry-0.png",{696.0/1920,270.0/1080,140.0/1920,110.0/1080},{0,0,0.65,0.65},.93,.20}, + {"Left bridge cap","PokemonBDSP/Autostory/CyclingRoad/cycling_road_north_entry-1.png",{194.0/1920,568.0/1080,160.0/1920,160.0/1080},{0,0.2,0.5,0.8},.93,.20}, + {"Right bridge cap","PokemonBDSP/Autostory/CyclingRoad/cycling_road_north_entry-2.png",{1180.0/1920,540.0/1080,300.0/1920,280.0/1080},{0.5,0.2,0.5,0.8},.93,.20} + }; + return match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); +} + +OverworldMatchResult match_galactic_hq_exterior(const ImageRGB32& frame){ + const std::vector configs{ + {"HQ sign","PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-0.png",{660.0/1920,615.0/1080,118.0/1920,124.0/1080},{0,0.2,0.65,0.8},.93,.20}, + {"HQ doorway lintel","PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-1.png",{874.0/1920,350.0/1080,172.0/1920,78.0/1080},{0.2,0,0.6,0.7},.93,.20} + }; + return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); +} + +OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame){ + // Center-exit pose in the recorded Center->Mart and bicycle-shop route. + // Reuse the existing shadow/appearance-aware Center identification. + auto result=match_eterna_center_entrance(frame); + result.dy-=.070463; + result.at_target=result.located && !result.coarse && + std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=POSITION_TOLERANCE_Y; + return result; +} + OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame){ // Actual crops from screenshot-20261018-171535781091. Separate left/right // search regions prevent one identical statue from satisfying both votes. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 4fefc6f8fb..d709c03f4c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -53,6 +53,13 @@ OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame); +OverworldMatchResult match_eterna_cycle_shop_entrance(const ImageRGB32& frame); +OverworldMatchResult match_eterna_cycle_shop_interior(const ImageRGB32& frame); +OverworldMatchResult match_eterna_cycle_shop_reward(const ImageRGB32& frame); +OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame); +OverworldMatchResult match_cycling_road_north_entry(const ImageRGB32& frame); +OverworldMatchResult match_galactic_hq_exterior(const ImageRGB32& frame); +OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame); // Two unique, agreeing landmarks close to the doorway; this confidence only // authorizes a watched entry attempt, never proves the interior location. From ee841d26c2f646799f3ad30a5acfca19f0386dd6 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Fri, 23 Oct 2026 16:27:21 -0500 Subject: [PATCH 28/39] Save BDSP route recovery and Hearthome Contest Hall checkpoint --- .../AutomationProgramPatterns.md | 76 +- CodingAgentContext/Backlog.md | 160 +++ CodingAgentContext/CopilotHandoff.md | 329 +++++ .../Inference/PokemonBDSP_DialogDetector.cpp | 9 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 26 +- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 4 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 31 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 9 +- .../PokemonBDSP_AutoStory_Segment_02.h | 321 ++--- .../PokemonBDSP_AutoStory_Segment_03.cpp | 1227 ++++++++++------- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 32 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 28 + .../PokemonBDSP_Autostory_Segment_01_tools.h | 6 + .../Utils/PokemonBDSP_OverworldMatchers.cpp | 366 ++++- .../Utils/PokemonBDSP_OverworldMatchers.h | 9 +- 15 files changed, 1872 insertions(+), 761 deletions(-) create mode 100644 CodingAgentContext/Backlog.md create mode 100644 CodingAgentContext/CopilotHandoff.md diff --git a/CodingAgentContext/AutomationProgramPatterns.md b/CodingAgentContext/AutomationProgramPatterns.md index fee4a67712..6ea9c04d44 100644 --- a/CodingAgentContext/AutomationProgramPatterns.md +++ b/CodingAgentContext/AutomationProgramPatterns.md @@ -118,7 +118,7 @@ while(true){ env.update_stats(); send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); throw; - }catch(OperationFailedException& e){ // an exception indicating an error somewhere in the loop + }catch(ScreenshotException& e){ // an exception indicating an error somewhere in the loop if(...){ // if we can recover from it // execute recover logic (usually is restarting the game) continue; @@ -157,4 +157,76 @@ ProgramName_Descriptor::ProgramName_Descriptor() AllowCommandsWhenRunning::DISABLE_COMMANDS ) {} -``` \ No newline at end of file +``` +## Screen-relative matcher positioning + +Repository rule: matcher targets, landmark sizes, search regions, reported offsets, +and alignment tolerances must use fractions of screen width and height, not fixed +pixel positions. Convert to pixel rectangles only where image processing requires +them, using the image dimensions. A fixed processing resolution is permitted; it +must not leak into the positioning API or movement decisions. Normalize each axis +separately, and account for aspect ratio when comparing horizontal/vertical error. +Keep this conversion within the game code; shared infrastructure changes require +prior review and approval. + +## Language-independent detection + +Design matchers and automation functions to work independently of the game +language unless they explicitly use OCR. Visual templates containing localized +text also create a language dependency; avoid treating them as language-neutral. +Prefer stable visual structure and screen-relative positions where reliable. +When OCR is necessary, make its language dependency explicit and support the +applicable game languages. The settings-verification migration is deferred in +Backlog.md; position detection versus OCR remains undecided. + +## Usage-cap handoff to Visual Studio GitHub Copilot + +Kevin requests that the final deliverable before approaching a Codex usage cap +be a copy-ready handoff prompt for Visual Studio GitHub Copilot. Use 90% consumed +in either available usage window as the default trigger, leaving room to finish +the handoff. Check available account usage at the start of substantial coding +work and before long operations. When the trigger is reached, bring authorized +work to a reviewable state, refresh CodingAgentContext/CopilotHandoff.md, include +the exact current task, pending work, files, tests, known risks, and all Kevin's +constraints, then make the handoff prompt the final deliverable of that turn. +Do not start another substantial implementation first. This is a during-work +practice, not a promise of continuous background monitoring. A hard usage cap +may prevent an additional reply, so keep the saved handoff current proactively. +The handoff is drafted for Kevin to paste; do not send messages or change +Copilot provider/account settings without separate authorization. + +## Lighting setup for new locator strips + +Kevin's preference: set the console clock to 13:00 before most new overworld +locator strips, preserving its current date. Set it once per contiguous strip, +not before every matcher or again for the same strip's post-battle return. +Allow the game lighting to settle after resuming. Existing locators already +validated across lighting conditions do not need retroactive clock changes. +Keep screen-relative positioning, visual confirmation, and bounded recovery; +controlled lighting does not replace those safeguards. + + +## Explicit checkpoint boundary contract + +Every explicit checkpoint starts at the repeatable end location of its predecessor, except the very first checkpoint in Segment 00. Specify position, facing direction and relevant state (such as Repel) consistently in both descriptions. Complete any location-normalizing entry/exit before handing off; save at the next checkpoint using the existing checkpoint save/retry helper. Do not define a new checkpoint at a variable timed-movement endpoint. + +Floaroma 02.01 -> 02.02: force Repel expiry/NO, travel blindly north into the central house and immediately exit, checking both door transitions. End 02.01 / start 02.02 outside that house facing south, Repel inactive. Per user preference, use blind calibrated south/east movement to the Pokemon Center, then watched entry and existing healing helpers; do not require facade/emblem locators on this local route. + +## Standing Repel movement budgets (Kevin, 2026-10-18) + +After successful `activate_repel(stream, context, num_icons, repel_type)`, begin a fresh cumulative movement budget for that Repel: + +| repel_type | Item | Movement before forcing expiry | +| --- | --- | --- | +| 0 | Repel | 12 seconds | +| 1 | Super Repel | 22 seconds | +| 2 | Max Repel | 30 seconds | + +Count actual route movement commands after activation, not wall-clock time. Exclude waits, menu navigation, dialogue and battles. At the applicable budget, use the existing forced-expiry/renewal helper at a suitable location before continuing the next route inputs. Restart the movement budget after a successful new application or renewal. Preserve the API: the third argument is `num_icons`; the fourth argument is `repel_type` (default 0). + +These are user-selected standing budgets. Recording this rule does not itself implement automatic movement accounting or change gameplay code. Reuse/expand existing helpers when implementing it. + + +## Building-entry locators: watch transitions throughout (Kevin, 2026-10-21) + +For future building-entry work, use a BlackScreenWatcher alongside the visual locator throughout the approach and alignment. If a transition starts early, cancel remaining outdoor movement/search, wait for the fade to finish, and confirm the intended interior before continuing. Also recognize an already-visible intended interior so entry is not replayed. A black screen alone does not identify the building. Reuse existing matching/transition helpers, preserve shared gates, and keep route sequencing in its owning segment. diff --git a/CodingAgentContext/Backlog.md b/CodingAgentContext/Backlog.md new file mode 100644 index 0000000000..72d9410449 --- /dev/null +++ b/CodingAgentContext/Backlog.md @@ -0,0 +1,160 @@ +# AutoStory backlog + +## Segment 01: Validate the maxed starter before movement + +Status: Deferred. Record only; do not implement yet. + +Add a `checkformaxlevel` function at the start of Segment 01, before any navigation or movement. Also cover direct starts at checkpoint 01.01 so advanced mode cannot bypass the validation. + +Validate the Pokemon in party slot 1: + +- Species matches the Starter Pokemon dropdown: Chimchar -> Infernape, Turtwig -> Torterra, Piplup -> Empoleon. +- Read its level and require level 100. +- Read its moves and verify the required AutoStory moveset for that evolved starter. Determine and document the exact required movesets when implementing this item. + +If any check fails, stop without running route movement and show this error: + +"do you have your starter maxed and in slot 1?" + +Unreadable species, level, or moves must count as a failed check. Validation may open the party/summary UI, but must complete before overworld movement begins. + +## AutoStory battles: Handle a fainted starter + +Status: Deferred. Do not implement yet. + +Detect starter fainting and the forced party-selection screen in tutorial battles. +Handle this as an unexpected battle state instead of B-mashing until a generic +timeout. Decide whether to switch, recover, or retry from the checkpoint. Ordinary +route operation should prevent this by verifying Potion healing between battles. +Keep the normal trainer strategy unchanged; this is rare-case recovery. + +## Segments 00 and 01: Optimize grass movement steps + +Status: Deferred until the full route through Roark is validated. Do not change timings yet. + +Review recordings and matching logs to reduce unnecessary walk-through-grass steps, especially open-path movements that do not deliberately align against a wall. Excess steps add encounter opportunities, detection waits, and position drift. + +Classify each section before changing it: + +- Wall alignment: retain sufficient movement to reliably reach the wall. +- Open-path travel: reduce steps to the required distance while preserving the next movement's starting position. +- Encounter search: stop movement as soon as an encounter is detected. + +Prioritize obvious excess steps. Compare baseline and optimized runs across varying encounter patterns, and verify both the destination and the following movement before accepting a shorter route. + +## Settings verification: Support every game language + +Status: Deferred. Record only; decide the detection approach later. + +Settings verification must work in every language supported by BDSP. The current +visual templates for setting names and values depend on their language and must +be replaced or extended before claiming multilingual support. + +Evaluate position-based detection of settings rows, selected values, and sliders +versus explicitly language-aware OCR. Prefer language-independent geometry where +reliable; decide which approach is better after comparing the localized screens. +Do not assume that text width, row layout, or value positions are identical in all +languages without validation. + +Verify every required setting, including persistence after saving and reopening +Options. Autosave must be confirmed OFF in every supported language before route +movement begins. Validate the chosen approach against captures for all supported +languages, including incorrect settings and ambiguous detection cases. + +Related requirement: ideally all matchers and automation functions should be +language-independent unless explicitly using OCR. Audit text-based visual +references and other language assumptions when extending or reviewing them. + +## AutoStory helpers: Audit and reorganize reusable tools + +Status: Deferred. Record only; do not implement yet. + +Examine existing AutoStory helpers, their callers, and their responsibilities. +Reclassify helpers by purpose rather than the segment where they were first +introduced. Identify duplicate or overlapping functions and prefer reusing or +expanding an existing implementation before developing another helper. + +Likely tool files (with matching headers where appropriate) include: + +- `AutoStory_BattleTools.cpp`: battle handling, move selection/sequences, and battle cleanup. +- `AutoStory_NavigationTools.cpp`: route movement, alignment, doorway transitions, and navigation recovery. +- `AutoStory_BagTools.cpp`: Bag navigation, item use, pocket detection, and NEW-flag clearing. + +Review the existing battle/navigation utilities and segment-specific tool files +before deciding the final file boundaries and names. Consolidate and move shared +helpers rather than creating parallel implementations. Keep segment-specific +route sequences in their segments. + +Include a code review of helper implementations and their callers throughout +AutoStory. Incorporate and adjust helpers and call sites so routes use the shared +tool helpers instead of unique one-off implementations wherever practical. +Expand shared helpers when their responsibilities fit, and remove superseded +duplicates after migrating callers. Aim to standardize most helpers; do not +force every helper into a common abstraction. Retain specialized helpers where +the behavior warrants them, and document why they remain specialized. + +Preserve behavior and public callers during +the reorganization, and validate affected routes and builds. + +## Checkpoint descriptions: Deferred wording and endpoint cleanup + +Status: Deferred. Do not spend implementation/review time on routine wording cleanup while developing and validating the route. + +Audit checkpoint names, start/end descriptions, and related comments together in a later cleanup pass. Match descriptions to verified gameplay positions and preserve the requirement that each checkpoint starts at the previous checkpoint's endpoint. There are many stale descriptions across AutoStory; handle them as a batch rather than piecemeal edits. The Eterna Forest endpoint outside the Center is one example. Actual route or checkpoint-state failures still need prompt fixes; cosmetic description cleanup stays deferred. + +## Project roadmap: Transition from Segment 03 implementation to Segment 04 testing + +Status: Deferred until Segment 03 implementation is complete and work reaches Segment 04. At that transition, begin testing the existing route and reviewing missing information. Do not start a broad helper refactor or roadmap audit during current route fixes. + +Revisit the earlier five-phase BDSP AutoStory roadmap (Setup, Streamlining, Cleanup, Beta Testing, Future Implementation) to reflect the present workflow: implement and validate recorded route segments incrementally, use logs/video to fix failures, and reuse or expand shared battle/navigation/Bag tools. + +Kevin clarified that Segment 04 is the first segment expected not to need substantial implementation. Treat segments 04 through 09 as existing route implementations to test and audit, not as validated or release-ready. When reaching Segment 04, look for missing route information, unsupported states and required functionality; implement evidence-supported gaps rather than rebuilding the segment. Continue that testing/gap-review workflow through the later segments. The 2026-10-23 source audit has now confirmed substantial missing gameplay; the inventory below supersedes any assumption that the later segments only need testing. Preserve implemented travel while filling these gaps. + +The revised roadmap should: + +- Separate remaining route implementation from live validation, failure recovery, helper consolidation, cleanup and release preparation. +- Audit segments 04–09 for existing route coverage, moving NPCs/key fights, checkpoint handoffs, item/money/buy-list requirements and one-off helper logic. Identify actual gaps rather than rebuilding completed routes. +- Link the existing "AutoStory helpers: Audit and reorganize reusable tools" task. Plan shared helper creation/expansion and consolidation across those later segments, especially BattleTools, NavigationTools and BagTools, while retaining justified specialized behavior. +- Keep broad file consolidation, unused-file removal, formatting and checkpoint wording cleanup in a deliberate later pass; do not let them displace current route validation. +- Define evidence-based validation and regression gates before beta testing and eventual main-repository PR/release. This task does not authorize creating/pushing a PR now. +- Retain a separate future-upgrades phase and list dependencies/acceptance criteria for each workstream. + +Deliver a revised roadmap with segment status, outstanding functionality, helper migration priorities, validation gates and a practical order of work when Segment 03 is complete and Segment 04 testing begins. + +## Segments 04–09: Fill missing gameplay and repair checkpoint handoffs + +Status: Backlogged required implementation, identified by the 2026-10-23 read-only source audit. Do not implement as part of recording this task. Complete current Segment 03 work first, then fill and validate these gaps in route order. Existing travel code is not yet live validated by this audit. + +Source locations below are relative to `SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/`. Line numbers describe the audited revision and may shift. + +### Missing gameplay + +- [ ] **Segment 04 — Pastoria Gym:** `StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp`, `go_to_pastoria_gym` (line 977), checkpoint 0417 (1185). The current route only enters the gym although the endpoint claims the fourth badge. Implement the puzzle, Crasher Wake battle, verified badge completion and the exit position required by Segment 05. +- [ ] **Segment 05 — Hearthome Gym:** `Segment_05.cpp`, `get_into_hearthome_gym` (1454), checkpoints 0516/0517 (1651/1662). Both checkpoints call the entrance route. Implement the puzzle, Fantina battle, verified badge completion and exit boundary; replace the duplicated entrance call with the actual interior phase. +- [ ] **Segment 06 — Canalave Gym:** `Segment_06.cpp`, `go_to_canalave_gym` (590), checkpoints 0610/0611 (730/741). Both call the entrance route. Implement the lift puzzle, Byron battle, verified badge completion and exit boundary. +- [ ] **Segment 07 — Snowpoint Gym:** `Segment_07.cpp`, `go_to_snowpoint_gym` (1729), checkpoints 0718/0719 (1954/1965). Both call the entrance route. Implement the ice puzzle, Candice battle, verified badge completion and exit boundary. +- [ ] **Segment 08 — Final Mount Coronet story:** `Segment_08.cpp`, `go_through_mount_coronet` (1784), checkpoint 0816 (2485). The function only waits, logs and returns true. Implement the final ascent, Spear Pillar story encounters/battles, supported legendary resolution and return to the position required by the next route. Dormant legacy `checkpoint_021`/legendary button sequences are not a working implementation and have no catch-success verification. +- [ ] **Segment 08 — Sunyshore Gym:** `Segment_08.cpp`, `go_to_sunyshore_gym` (2250), checkpoints 0825/0826 (2584/2600). Both call the route ending at the gym entrance. Implement the gear/platform puzzle, Volkner battle, verified eighth badge and exit boundary. +- [ ] **Segment 09 — League entry and completion:** `Segment_09.cpp` (1354–1429), checkpoints 0909–0914. All six functions below only wait, log and return true. Implement entry prerequisites, room traversal, trainer fights, necessary party/item preparation and verified Champion/Hall of Fame completion: + - `get_into_the_elite_4_entrance` (1354). + - `deal_with_aaron_elite_4` (1367). + - `deal_with_bertha_elite_4` (1380). + - `deal_with_flint_elite_4` (1393). + - `deal_with_lucian_elite_4` (1406). + - `deal_with_cynthia_and_finish` (1419). + +`Segment_XX.cpp` above abbreviates `StorySegments/PokemonBDSP_AutoStory_Segment_XX.cpp`. + +### Checkpoint and failure-handling gaps + +- [ ] Register **checkpoint 0717** in the global checkpoint list in `PokemonBDSP_AutoStory.cpp` (between 0716 and 0718, lines 155–157). Segment 07's own runner includes this Snowpoint Center heal/exit step; checkpoint mode currently skips it and changes the next route's starting scene. +- [ ] Include **checkpoints 0822–0826** in Segment 08's `run_segment` (85–123). They are present in the global checkpoint registry but omitted from the segment runner. Validate both supported execution modes against the same checkpoint sequence. +- [ ] Replace the duplicated gym-entrance calls listed above with real puzzle/battle phases. Verify that each checkpoint starts at its predecessor's actual endpoint and that the next segment starts from the verified post-badge position. +- [ ] Propagate `heal_pokemon` failure in seven wrappers that currently return true regardless: Segment 05 Solaceon (822), Celestic (1233), Hearthome (1444); Segment 06 Jubilife (276), Canalave (578); Segment 07 Veilstone (1134), Snowpoint (1715). The shared helper returns false when the Center exit transition is missing; subsequent pathing must not continue from an unconfirmed scene. +- [ ] In Segment 09 `ready_in_front_of_elite_4` (1291), check `buy_items`' result (1344–1349) and replace the assumption that one fixed west/east wiggle must expire Repel with a bounded, confirmed preparation sequence. + +### Acceptance criteria and order + +Use recorded/calibrated travel, existing battle/dialogue/navigation tools and explicit completion conditions. Do not substitute invented blind inputs or unconditional success for missing gameplay. Verify both successful progression and bounded failures, checkpoint restart positions, battle completion and doorway settling. Reuse or expand shared helpers where appropriate; coordinate broader consolidation with the existing helper-audit backlog item. + +Recommended order: finish Segment 03, then the gym phases in 04–07, final Coronet and Sunyshore in 08, and League completion in 09. Repair the corresponding checkpoint connections and ignored failures alongside each phase. Keep routine checkpoint wording cleanup deferred, but correct descriptions when needed to express the implemented start/end contract. diff --git a/CodingAgentContext/CopilotHandoff.md b/CodingAgentContext/CopilotHandoff.md new file mode 100644 index 0000000000..1d08e7635c --- /dev/null +++ b/CodingAgentContext/CopilotHandoff.md @@ -0,0 +1,329 @@ +# Current checkpoint — October 23, 2026 + +This section supersedes the historical October 12 status below. + +- Active source checkout: `C:/Users/Kevin/GitRepos/Arduino-Source`, branch + `BDSP/AutoStory`, tracking `origin/BDSP/AutoStory` in + `Developer-Butters/Arduino-Source`. Make all future source edits here. +- Canonical resource checkout: `C:/Users/Kevin/GitRepos/Packages`, branch + `BDSP-Autostory`, tracking Developer-Butters/Packages. Save image resources + there for their separate future PR. + Saved resource checkpoint: `c8dbef4769e9780f7f77cf03a0bb5f32958162eb`. The source checkout's nested Packages + copy must stay synchronized with this resource branch. +- The old `BDSP-AutoStory` checkout is a preserved recovery/build reference. + Do not make further implementation edits or push work from it. +- Saved work includes the current Route208 rock appearance bank, Buneary + dialogue/queue fixes, west-gate descent correction, Center exterior/interior + handoff and Fantina/Contest Hall alignment and required conversations. +- Most recent build from the old checkout is executable SHA256 + `1e39b375b752929dd8780aa69d4b4d70e6f1360bc133bd1878e6735d82e6f806`. + It passed 173 targeted checks. Migration preserves the source bytes; it + does not establish a new build against this checkout's newer framework. +- The new gate/Center/Fantina changes still need live checkpoint18 validation. + The earlier 15:01:34 run verified rocks, Buneary and actual Center healing, + but did not successfully interact with Fantina. That run and its recording + have ended; monitoring is paused. Do not start another run without request. +- Preserve East1197/rest298 and post-Buneary Repel expiry with NO renewal. + Segments04–09 implementation remains backlogged; do not expand scope. +- Recovery snapshot and prior test/video evidence remain in + `C:/Users/Kevin/Documents/Codex/2026-10-03/lo/work/`. +- For current shell operations use cmd.exe and the configured Python runtime; + the historical PowerShell examples below are not the current workflow. + +## Historical October 12 handoff + +# BDSP AutoStory handoff for Visual Studio GitHub Copilot + +Updated October 12, 2026, after implementing Route 204 trainer recovery. + +Continue Kevin's existing work in `C:\Users\Kevin\GitRepos\Arduino-Source`. +Read the current files, `git status`, staged AND unstaged diffs, this handoff, +`CodingAgentContext/Backlog.md`, and `CodingAgentContext/AutomationProgramPatterns.md`. +Preserve Kevin's edits. This snapshot explains the current implementation; +it is not an instruction to replay changes that are already present. + +## Immediate task + +Validate the next live Segment 02 recording after the new Route 204 changes. +Kevin has progressed through the Galactic double battle, Bebe's dialogue, and +Jubilife Pokemon Center healing. The current bottleneck is triggering the female +trainer after leaving the Center and then positioning at the Route 204 sign. +The latest changes compile and pass screenshot checks, but the new trainer +recovery and route movements still need a live test. Review the next recording +and matching logs before proposing further changes. + +Do not work on the unrelated Plex/MediaServer transfer task: Kevin accidentally +posted that question here and explicitly asked us to ignore it. + +## Keep Visual Studio and its workspace open + +Kevin reports that Copilot sometimes closes the workspace while running +PowerShell commands. The cause is NOT established. Distinguish an exited +terminal session, a closed solution/workspace, and an exited Visual Studio +process; they are different failures. Do not claim `exit` alone necessarily +closes the IDE. The following are preventive operating rules, not a diagnosis: + +1. Do not close, unload, reopen, reload, reset, or replace the solution/workspace + as part of a shell operation. Do not invoke IDE-close commands, DTE solution + closure, UI shortcuts, or launch another Visual Studio instance to run builds. +2. Do not kill `devenv.exe`, Visual Studio/ServiceHub processes, terminal hosts, + or their process trees. Do not use broad `Stop-Process`, `taskkill`, or cleanup + filters such as all PowerShell, Python, CMake, compiler, or IDE processes. + If a child task hangs, use the tool's cancellation or Visual Studio's Cancel + Build. Process termination requires a specific, verified task PID and Kevin's + authorization; never target the IDE or parent host. +3. Keep shell work scoped to explicit paths. Prefer the tool's working-directory + setting and absolute filenames. A directory change must not become an IDE + workspace operation. Never repurpose `$HOME`, `$home`, or `$CODEX_HOME`. +4. Do not dot-source operational scripts into the IDE's shared PowerShell + session. Prefer a saved script launched in a CHILD process: + + ```powershell + & powershell.exe -NoProfile -NonInteractive -File 'C:\absolute\path\task.ps1' + $taskExitCode = $LASTEXITCODE + if ($taskExitCode -ne 0) { throw "Task failed with exit code $taskExitCode" } + ``` + + Use `pwsh.exe` instead only if it is installed. Set `$ErrorActionPreference = + 'Stop'` inside the task script and use `return` for routine early completion. + Do not send bare `exit`, `logout`, or `Stop-Process -Id $PID` to a reused + parent terminal/runspace. If an exit code is necessary, confine `exit ` + to the explicitly launched child script. Invoke native programs with `&` and + structured arguments; avoid `Invoke-Expression` and string-built commands. +5. Run batch build scripts through `cmd.exe /d /c `. + `exit /b` belongs inside that child batch process, not an IDE-control action. + Capture output and return codes instead of closing the console or workspace + to mark a task complete. +6. If the workspace closes again, stop issuing cleanup/relaunch commands. + Report the exact last command, which surface closed, the error/output, and + available Copilot/Visual Studio logs. Preserve files and let Kevin decide + how to reopen the workspace. Do not automatically reset IDE settings. + +Microsoft documents that PowerShell `exit` terminates a script or PowerShell +instance, and that `powershell.exe -File` returns a child's script exit code: +[PowerShell language keywords](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_keywords?view=powershell-5.1). +Terminal commands in Visual Studio agent mode have the IDE process's permissions; +they are not confined to the solution's normal file-tool boundary: +[Visual Studio agent mode](https://learn.microsoft.com/visualstudio/ide/copilot-agent-mode?view=visualstudio). + +## Repository and commit state + +- Source repository: `C:\Users\Kevin\GitRepos\Arduino-Source`. + Branch: `BDSP/AutoStory`. + `origin`: `https://github.com/Developer-Butters/Arduino-Source.git`. + `upstream`: `https://github.com/PokemonAutomation/Arduino-Source.git`. +- October 11 rebase: originally 299 upstream commits behind and 23 fork commits + ahead. Replayed all 23 onto upstream main, then added two compatibility commits. + Published tip is `08760b8fe`; at that snapshot it was 0 behind / 25 ahead. + Fetch and inspect before claiming it is still current. +- Rebased compatibility commits: `69676bbc7` updates BDSP to upstream APIs; + `08760b8fe` restores authoritative shared detectors and localizes extensions. + Other games moved under `Source/VideoGames`; BDSP remains under + `SerialPrograms/Source/PokemonBDSP`. The BDSP battle helper's Sword/Shield + type-matchup include now uses `VideoGames/PokemonSwSh/Resources/`. +- Shared detector and shared developer/unit-test implementations match upstream. + Do not resurrect deleted `Tests/TestMap.cpp` or old shared BDSP debug checkboxes. + Use `OperationFailedExceptionWithScreenshot::fire`, `ErrorReportMode`, + `GlobalAutoPaths.h`, and upstream's `make_SingleSwitchProgram` / Descriptor + alias pattern. Do not reintroduce the old interfaces. +- Recovery copies remain in branch + `backup/BDSP-AutoStory-before-upstream-20261011` and the named rebase stash. + These are backups, not code to apply over current work blindly. The old local + OCR CharacterReductions path fix is already in upstream; no duplicate fix is needed. +- Only Segment 02's route file is currently staged. Its staged contents depend + on UNSTAGED helper/matcher changes. Do not commit that file alone and assume + the resulting checkout will build. Inspect all dependencies when Kevin asks + for a commit. The pending files include Segment 00 tools `.cpp/.h`, the + overworld matchers `.cpp/.h`, and local context/backlog/handoff documents. +- Packages is a SEPARATE repository at `...\BDSP-AutoStory\Packages`, on branch + `BDSP-Autostory`. Approved push remote is `developer-butters`: + `https://github.com/Developer-Butters/Packages.git`. + `origin` still points to PokemonAutomation upstream: DO NOT push there. + Published Packages tip is `89fc5f8`. New, uncommitted resources include 18 + Jubilife lighting references and `Autostory/Route204Sign/Sign.png` plus + `FenceCorner.png`. Other untracked test images/assets also exist; never add + everything indiscriminately. +- Do not pull the old branch history back into the rebased source branch. + Collaborators need a one-time fetch/resync preserving their local work first. + No further rebase, reset, commit, push, history rewrite, or upstream PR without + Kevin's specific instruction. Current implementation work is uncommitted. + +## Current Segment 02 behavior + +Primary file: +`SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp`. +Route function: `roark_to_mars(env, stream, context)`. + +1. Oreburgh Mart alignment and Repel buying precede Gate entry. Entry watches + black before up/left/down search; exit watches white before moving west and + waits for the fade to finish. The cleaned Route 203 path avoids descending + the upper stairs only to climb back up; it travels left, jumps down the ledge, + and combines the lower-stair corrections. +2. At the marked safe point, `force_repel_expiry(..., RepelRenewalChoice::NO)` + wiggles until expiry and declines renewal. `YES` remains supported. Cleanup + requires three continuous clear overworld seconds, not one transient frame. +3. Exactly one clock setup occurs when entering this contiguous NEW locator + strip: `set_time_fast(env, context, locator_time, true)` sets 13:00 and + preserves the current console date, followed by five seconds for lighting. + It covers street, Galactic plaza, post-battle return, and nearby Route 204 + work. Do not reset the clock for every matcher. Checkpoint retries enter the + strip again; no clock restoration is currently implemented or requested. + Existing earlier locators do not need retroactive clock changes. +4. The westward street approach is 3700ms. The old blind 2579ms upward move was + removed. `align_to_jubilife_approach` handles both axes and prioritizes street + lane alignment. Two strong coarse anchors may guide a horizontal correction + only when their signed offsets agree and the offset exceeds their spacing + uncertainty. Coarse matching never confirms arrival. Missing landmarks use + bounded right/left sweeps at several heights rather than continuous left/up. +5. The plaza matcher has a small normalized horizontal quantization allowance + to avoid oscillating between two reachable positions around the target. + The approach sidesteps left 200ms around the sign before watched upward + pulses; straight up plus A previously read the sign instead of triggering Rowan. +6. Dialogue/battle triggers stop locator movement. Battle preparation clears + dialogue with B until the battle menu is confirmed. The existing + `Barry_1_fight` handler is reused. Do not move onward merely because dialogue + was seen: menu confirmation and combat completion are separate requirements. +7. Rowan/Bebe dialogue clears in two passes separated by two seconds. Return + down 600ms before reacquiring the plaza. The old 1000ms return overshot, and + the additional downward recovery pulses are now disabled at this call site. + From the confirmed reference, the route proceeds to Jubilife Center. +8. After Center exit and westward alignment, `approach_route204_female_trainer` + watches throughout the 8000ms northward movement and a 2500ms stationary + detection window. It returns whether ShortDialogWatcher or trainer battle + menu appeared, cancelling remaining motion if either triggers. +9. `const bool missed_trainer` records the initial miss. If true, + `recover_route204_female_trainer` crosses her sight line DOWN 500ms then + UP 500ms, at most eight pairs, with fresh dialogue/menu watchers. Failure is + bounded and reported; it does not return to a two-minute generic wait. +10. Both paths confirm the menu, fight, clear post-battle dialogue, and activate + a fresh Repel. The first Repel was deliberately NOT renewed earlier. +11. `align_to_route204_sign` uses the supplied reference photo, sign and fence + corner, normalized positioning, visible unlabeled ROIs, and three-frame + target confirmation. It watches each corrective move and settling period + for the NEXT trainer's dialogue/menu. It also avoids steering during fades. + It returns true if the trainer interrupts alignment, false if at target. +12. If aligned without a trigger, a watched 1200ms left approach starts the next + trainer. If interrupted early, skip further positioning and use the same + confirmed-menu/combat/cleanup path. Never skip the battle handler and send + overworld moves into an active battle. The subsequent northward movement + and later cave-navigation placeholder remain to validate/complete. + +The latest live video, `2026-10-12 19-15-37.mp4`, demonstrated the missed female +trainer and the old stationary wait. The newly implemented recovery/sign flow +has NOT yet been observed in a live recording. Do not claim it is route-validated. + +## Matchers, resources, and verification + +Matcher registry: `AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp/.h`. +Current entries: Poketch, Oreburgh Mart, Jubilife Center, Route 201 Potion exit, +Sandgem junction, Route 202 grass exit, Jubilife street/plaza, and Route 204 sign. +The lighting tester is in `PokemonBDSP/Programs/TestPrograms/` and uses this +registry automatically. `Autostory` is the resource folder casing. + +- Valid Jubilife lighting runs: `jubilife-street-approach-1791772008` and + `jubilife-plaza-approach-1791772619`. Each now passes 120/120 captured frames. + Exclude `jubilife-street-approach-1791771478` and + `jubilife-plaza-approach-1791771937`: Kevin started those at wrong positions. +- Street references use building corner/base, avoiding repeated facade windows. + Plaza references use roof rim and fence. Eighteen references cover all five + captured frames per hour, including moving shadows. Alternatives for one + landmark are NOT independent votes; two distinct anchors must agree. +- Street target height preserves the original screenshot; the lighting-run + pose differed by 1/108 screen height. Do not move the target to make an + offset test pass. Original-reference checks passed too. +- Route 204 reference: + `build/Debug/Screenshots/screenshot-20261012-192258390819.png`. + Its two crops exclude trainer/player animation and the collectible item. + Thirteen native checks passed for 720p/1080p/1440p, both offset directions, + unrelated scenes, and masking people/items. Full-day lighting has not been + tested for this new matcher; runtime currently uses the strip's 13:00 setup. +- New clock/date option is in Segment 00 tools: `preserve_date` defaults false, + so existing callers retain exact-date behavior. The new clock-only path reads + and validates year/month/day using the existing console date reader before + setting hour/minute. This reuses console UI OCR; it adds no game-text OCR. +- Transition extensions now live in + `PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp/.h`, not CommonTools. + Use BDSP namespace qualification if global/local watcher names are ambiguous. +- Last complete Debug build/link succeeded after Route 204 integration. + Matcher checks prove detection properties, not live movement/battle success. + +## Kevin's constraints + +- Prefer focused BDSP-local changes. Shared directories such as CommandLine, + CommonFramework, CommonTools, ComputerPrograms, ControllerInput, Controllers, + Integrations, Kernels, ML, and shared NintendoSwitch/Pokemon code require a + walkthrough BEFORE editing and Kevin's explicit informed approval. Shared + changes require a separate commit with detailed necessity and cross-game + validation. SourceFiles.cmake registration is allowed for actual new files. +- Targets, search regions, sizes, offsets, and tolerances MUST be screen-relative. + Pixel conversion belongs only at image-processing boundaries. Account for + aspect ratio when comparing axes. Never hard-code capture-size movement logic. +- Prefer language-independent geometry and avoid OCR unless necessary. + Text-bearing templates are language-dependent even without OCR. Show ROIs + through normal Overlays; colored boxes only, no text labels. +- Preserve Kevin's unsaved Visual Studio edits. Before external edits to an + open file, have him save/close it or preserve both copies and reconcile them. + Do not overwrite editor contents or respond to a reload warning by discarding + unknown changes. Preserve Git baseline line endings: LF/CRLF changes previously + made Segments 03-09 look completely rewritten and must not recur. +- Fresh watchers belong to each separate detection phase. Keep state across + rounds only for deliberately latched end-battle watchers. +- Autosave must be verified OFF. Settings save/exit is A A A A B B with detection + retries and persistence verification. REAL saves are the default; FAKE saving + and post-catch diagnostic entry remain marked for removal before production. +- Evaluate actual evidence before adding black/white watchers. Do not spend + time looking for a watcher that Kevin says does not exist. +- No unsolicited subagents, commits, pushes, resource cleanup, process killing, + or work on deferred features. Retain bounded recovery and useful logs. + +## Earlier work and deferred items + +Segments 00/01 and associated tools were committed/pushed before this focus. +Tutorial work includes settings verification, post-catch saved checkpoint +boundary, robust capture/battle-result cleanup, generic grass movement with +optional destinations, shared confirmed-Run escape, visual 1-4 move counting +and selection, Potion HP verification, one wild Scratch training battle after +Potions, and Ember slot 3 for Tristan. Do not reintroduce named grass wrappers +or blind move-slot selection. Segment 01 Gate-to-mine movement is enabled. + +Defer maxed starter/species/level/moves validation before Segment 01, faint +handling, grass optimization, and multilingual settings until Kevin asks. +Also verify the existing REAL-save fallback before claiming checkpoint safety +is fully solved; earlier analysis found bookkeeping may advance after an +unverified fallback. These are not authorization to expand the current task. + +## Build and analysis workflow + +- Runtime log: `C:\Users\Kevin\GitRepos\Arduino-Source\build\Debug\SerialPrograms.log`. + It is large; filter exact run timestamps and do not confuse idle/old runs. +- Lighting data: `...\build\Debug\Screenshots\OverworldLightingTest`. + Recordings: `C:\Users\Kevin\Videos\Captures`. +- Analysis workspace: `C:\Users\Kevin\Documents\Codex\2026-10-03\lo\work`. + Use that directory for scratch files, not the repository. An external script + with relative `work/...` paths must run from its parent `...\lo`, not repo root. +- Build script: `work\build_rebased.py` in that analysis workspace. Python: + `C:\Users\Kevin\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe`. + It calls VS 18 Community `vcvars64.bat`, then CMake `--build` on the repo's + `build\Debug`, target `SerialPrograms`, eight workers. Log: + `work\link_utils_move.log`. `work\link_utils_move.cmd` is the generated batch + equivalent; run it as a child `cmd.exe /d /c`, never by closing the workspace. +- If LNK1168 says SerialPrograms.exe cannot be opened for writing, ask Kevin to + close SerialPrograms and rebuild. Do NOT kill it or Visual Studio. +- Existing native harnesses: `run_route204_tests.py`, + `run_jubilife_lighting_tests.py`, `run_jubilife_route_tests.py`, + `run_jubilife_approach_tests.py`, `run_native_battle_cursor_tests.py`, + `run_position_tests.py`, `run_transition_tests.py`. Read before using them. + They now use the GUI target's library set, not the old command-line CoreLib + assumption. Do not run harness builders concurrently: they share a batch path. + `work\Resources` provides the runtime resources needed by the test executables. +- PyAV is under `work\video_dependencies`; add it to Python's import path. + Seek to timestamps for long recordings. Crop only the OBSERVED game viewport + before testing a recorded image; UI borders/stats overlays can contaminate + match scores. Do not confuse screenshot tests with an untouched camera feed. +- A pre-existing ONNX API/runtime-version warning appears in some native test + startups. Image-based checks still passed. Do not silently modify shared + dependencies or claim ONNX-dependent features have been tested. + +Start with the current diff and Kevin's next recording. Explain the concrete +failure and the proposed correction, keep edits narrow, build appropriately, +and clearly distinguish offline validation from live route behavior. diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp index 3a06a4e2a9..c85aaced34 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_DialogDetector.cpp @@ -47,7 +47,14 @@ bool ShortDialogDetector::detect(const ImageViewRGB32& screen){ // Gardenia's opening reward line can reach this strip. Retain the // same white test in a second blank margin, below that long line. right_white=image_stats(extract_box_reference(screen,ImageFloatBox{.790,.915,.002,.025})); - if (!is_white(right_white)) return false; + if (!is_white(right_white)){ + // Buneary's long second line reaches the inner strip, while the + // outer strip can land on the tinted border. The blank lower + // margin must pass the same uniform-white test; retain both + // edge-variation checks below and the existing left margin. + right_white=image_stats(extract_box_reference(screen,ImageFloatBox{.790,.940,.002,.018})); + if (!is_white(right_white)) return false; + } } // ImageStats bottom = image_stats(extract_box_reference(screen, m_bottom)); // if (!is_white(bottom)){ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 9557e802e1..7ed2d3e940 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1347,9 +1347,10 @@ bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, in bool cleared=all_clear(); for (size_t pocket=0;pocket<32 && !cleared;pocket++) { if (pocket) pbf_press_dpad(context,DPAD_RIGHT,80ms,300ms); - // Pause on each selection so its NEW banner has time to clear. - // Keep three cumulative seconds of Down input per pocket. - for (size_t tap=0;tap<12;tap++) pbf_press_dpad(context,DPAD_DOWN,250ms,100ms); + // Let the initial selection and each subsequent item clear its NEW + // flag. Short taps avoid skipping rows through held-button repeat. + pbf_wait(context,1000ms); + for (size_t tap=0;tap<12;tap++) pbf_press_dpad(context,DPAD_DOWN,80ms,1000ms); cleared=all_clear(); } pbf_press_dpad(context,DPAD_UP,3000ms,250ms); @@ -1534,8 +1535,6 @@ bool find_bag_item( // *reference, bag_list_name_box(item.reference_row) // ).copy(); ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); - ImageMatchDetector selected_icon(reference, item.icon_box, item.max_icon_rmsd, true); - ImageMatchDetector selected_name(reference, item.name_box, item.max_name_rmsd, true); const auto deadline = std::chrono::steady_clock::now() + search_timeout; open_menu(stream, context, MenuCursorPosition::BAG, num_icons); @@ -1584,7 +1583,6 @@ bool find_bag_item( for (size_t pass = 0; pass < 3 && !empty; pass++){ size_t matching_frames = 0; size_t missing_cursor = 0; - size_t failed_confirmation = 0; size_t unchanged_pages = 0; ImageRGB32 previous_page; stream.log( @@ -1641,6 +1639,7 @@ bool find_bag_item( return false; } if (cursor == 0){ + matching_frames = 0; if (bag_list_is_empty(frame)){ empty = true; break; @@ -1657,8 +1656,9 @@ bool find_bag_item( if (target != 0){ previous_page = ImageRGB32(); unchanged_pages = 0; - if (target == cursor && selected_icon.detect(frame) && - (!item.check_name || selected_name.detect(frame))){ + // A unique list sprite/name match with the cursor on that + // row is sufficient; do not depend on the detail panel. + if (target == cursor){ if (++matching_frames >= 3){ stream.log("[Bag search] Item and cursor confirmed in row " + std::to_string(cursor) + ".", COLOR_GREEN); @@ -1669,16 +1669,6 @@ bool find_bag_item( continue; } matching_frames = 0; - if (target == cursor){ - if (++failed_confirmation < 3){ - pbf_wait(context, 150ms); - context.wait_for_all_requests(); - continue; - } - stream.log("[Bag search] Selected-item confirmation failed.", COLOR_ORANGE); - return false; - } - failed_confirmation = 0; const size_t count = plan_bag_item_move(cursor, target).presses; stream.log("[Bag search] Moving " + std::string(target > cursor ? "down " : "up ") + std::to_string(count) + " time(s), then checking again."); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 9144cde6e6..d312a63495 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -297,8 +297,8 @@ struct BagItemRowMatch { int bag_pocket_position(const ImageViewRGB32& frame); // Count normal, unobscured pocket icons; NEW badges prevent a tab match. size_t bag_clear_tab_count(const ImageViewRGB32& frame); -// Open bag, scan with 250ms Down / 100ms rest (3s cumulative Down per pocket) -// until all 8 tabs match, +// Open bag, pause 1s on the initial selection in each pocket, then scan +// with 80ms Down taps / 1s rest until all 8 tabs match, // then restore the top of the item list and close the bag. // Returns false after four bounded pocket passes. bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons=8); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index d0ff57e596..b2f179ebe6 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -262,35 +262,8 @@ namespace PokemonAutomation { pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); pbf_wait(context, 200ms); context.wait_for_all_requests(); - bool black_screen_found = false; - for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { - BlackScreenWatcher black_screen; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - for (size_t i = 0; i < 10; i++) { - pbf_press_button(context, BUTTON_DOWN, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - pbf_press_button(context, BUTTON_RIGHT, 180ms, 0ms); - } - pbf_move_left_joystick(context, { 0, 1 }, 10000ms, 0ms); - }, - { black_screen } - ); - if (ret == 0) { - stream.log("Black screen detected."); - black_screen_found = true; - } - } - context.wait_for_all_requests(); - - if (!black_screen_found) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "jubilife_to_oreburgh_gate: No black screen after 3 attempts.", - stream - ); - } + enter_cave_with_down_right_search(stream,context, + "jubilife_to_oreburgh_gate",10000ms); } void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index ab544d04f0..1025201993 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -113,11 +113,9 @@ namespace PokemonAutomation{ stream.log("[Oreburgh Gate entry] Cave visible; continuing the route.", COLOR_GREEN); } - enum class RepelRenewalChoice { YES, NO }; - // Call at a safe walking point, away from trainer/NPC prompts. - static void force_repel_expiry(VideoStream& stream, ProControllerContext& context, - RepelRenewalChoice choice, bool north_south = false) { + void force_repel_expiry(VideoStream& stream, ProControllerContext& context, + RepelRenewalChoice choice, bool north_south, bool require_expiry) { context.wait_for_all_requests(); stream.log(std::string("[Repel expiry] Wiggling ") + (north_south ? "north/south" : "west/east") + " until expiry; renewal response=" + @@ -147,6 +145,9 @@ namespace PokemonAutomation{ if (remaining > 0ms) pbf_wait(ctx, remaining); }, {{expired}}); if (result != 0) { + if (require_expiry) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: Required expiry prompt not observed within calibrated limit.",stream); stream.log("[Repel expiry] Calibrated 44.913s limit reached without an expiry prompt; checking clear overworld before continuing.", COLOR_ORANGE); } // Renewal has a gap before the final used-Repel message. A single diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h index 26602b2ba1..eed30fc4bd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h @@ -1,158 +1,163 @@ -/* BDSP AutoStory - Segment 02 - * From after acquiring the first badge until after acquiring the second badge. - * - * From: https://github.com/PokemonAutomation/ - * - */ - -#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H -#define PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H - -#include "../PokemonBDSP_AutoStoryTools.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - - -class AutoStory_Segment_02 : 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint{ -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: first badge acquired -// end: Floaroma central house exit, facing south, Repel inactive -void checkpoint_0201( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - -// Save/retry boundary at the central Floaroma house exit, matching 02.01 end. -class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: first badge acquired -// end: second Gym Badge acquired (Eterna City) -void checkpoint_0203( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); -class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: first badge acquired -// end: second Gym Badge acquired (Eterna City) -void checkpoint_0204( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - -class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: first badge acquired -// end: second Gym Badge acquired (Eterna City) -void checkpoint_0205( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - -class AutoStory_checkpoint_0206 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; -}; - -// start: first badge acquired -// end: second Gym Badge acquired (Eterna City) -void checkpoint_0206( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - - -} -} -} -#endif +/* BDSP AutoStory - Segment 02 + * From after acquiring the first badge until after acquiring the second badge. + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H +#define PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H + +#include "../PokemonBDSP_AutoStoryTools.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonBDSP{ + + +// Reused at safe walking points to synchronize Repel expiration and renewal. +enum class RepelRenewalChoice { YES, NO }; +void force_repel_expiry(VideoStream& stream, ProControllerContext& context, + RepelRenewalChoice choice, bool north_south=false, bool require_expiry=false); + +class AutoStory_Segment_02 : 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, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: Floaroma central house exit, facing south, Repel inactive +void checkpoint_0201( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +// Save/retry boundary at the central Floaroma house exit, matching 02.01 end. +class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_0203( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); +class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_0204( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_0205( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + +class AutoStory_checkpoint_0206 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + +// start: first badge acquired +// end: second Gym Badge acquired (Eterna City) +void checkpoint_0206( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats +); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 01e1c704d1..23291ae961 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -1,3 +1,4 @@ +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" /* BDSP AutoStory - Segment 03 * From after acquiring the second badge until after acquiring the third badge. * @@ -19,9 +20,11 @@ #include "../Detect/PokemonBDSP_AutoStory_OverworldDetector.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP_AutoStory_Segment_02.h" #include "PokemonBDSP_AutoStory_Segment_03.h" using namespace std::chrono_literals; @@ -34,18 +37,6 @@ namespace PokemonBDSP{ fight_trainer_using_moves(stream,context,2,label,post_battle_dialogue_cleanup); } - static void segment03_route_press(VideoStream& stream, ProControllerContext& context, - Button button, Milliseconds hold, Milliseconds release) { - context.wait_for_all_requests(); - auto frame=stream.video().snapshot(); - BattleMenuDetector menu(BattleType::TRAINER); - if (frame && menu.detect(*frame.frame)) { - segment03_route_battle(stream,context,"Recorded route encounter"); - return; - } - pbf_press_button(context,button,hold,release); - } - static void segment03_finish_transition(VideoStream& stream, ProControllerContext& context, const std::string& label) { BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, @@ -61,11 +52,21 @@ namespace PokemonBDSP{ // scene or declare arrival from a fade that has only just started. static void segment03_confirm_location(VideoStream& stream, ProControllerContext& context, OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, - size_t observations = 20, bool advance_dialogue = false, bool require_target = false) { + size_t observations = 20, bool advance_dialogue = false, bool require_target = false, + OverworldMatchResult (*instruction_card)(const ImageRGB32&) = nullptr) { context.wait_for_all_requests(); size_t confirmations=0; for (size_t observation=0;observation=3) { stream.log("["+label+"] Scene confirmed.",COLOR_GREEN); return; } @@ -84,16 +85,24 @@ namespace PokemonBDSP{ // Watch the approach itself, cancelling queued movement as soon as the // trainer speaks or a battle starts. The existing battle engine owns combat. static void segment03_expected_encounter(VideoStream& stream, ProControllerContext& context, - const std::string& label, const std::function& approach) { + const std::string& label, const std::function& approach, + Milliseconds encounter_wait=45000ms, bool require_encounter=true) { context.wait_for_all_requests(); ShortDialogWatcher dialogue; StartBattleDetector starting(stream.overlay()); BattleMenuWatcher menu(BattleType::TRAINER); - if (run_until(stream,context, - [&](ProControllerContext& ctx) { approach(ctx); pbf_wait(ctx,45000ms); }, - {{dialogue},{starting},{menu}}) < 0) + const int encountered=run_until(stream,context, + [&](ProControllerContext& ctx) { approach(ctx); pbf_wait(ctx,encounter_wait); }, + {{dialogue},{starting},{menu}}); + if (encountered<0) { + if (!require_encounter) { + stream.log("["+label+"] No encounter within "+std::to_string(encounter_wait.count())+ + "ms after approach; trainer dodged, continuing.",COLOR_GREEN); + return; + } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label+": Expected trainer encounter did not start.",stream); + } stream.log("["+label+"] Encounter detected; route movement cancelled."); segment03_route_battle(stream,context,label,false); } @@ -108,6 +117,7 @@ namespace PokemonBDSP{ } static void segment03_register_bike(VideoStream& stream, ProControllerContext& context) { + // Receiving Bike adds new flags after the earlier pre-shop Bag pass. if (!clear_new_item_flags(stream,context)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Bike: Could not clear reward flags.",stream); @@ -118,29 +128,186 @@ namespace PokemonBDSP{ if (!find_bag_item(stream,context,bike,8)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Bike: Bike selection not confirmed.",stream); + // Locate the Key Items tab and Bike first, then use the fixed Register + // sequence without depending on popup, wheel, or confirmation images. pbf_press_button(context,BUTTON_A,150ms,500ms); - context.wait_for_all_requests(); - segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikePopup.png", - {.79,.44,.15,.05},"Bike Register menu"); pbf_press_dpad(context,DPAD_DOWN,80ms,250ms); pbf_press_button(context,BUTTON_A,150ms,500ms); - context.wait_for_all_requests(); - segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikeRegisterWheel.png", - {.475,.375,.05,.085},"Bike registration wheel"); pbf_press_dpad(context,DPAD_RIGHT,80ms,250ms); pbf_press_button(context,BUTTON_A,150ms,500ms); - context.wait_for_all_requests(); - segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikeRegistered.png", - {.605,.365,.075,.11},"Registered Bike icon"); - segment03_wait_image(stream,context,"PokemonBDSP/Autostory/EternaBikeShop/BikeRegistered.png", - {.285,.835,.32,.048},"Bike registration confirmation"); pbf_mash_button(context,BUTTON_B,2000ms); context.wait_for_all_requests(); - stream.log("[Eterna Bike] Registration confirmed; preserving the reward's mounted state.",COLOR_GREEN); + stream.log("[Eterna Bike] Registration inputs sent; preserving the reward's mounted state.",COLOR_GREEN); } // Stop at the completed doorway transition, rather than spending the // remaining movement budget inside the next map. + // Recorded Cycling Road movement may cross a gate before its budget ends. + // Cancel at either fade color and resume only after that fade has cleared. + static bool segment03_cycling_move(VideoStream& stream, ProControllerContext& context, + double x, double y, Milliseconds duration, Milliseconds rest, + const std::string& label, bool require_transition = false) { + context.wait_for_all_requests(); + BlackScreenWatcher black; + WhiteScreenWatcher white; + BattleMenuWatcher battle(BattleType::TRAINER); + const int result=run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{x,y},duration,0ms); + pbf_wait(ctx,rest); + },{{black},{white},{battle}}); + if (result==2) { + segment03_route_battle(stream,context,label+" encounter"); + return false; + } + if (result<0) { + if (require_transition) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Transition did not start.",stream); + return false; + } + BlackScreenWatcher black_gone(COLOR_RED,{0.1,0.1,0.8,0.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + WhiteScreenWatcher white_gone(COLOR_RED,{0.1,0.1,0.8,0.8}, + 500,10,WhiteScreenWatcher::FinderType::GONE,500ms); + const int finished=result==1 + ? wait_until(stream,context,30s,{{white_gone}}) + : wait_until(stream,context,30s,{{black_gone}}); + if (finished!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Transition did not finish.",stream); + // A cleared fade can precede restored movement control. + pbf_wait(context,2000ms); + context.wait_for_all_requests(); + return true; + } + + // Returns true if an encounter began during alignment. The battle strip + // then owns it and counts completion without replaying entry movement. + static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContext& context) { + context.wait_for_all_requests(); + size_t confirmed=0,missing=0; + bool have_pose=false; + double previous_dx=0,previous_dy=0; + size_t observations_without_strong_alignment=0; + for (size_t observation=0;observations_without_strong_alignment<240;observation++) { + const auto frame=stream.video().snapshot(); + const auto match=frame ? match_cycling_road_bridge_approach(*frame.frame) : OverworldMatchResult{}; + const bool stable=match.located && have_pose && + std::abs(match.dx-previous_dx)<=1.0/192 && + std::abs(match.dy-previous_dy)<=1.0/108; + // Reliable rail/deck geometry and a settled pose renew the budget. + // Keep correcting while the visual alignment remains strong. + if (stable) observations_without_strong_alignment=0; + else ++observations_without_strong_alignment; + std::string diagnostic="[Cycling Road bridge] observation="+std::to_string(observation+1)+ + " located="+std::to_string(match.located)+" target="+std::to_string(match.at_target)+ + " stable="+std::to_string(stable)+ + " observations_without_strong_alignment="+std::to_string(observations_without_strong_alignment)+ + " offset="+std::to_string(match.dx)+","+std::to_string(match.dy); + for (const auto& anchor:match.landmarks) + diagnostic+=" ["+anchor.name+" coverage="+std::to_string(anchor.score)+ + " accepted="+std::to_string(anchor.accepted)+"]"; + stream.log(diagnostic); + double x=0,y=0; + Milliseconds hold=0ms,rest=250ms; + if (!match.located) { + confirmed=0;have_pose=false; + if (++missing>=6) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Bridge deck and right rail not acquired.",stream); + rest=500ms; + } else { + missing=0; + if (std::abs(match.dx)>.32 || std::abs(match.dy)>.60) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Bridge alignment offset exceeds bounds.",stream); + if (!stable) { + // The camera keeps easing after the bicycle stops. Require + // two agreeing observations before every new correction. + confirmed=0; + } else if (match.at_target) { + if (++confirmed>=3) { + stream.log("[Cycling Road] Upper white platform/right rail pose confirmed; starting watched downhill entry.",COLOR_GREEN); + return false; + } + } else { + confirmed=0; + // Align against the rail first. The demonstrated target is + // on the stationary upper white platform, before gravity. + if (std::abs(match.dx)>.012) { + x=match.dx>0 ? 1 : -1; + hold=std::abs(match.dx)>.04 ? 320ms : 240ms; + } else if (std::abs(match.dy)>1.0/54) { + y=match.dy>0 ? -1 : 1; + hold=std::abs(match.dy)>.06 ? 320ms : 200ms; + } + } + previous_dx=match.dx;previous_dy=match.dy; + // A movement invalidates the old pose. Reacquire two fresh + // settled measurements before another movement is allowed. + have_pose=hold==0ms; + } + BlackScreenWatcher black; + WhiteScreenWatcher white; + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + const int interrupted=run_until(stream,context, + [&](ProControllerContext& ctx) { + if (hold>0ms) pbf_move_left_joystick(ctx,{x,y},hold,rest); + else pbf_wait(ctx,rest); + ctx.wait_for_all_requests(); + },{{black},{white},{dialogue},{starting},{menu}}); + if (interrupted>=2) { + stream.log("[Cycling Road] Encounter during bridge approach; alignment movement cancelled."); + return true; + } + if (interrupted>=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Unexpected transition during bridge alignment.",stream); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Bridge lane alignment did not converge.",stream); + } + + + static void segment03_cycle_road_battles(VideoStream& stream, ProControllerContext& context, + bool encounter_pending) { + size_t completed=0; + auto watch=[&](bool initial) { + context.wait_for_all_requests(); + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + return run_until(stream,context, + [&](ProControllerContext& ctx) { + if (initial) pbf_move_left_joystick(ctx,{0,-1},3300ms,0ms); + // From the first encounter onward the controller stays + // neutral. The bicycle rolls naturally into the next trainer. + pbf_wait(ctx,completed>=3 ? 7000ms : 45000ms); + ctx.wait_for_all_requests(); + },{{dialogue},{starting},{menu}}); + }; + while (completed<4) { + const int result=encounter_pending ? 0 : watch(completed==0); + encounter_pending=false; + if (result<0) { + if (completed>=3) { + stream.log("[Cycling Road] Three battles complete; 7s without another encounter. Continuing south."); + return; + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Expected at least three battles before the south gate.",stream); + } + segment03_route_battle(stream,context, + "Cycling Road encounter "+std::to_string(completed+1),false); + ++completed; + stream.log("[Cycling Road] Completed battles: "+std::to_string(completed)+"/4."); + } + stream.log("[Cycling Road] Four battles complete; skipping the quiet wait."); + } + static void segment03_doorway(VideoStream& stream, ProControllerContext& context, bool north, std::chrono::milliseconds duration, const std::string& label, double horizontal = 0) { context.wait_for_all_requests(); @@ -414,9 +581,6 @@ namespace PokemonBDSP{ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Mart: Could not buy 3 Escape Ropes.",stream); segment03_doorway(stream,context,false,5000ms,"Eterna Mart exit"); - if (!clear_new_item_flags(stream,context)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Mart: Could not clear purchase flags.",stream); // Retrace to the Center column, then use its doorway to normalize the // position. Do not reverse the time spent loading the Mart interior. @@ -493,165 +657,159 @@ namespace PokemonBDSP{ pbf_wait(context, 403ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 642ms, 0ms); pbf_wait(context, 14ms); - segment03_confirm_location(stream,context,match_eterna_cycle_shop_counter, - "Eterna bicycle shop counter",20,false,true); - pbf_press_button(context,BUTTON_A,150ms,500ms); + // The recorded approach reaches the keeper even when the exact + // counter reference pose differs. Start talking before any reward wait. + // Cancel Up immediately on dialogue; all subsequent inputs are A only. + context.wait_for_all_requests(); + ShortDialogWatcher keeper_dialogue; + if (run_until(stream,context, + [](ProControllerContext& ctx) { + for (size_t press=0;press<20;press++) { + pbf_move_left_joystick(ctx,{0,1},80ms,100ms); + pbf_press_button(ctx,BUTTON_A,150ms,500ms); + } + },{{keeper_dialogue}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna bicycle shop: Keeper interaction did not produce dialogue.",stream); // The reward scene returns us outside already mounted. Wait for that // positive destination instead of replaying a fixed dialogue burst. segment03_confirm_location(stream,context,match_eterna_cycle_shop_reward, - "Eterna bicycle reward complete",180,true); + "Eterna bicycle reward complete",180,true,true, + match_eterna_bike_riding_card); segment03_register_bike(stream,context); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 733ms, 0ms); - pbf_wait(context, 316ms); - segment03_doorway(stream,context,false,3280ms,"Cycling Road north gate entrance"); - segment03_doorway(stream,context,false,1789ms,"Cycling Road north gate exit"); - align_to_overworld_landmarks(stream,context,match_cycling_road_north_entry, - "Cycling Road north entrance",false,true); - segment03_expected_encounter(stream,context,"Cycling Road Axel", - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,-1},3291ms,0ms); - pbf_wait(ctx,2531ms); - }); - segment03_expected_encounter(stream,context,"Cycling Road Nicole", + segment03_cycling_move(stream,context,-1,0,733ms,316ms,"Cycling Road approach"); + segment03_cycling_move(stream,context,0,-1,3280ms,3000ms,"Cycling Road north gate entrance",true); + segment03_cycling_move(stream,context,0,-1,1789ms,3000ms,"Cycling Road north gate exit",true); + // Fade completion can precede restored player control. Settle and + // clear the steps before acquiring the bridge, not the cliff wall. + pbf_wait(context,2000ms); + context.wait_for_all_requests(); + OverworldWatcher north_gate_overworld; + if (wait_until(stream,context,10s,{{north_gate_overworld}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Overworld not visible after north gate exit settle.",stream); + BlackScreenWatcher north_gate_black; + WhiteScreenWatcher north_gate_white; + ShortDialogWatcher north_gate_dialogue; + StartBattleDetector north_gate_starting(stream.overlay()); + BattleMenuWatcher north_gate_menu(BattleType::TRAINER); + const int clearance=run_until(stream,context, [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},676ms,0ms); - pbf_wait(ctx,2016ms); - }); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 145ms, 0ms); - pbf_wait(context, 1136ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 424ms, 0ms); - pbf_wait(context, 374ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 160ms, 0ms); - pbf_wait(context, 895ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 224ms, 0ms); - pbf_wait(context, 643ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 838ms, 0ms); - pbf_wait(context, 1761ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1839ms, 0ms); - pbf_wait(context, 1407ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 3343ms, 0ms); - pbf_wait(context, 3457ms); - segment03_route_press(stream,context, BUTTON_A, 233ms, 0ms); - pbf_wait(context, 135ms); - segment03_route_press(stream,context, BUTTON_A, 175ms, 0ms); - pbf_wait(context, 159ms); - segment03_route_press(stream,context, BUTTON_A, 91ms, 0ms); - pbf_wait(context, 340ms); - segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 243ms); - segment03_route_press(stream,context, BUTTON_A, 223ms, 0ms); - pbf_wait(context, 375ms); - segment03_route_press(stream,context, BUTTON_A, 346ms, 0ms); - pbf_wait(context, 1168ms); - segment03_route_press(stream,context, BUTTON_A, 232ms, 0ms); - pbf_wait(context, 589ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 550ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 871ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 956ms); - segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); - pbf_wait(context, 295ms); - segment03_route_press(stream,context, BUTTON_B, 172ms, 0ms); - pbf_wait(context, 206ms); - segment03_route_press(stream,context, BUTTON_B, 193ms, 0ms); - pbf_wait(context, 175ms); - segment03_route_press(stream,context, BUTTON_B, 175ms, 0ms); - pbf_wait(context, 307ms); - segment03_route_press(stream,context, BUTTON_B, 181ms, 0ms); - pbf_wait(context, 325ms); - segment03_route_press(stream,context, BUTTON_B, 144ms, 0ms); - pbf_wait(context, 417ms); - segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); - pbf_wait(context, 309ms); - segment03_route_press(stream,context, BUTTON_B, 173ms, 0ms); - pbf_wait(context, 827ms); - segment03_route_press(stream,context, BUTTON_B, 239ms, 0ms); - pbf_wait(context, 473ms); - segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); - pbf_wait(context, 321ms); - segment03_route_press(stream,context, BUTTON_B, 128ms, 0ms); - pbf_wait(context, 426ms); - segment03_route_press(stream,context, BUTTON_B, 158ms, 0ms); - pbf_wait(context, 488ms); - segment03_route_press(stream,context, BUTTON_B, 231ms, 0ms); - pbf_wait(context, 724ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 404ms); - segment03_route_press(stream,context, BUTTON_B, 109ms, 0ms); - pbf_wait(context, 357ms); - segment03_route_press(stream,context, BUTTON_B, 199ms, 0ms); - pbf_wait(context, 416ms); - segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); - pbf_wait(context, 175ms); - segment03_route_press(stream,context, BUTTON_B, 189ms, 0ms); - pbf_wait(context, 345ms); - segment03_route_press(stream,context, BUTTON_B, 189ms, 0ms); - pbf_wait(context, 277ms); - segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); - pbf_wait(context, 368ms); - segment03_route_press(stream,context, BUTTON_B, 232ms, 0ms); - pbf_wait(context, 189ms); - segment03_route_press(stream,context, BUTTON_B, 172ms, 0ms); - pbf_wait(context, 474ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 504ms); - segment03_route_press(stream,context, BUTTON_B, 228ms, 0ms); - pbf_wait(context, 372ms); - segment03_route_press(stream,context, BUTTON_B, 162ms, 0ms); - pbf_wait(context, 4324ms); - segment03_expected_encounter(stream,context,"Route 207 Camper", + pbf_move_left_joystick(ctx,{0,-1},750ms,250ms); + ctx.wait_for_all_requests(); + },{{north_gate_black},{north_gate_white},{north_gate_dialogue},{north_gate_starting},{north_gate_menu}}); + if (clearance==0 || clearance==1) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Unexpected transition while clearing north gate steps.",stream); + const bool cycling_encounter=clearance>=2 || segment03_align_cycle_start(stream,context); + segment03_cycle_road_battles(stream,context,cycling_encounter); + // Any approach movement may already enter the south gate. Preserve + // that transition instead of requesting two more doorway crossings. + bool south_gate_entered=segment03_cycling_move(stream,context,-1,0,129ms,1499ms,"Cycling Road south gate approach"); + if (!south_gate_entered) + south_gate_entered=segment03_cycling_move(stream,context,-1,0,361ms,3345ms,"Cycling Road south gate approach"); + if (!south_gate_entered) + south_gate_entered=segment03_cycling_move(stream,context,0,-1,472ms,2574ms,"Cycling Road south gate approach"); + if (!south_gate_entered) + segment03_cycling_move(stream,context,0,-1,1977ms,1544ms,"Cycling Road south gate entrance",true); + segment03_confirm_location(stream,context,match_cycling_road_south_gate_interior, + "Cycling Road south gate interior"); + segment03_cycling_move(stream,context,0,-1,1977ms,1544ms,"Cycling Road south gate exit",true); + OverworldWatcher south_gate_overworld; + if (wait_until(stream,context,10s,{{south_gate_overworld}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Overworld not visible after south gate exit.",stream); + // Stay southbound until the scripted Dawn/Lucas dialogue actually + // appears. Cancel movement at the dialogue instead of spending inputs + // against the cutscene, and never begin the eastward trainer route early. + bool dawn_dialogue_seen=false; + for (size_t attempt=0;attempt<4 && !dawn_dialogue_seen;attempt++) { + ShortDialogWatcher dawn_dialogue; + // A dialogue already open after the transition needs no movement. + if (wait_until(stream,context,500ms,{{dawn_dialogue}})==0) { + dawn_dialogue_seen=true; + break; + } + BlackScreenWatcher dawn_black; + WhiteScreenWatcher dawn_white; + StartBattleDetector dawn_starting(stream.overlay()); + BattleMenuWatcher dawn_menu(BattleType::TRAINER); + const Milliseconds south=attempt==0 ? 1546ms : 3000ms; + stream.log("[Route 207 Dawn] South approach attempt="+std::to_string(attempt+1)); + const int result=run_until(stream,context, + [south](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,-1},south,500ms); + ctx.wait_for_all_requests(); + },{{dawn_dialogue},{dawn_black},{dawn_white},{dawn_starting},{dawn_menu}}); + if (result==0) dawn_dialogue_seen=true; + else if (result>=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 207 Dawn: Unexpected transition or battle before dialogue.",stream); + } + if (!dawn_dialogue_seen) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 207 Dawn: Dialogue not encountered after southward retries.",stream); + stream.log("[Route 207 Dawn] Dialogue detected; south movement cancelled.",COLOR_GREEN); + bool dawn_dialogue_cleared=false; + for (size_t pass=0;pass<6 && !dawn_dialogue_cleared;pass++) { + // A advances the conversation and accepts its default choice. + // Reuse the existing dialogue helper, then guard against a brief + // gap between text boxes being mistaken for the end of the scene. + mash_until_dialogue_ends(stream,context,BUTTON_A,60s); + context.wait_for_all_requests(); + ShortDialogWatcher resumed_dialogue; + if (wait_until(stream,context,2000ms,{{resumed_dialogue}})==0) continue; + const auto frame=stream.video().snapshot(); + OverworldDetector overworld; + dawn_dialogue_cleared=frame && overworld.detect(*frame.frame); + } + if (!dawn_dialogue_cleared) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 207 Dawn: Dialogue did not remain cleared.",stream); + pbf_wait(context, 6000ms); + stream.log("[Route 207 Dawn] Dialogue cleared; continuing to Camper Anthony.",COLOR_GREEN); + // restof206.txt begins after Dawn. Recorded battle/menu inputs are + // replaced by the encounter watchers and existing move-2 battle engine. + segment03_expected_encounter(stream,context,"Route 207 Camper Anthony", [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 360ms, 0ms); - pbf_wait(ctx, 188ms); - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 944ms, 0ms); - pbf_wait(ctx, 161ms); - pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 388ms, 0ms); - pbf_wait(ctx, 39ms); - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 2236ms, 0ms); - pbf_wait(ctx, 1820ms); + pbf_move_left_joystick(ctx,{0,-1},1013ms,1667ms); + pbf_move_left_joystick(ctx,{1,0},3309ms,1011ms); }); - segment03_expected_encounter(stream,context,"Route 207 Hiker", + // The recorded approach triggers Lauren next without further steering. + segment03_expected_encounter(stream,context,"Route 207 Picnicker Lauren", + [](ProControllerContext&) {}); + // Justin can be dodged. After the recorded approach, wait three seconds + // for an encounter; otherwise keep the remaining Coronet route. + segment03_expected_encounter(stream,context,"Route 207 Hiker Justin", [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},2758ms,0ms); - pbf_wait(ctx,94ms); - }); + pbf_move_left_joystick(ctx,{1,0},994ms,508ms); + pbf_move_left_joystick(ctx,{0,1},534ms,457ms); + pbf_move_left_joystick(ctx,{1,0},2076ms,149ms); + },3000ms,false); + // Keep the recorded route after Justin; replace its final blind doorway + // excerpt with the same watched down/right search used at Oreburgh Gate. + bool coronet_entered=segment03_cycling_move(stream,context,1,0,942ms,443ms,"Mount Coronet approach"); + if (!coronet_entered) + coronet_entered=segment03_cycling_move(stream,context,0,-1,776ms,119ms,"Mount Coronet approach"); + if (!coronet_entered) + coronet_entered=segment03_cycling_move(stream,context,1,0,627ms,147ms,"Mount Coronet approach"); + if (!coronet_entered) + coronet_entered=segment03_cycling_move(stream,context,0,1,725ms,1008ms,"Mount Coronet approach"); + if (!coronet_entered) { + enter_cave_with_down_right_search(stream,context,"Mount Coronet west entrance"); + segment03_finish_transition(stream,context,"Mount Coronet west entrance"); + // finish_transition includes 500ms; allow two seconds in total. + pbf_wait(context,1500ms); + context.wait_for_all_requests(); + } + OverworldWatcher coronet_overworld; + if (wait_until(stream,context,10s,{{coronet_overworld}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mount Coronet: Interior not ready after west entrance.",stream); context.wait_for_all_requests(); } void coronet1(VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1093ms, 0ms); - pbf_wait(context, 832ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 178ms, 0ms); - pbf_wait(context, 305ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); - pbf_wait(context, 2805ms); - segment03_route_press(stream,context, BUTTON_X, 163ms, 0ms); - pbf_wait(context, 837ms); - segment03_route_press(stream,context, BUTTON_A, 166ms, 0ms); - pbf_wait(context, 2708ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 171ms, 0ms); - pbf_wait(context, 308ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 185ms, 0ms); - pbf_wait(context, 967ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 196ms, 0ms); - pbf_wait(context, 52ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 182ms, 0ms); - pbf_wait(context, 507ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 138ms, 0ms); - pbf_wait(context, 1004ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 179ms, 0ms); - pbf_wait(context, 844ms); - segment03_route_press(stream,context, BUTTON_A, 192ms, 0ms); - pbf_wait(context, 487ms); - segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); - pbf_wait(context, 677ms); - segment03_route_press(stream,context, BUTTON_A, 176ms, 0ms); - pbf_wait(context, 989ms); - segment03_route_press(stream,context, BUTTON_B, 179ms, 0ms); - pbf_wait(context, 736ms); - segment03_route_press(stream,context, BUTTON_B, 186ms, 0ms); - pbf_wait(context, 551ms); + activate_repel(stream, context, 5); pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); pbf_wait(context, 104ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 852ms, 0ms); @@ -660,39 +818,39 @@ namespace PokemonBDSP{ pbf_move_left_joystick(context, {1.000000, 1.000000}, 78ms, 0ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 983ms, 0ms); pbf_wait(context, 1754ms); - segment03_route_press(stream,context, BUTTON_A, 173ms, 0ms); + pbf_press_button(context, BUTTON_A, 173ms, 0ms); pbf_wait(context, 1072ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_press_button(context, BUTTON_A, 168ms, 0ms); pbf_wait(context, 155ms); - segment03_route_press(stream,context, BUTTON_A, 142ms, 0ms); + pbf_press_button(context, BUTTON_A, 142ms, 0ms); pbf_wait(context, 136ms); - segment03_route_press(stream,context, BUTTON_A, 159ms, 0ms); + pbf_press_button(context, BUTTON_A, 159ms, 0ms); pbf_wait(context, 1598ms); - segment03_route_press(stream,context, BUTTON_A, 244ms, 0ms); + pbf_press_button(context, BUTTON_A, 244ms, 0ms); pbf_wait(context, 148ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_press_button(context, BUTTON_A, 170ms, 0ms); pbf_wait(context, 587ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_press_button(context, BUTTON_A, 211ms, 0ms); pbf_wait(context, 135ms); - segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); pbf_wait(context, 321ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_press_button(context, BUTTON_A, 211ms, 0ms); pbf_wait(context, 205ms); - segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); pbf_wait(context, 672ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_press_button(context, BUTTON_A, 182ms, 0ms); pbf_wait(context, 122ms); - segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_press_button(context, BUTTON_A, 172ms, 0ms); pbf_wait(context, 362ms); - segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_press_button(context, BUTTON_A, 172ms, 0ms); pbf_wait(context, 373ms); - segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_press_button(context, BUTTON_A, 199ms, 0ms); pbf_wait(context, 150ms); - segment03_route_press(stream,context, BUTTON_A, 183ms, 0ms); + pbf_press_button(context, BUTTON_A, 183ms, 0ms); pbf_wait(context, 190ms); - segment03_route_press(stream,context, BUTTON_A, 188ms, 0ms); + pbf_press_button(context, BUTTON_A, 188ms, 0ms); pbf_wait(context, 213ms); - segment03_route_press(stream,context, BUTTON_A, 143ms, 0ms); + pbf_press_button(context, BUTTON_A, 143ms, 0ms); pbf_wait(context, 7001ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 999ms, 0ms); pbf_wait(context, 268ms); @@ -717,246 +875,248 @@ namespace PokemonBDSP{ context.wait_for_all_requests(); } void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, {1.000000, -0.000000}, 4828ms, 0ms); - pbf_wait(context, 104ms); - segment03_route_battle(stream,context,"Route 208 Hiker"); + // The expiry routine starts west. Step east first to leave room for + // its west/east pairs, then clear the old Repel before route movement. + context.wait_for_all_requests(); + pbf_wait(context, 250ms); + pbf_move_left_joystick(context,{1,0},1000ms,0ms); + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + // Leave Repel off during the potentially long Hiker sight-line sweeps. + force_repel_expiry(stream,context,RepelRenewalChoice::NO,false,true); + + // Either Hiker may interrupt the crossing. Resume toward the east + // wall after each fight; finish only after a complete approach and + // sight-line sweep has no remaining encounter. Trainer names and a + // fixed battle count cannot distinguish the one- and two-fight routes. + bool bridge_sweep_clear=false; + size_t bridge_battles=0,approach_dialogues=0; + for (size_t pass=0;pass<6 && !bridge_sweep_clear;pass++) { + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + const int engaged=run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{1,0},4828ms,104ms); + ctx.wait_for_all_requests(); + pbf_wait(ctx,3000ms); + ctx.wait_for_all_requests(); + for (size_t pair=0;pair<8;pair++) { + pbf_move_left_joystick(ctx,{0,1},500ms,150ms); + pbf_move_left_joystick(ctx,{0,-1},500ms,150ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx,2500ms); + },{{dialogue},{starting},{menu}}); + if (engaged<0) { + bridge_sweep_clear=true; + break; + } + if (engaged==0) { + // A short box can be a Repel prompt. Advance it with A (the + // default renewal choice) while watching for actual combat. + // Never send a nonbattle choice arrow to the battle engine. + StartBattleDetector dialogue_starting(stream.overlay()); + BattleMenuWatcher dialogue_menu(BattleType::TRAINER); + bool dialogue_cleared=false; + const int battle_started=run_until(stream,context, + [&](ProControllerContext& ctx) { + mash_until_dialogue_ends(stream,ctx,BUTTON_A,20s); + ctx.wait_for_all_requests(); + pbf_wait(ctx,3000ms); + ctx.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + OverworldDetector overworld; + ShortDialogDetector short_dialogue; + dialogue_cleared=frame && overworld.detect(*frame.frame) && + !short_dialogue.detect(*frame.frame); + },{{dialogue_starting},{dialogue_menu}}); + if (battle_started<0) { + if (!dialogue_cleared || ++approach_dialogues>2) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Approach dialogue did not clear within bounded recovery.",stream); + stream.log("[Route 208 bridge] Nonbattle dialogue cleared; resuming watched crossing."); + continue; + } + } + segment03_route_battle(stream,context,"Route 208 Hiker"); + if (++bridge_battles>2) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Unexpected additional Hiker battle.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + } + if (!bridge_sweep_clear) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Watched crossing/sight-line sweep did not clear.",stream); + stream.log("[Route 208 bridge] Full east approach and sight-line sweep clear after " + + std::to_string(bridge_battles) + " battle(s); continuing toward the stairs.",COLOR_GREEN); pbf_move_left_joystick(context, {1.000000, -0.000000}, 421ms, 0ms); pbf_wait(context, 178ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 1436ms, 0ms); pbf_wait(context, 81ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 478ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1197ms, 0ms); pbf_wait(context, 298ms); - segment03_route_press(stream,context, BUTTON_A, 218ms, 0ms); - pbf_wait(context, 274ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 611ms); - segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 359ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 189ms, 0ms); - pbf_wait(context, 98ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 719ms, 0ms); - pbf_wait(context, 55ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1300ms, 0ms); - pbf_wait(context, 432ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1453ms, 0ms); - pbf_wait(context, 522ms); - segment03_route_battle(stream,context,"Aroma Lady Hannah"); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1252ms, 0ms); - pbf_wait(context, 491ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2576ms, 0ms); - pbf_wait(context, 211ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 282ms, 0ms); - pbf_wait(context, 198ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 602ms, 0ms); - pbf_wait(context, 1732ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1878ms, 0ms); - pbf_wait(context, 1588ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2201ms, 0ms); - pbf_wait(context, 6189ms); - segment03_route_press(stream,context, BUTTON_A, 276ms, 0ms); - pbf_wait(context, 201ms); - segment03_route_press(stream,context, BUTTON_A, 111ms, 0ms); - pbf_wait(context, 1090ms); - segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); - pbf_wait(context, 218ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 255ms); - segment03_route_press(stream,context, BUTTON_A, 238ms, 0ms); - pbf_wait(context, 272ms); - segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); - pbf_wait(context, 171ms); - segment03_route_press(stream,context, BUTTON_A, 202ms, 0ms); - pbf_wait(context, 451ms); - segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); - pbf_wait(context, 267ms); - segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); - pbf_wait(context, 482ms); - segment03_route_press(stream,context, BUTTON_A, 227ms, 0ms); - pbf_wait(context, 813ms); - segment03_route_press(stream,context, BUTTON_A, 186ms, 0ms); - pbf_wait(context, 427ms); - segment03_route_press(stream,context, BUTTON_A, 210ms, 0ms); - pbf_wait(context, 243ms); - segment03_route_press(stream,context, BUTTON_A, 187ms, 0ms); - pbf_wait(context, 191ms); - segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); - pbf_wait(context, 190ms); - segment03_route_press(stream,context, BUTTON_A, 212ms, 0ms); - pbf_wait(context, 3054ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3448ms, 0ms); - pbf_wait(context, 437ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2292ms, 0ms); - pbf_wait(context, 1582ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1125ms, 0ms); - pbf_wait(context, 334ms); - segment03_route_press(stream,context, BUTTON_B, 177ms, 0ms); - pbf_wait(context, 357ms); - segment03_route_press(stream,context, BUTTON_B, 216ms, 0ms); - pbf_wait(context, 400ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2228ms, 0ms); - pbf_wait(context, 304ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 279ms, 0ms); - pbf_wait(context, 174ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 498ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 76ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1452ms, 0ms); - pbf_wait(context, 21ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 648ms, 0ms); - pbf_wait(context, 221ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1161ms, 0ms); - pbf_wait(context, 59ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 533ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 96ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 81ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 37ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 941ms, 0ms); - pbf_wait(context, 2095ms); + // Fixed eastward travel can stop beyond the stair column. Center above + // the stairs using the native left-rock pose before moving south. + align_to_overworld_landmarks(stream,context,match_route208_stair_top, + "Route 208 stair-top rocks",false,true); + if (!activate_repel(stream,context,5)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 stair top: Repel activation failed.",stream); + context.wait_for_all_requests(); + // Reach the southern hedge below the honey tree before heading east. + // The former 2s descent could leave the player level with its trunk, + // consuming the eastward hold against the tree on attempts3 and4. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2800ms, 0ms); + pbf_wait(context, 643ms); + // Entry may occur during an earlier approach leg. Once a fade is + // caught, skip the remaining outdoor approach rather than replaying it + // inside the gatehouse. The existing watcher cancels at black/white, + // waits for GONE500ms, then settles for2s before the next input. + bool hearthome_gate_entered=segment03_cycling_move(stream,context, + 1,0,3500ms,443ms,"Hearthome west gate approach"); + if (!hearthome_gate_entered) + hearthome_gate_entered=segment03_cycling_move(stream,context, + 0,1,250ms,405ms,"Hearthome west gate doorway alignment"); + if (!hearthome_gate_entered) + hearthome_gate_entered=segment03_cycling_move(stream,context, + 1,0,600ms,1617ms,"Hearthome west gate entrance",true); + if (!hearthome_gate_entered) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome west gate: Interior transition not confirmed.",stream); + if (!segment03_cycling_move(stream,context, + 1,0,1800ms,1588ms,"Hearthome west gate exit",true)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome west gate: Exterior transition not confirmed.",stream); + OverworldWatcher hearthome_outside; + if (wait_until(stream,context,10s,{{hearthome_outside}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome west gate: Overworld not ready after exit.",stream); + if (segment03_cycling_move(stream,context, + 1,0,2200ms,6189ms,"Hearthome Buneary approach")) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Buneary approach: Unexpected extra transition.",stream); + // Begin the scene deadline after the approach requests finish, not + // while the recorded movement and neutral waits remain queued. + context.wait_for_all_requests(); + ShortDialogWatcher buneary_dialogue; + if (wait_until(stream,context,15s,{{buneary_dialogue}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Buneary: Dialogue did not start.",stream); + bool buneary_cleared=false; + for (size_t pass=0;pass<6 && !buneary_cleared;pass++) { + mash_until_dialogue_ends(stream,context,BUTTON_A,60s); + context.wait_for_all_requests(); + ShortDialogWatcher resumed_dialogue; + if (wait_until(stream,context,2000ms,{{resumed_dialogue}})==0) continue; + // Let the NPC finish walking away before any expiration movement. + pbf_wait(context,6000ms); + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + OverworldDetector overworld; + ShortDialogDetector dialogue; + buneary_cleared=frame && overworld.detect(*frame.frame) && !dialogue.detect(*frame.frame); + } + if (!buneary_cleared) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Buneary: Scene did not clear.",stream); + stream.log("[Hearthome Buneary] Scene cleared; expiring Repel without renewal before Center travel.",COLOR_GREEN); + pbf_move_left_joystick(context,{1,0},500ms,0ms); + context.wait_for_all_requests(); + force_repel_expiry(stream,context,RepelRenewalChoice::NO,false,true); + // Use the same whole-approach watcher as the Eterna bicycle shop. + // A doorway reached during recorded travel or alignment cancels all + // remaining outdoor inputs; the room must then be confirmed visually. + bool center_entered=false; + BlackScreenWatcher center_transition; + const int center_entry=run_until(stream,context, + [&](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3448ms, 0ms); + pbf_wait(ctx, 437ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 2292ms, 0ms); + pbf_wait(ctx, 1582ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1125ms, 0ms); + pbf_wait(ctx, 334ms); + // Repel expired before this travel; no blind response inputs remain. + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 2228ms, 0ms); + pbf_wait(ctx, 304ms); + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 279ms, 0ms); + pbf_wait(ctx, 174ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 498ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, 1.000000}, 76ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1452ms, 0ms); + pbf_wait(ctx, 21ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 648ms, 0ms); + pbf_wait(ctx, 221ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1161ms, 0ms); + pbf_wait(ctx, 59ms); + align_to_overworld_landmarks(stream,ctx,match_hearthome_center_entrance, + "Hearthome Center exterior alignment",false,true,false, + match_hearthome_center_interior,¢er_entered); + if (!center_entered) { + pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); + pbf_wait(ctx,3000ms); + } + },{{center_transition}}); + if (center_entry!=0 && !center_entered) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Center: Entry transition not detected.",stream); + segment03_finish_transition(stream,context,"Hearthome Center entrance"); + pbf_wait(context,1500ms); + context.wait_for_all_requests(); + segment03_confirm_location(stream,context,match_hearthome_center_interior, + "Hearthome Center interior"); if (!heal_pokemon(stream,context,"Hearthome")) OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT,"Hearthome Center exit not confirmed.",stream); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2092ms, 0ms); - pbf_wait(context, 185ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1004ms, 0ms); - pbf_wait(context, 62ms); - segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); - pbf_wait(context, 277ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 131ms, 0ms); - pbf_wait(context, 70ms); - segment03_route_press(stream,context, BUTTON_A, 190ms, 0ms); - pbf_wait(context, 512ms); - segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 169ms); - segment03_route_press(stream,context, BUTTON_A, 155ms, 0ms); - pbf_wait(context, 150ms); - segment03_route_press(stream,context, BUTTON_A, 167ms, 0ms); - pbf_wait(context, 129ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 179ms); - segment03_route_press(stream,context, BUTTON_A, 162ms, 0ms); - pbf_wait(context, 323ms); - segment03_route_press(stream,context, BUTTON_A, 207ms, 0ms); - pbf_wait(context, 160ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 287ms); - segment03_route_press(stream,context, BUTTON_A, 206ms, 0ms); - pbf_wait(context, 188ms); - segment03_route_press(stream,context, BUTTON_A, 176ms, 0ms); - pbf_wait(context, 233ms); - segment03_route_press(stream,context, BUTTON_A, 243ms, 0ms); - pbf_wait(context, 381ms); - segment03_route_press(stream,context, BUTTON_A, 220ms, 0ms); - pbf_wait(context, 287ms); - segment03_route_press(stream,context, BUTTON_A, 197ms, 0ms); - pbf_wait(context, 221ms); - segment03_route_press(stream,context, BUTTON_A, 205ms, 0ms); - pbf_wait(context, 191ms); - segment03_route_press(stream,context, BUTTON_A, 165ms, 0ms); - pbf_wait(context, 4201ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1179ms, 0ms); - pbf_wait(context, 4268ms); - segment03_route_press(stream,context, BUTTON_A, 181ms, 0ms); - pbf_wait(context, 219ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 123ms); - segment03_route_press(stream,context, BUTTON_A, 185ms, 0ms); - pbf_wait(context, 121ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 805ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 140ms); - segment03_route_press(stream,context, BUTTON_A, 102ms, 0ms); - pbf_wait(context, 218ms); - segment03_route_press(stream,context, BUTTON_B, 181ms, 0ms); - pbf_wait(context, 127ms); - segment03_route_press(stream,context, BUTTON_B, 167ms, 0ms); - pbf_wait(context, 539ms); - segment03_route_press(stream,context, BUTTON_B, 179ms, 0ms); - pbf_wait(context, 246ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 363ms); - segment03_route_press(stream,context, BUTTON_B, 182ms, 0ms); - pbf_wait(context, 176ms); - segment03_route_press(stream,context, BUTTON_B, 237ms, 0ms); - pbf_wait(context, 163ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 123ms); - segment03_route_press(stream,context, BUTTON_B, 186ms, 0ms); - pbf_wait(context, 173ms); - segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); - pbf_wait(context, 121ms); - segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); - pbf_wait(context, 337ms); - segment03_route_press(stream,context, BUTTON_B, 228ms, 0ms); - pbf_wait(context, 149ms); - segment03_route_press(stream,context, BUTTON_B, 236ms, 0ms); - pbf_wait(context, 138ms); - segment03_route_press(stream,context, BUTTON_B, 194ms, 0ms); - pbf_wait(context, 139ms); - segment03_route_press(stream,context, BUTTON_B, 195ms, 0ms); - pbf_wait(context, 162ms); - segment03_route_press(stream,context, BUTTON_B, 188ms, 0ms); - pbf_wait(context, 571ms); - segment03_route_press(stream,context, BUTTON_B, 229ms, 0ms); - pbf_wait(context, 122ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 122ms); - segment03_route_press(stream,context, BUTTON_B, 181ms, 0ms); - pbf_wait(context, 176ms); - segment03_route_press(stream,context, BUTTON_B, 170ms, 0ms); - pbf_wait(context, 175ms); - segment03_route_press(stream,context, BUTTON_B, 203ms, 0ms); - pbf_wait(context, 212ms); - segment03_route_press(stream,context, BUTTON_B, 209ms, 0ms); - pbf_wait(context, 274ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 214ms); - segment03_route_press(stream,context, BUTTON_B, 186ms, 0ms); - pbf_wait(context, 547ms); - segment03_route_press(stream,context, BUTTON_B, 212ms, 0ms); - pbf_wait(context, 1964ms); - segment03_route_press(stream,context, BUTTON_B, 195ms, 0ms); - pbf_wait(context, 139ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 279ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 350ms); - segment03_route_press(stream,context, BUTTON_B, 250ms, 0ms); - pbf_wait(context, 150ms); - segment03_route_press(stream,context, BUTTON_B, 196ms, 0ms); - pbf_wait(context, 488ms); - segment03_route_press(stream,context, BUTTON_B, 209ms, 0ms); - pbf_wait(context, 225ms); - segment03_route_press(stream,context, BUTTON_B, 242ms, 0ms); - pbf_wait(context, 347ms); - segment03_route_press(stream,context, BUTTON_B, 237ms, 0ms); - pbf_wait(context, 232ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 400ms); - segment03_route_press(stream,context, BUTTON_B, 226ms, 0ms); - pbf_wait(context, 162ms); - segment03_route_press(stream,context, BUTTON_B, 145ms, 0ms); - pbf_wait(context, 365ms); - segment03_route_press(stream,context, BUTTON_B, 209ms, 0ms); - pbf_wait(context, 560ms); - segment03_route_press(stream,context, BUTTON_B, 224ms, 0ms); - pbf_wait(context, 540ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 625ms); - segment03_route_press(stream,context, BUTTON_B, 198ms, 0ms); - pbf_wait(context, 218ms); - segment03_route_press(stream,context, BUTTON_B, 183ms, 0ms); - pbf_wait(context, 432ms); - segment03_route_press(stream,context, BUTTON_B, 235ms, 0ms); - pbf_wait(context, 323ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 189ms); - segment03_route_press(stream,context, BUTTON_B, 210ms, 0ms); - pbf_wait(context, 365ms); - segment03_route_press(stream,context, BUTTON_B, 236ms, 0ms); - pbf_wait(context, 189ms); - segment03_route_press(stream,context, BUTTON_B, 170ms, 0ms); - pbf_wait(context, 3365ms); + // Let the exit scene and camera settle before spending the Hall travel + // budget. The old east hold began while the exit was still fading in. + segment03_finish_transition(stream,context,"Hearthome Center heal exit"); + pbf_wait(context,1500ms); + context.wait_for_all_requests(); + OverworldWatcher center_exit_ready; + if (wait_until(stream,context,10s,{{center_exit_ready}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Center: Exterior not ready after healing.",stream); + pbf_move_left_joystick(context,{1,0},2092ms,185ms); + // Bring the Hall into view without the old 1004ms north overshoot. + pbf_move_left_joystick(context,{0,1},600ms,500ms); + context.wait_for_all_requests(); + align_to_overworld_landmarks(stream,context,match_hearthome_contest_fantina, + "Hearthome Contest Hall Fantina",false,true); + // Confirm the actual first interaction before clearing its text. Do not + // spend the old blind north/west holds against the side of the Hall. + pbf_move_left_joystick(context,{0,1},16ms,100ms); + pbf_press_button(context,BUTTON_A,194ms,277ms); + context.wait_for_all_requests(); + ShortDialogWatcher fantina_dialogue; + if (wait_until(stream,context,15s,{{fantina_dialogue}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Fantina dialogue did not start.",stream); + clear_post_battle_dialogue(stream,context,BUTTON_B); + pbf_wait(context,2000ms); + clear_post_battle_dialogue(stream,context,BUTTON_B); + // Preserve the separate north1179 Hall-entry phase and initial choice + // inputs. A failed doorway or absent conversation must not finish18. + segment03_cycling_move(stream,context,0,1,1179ms,4268ms, + "Hearthome Contest Hall entrance",true); + pbf_press_button(context,BUTTON_A,181ms,219ms); + context.wait_for_all_requests(); + ShortDialogWatcher contest_dialogue; + if (wait_until(stream,context,15s,{{contest_dialogue}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Interior dialogue did not start.",stream); + pbf_press_button(context,BUTTON_A,195ms,123ms); + pbf_press_button(context,BUTTON_A,185ms,121ms); + pbf_press_button(context,BUTTON_A,170ms,805ms); + pbf_press_button(context,BUTTON_A,195ms,140ms); + pbf_press_button(context,BUTTON_A,102ms,218ms); + clear_post_battle_dialogue(stream,context,BUTTON_B); + pbf_wait(context,2000ms); + clear_post_battle_dialogue(stream,context,BUTTON_B); context.wait_for_all_requests(); } + void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context) { pbf_move_left_joystick(context, {0.000000, -1.000000}, 903ms, 0ms); pbf_wait(context, 1455ms); @@ -1047,23 +1207,23 @@ namespace PokemonBDSP{ pbf_wait(context, 56ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 567ms, 0ms); pbf_wait(context, 855ms); - segment03_route_press(stream,context, BUTTON_X, 212ms, 0ms); + pbf_press_button(context, BUTTON_X, 212ms, 0ms); pbf_wait(context, 683ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 184ms, 0ms); pbf_wait(context, 415ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 129ms, 0ms); pbf_wait(context, 190ms); - segment03_route_press(stream,context, BUTTON_A, 196ms, 0ms); + pbf_press_button(context, BUTTON_A, 196ms, 0ms); pbf_wait(context, 1326ms); - segment03_route_press(stream,context, BUTTON_A, 144ms, 0ms); + pbf_press_button(context, BUTTON_A, 144ms, 0ms); pbf_wait(context, 591ms); - segment03_route_press(stream,context, BUTTON_A, 142ms, 0ms); + pbf_press_button(context, BUTTON_A, 142ms, 0ms); pbf_wait(context, 1017ms); - segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); pbf_wait(context, 411ms); - segment03_route_press(stream,context, BUTTON_B, 190ms, 0ms); + pbf_press_button(context, BUTTON_B, 190ms, 0ms); pbf_wait(context, 594ms); - segment03_route_press(stream,context, BUTTON_B, 172ms, 0ms); + pbf_press_button(context, BUTTON_B, 172ms, 0ms); pbf_wait(context, 190ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 317ms, 0ms); pbf_move_left_joystick(context, {-1.000000, 1.000000}, 266ms, 0ms); @@ -1142,13 +1302,13 @@ namespace PokemonBDSP{ pbf_move_left_joystick(context, {-1.000000, -1.000000}, 44ms, 0ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 300ms, 0ms); pbf_wait(context, 371ms); - segment03_route_press(stream,context, BUTTON_A, 181ms, 0ms); + pbf_press_button(context, BUTTON_A, 181ms, 0ms); pbf_wait(context, 136ms); - segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_press_button(context, BUTTON_A, 177ms, 0ms); pbf_wait(context, 137ms); - segment03_route_press(stream,context, BUTTON_A, 146ms, 0ms); + pbf_press_button(context, BUTTON_A, 146ms, 0ms); pbf_wait(context, 286ms); - segment03_route_press(stream,context, BUTTON_A, 150ms, 0ms); + pbf_press_button(context, BUTTON_A, 150ms, 0ms); pbf_wait(context, 175ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 310ms, 0ms); pbf_move_left_joystick(context, {-1.000000, -1.000000}, 124ms, 0ms); @@ -1156,83 +1316,83 @@ namespace PokemonBDSP{ pbf_wait(context, 320ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 327ms, 0ms); pbf_wait(context, 118ms); - segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_press_button(context, BUTTON_A, 213ms, 0ms); pbf_wait(context, 173ms); - segment03_route_press(stream,context, BUTTON_A, 205ms, 0ms); + pbf_press_button(context, BUTTON_A, 205ms, 0ms); pbf_wait(context, 106ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_press_button(context, BUTTON_A, 170ms, 0ms); pbf_wait(context, 115ms); - segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_press_button(context, BUTTON_A, 177ms, 0ms); pbf_wait(context, 145ms); - segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); + pbf_press_button(context, BUTTON_A, 194ms, 0ms); pbf_wait(context, 334ms); - segment03_route_press(stream,context, BUTTON_A, 217ms, 0ms); + pbf_press_button(context, BUTTON_A, 217ms, 0ms); pbf_wait(context, 174ms); - segment03_route_press(stream,context, BUTTON_A, 190ms, 0ms); + pbf_press_button(context, BUTTON_A, 190ms, 0ms); pbf_wait(context, 234ms); - segment03_route_press(stream,context, BUTTON_A, 233ms, 0ms); + pbf_press_button(context, BUTTON_A, 233ms, 0ms); pbf_wait(context, 119ms); - segment03_route_press(stream,context, BUTTON_A, 201ms, 0ms); + pbf_press_button(context, BUTTON_A, 201ms, 0ms); pbf_wait(context, 133ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_press_button(context, BUTTON_A, 195ms, 0ms); pbf_wait(context, 123ms); - segment03_route_press(stream,context, BUTTON_A, 214ms, 0ms); + pbf_press_button(context, BUTTON_A, 214ms, 0ms); pbf_wait(context, 109ms); - segment03_route_press(stream,context, BUTTON_B, 217ms, 0ms); + pbf_press_button(context, BUTTON_B, 217ms, 0ms); pbf_wait(context, 157ms); - segment03_route_press(stream,context, BUTTON_B, 162ms, 0ms); + pbf_press_button(context, BUTTON_B, 162ms, 0ms); pbf_wait(context, 523ms); - segment03_route_press(stream,context, BUTTON_B, 192ms, 0ms); + pbf_press_button(context, BUTTON_B, 192ms, 0ms); pbf_wait(context, 79ms); - segment03_route_press(stream,context, BUTTON_B, 132ms, 0ms); + pbf_press_button(context, BUTTON_B, 132ms, 0ms); pbf_wait(context, 115ms); - segment03_route_press(stream,context, BUTTON_B, 126ms, 0ms); + pbf_press_button(context, BUTTON_B, 126ms, 0ms); pbf_wait(context, 211ms); - segment03_route_press(stream,context, BUTTON_B, 156ms, 0ms); + pbf_press_button(context, BUTTON_B, 156ms, 0ms); pbf_wait(context, 528ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 156ms, 0ms); pbf_wait(context, 559ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 171ms, 0ms); pbf_wait(context, 65ms); - segment03_route_press(stream,context, BUTTON_A, 204ms, 0ms); + pbf_press_button(context, BUTTON_A, 204ms, 0ms); pbf_wait(context, 172ms); - segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_press_button(context, BUTTON_A, 177ms, 0ms); pbf_wait(context, 113ms); - segment03_route_press(stream,context, BUTTON_A, 173ms, 0ms); + pbf_press_button(context, BUTTON_A, 173ms, 0ms); pbf_wait(context, 92ms); - segment03_route_press(stream,context, BUTTON_A, 158ms, 0ms); + pbf_press_button(context, BUTTON_A, 158ms, 0ms); pbf_wait(context, 102ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_press_button(context, BUTTON_A, 168ms, 0ms); pbf_wait(context, 104ms); - segment03_route_press(stream,context, BUTTON_A, 179ms, 0ms); + pbf_press_button(context, BUTTON_A, 179ms, 0ms); pbf_wait(context, 106ms); - segment03_route_press(stream,context, BUTTON_A, 194ms, 0ms); + pbf_press_button(context, BUTTON_A, 194ms, 0ms); pbf_wait(context, 402ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_press_button(context, BUTTON_A, 182ms, 0ms); pbf_wait(context, 390ms); - segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_press_button(context, BUTTON_A, 193ms, 0ms); pbf_wait(context, 571ms); - segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_press_button(context, BUTTON_A, 213ms, 0ms); pbf_wait(context, 2265ms); - segment03_route_press(stream,context, BUTTON_A, 218ms, 0ms); + pbf_press_button(context, BUTTON_A, 218ms, 0ms); pbf_wait(context, 934ms); - segment03_route_press(stream,context, BUTTON_A, 232ms, 0ms); + pbf_press_button(context, BUTTON_A, 232ms, 0ms); pbf_wait(context, 171ms); - segment03_route_press(stream,context, BUTTON_A, 180ms, 0ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); pbf_wait(context, 488ms); - segment03_route_press(stream,context, BUTTON_A, 229ms, 0ms); + pbf_press_button(context, BUTTON_A, 229ms, 0ms); pbf_wait(context, 154ms); - segment03_route_press(stream,context, BUTTON_A, 193ms, 0ms); + pbf_press_button(context, BUTTON_A, 193ms, 0ms); pbf_wait(context, 287ms); - segment03_route_press(stream,context, BUTTON_A, 199ms, 0ms); + pbf_press_button(context, BUTTON_A, 199ms, 0ms); pbf_wait(context, 855ms); - segment03_route_press(stream,context, BUTTON_A, 249ms, 0ms); + pbf_press_button(context, BUTTON_A, 249ms, 0ms); pbf_wait(context, 594ms); - segment03_route_press(stream,context, BUTTON_A, 218ms, 0ms); + pbf_press_button(context, BUTTON_A, 218ms, 0ms); pbf_wait(context, 172ms); - segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_press_button(context, BUTTON_A, 172ms, 0ms); pbf_wait(context, 363ms); - segment03_route_press(stream,context, BUTTON_A, 207ms, 0ms); + pbf_press_button(context, BUTTON_A, 207ms, 0ms); pbf_wait(context, 1485ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 400ms, 0ms); pbf_move_left_joystick(context, {1.000000, 1.000000}, 49ms, 0ms); @@ -1278,9 +1438,9 @@ namespace PokemonBDSP{ pbf_wait(context, 49ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 549ms, 0ms); pbf_wait(context, 391ms); - segment03_route_press(stream,context, BUTTON_B, 175ms, 0ms); + pbf_press_button(context, BUTTON_B, 175ms, 0ms); pbf_wait(context, 222ms); - segment03_route_press(stream,context, BUTTON_B, 141ms, 0ms); + pbf_press_button(context, BUTTON_B, 141ms, 0ms); pbf_wait(context, 781ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 180ms, 0ms); pbf_move_left_joystick(context, {1.000000, -1.000000}, 38ms, 0ms); @@ -1294,47 +1454,47 @@ namespace PokemonBDSP{ pbf_wait(context, 3187ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 795ms, 0ms); pbf_wait(context, 72ms); - segment03_route_press(stream,context, BUTTON_A, 178ms, 0ms); + pbf_press_button(context, BUTTON_A, 178ms, 0ms); pbf_wait(context, 540ms); - segment03_route_press(stream,context, BUTTON_A, 159ms, 0ms); + pbf_press_button(context, BUTTON_A, 159ms, 0ms); pbf_wait(context, 655ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_press_button(context, BUTTON_A, 168ms, 0ms); pbf_wait(context, 1683ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 163ms, 0ms); pbf_wait(context, 2372ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 160ms, 0ms); pbf_wait(context, 454ms); - segment03_route_press(stream,context, BUTTON_A, 165ms, 0ms); + pbf_press_button(context, BUTTON_A, 165ms, 0ms); pbf_wait(context, 1591ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 147ms, 0ms); pbf_wait(context, 853ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 91ms, 0ms); pbf_wait(context, 389ms); - segment03_route_press(stream,context, BUTTON_A, 162ms, 0ms); + pbf_press_button(context, BUTTON_A, 162ms, 0ms); pbf_wait(context, 438ms); - segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); pbf_wait(context, 106ms); - segment03_route_press(stream,context, BUTTON_A, 144ms, 0ms); + pbf_press_button(context, BUTTON_A, 144ms, 0ms); pbf_wait(context, 287ms); - segment03_route_press(stream,context, BUTTON_A, 169ms, 0ms); + pbf_press_button(context, BUTTON_A, 169ms, 0ms); pbf_wait(context, 534ms); - segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); pbf_wait(context, 655ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_press_button(context, BUTTON_B, 211ms, 0ms); pbf_wait(context, 473ms); - segment03_route_press(stream,context, BUTTON_B, 168ms, 0ms); + pbf_press_button(context, BUTTON_B, 168ms, 0ms); pbf_wait(context, 401ms); - segment03_route_press(stream,context, BUTTON_B, 175ms, 0ms); + pbf_press_button(context, BUTTON_B, 175ms, 0ms); pbf_wait(context, 439ms); - segment03_route_press(stream,context, BUTTON_B, 78ms, 0ms); + pbf_press_button(context, BUTTON_B, 78ms, 0ms); pbf_wait(context, 723ms); - segment03_route_press(stream,context, BUTTON_B, 159ms, 0ms); + pbf_press_button(context, BUTTON_B, 159ms, 0ms); pbf_wait(context, 213ms); - segment03_route_press(stream,context, BUTTON_B, 122ms, 0ms); + pbf_press_button(context, BUTTON_B, 122ms, 0ms); pbf_wait(context, 544ms); - segment03_route_press(stream,context, BUTTON_B, 144ms, 0ms); + pbf_press_button(context, BUTTON_B, 144ms, 0ms); pbf_wait(context, 567ms); - segment03_route_press(stream,context, BUTTON_B, 167ms, 0ms); + pbf_press_button(context, BUTTON_B, 167ms, 0ms); pbf_wait(context, 216ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1117ms, 0ms); pbf_wait(context, 1924ms); @@ -1346,45 +1506,45 @@ namespace PokemonBDSP{ pbf_wait(context, 321ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 745ms, 0ms); pbf_wait(context, 3127ms); - segment03_route_press(stream,context, BUTTON_A, 213ms, 0ms); + pbf_press_button(context, BUTTON_A, 213ms, 0ms); pbf_wait(context, 144ms); - segment03_route_press(stream,context, BUTTON_A, 184ms, 0ms); + pbf_press_button(context, BUTTON_A, 184ms, 0ms); pbf_wait(context, 550ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_press_button(context, BUTTON_A, 211ms, 0ms); pbf_wait(context, 122ms); - segment03_route_press(stream,context, BUTTON_A, 188ms, 0ms); + pbf_press_button(context, BUTTON_A, 188ms, 0ms); pbf_wait(context, 130ms); - segment03_route_press(stream,context, BUTTON_A, 160ms, 0ms); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); pbf_wait(context, 272ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_press_button(context, BUTTON_A, 168ms, 0ms); pbf_wait(context, 5334ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 159ms, 0ms); pbf_wait(context, 204ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_press_button(context, BUTTON_A, 170ms, 0ms); pbf_wait(context, 698ms); - segment03_route_press(stream,context, BUTTON_A, 195ms, 0ms); + pbf_press_button(context, BUTTON_A, 195ms, 0ms); pbf_wait(context, 230ms); - segment03_route_press(stream,context, BUTTON_A, 169ms, 0ms); + pbf_press_button(context, BUTTON_A, 169ms, 0ms); pbf_wait(context, 773ms); - segment03_route_press(stream,context, BUTTON_X, 212ms, 0ms); + pbf_press_button(context, BUTTON_X, 212ms, 0ms); pbf_wait(context, 1555ms); - segment03_route_press(stream,context, BUTTON_X, 233ms, 0ms); + pbf_press_button(context, BUTTON_X, 233ms, 0ms); pbf_wait(context, 1255ms); - segment03_route_press(stream,context, BUTTON_A, 173ms, 0ms); + pbf_press_button(context, BUTTON_A, 173ms, 0ms); pbf_wait(context, 1640ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 177ms, 0ms); pbf_wait(context, 278ms); - segment03_route_press(stream,context, BUTTON_A, 189ms, 0ms); + pbf_press_button(context, BUTTON_A, 189ms, 0ms); pbf_wait(context, 506ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_press_button(context, BUTTON_A, 182ms, 0ms); pbf_wait(context, 700ms); - segment03_route_press(stream,context, BUTTON_A, 167ms, 0ms); + pbf_press_button(context, BUTTON_A, 167ms, 0ms); pbf_wait(context, 869ms); - segment03_route_press(stream,context, BUTTON_B, 176ms, 0ms); + pbf_press_button(context, BUTTON_B, 176ms, 0ms); pbf_wait(context, 356ms); - segment03_route_press(stream,context, BUTTON_B, 211ms, 0ms); + pbf_press_button(context, BUTTON_B, 211ms, 0ms); pbf_wait(context, 257ms); - segment03_route_press(stream,context, BUTTON_B, 158ms, 0ms); + pbf_press_button(context, BUTTON_B, 158ms, 0ms); pbf_wait(context, 496ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1677ms, 0ms); pbf_move_left_joystick(context, {-1.000000, 1.000000}, 14ms, 0ms); @@ -1421,11 +1581,11 @@ namespace PokemonBDSP{ pbf_wait(context, 55ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 1542ms, 0ms); pbf_wait(context, 192ms); - segment03_route_press(stream,context, BUTTON_A, 174ms, 0ms); + pbf_press_button(context, BUTTON_A, 174ms, 0ms); pbf_wait(context, 841ms); - segment03_route_press(stream,context, BUTTON_A, 184ms, 0ms); + pbf_press_button(context, BUTTON_A, 184ms, 0ms); pbf_wait(context, 549ms); - segment03_route_press(stream,context, BUTTON_A, 177ms, 0ms); + pbf_press_button(context, BUTTON_A, 177ms, 0ms); pbf_wait(context, 261ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 541ms, 0ms); pbf_wait(context, 6ms); @@ -1468,9 +1628,9 @@ namespace PokemonBDSP{ pbf_wait(context, 1256ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 1047ms, 0ms); pbf_wait(context, 503ms); - segment03_route_press(stream,context, BUTTON_B, 189ms, 0ms); + pbf_press_button(context, BUTTON_B, 189ms, 0ms); pbf_wait(context, 222ms); - segment03_route_press(stream,context, BUTTON_B, 183ms, 0ms); + pbf_press_button(context, BUTTON_B, 183ms, 0ms); pbf_wait(context, 637ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 1824ms, 0ms); pbf_wait(context, 1256ms); @@ -1494,21 +1654,21 @@ namespace PokemonBDSP{ pbf_wait(context, 57ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 3227ms, 0ms); pbf_wait(context, 4788ms); - segment03_route_press(stream,context, BUTTON_A, 182ms, 0ms); + pbf_press_button(context, BUTTON_A, 182ms, 0ms); pbf_wait(context, 388ms); - segment03_route_press(stream,context, BUTTON_A, 211ms, 0ms); + pbf_press_button(context, BUTTON_A, 211ms, 0ms); pbf_wait(context, 176ms); - segment03_route_press(stream,context, BUTTON_A, 172ms, 0ms); + pbf_press_button(context, BUTTON_A, 172ms, 0ms); pbf_wait(context, 509ms); - segment03_route_press(stream,context, BUTTON_A, 198ms, 0ms); + pbf_press_button(context, BUTTON_A, 198ms, 0ms); pbf_wait(context, 255ms); - segment03_route_press(stream,context, BUTTON_A, 170ms, 0ms); + pbf_press_button(context, BUTTON_A, 170ms, 0ms); pbf_wait(context, 408ms); - segment03_route_press(stream,context, BUTTON_A, 212ms, 0ms); + pbf_press_button(context, BUTTON_A, 212ms, 0ms); pbf_wait(context, 189ms); - segment03_route_press(stream,context, BUTTON_A, 166ms, 0ms); + pbf_press_button(context, BUTTON_A, 166ms, 0ms); pbf_wait(context, 458ms); - segment03_route_press(stream,context, BUTTON_A, 168ms, 0ms); + pbf_press_button(context, BUTTON_A, 168ms, 0ms); pbf_wait(context, 3630ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 744ms, 0ms); // The recording ends outside the gym door, facing north. @@ -1804,7 +1964,7 @@ void Checkpoint_0301( } std::string AutoStory_Checkpoint_0302::name() const { return "03.02 - Jupiter to Mount Coronet"; } std::string AutoStory_Checkpoint_0302::start_text() const { return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; } -std::string AutoStory_Checkpoint_0302::end_text() const { return "On Route 207 after the Hiker battle, before the west entrance of Mount Coronet."; } +std::string AutoStory_Checkpoint_0302::end_text() const { return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; } void AutoStory_Checkpoint_0302::run_checkpoint( SingleSwitchProgramEnvironment& env, @@ -1824,6 +1984,17 @@ void Checkpoint_0302( ) { checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { + // Set daylight once for this outdoor locator strip, including the + // Center, Mart return, bicycle shop, and north Route 206 gate. + context.wait_for_all_requests(); + env.console.log("[Eterna locator setup] Setting 14:00; preserving the current date."); + DateTime locator_time; + locator_time.hour = 14; + locator_time.minute = 0; + locator_time.second = 0; + set_time_fast(env, context, locator_time, true); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); // Recorded route with feedback battle handling. jupiter_to_coronet(env.console, context); @@ -1833,7 +2004,7 @@ void Checkpoint_0302( ); } std::string AutoStory_Checkpoint_0303::name() const { return "03.03 - Mount Coronet"; } -std::string AutoStory_Checkpoint_0303::start_text() const { return "On Route 207 after the Hiker battle, before the west entrance of Mount Coronet."; } +std::string AutoStory_Checkpoint_0303::start_text() const { return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; } std::string AutoStory_Checkpoint_0303::end_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } void AutoStory_Checkpoint_0303::run_checkpoint( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index d4c1737304..53e2bfe0d8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1303,9 +1303,10 @@ namespace PokemonAutomation { } else if (result.located) { confirmations = 0; missing = 0; - // A temporarily obscured third Jupiter anchor must not - // move us away from an otherwise aligned position. - if (matcher==match_galactic_hq_jupiter && result.coarse && + // Wait at an aligned pose while strict visual support + // recovers, rather than stepping away under a cloud. + if ((matcher==match_galactic_hq_jupiter || + matcher==match_route208_hannah_departure || matcher==match_route208_stair_top) && result.coarse && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/54){ pbf_wait(context,500ms); context.wait_for_all_requests(); @@ -1343,7 +1344,9 @@ namespace PokemonAutomation { result.dy < -1.0/54; const bool jupiter_anchor_horizontal=matcher==match_galactic_hq_jupiter && !jupiter_row_recovery && result.coarse && std::abs(result.dx)>1.0/128; - const bool horizontal = !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && + // Stay south of the Contest Hall wall/sign before crossing to Fantina. + const bool contest_row_recovery=matcher==match_hearthome_contest_fantina && result.dy>1.0/54; + const bool horizontal = !contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && ((horizontal_first && std::abs(result.dx)>1.0/128) || std::abs(result.dx)>=std::abs(result.dy)*9.0/16))); if (coarse_horizontal) stream.log("[" + label + @@ -1372,11 +1375,22 @@ namespace PokemonAutomation { pbf_wait(context, 500ms); } else { confirmations = 0; - if (matcher==match_galactic_hq_jupiter){ - // Blind room-wide sweeps can enter the nearby stairs. - // Reobserve in place, then fail if no anchors recover. - if (++missing>6) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Chair/stair anchors missing; no blind search near stairs.",stream); + if (matcher==match_hearthome_center_entrance || matcher==match_hearthome_contest_fantina){ + // Do not sweep into a different doorway or around the Hall's + // side when a cloud, NPC or fade hides the required anchors. + if (++missing>20) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Scene anchors missing; stationary reacquisition exhausted.",stream); + pbf_wait(context,500ms); + context.wait_for_all_requests(); + continue; + } + if (matcher==match_galactic_hq_jupiter || matcher==match_route208_stair_top){ + // Blind sweeps can enter nearby stairs. The outdoor + // rock pose gets ten seconds for a cloud to pass. + const bool stair_rocks=matcher==match_route208_stair_top; + if (++missing>(stair_rocks ? 20 : 6)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+(stair_rocks ? ": Rock anchors missing; no blind search near stairs." : + ": Chair/stair anchors missing; no blind search near stairs."),stream); pbf_wait(context,500ms); context.wait_for_all_requests(); continue; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 182fd95efd..8beb88ca77 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -36,6 +36,34 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { +// Extracted from the Segment 01 Oreburgh Gate approach. Both callers use +// the same bounded down/right search and cancel queued input at the black fade. +// Oreburgh preserves its original north sweep; Coronet uses only down/right. +void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, + const std::string& label, Milliseconds north_fallback) { + context.wait_for_all_requests(); + for (size_t attempt=0;attempt<3;attempt++) { + BlackScreenWatcher black_screen; + const int result=run_until(stream,context, + [north_fallback](ProControllerContext& ctx) { + for (size_t i=0;i<10;i++) { + pbf_press_button(ctx,BUTTON_DOWN,180ms,0ms); + pbf_press_button(ctx,BUTTON_RIGHT,180ms,0ms); + pbf_press_button(ctx,BUTTON_RIGHT,180ms,0ms); + } + if (north_fallback>0ms) + pbf_move_left_joystick(ctx,{0,1},north_fallback,0ms); + },{{black_screen}}); + context.wait_for_all_requests(); + if (result==0) { + stream.log(label+": Black screen detected."); + return; + } + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": No black screen after 3 attempts.",stream); +} + static bool mine_confirm_escape_via_menu( VideoStream& stream, ProControllerContext& context, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h index 76cad0fe64..0ec7f5663f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h @@ -5,6 +5,12 @@ #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { +// Reuse the Oreburgh Gate down/right entry search. Returns at fade start; +// caller owns destination-specific fade completion and interior confirmation. +// north_fallback preserves Oreburgh's final north sweep when explicitly set. +void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, + const std::string& label, Milliseconds north_fallback = Milliseconds(0)); + // Shared confirmed-Run escape: verifies exit transition or main menu. void escape_wild_battle(VideoStream& stream, ProControllerContext& context, BattleType battle_type = BattleType::STANDARD, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index bc1c487575..cf44218bf6 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -18,7 +18,7 @@ static cv::Mat gray_image(const ImageRGB32& image){ const_cast(image.data()),image.bytes_per_row()); cv::Mat gray; cv::cvtColor(pixels,gray,cv::COLOR_BGRA2GRAY); return gray; } -// Opt-in only for the outdoor Cut anchors. Divide out slow local lighting +// Opt-in for selected outdoor structural anchors. Divide out slow local lighting // changes so a moving cloud does not alter the printed sign/emblem structure. static cv::Mat normalize_landmark_illumination(const cv::Mat& gray){ cv::Mat pixels,background,normalized; @@ -411,6 +411,217 @@ OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame){ } return best; } +OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame){ + // Native screenshot 20261022-213928168268: centered above the east stairs. + // The isolated upper rock and lower rock pair must both agree before descent. + OverworldMatchResult best; + double best_score=-1; + // Stationary hourly captures supply lighting appearances, not new goals. + // Both physical rock groups still need the same confidence and agreement. + const char* references[][2]={ + {"PokemonBDSP/Autostory/Route208Stairs/UpperRock.png","PokemonBDSP/Autostory/Route208Stairs/LowerRocks.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Lower.png"}, + }; + for (const auto& reference:references){ + for (double scale : {.90,.95,1.0,1.05,1.10}){ + std::vector configs={ + {"Stair-top upper rock",reference[0], + {170.0/1920,350.0/1080,120.0/1920,120.0/1080},{0,0,1,1},.93,.20}, + {"Stair-top lower rock pair",reference[1], + {240.0/1920,425.0/1080,130.0/1920,180.0/1080},{0,0,1,1},.93,.20} + }; + for (auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + double score=1; + for (const auto& landmark:candidate.landmarks) score=std::min(score,landmark.score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + } + if (best.located) return best; + const char* approaches[][2]={ + {"PokemonBDSP/Autostory/Route208Stairs/Approach0507Upper.png","PokemonBDSP/Autostory/Route208Stairs/Approach0507Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Approach0538Upper.png","PokemonBDSP/Autostory/Route208Stairs/Approach0538Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Approach0549Upper.png","PokemonBDSP/Autostory/Route208Stairs/Approach0549Lower.png"}, + // The eastern cliff pose changes rock perspective and the local shadow + // pattern. These real-run appearances guide acquisition only: retain + // .92 acquisition NCC/.20 uniqueness and two-rock offset agreement. + // Arrival still requires the original .93 strict reference bank. + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Lower.png"}, + }; + for (const auto& reference:approaches){ + for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ + std::vector configs={ + {"Stair-top approach upper rock",reference[0], + {170.0/1920,350.0/1080,120.0/1920,120.0/1080},{0,0,.50,.85},.92,.20}, + {"Stair-top approach lower rock pair",reference[1], + {240.0/1920,425.0/1080,130.0/1920,180.0/1080},{0,0,.50,.85},.92,.20} + }; + for (auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + candidate.coarse=true;candidate.at_target=false; + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if (candidate.located && (!best.located || score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + } + return best; +} +OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ + OverworldMatchResult best; + double best_score=-1; + for (int appearance : {0,865,930,965,978}){ + for (double scale : {.90,.95,1.0,1.05,1.10}){ + std::vector configs={ + {"Field stair foot","PokemonBDSP/Autostory/Route208Hannah/StairFoot.png", + {225.0/1920,275.0/1080,250.0/1920,110.0/1080},{0,0,1,1},.93,.20,LandmarkFeatures::LOCAL_CONTRAST}, + {"Field sign frame","PokemonBDSP/Autostory/Route208Hannah/TreeSign.png", + {1000.0/1920,125.0/1080,100.0/1920,80.0/1080},{0,0,1,1},.93,.20}, + {"Field house sign frame","PokemonBDSP/Autostory/Route208Hannah/HouseSign.png", + {1440.0/1920,110.0/1080,110.0/1920,110.0/1080},{0,0,1,1},.93,.20} + }; + if (appearance){ + // Recorded dodges stand farther east, with the stairs clipped. + // These agreeing signs guide acquisition only; the successful + // departure reference above alone may confirm arrival. + configs.erase(configs.begin()); + configs[0].path=appearance==865 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign865.png" : + appearance==930 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign930.png" : appearance==965 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign965.png" : "PokemonBDSP/Autostory/Route208Hannah/FieldSign978.png"; + configs[1].path=appearance==865 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign865.png" : + appearance==930 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign930.png" : appearance==965 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign965.png" : "PokemonBDSP/Autostory/Route208Hannah/HouseSign978.png"; + } + for (auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + if (appearance){candidate.coarse=true;candidate.at_target=false;} + double score=1; + size_t accepted=0; + for (const auto& landmark:candidate.landmarks) if (landmark.accepted) {score=std::min(score,landmark.score);accepted++;} + if (!accepted) score=-1; + if ((candidate.located && !best.located) || + (candidate.located && best.located && !candidate.coarse && best.coarse) || + (candidate.located==best.located && candidate.coarse==best.coarse && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + } + return best; +} +OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame){ + // Stop on the street, south of the trigger tile. The roof emblem and + // orange awning must agree; a ball-shaped interior display is not enough. + OverworldMatchResult best; + double best_score=-1; + for (const char* ball : {"PokemonBDSP/Autostory/HearthomeCenter/Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/BallLive.png"}){ + for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ + const double w=112.0/1920*scale,h=108.0/1080*scale; + const double aw=220.0/1920*scale,ah=50.0/1080*scale; + auto candidate=match_landmarks(frame,{ + {"Hearthome Center roof ball",ball, + {.5-w/2,170.0/1080-h/2,w,h},{0,0,1,.65},.95,.20}, + {"Hearthome Center orange awning","PokemonBDSP/Autostory/HearthomeCenter/Awning.png", + {962.0/1920-aw/2,256.0/1080-ah/2,aw,ah},{0,0,1,.65},.95,.20} + },2,1.0/128,POSITION_TOLERANCE_Y,true); + double score=1; + for (const auto& anchor:candidate.landmarks) score=std::min(score,anchor.score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + } + return best; +} +OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ + // Three observed entry-camera angles. Perspective changes the machine + // and register independently, so require a pair from the same real view. + struct View { const char* machine; const char* desk; ImageFloatBox a,b; }; + const View views[]={ + {"InteriorMachine.png","InteriorRegister.png",{660.0/1920,160.0/1080,220.0/1920,185.0/1080}, + {1150.0/1920,155.0/1080,140.0/1920,175.0/1080}}, + {"EntryRightMachine.png","EntryRightRegister.png",{390.0/1920,160.0/1080,240.0/1920,185.0/1080}, + {905.0/1920,155.0/1080,130.0/1920,175.0/1080}}, + {"EntryLeftMachine.png","EntryLeftRegister.png",{1045.0/1920,160.0/1080,240.0/1920,185.0/1080}, + {1550.0/1920,220.0/1080,155.0/1920,110.0/1080}} + }; + OverworldMatchResult best; + double best_score=-1; + for (const auto& view:views){ + const std::string machine="PokemonBDSP/Autostory/HearthomeCenter/"+std::string(view.machine); + const std::string desk="PokemonBDSP/Autostory/HearthomeCenter/"+std::string(view.desk); + for (double scale : {.90,.95,1.0,1.05,1.10}){ + auto a=view.a,b=view.b; + a.x=.5+(a.x-.5)*scale;a.y=.5+(a.y-.5)*scale;a.width*=scale;a.height*=scale; + b.x=.5+(b.x-.5)*scale;b.y=.5+(b.y-.5)*scale;b.width*=scale;b.height*=scale; + auto candidate=match_landmarks(frame,{ + {"Center healing machine",machine.c_str(),a,{0,0,1,.8},.93,.20}, + {"Center register",desk.c_str(),b,{0,0,1,.8},.93,.20} + },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + double score=1; + for (const auto& anchor:candidate.landmarks) score=std::min(score,anchor.score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + } + return best; +} +OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame){ + // Fantina stands in front of the blue Contest Hall doors. Approach from + // the south; the dress and the door above her must move together. + OverworldMatchResult best; + double best_score=-1; + for (double scale : {.90,.95,1.0,1.05,1.10}){ + const double w=190.0/1920*scale,h=180.0/1080*scale; + const double dw=220.0/1920*scale,dh=70.0/1080*scale; + auto candidate=match_landmarks(frame,{ + {"Fantina pink dress","PokemonBDSP/Autostory/HearthomeContest/Fantina.png", + {.5-w/2,410.0/1080-h/2,w,h},{0,.1,1,.8},.93,.15}, + {"Contest Hall blue door","PokemonBDSP/Autostory/HearthomeContest/BlueDoor.png", + {930.0/1920-dw/2,285.0/1080-dh/2,dw,dh},{0,.05,1,.8},.93,.15} + },2,1.0/128,1.0/54,true); + double score=1; + for (const auto& anchor:candidate.landmarks) score=std::min(score,anchor.score); + if ((candidate.located && !best.located) || + (candidate.located==best.located && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + return best; +} OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ // Recorded centered doorway pose. All three stationary landmarks must agree // before entering; two can guide acquisition without confirming arrival. @@ -644,13 +855,26 @@ OverworldMatchResult match_eterna_cycle_shop_interior(const ImageRGB32& frame){ OverworldMatchResult match_eterna_cycle_shop_reward(const ImageRGB32& frame){ // Reward returns the player outside on the bicycle, at a separate pose. + // Reuse the stable roof braces; the nearby post changes appearance/shading. const std::vector configs{ {"Shop sign","PokemonBDSP/Autostory/EternaBikeShop/reward-sign.png",{580.0/1920,330.0/1080,100.0/1920,120.0/1080},{0,.1,.65,.75},.93,.20}, - {"Shop post","PokemonBDSP/Autostory/EternaBikeShop/reward-post.png",{1020.0/1920,350.0/1080,100.0/1920,106.0/1080},{.3,.1,.65,.75},.93,.20} + {"Shop roof braces","PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png",{706.0/1920,128.0/1080,470.0/1920,36.0/1080},{0,0,.95,.75},.93,.20} }; return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); } +OverworldMatchResult match_eterna_bike_riding_card(const ImageRGB32& frame){ + // Fixed UI card: require both the blue illustration and B Close prompt. + // Small search boxes tolerate capture offsets without matching other menus. + const std::vector configs{ + {"Blue Bike illustration","PokemonBDSP/Autostory/EternaBikeShop/BikeRidingBlueFeature.png", + {104.0/1920,314.0/1080,520.0/1920,450.0/1080},{.035,.26,.32,.48},.93,.20,LandmarkFeatures::BLUE_FRACTION}, + {"B Close prompt","PokemonBDSP/Autostory/EternaBikeShop/BikeRidingClosePrompt.png", + {1570.0/1920,730.0/1080,160.0/1920,60.0/1080},{.79,.65,.14,.10},.93,.20} + }; + return match_landmarks(frame,configs,2,1.0/64,1.0/54,true); +} + OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame){ const std::vector configs{ {"Red frame","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-0.png",{620.0/1920,65.0/1080,390.0/1920,240.0/1080},{0.1,0,0.65,0.55},.93,.20}, @@ -659,14 +883,133 @@ OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame){ return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); } -OverworldMatchResult match_cycling_road_north_entry(const ImageRGB32& frame){ - const std::vector configs{ - {"Route sign","PokemonBDSP/Autostory/CyclingRoad/cycling_road_north_entry-0.png",{696.0/1920,270.0/1080,140.0/1920,110.0/1080},{0,0,0.65,0.65},.93,.20}, - {"Left bridge cap","PokemonBDSP/Autostory/CyclingRoad/cycling_road_north_entry-1.png",{194.0/1920,568.0/1080,160.0/1920,160.0/1080},{0,0.2,0.5,0.8},.93,.20}, - {"Right bridge cap","PokemonBDSP/Autostory/CyclingRoad/cycling_road_north_entry-2.png",{1180.0/1920,540.0/1080,300.0/1920,280.0/1080},{0.5,0.2,0.5,0.8},.93,.20} - }; - return match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); +// Two stationary architectural features confirm the intended gate interior. +// The doorway and west window avoid the moving player, NPCs and localized text. +OverworldMatchResult match_cycling_road_south_gate_interior(const ImageRGB32& frame){ + return match_landmarks(frame,{ + {"Gate north doorway","PokemonBDSP/Autostory/CyclingRoadSouthGate/NorthDoorway.png",{630.0/1920,118.0/1080,220.0/1920,270.0/1080},{.15,0,.70,.55},.93,.20}, + {"Gate west window","PokemonBDSP/Autostory/CyclingRoadSouthGate/WestWindow.png",{302.0/1920,140.0/1080,157.0/1920,145.0/1080},{.05,0,.35,.55},.93,.20} + },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y); } +OverworldMatchResult match_cycling_road_bridge_approach(const ImageRGB32& frame){ + // Use the actual bridge entrance: cyan right rail with a broad metal deck + // immediately to its left. Neither the Route 206 sign nor gate is involved. + OverworldMatchResult best; + if (!frame) return best; + cv::Mat pixels((int)frame.height(),(int)frame.width(),CV_8UC4, + const_cast(frame.data()),frame.bytes_per_row()); + cv::Mat small,bgr,hsv,cyan; + cv::resize(pixels,small,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::cvtColor(small,bgr,cv::COLOR_BGRA2BGR); + cv::cvtColor(bgr,hsv,cv::COLOR_BGR2HSV); + // Hue/saturation retain the cyan rail under the recorded moving shadows. + cv::inRange(hsv,cv::Scalar(78,95,55),cv::Scalar(105,255,255),cyan); + cv::morphologyEx(cyan,cyan,cv::MORPH_CLOSE,cv::Mat::ones(3,3,CV_8U)); + const int w=hsv.cols,h=hsv.rows; + std::vector> contours; + cv::findContours(cyan,contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE); + double best_support=0; + bool ambiguous=false; + for (const auto& contour:contours){ + const auto rail=cv::boundingRect(contour); + if (rail.xw*.85 || rail.height(row,x); + // Shadowed metal remains low-saturation; the grass edge + // and paired rail reject unrelated dark neutral panels. + neutral+=p[1]<=80 && p[2]>=60; + } + const double fraction=(double)neutral/((x1-x0)*sustained); + if (fraction<.72) supported=false; + total+=fraction/2; + } + if (supported){deck_y=y;deck_support=total;break;} + } + if (deck_y<0 || deck_y(row,x); + grass+=p[0]>=25 && p[0]<=85 && p[1]>=60 && p[2]>=30; + } + if ((double)grass/((right-left)*(above_end-above_begin))<.45) continue; + // Measure the inner rail boundary at deck height, rather than using + // the cliff wall or the rail cap's perspective-dependent bounding box. + double inner=0,ink=0;size_t rows=0; + const int measure_begin=std::max(deck_y+(int)(h*.015),(int)(h*.52)); + const int measure_end=std::min(h,measure_begin+(int)(h*.02)); + for (int y=measure_begin;y(y,x)) { + int last=x,count=0; + for (int edge=x;edge(y,edge)){last=edge;++count;} + const int width=last-x+1; + if (width>=w*.008 && width<=w*.055 && (double)count/width>=.80) { + inner+=x;ink+=(double)count/width;++rows; + } + break; + } + } + } + if (rows<3 || rows<(measure_end-measure_begin)*.75) continue; + inner/=rows; + OverworldMatchResult candidate; + candidate.located=true; + candidate.dx=inner/w-.55; + candidate.dy=(double)deck_y/h-.36; + // Park on the upper white platform beside the right rail. The user + // demonstrated this stationary pose in the 12:33:35 recording; the + // downhill section starts well beyond it. Measure the rail at wheel + // height, not at its distant cap or the offset cliff wall. + candidate.at_target=std::abs(candidate.dx)<=.012 && + std::abs(candidate.dy)<=1.0/54; + OverworldLandmarkResult rail_anchor; + rail_anchor.name="Bridge right rail inner edge"; + rail_anchor.accepted=true;rail_anchor.score=ink/rows; + rail_anchor.dx=candidate.dx;rail_anchor.dy=candidate.dy; + rail_anchor.search={.25,0,.68,1}; + rail_anchor.found={(double)inner/w,(double)rail.y/h,(double)rail.width/w,(double)rail.height/h}; + OverworldLandmarkResult deck_anchor; + deck_anchor.name="Bridge deck leading edge"; + deck_anchor.accepted=true;deck_anchor.score=deck_support; + deck_anchor.dx=candidate.dx;deck_anchor.dy=candidate.dy; + deck_anchor.search={0,(double)begin/h,(double)right/w,(double)(end-begin)/h}; + deck_anchor.found={(double)left/w,(double)deck_y/h,(double)(right-left)/w,(double)sustained/h}; + candidate.landmarks={rail_anchor,deck_anchor}; + const double support=ink/rows+deck_support; + if (best.located && (std::abs(best.dx-candidate.dx)>AGREEMENT_X || + std::abs(best.dy-candidate.dy)>AGREEMENT_Y)) ambiguous=true; + if (support>best_support){best_support=support;best=std::move(candidate);} + } + if (ambiguous){best.located=false;best.at_target=false;} + return best; +} + OverworldMatchResult match_galactic_hq_exterior(const ImageRGB32& frame){ const std::vector configs{ @@ -1282,6 +1625,11 @@ const std::vector& overworld_matchers(){ static const std:: {"oreburgh-mart","Oreburgh Mart emblem",match_oreburgh_mart}, {"jubilife-center","Jubilife Pokemon Center entrance",match_jubilife_center_entrance}, {"eterna-center","Eterna Pokemon Center doorway",match_eterna_center_entrance}, + {"hearthome-center","Hearthome Pokemon Center doorway",match_hearthome_center_entrance}, + {"hearthome-center-interior","Hearthome Pokemon Center interior",match_hearthome_center_interior}, + {"hearthome-fantina","Fantina outside the Hearthome Contest Hall",match_hearthome_contest_fantina}, + {"route208-stair-top","Route 208 stair-top rocks",match_route208_stair_top}, + {"route208-hannah-departure","Route 208 field after Hannah",match_route208_hannah_departure}, {"eterna-gym","Eterna Gym doorway",match_eterna_gym_entrance}, {"galactic-hq-jupiter","Galactic HQ Jupiter interaction",match_galactic_hq_jupiter}, {"eterna-gym-statues","Eterna Gym interior statues",match_eterna_gym_statues}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index d709c03f4c..5cf7b29033 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -50,14 +50,21 @@ OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame); OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame); OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame); OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame); +OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame); +OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame); +OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame); +OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame); +OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame); OverworldMatchResult match_eterna_cycle_shop_entrance(const ImageRGB32& frame); OverworldMatchResult match_eterna_cycle_shop_interior(const ImageRGB32& frame); OverworldMatchResult match_eterna_cycle_shop_reward(const ImageRGB32& frame); +OverworldMatchResult match_eterna_bike_riding_card(const ImageRGB32& frame); OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame); -OverworldMatchResult match_cycling_road_north_entry(const ImageRGB32& frame); +OverworldMatchResult match_cycling_road_bridge_approach(const ImageRGB32& frame); +OverworldMatchResult match_cycling_road_south_gate_interior(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_exterior(const ImageRGB32& frame); OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame); From a2484622efb0afb95808df60b2cfdf8cddc589af Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 24 Oct 2026 15:09:51 -0500 Subject: [PATCH 29/39] Restore pending BDSP recovery and recording safeguards Reconcile the saved bridge, stair fallback, and Hearthome locator changes while retaining the 1825 ms descent. Preserve generated Visual Studio snapshots outside the repository and require explicit recording-stop confirmation before any queries. --- .../AutomationProgramPatterns.md | 11 ++ .../PokemonBDSP_AutoStory_Segment_03.cpp | 102 ++++++++++----- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 40 ++++-- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 3 +- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 118 +++++++++++------- .../Utils/PokemonBDSP_OverworldMatchers.h | 1 + 6 files changed, 192 insertions(+), 83 deletions(-) diff --git a/CodingAgentContext/AutomationProgramPatterns.md b/CodingAgentContext/AutomationProgramPatterns.md index 6ea9c04d44..c6b287d4ff 100644 --- a/CodingAgentContext/AutomationProgramPatterns.md +++ b/CodingAgentContext/AutomationProgramPatterns.md @@ -230,3 +230,14 @@ These are user-selected standing budgets. Recording this rule does not itself im ## Building-entry locators: watch transitions throughout (Kevin, 2026-10-21) For future building-entry work, use a BlackScreenWatcher alongside the visual locator throughout the approach and alignment. If a transition starts early, cancel remaining outdoor movement/search, wait for the fade to finish, and confirm the intended interior before continuing. Also recognize an already-visible intended interior so entry is not replayed. A black screen alone does not identify the building. Reuse existing matching/transition helpers, preserve shared gates, and keep route sequencing in its owning segment. + +## Recording review timing (Kevin, 2026-10-24) + +While Kevin is recording, do not make any queries or inspections: no OBS, +SerialPrograms, logs, processes, Git, screenshots, or recording-file checks. +Do not open, probe, seek, decode, extract frames from, or review an active video. +Kevin suspects these activities may contribute to system instability. +Treat recording as active until Kevin explicitly confirms it has stopped; +do not query the system to determine whether it has stopped. Resume checks +only after that confirmation. Do not stop or otherwise control recording +without separate explicit authorization. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 23291ae961..e561d53249 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -886,12 +886,12 @@ namespace PokemonBDSP{ force_repel_expiry(stream,context,RepelRenewalChoice::NO,false,true); // Either Hiker may interrupt the crossing. Resume toward the east - // wall after each fight; finish only after a complete approach and - // sight-line sweep has no remaining encounter. Trainer names and a - // fixed battle count cannot distinguish the one- and two-fight routes. - bool bridge_sweep_clear=false; + // wall after each fight. The wiggles are one encounter-triggering + // attempt: once started, do not restart or finish them after an + // interruption. Keep the resumed east approach watched for a later Hiker. + bool bridge_approach_clear=false,bridge_wiggle_started=false; size_t bridge_battles=0,approach_dialogues=0; - for (size_t pass=0;pass<6 && !bridge_sweep_clear;pass++) { + for (size_t pass=0;pass<6 && !bridge_approach_clear;pass++) { ShortDialogWatcher dialogue; StartBattleDetector starting(stream.overlay()); BattleMenuWatcher menu(BattleType::TRAINER); @@ -901,15 +901,22 @@ namespace PokemonBDSP{ ctx.wait_for_all_requests(); pbf_wait(ctx,3000ms); ctx.wait_for_all_requests(); - for (size_t pair=0;pair<8;pair++) { - pbf_move_left_joystick(ctx,{0,1},500ms,150ms); - pbf_move_left_joystick(ctx,{0,-1},500ms,150ms); - ctx.wait_for_all_requests(); + if (!bridge_wiggle_started) { + // Mark before the first input: an encounter during + // this set fulfills its purpose and consumes the set. + bridge_wiggle_started=true; + for (size_t pair=0;pair<8;pair++) { + pbf_move_left_joystick(ctx,{0,1},500ms,150ms); + pbf_move_left_joystick(ctx,{0,-1},500ms,150ms); + ctx.wait_for_all_requests(); + } + } else { + stream.log("[Route 208 bridge] Wiggle set already used; continuing watched east approach without repeating it."); } pbf_wait(ctx,2500ms); },{{dialogue},{starting},{menu}}); if (engaged<0) { - bridge_sweep_clear=true; + bridge_approach_clear=true; break; } if (engaged==0) { @@ -946,10 +953,10 @@ namespace PokemonBDSP{ pbf_wait(context,500ms); context.wait_for_all_requests(); } - if (!bridge_sweep_clear) + if (!bridge_approach_clear) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 bridge: Watched crossing/sight-line sweep did not clear.",stream); - stream.log("[Route 208 bridge] Full east approach and sight-line sweep clear after " + + "Route 208 bridge: Watched east approach did not clear.",stream); + stream.log("[Route 208 bridge] Full east approach clear after " + std::to_string(bridge_battles) + " battle(s); continuing toward the stairs.",COLOR_GREEN); pbf_move_left_joystick(context, {1.000000, -0.000000}, 421ms, 0ms); pbf_wait(context, 178ms); @@ -959,34 +966,61 @@ namespace PokemonBDSP{ pbf_wait(context, 298ms); // Fixed eastward travel can stop beyond the stair column. Center above // the stairs using the native left-rock pose before moving south. + bool rock_acquisition_timed_out=false; align_to_overworld_landmarks(stream,context,match_route208_stair_top, - "Route 208 stair-top rocks",false,true); + "Route 208 stair-top rocks",false,true,false,nullptr,nullptr,0,0,0, + &rock_acquisition_timed_out); if (!activate_repel(stream,context,5)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Route 208 stair top: Repel activation failed.",stream); context.wait_for_all_requests(); - // Reach the southern hedge below the honey tree before heading east. - // The former 2s descent could leave the player level with its trunk, - // consuming the eastward hold against the tree on attempts3 and4. - pbf_move_left_joystick(context, {0.000000, -1.000000}, 2800ms, 0ms); - pbf_wait(context, 643ms); + if (rock_acquisition_timed_out) { + stream.log("[Route 208 stair fallback] Using east 2000ms, southwest 1500ms, then south trial 1650ms."); + // Keep the recorded inputs explicit for review and calibration. + // Watch this whole recovery phase; cancel its remaining inputs if + // a transition or battle starts before the gate approach. + BlackScreenWatcher fallback_black; + WhiteScreenWatcher fallback_white; + BattleMenuWatcher fallback_battle(BattleType::TRAINER); + const int fallback_interrupted=run_until(stream,context, + [&](ProControllerContext& ctx) { + // East: clamp against the cliff edge. + pbf_move_left_joystick(ctx,{1,0},2000ms,0ms); + ctx.wait_for_all_requests(); + // Southwest: drag toward the stair column. + pbf_move_left_joystick(ctx,{-1,-1},1500ms,0ms); + ctx.wait_for_all_requests(); + // South: user-requested 1650ms trial; recovery may start farther down the stairs. + pbf_move_left_joystick(ctx,{0,-1},1650ms,0ms); + pbf_wait(ctx,643ms); + ctx.wait_for_all_requests(); + },{{fallback_black},{fallback_white},{fallback_battle}}); + if (fallback_interrupted>=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 stair fallback: Unexpected transition or battle before west gate approach.",stream); + } else { + // Pass just above the honey tree, between the southern trainer + // and Hannah to the north. Calibrate this 1825ms trial from video. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1825ms, 0ms); + pbf_wait(context, 643ms); + } // Entry may occur during an earlier approach leg. Once a fade is // caught, skip the remaining outdoor approach rather than replaying it // inside the gatehouse. The existing watcher cancels at black/white, // waits for GONE500ms, then settles for2s before the next input. bool hearthome_gate_entered=segment03_cycling_move(stream,context, - 1,0,3500ms,443ms,"Hearthome west gate approach"); + 1,0,rock_acquisition_timed_out ?3422ms:3500ms,443ms,"Hearthome west gate approach"); if (!hearthome_gate_entered) hearthome_gate_entered=segment03_cycling_move(stream,context, - 0,1,250ms,405ms,"Hearthome west gate doorway alignment"); + 0,1,rock_acquisition_timed_out ?241ms:250ms,405ms,"Hearthome west gate doorway alignment"); if (!hearthome_gate_entered) hearthome_gate_entered=segment03_cycling_move(stream,context, - 1,0,600ms,1617ms,"Hearthome west gate entrance",true); + 1,0,rock_acquisition_timed_out ?633ms:600ms,1617ms,"Hearthome west gate entrance",true); if (!hearthome_gate_entered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome west gate: Interior transition not confirmed.",stream); if (!segment03_cycling_move(stream,context, - 1,0,1800ms,1588ms,"Hearthome west gate exit",true)) + 1,0,rock_acquisition_timed_out ?1743ms:1800ms,1588ms,"Hearthome west gate exit",true)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome west gate: Exterior transition not confirmed.",stream); OverworldWatcher hearthome_outside; @@ -1032,24 +1066,26 @@ namespace PokemonBDSP{ BlackScreenWatcher center_transition; const int center_entry=run_until(stream,context, [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3448ms, 0ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3500ms, 0ms); pbf_wait(ctx, 437ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 2292ms, 0ms); + // Clear the northbound lamp column before crossing the plaza. + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 250ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 3000ms, 0ms); pbf_wait(ctx, 1582ms); - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1125ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1150ms, 0ms); pbf_wait(ctx, 334ms); // Repel expired before this travel; no blind response inputs remain. - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 2228ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 2250ms, 0ms); pbf_wait(ctx, 304ms); - pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 279ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 300ms, 0ms); pbf_wait(ctx, 174ms); - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 498ms, 0ms); - pbf_move_left_joystick(ctx, {-1.000000, 1.000000}, 76ms, 0ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1452ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, 1.000000}, 100ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1800ms, 0ms); pbf_wait(ctx, 21ms); - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 648ms, 0ms); pbf_wait(ctx, 221ms); - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1161ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 250ms, 0ms); pbf_wait(ctx, 59ms); align_to_overworld_landmarks(stream,ctx,match_hearthome_center_entrance, "Hearthome Center exterior alignment",false,true,false, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 53e2bfe0d8..ebd37d0225 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1236,7 +1236,9 @@ namespace PokemonAutomation { OverworldMatchResult (*entry_matcher)(const ImageRGB32&), bool* entry_detected, double arrival_tolerance_x, double arrival_tolerance_y, - double moderate_entry_confidence) { + double moderate_entry_confidence, bool* rock_acquisition_timed_out) { + if (rock_acquisition_timed_out) *rock_acquisition_timed_out=false; + auto missing_rocks_since=std::chrono::steady_clock::time_point::max(); if (entry_detected) *entry_detected=false; context.wait_for_all_requests(); pbf_wait(context, 500ms); @@ -1276,6 +1278,8 @@ namespace PokemonAutomation { label + ": Unexpected dialogue/battle during alignment.", stream); } const auto result = matcher(*frame.frame); + if (result.located) + missing_rocks_since=std::chrono::steady_clock::time_point::max(); VideoOverlaySet overlays(stream.overlay()); for (const auto& landmark : result.landmarks) { overlays.add(COLOR_CYAN, landmark.search); @@ -1346,9 +1350,17 @@ namespace PokemonAutomation { !jupiter_row_recovery && result.coarse && std::abs(result.dx)>1.0/128; // Stay south of the Contest Hall wall/sign before crossing to Fantina. const bool contest_row_recovery=matcher==match_hearthome_contest_fantina && result.dy>1.0/54; + // Keep the stair approach horizontal-first until close to + // its column. Recorded fine poses otherwise spent repeated + // 16ms sideways turns while a larger vertical error waited. + // Fine correction changes axis priority, never arrival gates. + const bool stair_fine_correction=matcher==match_route208_stair_top && + !result.coarse && std::abs(result.dx)<=1.0/64; const bool horizontal = !contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && - ((horizontal_first && std::abs(result.dx)>1.0/128) || + ((horizontal_first && !stair_fine_correction && std::abs(result.dx)>1.0/128) || std::abs(result.dx)>=std::abs(result.dy)*9.0/16))); + if (stair_fine_correction && horizontal_first && !horizontal && std::abs(result.dx)>1.0/128) + stream.log("["+label+"] Near stair column; correcting the larger vertical error first."); if (coarse_horizontal) stream.log("[" + label + "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); const double error = horizontal ? result.dx : result.dy*9.0/16; @@ -1385,12 +1397,26 @@ namespace PokemonAutomation { continue; } if (matcher==match_galactic_hq_jupiter || matcher==match_route208_stair_top){ - // Blind sweeps can enter nearby stairs. The outdoor - // rock pose gets ten seconds for a cloud to pass. + // Keep generic missing-anchor behavior unchanged. Route208's + // explicit caller can instead request its recorded recovery. const bool stair_rocks=matcher==match_route208_stair_top; - if (++missing>(stair_rocks ? 20 : 6)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+(stair_rocks ? ": Rock anchors missing; no blind search near stairs." : - ": Chair/stair anchors missing; no blind search near stairs."),stream); + if (stair_rocks && rock_acquisition_timed_out) { + const auto now=std::chrono::steady_clock::now(); + if (missing_rocks_since==std::chrono::steady_clock::time_point::max()) + missing_rocks_since=now; + // Matcher processing can take seconds per observation; + // measure real elapsed time rather than counting waits. + if (now-missing_rocks_since>=10s) { + context.wait_for_all_requests(); + *rock_acquisition_timed_out=true; + stream.log("["+label+"] Rock acquisition timed out after 10s; handing off to the requested cliff recovery."); + return; + } + } else if (++missing>(stair_rocks ? 20 : 6)) { + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+(stair_rocks ? ": Rock anchors missing; no blind search near stairs." : + ": Chair/stair anchors missing; no blind search near stairs."),stream); + } pbf_wait(context,500ms); context.wait_for_all_requests(); continue; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index f3574d296f..247a70d04f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -37,7 +37,8 @@ namespace PokemonAutomation { OverworldMatchResult (*entry_matcher)(const ImageRGB32&) = nullptr, bool* entry_detected = nullptr, double arrival_tolerance_x = 0, double arrival_tolerance_y = 0, - double moderate_entry_confidence = 0); + double moderate_entry_confidence = 0, + bool* rock_acquisition_timed_out = nullptr); void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index cf44218bf6..a9f4c6cfde 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -539,30 +539,29 @@ OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ return best; } OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame){ - // Stop on the street, south of the trigger tile. The roof emblem and - // orange awning must agree; a ball-shaped interior display is not enough. - OverworldMatchResult best; - double best_score=-1; - for (const char* ball : {"PokemonBDSP/Autostory/HearthomeCenter/Ball.png", - "PokemonBDSP/Autostory/HearthomeCenter/BallLive.png"}){ - for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ - const double w=112.0/1920*scale,h=108.0/1080*scale; - const double aw=220.0/1920*scale,ah=50.0/1080*scale; - auto candidate=match_landmarks(frame,{ - {"Hearthome Center roof ball",ball, - {.5-w/2,170.0/1080-h/2,w,h},{0,0,1,.65},.95,.20}, - {"Hearthome Center orange awning","PokemonBDSP/Autostory/HearthomeCenter/Awning.png", - {962.0/1920-aw/2,256.0/1080-ah/2,aw,ah},{0,0,1,.65},.95,.20} - },2,1.0/128,POSITION_TOLERANCE_Y,true); - double score=1; - for (const auto& anchor:candidate.landmarks) score=std::min(score,anchor.score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate);best_score=score; + // Real stationary hourly views cover daylight, night, dusk and cloud edges. + // The target is Kevin's street pose below the door; entry remains watched. + struct Reference { const char* ball; const char* awning; double ball_width,ball_height; }; + const Reference references[]={ + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting00Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting00Awning.png",144.0/1920,128.0/1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting01Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting01Awning.png",144.0/1920,128.0/1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting02Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting02Awning.png",144.0/1920,128.0/1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting03Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting03Awning.png",144.0/1920,128.0/1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting04Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting04Awning.png",112.0/1920,108.0/1080} + }; + std::vector variants; + for (const auto& reference:references){ + for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ + const double w=reference.ball_width*scale,h=reference.ball_height*scale; + const double aw=272.0/1920*scale,ah=80.0/1080*scale; + variants.push_back({"Hearthome Center roof ball",reference.ball, + {972.0/1920-w/2,208.0/1080-h/2,w,h},{0,0,1,.65},.95,.20}); + variants.push_back({"Hearthome Center orange awning",reference.awning, + {968.0/1920-aw/2,308.0/1080-ah/2,aw,ah},{0,0,1,.65},.95,.20}); } } - } - return best; + return match_lighting_pair(frame,variants,"Hearthome Center roof ball", + "Hearthome Center orange awning",1.0/128); } OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ // Three observed entry-camera angles. Perspective changes the machine @@ -600,28 +599,62 @@ OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ return best; } OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame){ - // Fantina stands in front of the blue Contest Hall doors. Approach from - // the south; the dress and the door above her must move together. - OverworldMatchResult best; - double best_score=-1; - for (double scale : {.90,.95,1.0,1.05,1.10}){ - const double w=190.0/1920*scale,h=180.0/1080*scale; - const double dw=220.0/1920*scale,dh=70.0/1080*scale; - auto candidate=match_landmarks(frame,{ - {"Fantina pink dress","PokemonBDSP/Autostory/HearthomeContest/Fantina.png", - {.5-w/2,410.0/1080-h/2,w,h},{0,.1,1,.8},.93,.15}, - {"Contest Hall blue door","PokemonBDSP/Autostory/HearthomeContest/BlueDoor.png", - {930.0/1920-dw/2,285.0/1080-dh/2,dw,dh},{0,.05,1,.8},.93,.15} - },2,1.0/128,1.0/54,true); - double score=1; - for (const auto& anchor:candidate.landmarks) score=std::min(score,anchor.score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate);best_score=score; + // Static sign rim above localized text and roof emblem; Fantina can move. + // Hourly captures vary the sign illumination. The roof remains reliable. + const char* signs[] = { + "PokemonBDSP/Autostory/HearthomeContest/SignRim.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting00Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting01Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting02Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting03Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting04Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting05Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting06Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting07Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting08Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting09Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting10Sign.png" + }; + std::vector variants; + for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ + const double sw=162.0/1920*scale,sh=62.0/1080*scale; + const double rw=136.0/1920*scale,rh=98.0/1080*scale; + for (const char* sign : signs){ + variants.push_back({"Contest Hall sign frame",sign, + {595.0/1920-sw/2,467.0/1080-sh/2,sw,sh},{0,.15,.75,.65},.93,.15}); } + variants.push_back({"Contest Hall roof emblem","PokemonBDSP/Autostory/HearthomeContest/RoofEmblem.png", + {940.0/1920-rw/2,175.0/1080-rh/2,rw,rh},{.1,0,.8,.65},.93,.15}); } - return best; + return match_lighting_pair(frame,variants,"Contest Hall sign frame", + "Contest Hall roof emblem",1.0/128); +} +OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame){ + // Wider fixed facade distinguishes the window bays at night. Real hourly + // references cover the illuminated windows; cursor, NPCs and text stay out. + const char* facades[] = { + "PokemonBDSP/Autostory/HearthomeGym/LeftFacade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting00Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting01Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting02Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting03Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting04Facade.png" + }; + std::vector variants; + for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ + const double aw=160.0/1920*scale,ah=84.0/1080*scale; + const double fw=218.0/1920*scale,fh=222.0/1080*scale; + variants.push_back({"Hearthome Gym doorway arch","PokemonBDSP/Autostory/HearthomeGym/DoorArch.png", + {966.0/1920-aw/2,236.0/1080-ah/2,aw,ah},{.15,0,.75,.65},.93,.20}); + for (const char* facade : facades){ + variants.push_back({"Hearthome Gym left facade",facade, + {719.0/1920-fw/2,315.0/1080-fh/2,fw,fh},{0,0,.55,.65},.93,.20}); + } + } + return match_lighting_pair(frame,variants,"Hearthome Gym doorway arch", + "Hearthome Gym left facade",1.0/128); } + OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ // Recorded centered doorway pose. All three stationary landmarks must agree // before entering; two can guide acquisition without confirming arrival. @@ -1627,7 +1660,8 @@ const std::vector& overworld_matchers(){ static const std:: {"eterna-center","Eterna Pokemon Center doorway",match_eterna_center_entrance}, {"hearthome-center","Hearthome Pokemon Center doorway",match_hearthome_center_entrance}, {"hearthome-center-interior","Hearthome Pokemon Center interior",match_hearthome_center_interior}, - {"hearthome-fantina","Fantina outside the Hearthome Contest Hall",match_hearthome_contest_fantina}, + {"hearthome-fantina","Hearthome Contest Hall sign + roof emblem (Fantina)",match_hearthome_contest_fantina}, + {"hearthome-gym","Hearthome Gym doorway (arch + facade)",match_hearthome_gym_entrance}, {"route208-stair-top","Route 208 stair-top rocks",match_route208_stair_top}, {"route208-hannah-departure","Route 208 field after Hannah",match_route208_hannah_departure}, {"eterna-gym","Eterna Gym doorway",match_eterna_gym_entrance}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 5cf7b29033..bd39058b8e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -53,6 +53,7 @@ OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame); OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame); OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame); OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame); +OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame); OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame); From 3dbe9550ab1926077281b71547c6ca919eeb8c9e Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 24 Oct 2026 21:24:48 -0500 Subject: [PATCH 30/39] Keep agent context and working notes local --- .github/copilot-instructions.md | 4 - .gitignore | 6 + .../AutomationProgramPatterns.md | 243 ------ CodingAgentContext/Backlog.md | 160 ---- CodingAgentContext/CopilotHandoff.md | 329 ------- .../AutoStory/DIRECTORY_CLEANUP_SUMMARY.md | 114 --- .../StorySegments/butters segment data.md | 817 ------------------ .../AutoStory/butters segment data.md | 817 ------------------ 8 files changed, 6 insertions(+), 2484 deletions(-) delete mode 100644 .github/copilot-instructions.md delete mode 100644 CodingAgentContext/AutomationProgramPatterns.md delete mode 100644 CodingAgentContext/Backlog.md delete mode 100644 CodingAgentContext/CopilotHandoff.md delete mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md delete mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md delete mode 100644 SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index fd71411423..0000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,4 +0,0 @@ -# Copilot Instructions - -## Project Guidelines -- Avoid running PowerShell terminal commands in this workspace; they frequently crash/close the project because they aren't run in the correct directory. Use file-reading, search and edit tools instead. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 053e1db970..5bc3147fce 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,9 @@ build_*/ .cache/* Packages/* +# Local agent context and working notes +/.github/copilot-instructions.md +/CodingAgentContext/ +/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md +/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md +/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md diff --git a/CodingAgentContext/AutomationProgramPatterns.md b/CodingAgentContext/AutomationProgramPatterns.md deleted file mode 100644 index c6b287d4ff..0000000000 --- a/CodingAgentContext/AutomationProgramPatterns.md +++ /dev/null @@ -1,243 +0,0 @@ -# Automation Program Patterns - -## Basic Program Structure (for Single-Switch Programs) - -### Required Files -``` -GameName_ProgramName.h // Header with class declarations -GameName_ProgramName.cpp // Implementation -``` -Example game names: `PokemonLA`, `PokemonSwSh`, `PokemonSV`, `PokemonBDSP` - -### Core Classes -```cpp -class ProgramName_Descriptor : public SingleSwitchProgramDescriptor -class ProgramName : public SingleSwitchProgramInstance -``` - -## Essential Includes - -### Header (.h) -```cpp -#include "CommonFramework/Notifications/EventNotificationsTable.h" -#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" -``` - -### Implementation (.cpp) -```cpp -#include "CommonFramework/Exceptions/ProgramFinishedException.h" -#include "CommonFramework/Notifications/ProgramNotifications.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -``` - -## Example Program Options - -### Common Option Types -```cpp -SimpleIntegerOption NUM_BOXES( - "Number of Boxes:", - LockMode::LOCK_WHILE_RUNNING, - 5, 1, 30 // default, min, max -); - -BooleanCheckBoxOption ENABLE_FEATURE( - "Enable Feature:", - LockMode::LOCK_WHILE_RUNNING, - true -); -``` - -### Register Options -```cpp -PA_ADD_OPTION(NUM_BOXES); -PA_ADD_OPTION(ENABLE_FEATURE); -``` - -## Notifications - -### Declaration (in .h) -```cpp -EventNotificationOption NOTIFICATION_STATUS_UPDATE; -EventNotificationsOption NOTIFICATIONS; -``` - -### Initialization (in constructor) -```cpp -NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) -, NOTIFICATIONS({ - &NOTIFICATION_STATUS_UPDATE, - &NOTIFICATION_PROGRAM_FINISH, - &NOTIFICATION_ERROR_FATAL, -}) -``` - -### Sending Notifications -```cpp -send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); -send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); -send_program_fatal_error_notification(env, NOTIFICATION_ERROR_FATAL, "Error message"); -``` - -## Controller Input - -### Button Presses -```cpp -pbf_press_button(context, BUTTON_A, 10, 100); // button, hold_time, release_time -pbf_press_dpad(context, DPAD_DOWN, 20, 50); -context.wait_for_all_requests(); // wait for button to finish execution -``` - -### Delays -```cpp -context.wait_for(std::chrono::milliseconds(1000)); -``` - -## Main Program Function Pattern - -### Simplest Program -```cpp -void ProgramName::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - // button presses and inference routines -} -``` - -## Looping Program with Ability to Recover from Minor Error - -```cpp -while(true){ - try{ - // Initialization logic - - env.update_stats(); - send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); - - // Main program logic - - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); - }catch(ProgramFinishedException&){ // an exception indicating the program has finished - env.update_stats(); - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); - throw; - }catch(ScreenshotException& e){ // an exception indicating an error somewhere in the loop - if(...){ // if we can recover from it - // execute recover logic (usually is restarting the game) - continue; - } - // we cannot recover from the exception. Throw the exception to the higher level to - // trigger fatal error noticiation and cleanup. - throw; - } -} -``` - - -## Program Registration - -### In GameName_Panels.cpp -```cpp -// Add include -#include "Programs/Category/GameName_ProgramName.h" - -// Add to make_panels() -ret.emplace_back(make_single_switch_program()); -``` -Examples: `PokemonLA_Panels.cpp`, `PokemonSwSh_Panels.cpp`, `PokemonSV_Panels.cpp` - -## Descriptor Configuration - -```cpp -ProgramName_Descriptor::ProgramName_Descriptor() - : SingleSwitchProgramDescriptor( - "GameName:ProgramName", // Unique ID - STRING_POKEMON + " [GameName]", "Display Name", // Category, Name - "ComputerControl/blob/master/Wiki/...", // Wiki URL - "Brief description.", // Description - ProgramControllerClass::StandardController_NoRestrictions, - FeedbackType::REQUIRED, // or NONE - AllowCommandsWhenRunning::DISABLE_COMMANDS - ) -{} -``` -## Screen-relative matcher positioning - -Repository rule: matcher targets, landmark sizes, search regions, reported offsets, -and alignment tolerances must use fractions of screen width and height, not fixed -pixel positions. Convert to pixel rectangles only where image processing requires -them, using the image dimensions. A fixed processing resolution is permitted; it -must not leak into the positioning API or movement decisions. Normalize each axis -separately, and account for aspect ratio when comparing horizontal/vertical error. -Keep this conversion within the game code; shared infrastructure changes require -prior review and approval. - -## Language-independent detection - -Design matchers and automation functions to work independently of the game -language unless they explicitly use OCR. Visual templates containing localized -text also create a language dependency; avoid treating them as language-neutral. -Prefer stable visual structure and screen-relative positions where reliable. -When OCR is necessary, make its language dependency explicit and support the -applicable game languages. The settings-verification migration is deferred in -Backlog.md; position detection versus OCR remains undecided. - -## Usage-cap handoff to Visual Studio GitHub Copilot - -Kevin requests that the final deliverable before approaching a Codex usage cap -be a copy-ready handoff prompt for Visual Studio GitHub Copilot. Use 90% consumed -in either available usage window as the default trigger, leaving room to finish -the handoff. Check available account usage at the start of substantial coding -work and before long operations. When the trigger is reached, bring authorized -work to a reviewable state, refresh CodingAgentContext/CopilotHandoff.md, include -the exact current task, pending work, files, tests, known risks, and all Kevin's -constraints, then make the handoff prompt the final deliverable of that turn. -Do not start another substantial implementation first. This is a during-work -practice, not a promise of continuous background monitoring. A hard usage cap -may prevent an additional reply, so keep the saved handoff current proactively. -The handoff is drafted for Kevin to paste; do not send messages or change -Copilot provider/account settings without separate authorization. - -## Lighting setup for new locator strips - -Kevin's preference: set the console clock to 13:00 before most new overworld -locator strips, preserving its current date. Set it once per contiguous strip, -not before every matcher or again for the same strip's post-battle return. -Allow the game lighting to settle after resuming. Existing locators already -validated across lighting conditions do not need retroactive clock changes. -Keep screen-relative positioning, visual confirmation, and bounded recovery; -controlled lighting does not replace those safeguards. - - -## Explicit checkpoint boundary contract - -Every explicit checkpoint starts at the repeatable end location of its predecessor, except the very first checkpoint in Segment 00. Specify position, facing direction and relevant state (such as Repel) consistently in both descriptions. Complete any location-normalizing entry/exit before handing off; save at the next checkpoint using the existing checkpoint save/retry helper. Do not define a new checkpoint at a variable timed-movement endpoint. - -Floaroma 02.01 -> 02.02: force Repel expiry/NO, travel blindly north into the central house and immediately exit, checking both door transitions. End 02.01 / start 02.02 outside that house facing south, Repel inactive. Per user preference, use blind calibrated south/east movement to the Pokemon Center, then watched entry and existing healing helpers; do not require facade/emblem locators on this local route. - -## Standing Repel movement budgets (Kevin, 2026-10-18) - -After successful `activate_repel(stream, context, num_icons, repel_type)`, begin a fresh cumulative movement budget for that Repel: - -| repel_type | Item | Movement before forcing expiry | -| --- | --- | --- | -| 0 | Repel | 12 seconds | -| 1 | Super Repel | 22 seconds | -| 2 | Max Repel | 30 seconds | - -Count actual route movement commands after activation, not wall-clock time. Exclude waits, menu navigation, dialogue and battles. At the applicable budget, use the existing forced-expiry/renewal helper at a suitable location before continuing the next route inputs. Restart the movement budget after a successful new application or renewal. Preserve the API: the third argument is `num_icons`; the fourth argument is `repel_type` (default 0). - -These are user-selected standing budgets. Recording this rule does not itself implement automatic movement accounting or change gameplay code. Reuse/expand existing helpers when implementing it. - - -## Building-entry locators: watch transitions throughout (Kevin, 2026-10-21) - -For future building-entry work, use a BlackScreenWatcher alongside the visual locator throughout the approach and alignment. If a transition starts early, cancel remaining outdoor movement/search, wait for the fade to finish, and confirm the intended interior before continuing. Also recognize an already-visible intended interior so entry is not replayed. A black screen alone does not identify the building. Reuse existing matching/transition helpers, preserve shared gates, and keep route sequencing in its owning segment. - -## Recording review timing (Kevin, 2026-10-24) - -While Kevin is recording, do not make any queries or inspections: no OBS, -SerialPrograms, logs, processes, Git, screenshots, or recording-file checks. -Do not open, probe, seek, decode, extract frames from, or review an active video. -Kevin suspects these activities may contribute to system instability. -Treat recording as active until Kevin explicitly confirms it has stopped; -do not query the system to determine whether it has stopped. Resume checks -only after that confirmation. Do not stop or otherwise control recording -without separate explicit authorization. diff --git a/CodingAgentContext/Backlog.md b/CodingAgentContext/Backlog.md deleted file mode 100644 index 72d9410449..0000000000 --- a/CodingAgentContext/Backlog.md +++ /dev/null @@ -1,160 +0,0 @@ -# AutoStory backlog - -## Segment 01: Validate the maxed starter before movement - -Status: Deferred. Record only; do not implement yet. - -Add a `checkformaxlevel` function at the start of Segment 01, before any navigation or movement. Also cover direct starts at checkpoint 01.01 so advanced mode cannot bypass the validation. - -Validate the Pokemon in party slot 1: - -- Species matches the Starter Pokemon dropdown: Chimchar -> Infernape, Turtwig -> Torterra, Piplup -> Empoleon. -- Read its level and require level 100. -- Read its moves and verify the required AutoStory moveset for that evolved starter. Determine and document the exact required movesets when implementing this item. - -If any check fails, stop without running route movement and show this error: - -"do you have your starter maxed and in slot 1?" - -Unreadable species, level, or moves must count as a failed check. Validation may open the party/summary UI, but must complete before overworld movement begins. - -## AutoStory battles: Handle a fainted starter - -Status: Deferred. Do not implement yet. - -Detect starter fainting and the forced party-selection screen in tutorial battles. -Handle this as an unexpected battle state instead of B-mashing until a generic -timeout. Decide whether to switch, recover, or retry from the checkpoint. Ordinary -route operation should prevent this by verifying Potion healing between battles. -Keep the normal trainer strategy unchanged; this is rare-case recovery. - -## Segments 00 and 01: Optimize grass movement steps - -Status: Deferred until the full route through Roark is validated. Do not change timings yet. - -Review recordings and matching logs to reduce unnecessary walk-through-grass steps, especially open-path movements that do not deliberately align against a wall. Excess steps add encounter opportunities, detection waits, and position drift. - -Classify each section before changing it: - -- Wall alignment: retain sufficient movement to reliably reach the wall. -- Open-path travel: reduce steps to the required distance while preserving the next movement's starting position. -- Encounter search: stop movement as soon as an encounter is detected. - -Prioritize obvious excess steps. Compare baseline and optimized runs across varying encounter patterns, and verify both the destination and the following movement before accepting a shorter route. - -## Settings verification: Support every game language - -Status: Deferred. Record only; decide the detection approach later. - -Settings verification must work in every language supported by BDSP. The current -visual templates for setting names and values depend on their language and must -be replaced or extended before claiming multilingual support. - -Evaluate position-based detection of settings rows, selected values, and sliders -versus explicitly language-aware OCR. Prefer language-independent geometry where -reliable; decide which approach is better after comparing the localized screens. -Do not assume that text width, row layout, or value positions are identical in all -languages without validation. - -Verify every required setting, including persistence after saving and reopening -Options. Autosave must be confirmed OFF in every supported language before route -movement begins. Validate the chosen approach against captures for all supported -languages, including incorrect settings and ambiguous detection cases. - -Related requirement: ideally all matchers and automation functions should be -language-independent unless explicitly using OCR. Audit text-based visual -references and other language assumptions when extending or reviewing them. - -## AutoStory helpers: Audit and reorganize reusable tools - -Status: Deferred. Record only; do not implement yet. - -Examine existing AutoStory helpers, their callers, and their responsibilities. -Reclassify helpers by purpose rather than the segment where they were first -introduced. Identify duplicate or overlapping functions and prefer reusing or -expanding an existing implementation before developing another helper. - -Likely tool files (with matching headers where appropriate) include: - -- `AutoStory_BattleTools.cpp`: battle handling, move selection/sequences, and battle cleanup. -- `AutoStory_NavigationTools.cpp`: route movement, alignment, doorway transitions, and navigation recovery. -- `AutoStory_BagTools.cpp`: Bag navigation, item use, pocket detection, and NEW-flag clearing. - -Review the existing battle/navigation utilities and segment-specific tool files -before deciding the final file boundaries and names. Consolidate and move shared -helpers rather than creating parallel implementations. Keep segment-specific -route sequences in their segments. - -Include a code review of helper implementations and their callers throughout -AutoStory. Incorporate and adjust helpers and call sites so routes use the shared -tool helpers instead of unique one-off implementations wherever practical. -Expand shared helpers when their responsibilities fit, and remove superseded -duplicates after migrating callers. Aim to standardize most helpers; do not -force every helper into a common abstraction. Retain specialized helpers where -the behavior warrants them, and document why they remain specialized. - -Preserve behavior and public callers during -the reorganization, and validate affected routes and builds. - -## Checkpoint descriptions: Deferred wording and endpoint cleanup - -Status: Deferred. Do not spend implementation/review time on routine wording cleanup while developing and validating the route. - -Audit checkpoint names, start/end descriptions, and related comments together in a later cleanup pass. Match descriptions to verified gameplay positions and preserve the requirement that each checkpoint starts at the previous checkpoint's endpoint. There are many stale descriptions across AutoStory; handle them as a batch rather than piecemeal edits. The Eterna Forest endpoint outside the Center is one example. Actual route or checkpoint-state failures still need prompt fixes; cosmetic description cleanup stays deferred. - -## Project roadmap: Transition from Segment 03 implementation to Segment 04 testing - -Status: Deferred until Segment 03 implementation is complete and work reaches Segment 04. At that transition, begin testing the existing route and reviewing missing information. Do not start a broad helper refactor or roadmap audit during current route fixes. - -Revisit the earlier five-phase BDSP AutoStory roadmap (Setup, Streamlining, Cleanup, Beta Testing, Future Implementation) to reflect the present workflow: implement and validate recorded route segments incrementally, use logs/video to fix failures, and reuse or expand shared battle/navigation/Bag tools. - -Kevin clarified that Segment 04 is the first segment expected not to need substantial implementation. Treat segments 04 through 09 as existing route implementations to test and audit, not as validated or release-ready. When reaching Segment 04, look for missing route information, unsupported states and required functionality; implement evidence-supported gaps rather than rebuilding the segment. Continue that testing/gap-review workflow through the later segments. The 2026-10-23 source audit has now confirmed substantial missing gameplay; the inventory below supersedes any assumption that the later segments only need testing. Preserve implemented travel while filling these gaps. - -The revised roadmap should: - -- Separate remaining route implementation from live validation, failure recovery, helper consolidation, cleanup and release preparation. -- Audit segments 04–09 for existing route coverage, moving NPCs/key fights, checkpoint handoffs, item/money/buy-list requirements and one-off helper logic. Identify actual gaps rather than rebuilding completed routes. -- Link the existing "AutoStory helpers: Audit and reorganize reusable tools" task. Plan shared helper creation/expansion and consolidation across those later segments, especially BattleTools, NavigationTools and BagTools, while retaining justified specialized behavior. -- Keep broad file consolidation, unused-file removal, formatting and checkpoint wording cleanup in a deliberate later pass; do not let them displace current route validation. -- Define evidence-based validation and regression gates before beta testing and eventual main-repository PR/release. This task does not authorize creating/pushing a PR now. -- Retain a separate future-upgrades phase and list dependencies/acceptance criteria for each workstream. - -Deliver a revised roadmap with segment status, outstanding functionality, helper migration priorities, validation gates and a practical order of work when Segment 03 is complete and Segment 04 testing begins. - -## Segments 04–09: Fill missing gameplay and repair checkpoint handoffs - -Status: Backlogged required implementation, identified by the 2026-10-23 read-only source audit. Do not implement as part of recording this task. Complete current Segment 03 work first, then fill and validate these gaps in route order. Existing travel code is not yet live validated by this audit. - -Source locations below are relative to `SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/`. Line numbers describe the audited revision and may shift. - -### Missing gameplay - -- [ ] **Segment 04 — Pastoria Gym:** `StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp`, `go_to_pastoria_gym` (line 977), checkpoint 0417 (1185). The current route only enters the gym although the endpoint claims the fourth badge. Implement the puzzle, Crasher Wake battle, verified badge completion and the exit position required by Segment 05. -- [ ] **Segment 05 — Hearthome Gym:** `Segment_05.cpp`, `get_into_hearthome_gym` (1454), checkpoints 0516/0517 (1651/1662). Both checkpoints call the entrance route. Implement the puzzle, Fantina battle, verified badge completion and exit boundary; replace the duplicated entrance call with the actual interior phase. -- [ ] **Segment 06 — Canalave Gym:** `Segment_06.cpp`, `go_to_canalave_gym` (590), checkpoints 0610/0611 (730/741). Both call the entrance route. Implement the lift puzzle, Byron battle, verified badge completion and exit boundary. -- [ ] **Segment 07 — Snowpoint Gym:** `Segment_07.cpp`, `go_to_snowpoint_gym` (1729), checkpoints 0718/0719 (1954/1965). Both call the entrance route. Implement the ice puzzle, Candice battle, verified badge completion and exit boundary. -- [ ] **Segment 08 — Final Mount Coronet story:** `Segment_08.cpp`, `go_through_mount_coronet` (1784), checkpoint 0816 (2485). The function only waits, logs and returns true. Implement the final ascent, Spear Pillar story encounters/battles, supported legendary resolution and return to the position required by the next route. Dormant legacy `checkpoint_021`/legendary button sequences are not a working implementation and have no catch-success verification. -- [ ] **Segment 08 — Sunyshore Gym:** `Segment_08.cpp`, `go_to_sunyshore_gym` (2250), checkpoints 0825/0826 (2584/2600). Both call the route ending at the gym entrance. Implement the gear/platform puzzle, Volkner battle, verified eighth badge and exit boundary. -- [ ] **Segment 09 — League entry and completion:** `Segment_09.cpp` (1354–1429), checkpoints 0909–0914. All six functions below only wait, log and return true. Implement entry prerequisites, room traversal, trainer fights, necessary party/item preparation and verified Champion/Hall of Fame completion: - - `get_into_the_elite_4_entrance` (1354). - - `deal_with_aaron_elite_4` (1367). - - `deal_with_bertha_elite_4` (1380). - - `deal_with_flint_elite_4` (1393). - - `deal_with_lucian_elite_4` (1406). - - `deal_with_cynthia_and_finish` (1419). - -`Segment_XX.cpp` above abbreviates `StorySegments/PokemonBDSP_AutoStory_Segment_XX.cpp`. - -### Checkpoint and failure-handling gaps - -- [ ] Register **checkpoint 0717** in the global checkpoint list in `PokemonBDSP_AutoStory.cpp` (between 0716 and 0718, lines 155–157). Segment 07's own runner includes this Snowpoint Center heal/exit step; checkpoint mode currently skips it and changes the next route's starting scene. -- [ ] Include **checkpoints 0822–0826** in Segment 08's `run_segment` (85–123). They are present in the global checkpoint registry but omitted from the segment runner. Validate both supported execution modes against the same checkpoint sequence. -- [ ] Replace the duplicated gym-entrance calls listed above with real puzzle/battle phases. Verify that each checkpoint starts at its predecessor's actual endpoint and that the next segment starts from the verified post-badge position. -- [ ] Propagate `heal_pokemon` failure in seven wrappers that currently return true regardless: Segment 05 Solaceon (822), Celestic (1233), Hearthome (1444); Segment 06 Jubilife (276), Canalave (578); Segment 07 Veilstone (1134), Snowpoint (1715). The shared helper returns false when the Center exit transition is missing; subsequent pathing must not continue from an unconfirmed scene. -- [ ] In Segment 09 `ready_in_front_of_elite_4` (1291), check `buy_items`' result (1344–1349) and replace the assumption that one fixed west/east wiggle must expire Repel with a bounded, confirmed preparation sequence. - -### Acceptance criteria and order - -Use recorded/calibrated travel, existing battle/dialogue/navigation tools and explicit completion conditions. Do not substitute invented blind inputs or unconditional success for missing gameplay. Verify both successful progression and bounded failures, checkpoint restart positions, battle completion and doorway settling. Reuse or expand shared helpers where appropriate; coordinate broader consolidation with the existing helper-audit backlog item. - -Recommended order: finish Segment 03, then the gym phases in 04–07, final Coronet and Sunyshore in 08, and League completion in 09. Repair the corresponding checkpoint connections and ignored failures alongside each phase. Keep routine checkpoint wording cleanup deferred, but correct descriptions when needed to express the implemented start/end contract. diff --git a/CodingAgentContext/CopilotHandoff.md b/CodingAgentContext/CopilotHandoff.md deleted file mode 100644 index 1d08e7635c..0000000000 --- a/CodingAgentContext/CopilotHandoff.md +++ /dev/null @@ -1,329 +0,0 @@ -# Current checkpoint — October 23, 2026 - -This section supersedes the historical October 12 status below. - -- Active source checkout: `C:/Users/Kevin/GitRepos/Arduino-Source`, branch - `BDSP/AutoStory`, tracking `origin/BDSP/AutoStory` in - `Developer-Butters/Arduino-Source`. Make all future source edits here. -- Canonical resource checkout: `C:/Users/Kevin/GitRepos/Packages`, branch - `BDSP-Autostory`, tracking Developer-Butters/Packages. Save image resources - there for their separate future PR. - Saved resource checkpoint: `c8dbef4769e9780f7f77cf03a0bb5f32958162eb`. The source checkout's nested Packages - copy must stay synchronized with this resource branch. -- The old `BDSP-AutoStory` checkout is a preserved recovery/build reference. - Do not make further implementation edits or push work from it. -- Saved work includes the current Route208 rock appearance bank, Buneary - dialogue/queue fixes, west-gate descent correction, Center exterior/interior - handoff and Fantina/Contest Hall alignment and required conversations. -- Most recent build from the old checkout is executable SHA256 - `1e39b375b752929dd8780aa69d4b4d70e6f1360bc133bd1878e6735d82e6f806`. - It passed 173 targeted checks. Migration preserves the source bytes; it - does not establish a new build against this checkout's newer framework. -- The new gate/Center/Fantina changes still need live checkpoint18 validation. - The earlier 15:01:34 run verified rocks, Buneary and actual Center healing, - but did not successfully interact with Fantina. That run and its recording - have ended; monitoring is paused. Do not start another run without request. -- Preserve East1197/rest298 and post-Buneary Repel expiry with NO renewal. - Segments04–09 implementation remains backlogged; do not expand scope. -- Recovery snapshot and prior test/video evidence remain in - `C:/Users/Kevin/Documents/Codex/2026-10-03/lo/work/`. -- For current shell operations use cmd.exe and the configured Python runtime; - the historical PowerShell examples below are not the current workflow. - -## Historical October 12 handoff - -# BDSP AutoStory handoff for Visual Studio GitHub Copilot - -Updated October 12, 2026, after implementing Route 204 trainer recovery. - -Continue Kevin's existing work in `C:\Users\Kevin\GitRepos\Arduino-Source`. -Read the current files, `git status`, staged AND unstaged diffs, this handoff, -`CodingAgentContext/Backlog.md`, and `CodingAgentContext/AutomationProgramPatterns.md`. -Preserve Kevin's edits. This snapshot explains the current implementation; -it is not an instruction to replay changes that are already present. - -## Immediate task - -Validate the next live Segment 02 recording after the new Route 204 changes. -Kevin has progressed through the Galactic double battle, Bebe's dialogue, and -Jubilife Pokemon Center healing. The current bottleneck is triggering the female -trainer after leaving the Center and then positioning at the Route 204 sign. -The latest changes compile and pass screenshot checks, but the new trainer -recovery and route movements still need a live test. Review the next recording -and matching logs before proposing further changes. - -Do not work on the unrelated Plex/MediaServer transfer task: Kevin accidentally -posted that question here and explicitly asked us to ignore it. - -## Keep Visual Studio and its workspace open - -Kevin reports that Copilot sometimes closes the workspace while running -PowerShell commands. The cause is NOT established. Distinguish an exited -terminal session, a closed solution/workspace, and an exited Visual Studio -process; they are different failures. Do not claim `exit` alone necessarily -closes the IDE. The following are preventive operating rules, not a diagnosis: - -1. Do not close, unload, reopen, reload, reset, or replace the solution/workspace - as part of a shell operation. Do not invoke IDE-close commands, DTE solution - closure, UI shortcuts, or launch another Visual Studio instance to run builds. -2. Do not kill `devenv.exe`, Visual Studio/ServiceHub processes, terminal hosts, - or their process trees. Do not use broad `Stop-Process`, `taskkill`, or cleanup - filters such as all PowerShell, Python, CMake, compiler, or IDE processes. - If a child task hangs, use the tool's cancellation or Visual Studio's Cancel - Build. Process termination requires a specific, verified task PID and Kevin's - authorization; never target the IDE or parent host. -3. Keep shell work scoped to explicit paths. Prefer the tool's working-directory - setting and absolute filenames. A directory change must not become an IDE - workspace operation. Never repurpose `$HOME`, `$home`, or `$CODEX_HOME`. -4. Do not dot-source operational scripts into the IDE's shared PowerShell - session. Prefer a saved script launched in a CHILD process: - - ```powershell - & powershell.exe -NoProfile -NonInteractive -File 'C:\absolute\path\task.ps1' - $taskExitCode = $LASTEXITCODE - if ($taskExitCode -ne 0) { throw "Task failed with exit code $taskExitCode" } - ``` - - Use `pwsh.exe` instead only if it is installed. Set `$ErrorActionPreference = - 'Stop'` inside the task script and use `return` for routine early completion. - Do not send bare `exit`, `logout`, or `Stop-Process -Id $PID` to a reused - parent terminal/runspace. If an exit code is necessary, confine `exit ` - to the explicitly launched child script. Invoke native programs with `&` and - structured arguments; avoid `Invoke-Expression` and string-built commands. -5. Run batch build scripts through `cmd.exe /d /c `. - `exit /b` belongs inside that child batch process, not an IDE-control action. - Capture output and return codes instead of closing the console or workspace - to mark a task complete. -6. If the workspace closes again, stop issuing cleanup/relaunch commands. - Report the exact last command, which surface closed, the error/output, and - available Copilot/Visual Studio logs. Preserve files and let Kevin decide - how to reopen the workspace. Do not automatically reset IDE settings. - -Microsoft documents that PowerShell `exit` terminates a script or PowerShell -instance, and that `powershell.exe -File` returns a child's script exit code: -[PowerShell language keywords](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_keywords?view=powershell-5.1). -Terminal commands in Visual Studio agent mode have the IDE process's permissions; -they are not confined to the solution's normal file-tool boundary: -[Visual Studio agent mode](https://learn.microsoft.com/visualstudio/ide/copilot-agent-mode?view=visualstudio). - -## Repository and commit state - -- Source repository: `C:\Users\Kevin\GitRepos\Arduino-Source`. - Branch: `BDSP/AutoStory`. - `origin`: `https://github.com/Developer-Butters/Arduino-Source.git`. - `upstream`: `https://github.com/PokemonAutomation/Arduino-Source.git`. -- October 11 rebase: originally 299 upstream commits behind and 23 fork commits - ahead. Replayed all 23 onto upstream main, then added two compatibility commits. - Published tip is `08760b8fe`; at that snapshot it was 0 behind / 25 ahead. - Fetch and inspect before claiming it is still current. -- Rebased compatibility commits: `69676bbc7` updates BDSP to upstream APIs; - `08760b8fe` restores authoritative shared detectors and localizes extensions. - Other games moved under `Source/VideoGames`; BDSP remains under - `SerialPrograms/Source/PokemonBDSP`. The BDSP battle helper's Sword/Shield - type-matchup include now uses `VideoGames/PokemonSwSh/Resources/`. -- Shared detector and shared developer/unit-test implementations match upstream. - Do not resurrect deleted `Tests/TestMap.cpp` or old shared BDSP debug checkboxes. - Use `OperationFailedExceptionWithScreenshot::fire`, `ErrorReportMode`, - `GlobalAutoPaths.h`, and upstream's `make_SingleSwitchProgram` / Descriptor - alias pattern. Do not reintroduce the old interfaces. -- Recovery copies remain in branch - `backup/BDSP-AutoStory-before-upstream-20261011` and the named rebase stash. - These are backups, not code to apply over current work blindly. The old local - OCR CharacterReductions path fix is already in upstream; no duplicate fix is needed. -- Only Segment 02's route file is currently staged. Its staged contents depend - on UNSTAGED helper/matcher changes. Do not commit that file alone and assume - the resulting checkout will build. Inspect all dependencies when Kevin asks - for a commit. The pending files include Segment 00 tools `.cpp/.h`, the - overworld matchers `.cpp/.h`, and local context/backlog/handoff documents. -- Packages is a SEPARATE repository at `...\BDSP-AutoStory\Packages`, on branch - `BDSP-Autostory`. Approved push remote is `developer-butters`: - `https://github.com/Developer-Butters/Packages.git`. - `origin` still points to PokemonAutomation upstream: DO NOT push there. - Published Packages tip is `89fc5f8`. New, uncommitted resources include 18 - Jubilife lighting references and `Autostory/Route204Sign/Sign.png` plus - `FenceCorner.png`. Other untracked test images/assets also exist; never add - everything indiscriminately. -- Do not pull the old branch history back into the rebased source branch. - Collaborators need a one-time fetch/resync preserving their local work first. - No further rebase, reset, commit, push, history rewrite, or upstream PR without - Kevin's specific instruction. Current implementation work is uncommitted. - -## Current Segment 02 behavior - -Primary file: -`SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp`. -Route function: `roark_to_mars(env, stream, context)`. - -1. Oreburgh Mart alignment and Repel buying precede Gate entry. Entry watches - black before up/left/down search; exit watches white before moving west and - waits for the fade to finish. The cleaned Route 203 path avoids descending - the upper stairs only to climb back up; it travels left, jumps down the ledge, - and combines the lower-stair corrections. -2. At the marked safe point, `force_repel_expiry(..., RepelRenewalChoice::NO)` - wiggles until expiry and declines renewal. `YES` remains supported. Cleanup - requires three continuous clear overworld seconds, not one transient frame. -3. Exactly one clock setup occurs when entering this contiguous NEW locator - strip: `set_time_fast(env, context, locator_time, true)` sets 13:00 and - preserves the current console date, followed by five seconds for lighting. - It covers street, Galactic plaza, post-battle return, and nearby Route 204 - work. Do not reset the clock for every matcher. Checkpoint retries enter the - strip again; no clock restoration is currently implemented or requested. - Existing earlier locators do not need retroactive clock changes. -4. The westward street approach is 3700ms. The old blind 2579ms upward move was - removed. `align_to_jubilife_approach` handles both axes and prioritizes street - lane alignment. Two strong coarse anchors may guide a horizontal correction - only when their signed offsets agree and the offset exceeds their spacing - uncertainty. Coarse matching never confirms arrival. Missing landmarks use - bounded right/left sweeps at several heights rather than continuous left/up. -5. The plaza matcher has a small normalized horizontal quantization allowance - to avoid oscillating between two reachable positions around the target. - The approach sidesteps left 200ms around the sign before watched upward - pulses; straight up plus A previously read the sign instead of triggering Rowan. -6. Dialogue/battle triggers stop locator movement. Battle preparation clears - dialogue with B until the battle menu is confirmed. The existing - `Barry_1_fight` handler is reused. Do not move onward merely because dialogue - was seen: menu confirmation and combat completion are separate requirements. -7. Rowan/Bebe dialogue clears in two passes separated by two seconds. Return - down 600ms before reacquiring the plaza. The old 1000ms return overshot, and - the additional downward recovery pulses are now disabled at this call site. - From the confirmed reference, the route proceeds to Jubilife Center. -8. After Center exit and westward alignment, `approach_route204_female_trainer` - watches throughout the 8000ms northward movement and a 2500ms stationary - detection window. It returns whether ShortDialogWatcher or trainer battle - menu appeared, cancelling remaining motion if either triggers. -9. `const bool missed_trainer` records the initial miss. If true, - `recover_route204_female_trainer` crosses her sight line DOWN 500ms then - UP 500ms, at most eight pairs, with fresh dialogue/menu watchers. Failure is - bounded and reported; it does not return to a two-minute generic wait. -10. Both paths confirm the menu, fight, clear post-battle dialogue, and activate - a fresh Repel. The first Repel was deliberately NOT renewed earlier. -11. `align_to_route204_sign` uses the supplied reference photo, sign and fence - corner, normalized positioning, visible unlabeled ROIs, and three-frame - target confirmation. It watches each corrective move and settling period - for the NEXT trainer's dialogue/menu. It also avoids steering during fades. - It returns true if the trainer interrupts alignment, false if at target. -12. If aligned without a trigger, a watched 1200ms left approach starts the next - trainer. If interrupted early, skip further positioning and use the same - confirmed-menu/combat/cleanup path. Never skip the battle handler and send - overworld moves into an active battle. The subsequent northward movement - and later cave-navigation placeholder remain to validate/complete. - -The latest live video, `2026-10-12 19-15-37.mp4`, demonstrated the missed female -trainer and the old stationary wait. The newly implemented recovery/sign flow -has NOT yet been observed in a live recording. Do not claim it is route-validated. - -## Matchers, resources, and verification - -Matcher registry: `AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp/.h`. -Current entries: Poketch, Oreburgh Mart, Jubilife Center, Route 201 Potion exit, -Sandgem junction, Route 202 grass exit, Jubilife street/plaza, and Route 204 sign. -The lighting tester is in `PokemonBDSP/Programs/TestPrograms/` and uses this -registry automatically. `Autostory` is the resource folder casing. - -- Valid Jubilife lighting runs: `jubilife-street-approach-1791772008` and - `jubilife-plaza-approach-1791772619`. Each now passes 120/120 captured frames. - Exclude `jubilife-street-approach-1791771478` and - `jubilife-plaza-approach-1791771937`: Kevin started those at wrong positions. -- Street references use building corner/base, avoiding repeated facade windows. - Plaza references use roof rim and fence. Eighteen references cover all five - captured frames per hour, including moving shadows. Alternatives for one - landmark are NOT independent votes; two distinct anchors must agree. -- Street target height preserves the original screenshot; the lighting-run - pose differed by 1/108 screen height. Do not move the target to make an - offset test pass. Original-reference checks passed too. -- Route 204 reference: - `build/Debug/Screenshots/screenshot-20261012-192258390819.png`. - Its two crops exclude trainer/player animation and the collectible item. - Thirteen native checks passed for 720p/1080p/1440p, both offset directions, - unrelated scenes, and masking people/items. Full-day lighting has not been - tested for this new matcher; runtime currently uses the strip's 13:00 setup. -- New clock/date option is in Segment 00 tools: `preserve_date` defaults false, - so existing callers retain exact-date behavior. The new clock-only path reads - and validates year/month/day using the existing console date reader before - setting hour/minute. This reuses console UI OCR; it adds no game-text OCR. -- Transition extensions now live in - `PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.cpp/.h`, not CommonTools. - Use BDSP namespace qualification if global/local watcher names are ambiguous. -- Last complete Debug build/link succeeded after Route 204 integration. - Matcher checks prove detection properties, not live movement/battle success. - -## Kevin's constraints - -- Prefer focused BDSP-local changes. Shared directories such as CommandLine, - CommonFramework, CommonTools, ComputerPrograms, ControllerInput, Controllers, - Integrations, Kernels, ML, and shared NintendoSwitch/Pokemon code require a - walkthrough BEFORE editing and Kevin's explicit informed approval. Shared - changes require a separate commit with detailed necessity and cross-game - validation. SourceFiles.cmake registration is allowed for actual new files. -- Targets, search regions, sizes, offsets, and tolerances MUST be screen-relative. - Pixel conversion belongs only at image-processing boundaries. Account for - aspect ratio when comparing axes. Never hard-code capture-size movement logic. -- Prefer language-independent geometry and avoid OCR unless necessary. - Text-bearing templates are language-dependent even without OCR. Show ROIs - through normal Overlays; colored boxes only, no text labels. -- Preserve Kevin's unsaved Visual Studio edits. Before external edits to an - open file, have him save/close it or preserve both copies and reconcile them. - Do not overwrite editor contents or respond to a reload warning by discarding - unknown changes. Preserve Git baseline line endings: LF/CRLF changes previously - made Segments 03-09 look completely rewritten and must not recur. -- Fresh watchers belong to each separate detection phase. Keep state across - rounds only for deliberately latched end-battle watchers. -- Autosave must be verified OFF. Settings save/exit is A A A A B B with detection - retries and persistence verification. REAL saves are the default; FAKE saving - and post-catch diagnostic entry remain marked for removal before production. -- Evaluate actual evidence before adding black/white watchers. Do not spend - time looking for a watcher that Kevin says does not exist. -- No unsolicited subagents, commits, pushes, resource cleanup, process killing, - or work on deferred features. Retain bounded recovery and useful logs. - -## Earlier work and deferred items - -Segments 00/01 and associated tools were committed/pushed before this focus. -Tutorial work includes settings verification, post-catch saved checkpoint -boundary, robust capture/battle-result cleanup, generic grass movement with -optional destinations, shared confirmed-Run escape, visual 1-4 move counting -and selection, Potion HP verification, one wild Scratch training battle after -Potions, and Ember slot 3 for Tristan. Do not reintroduce named grass wrappers -or blind move-slot selection. Segment 01 Gate-to-mine movement is enabled. - -Defer maxed starter/species/level/moves validation before Segment 01, faint -handling, grass optimization, and multilingual settings until Kevin asks. -Also verify the existing REAL-save fallback before claiming checkpoint safety -is fully solved; earlier analysis found bookkeeping may advance after an -unverified fallback. These are not authorization to expand the current task. - -## Build and analysis workflow - -- Runtime log: `C:\Users\Kevin\GitRepos\Arduino-Source\build\Debug\SerialPrograms.log`. - It is large; filter exact run timestamps and do not confuse idle/old runs. -- Lighting data: `...\build\Debug\Screenshots\OverworldLightingTest`. - Recordings: `C:\Users\Kevin\Videos\Captures`. -- Analysis workspace: `C:\Users\Kevin\Documents\Codex\2026-10-03\lo\work`. - Use that directory for scratch files, not the repository. An external script - with relative `work/...` paths must run from its parent `...\lo`, not repo root. -- Build script: `work\build_rebased.py` in that analysis workspace. Python: - `C:\Users\Kevin\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe`. - It calls VS 18 Community `vcvars64.bat`, then CMake `--build` on the repo's - `build\Debug`, target `SerialPrograms`, eight workers. Log: - `work\link_utils_move.log`. `work\link_utils_move.cmd` is the generated batch - equivalent; run it as a child `cmd.exe /d /c`, never by closing the workspace. -- If LNK1168 says SerialPrograms.exe cannot be opened for writing, ask Kevin to - close SerialPrograms and rebuild. Do NOT kill it or Visual Studio. -- Existing native harnesses: `run_route204_tests.py`, - `run_jubilife_lighting_tests.py`, `run_jubilife_route_tests.py`, - `run_jubilife_approach_tests.py`, `run_native_battle_cursor_tests.py`, - `run_position_tests.py`, `run_transition_tests.py`. Read before using them. - They now use the GUI target's library set, not the old command-line CoreLib - assumption. Do not run harness builders concurrently: they share a batch path. - `work\Resources` provides the runtime resources needed by the test executables. -- PyAV is under `work\video_dependencies`; add it to Python's import path. - Seek to timestamps for long recordings. Crop only the OBSERVED game viewport - before testing a recorded image; UI borders/stats overlays can contaminate - match scores. Do not confuse screenshot tests with an untouched camera feed. -- A pre-existing ONNX API/runtime-version warning appears in some native test - startups. Image-based checks still passed. Do not silently modify shared - dependencies or claim ONNX-dependent features have been tested. - -Start with the current diff and Kevin's next recording. Explain the concrete -failure and the proposed correction, keep edits narrow, build appropriately, -and clearly distinguish offline validation from live route behavior. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md deleted file mode 100644 index f077c236d8..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md +++ /dev/null @@ -1,114 +0,0 @@ -# Story Directory Cleanup and Organization Summary - -## Overview -This document summarizes the cleanup and organization of the PokemonBDSP AutoStory directory (`SerialPrograms/Source/PokemonBDSP/Programs/Story`). - -## Cleanup Actions Completed - -### 1. Deleted Temporary Fix Documentation (9 files) -The following temporary documentation files created during development have been deleted as they are no longer needed: - -- ✅ `COMPILATION_FIXES.md` - Notes on compiler error fixes -- ✅ `INCLUDE_PATH_FIX.md` - Include path configuration notes -- ✅ `LINKER_ERROR_FIX.md` - Linker error resolution notes -- ✅ `PROCONTROLLER_MISSING_INCLUDE_FIX.md` - ProController header fixes -- ✅ `DIALOGUE_DETECTION_FIX.md` - Dialogue detection fixes -- ✅ `DIALOGUE_DETECTION_IMPLEMENTATION.md` - Implementation details -- ✅ `COLOR_FIX_IMPLEMENTATION.md` - Color detection implementation -- ✅ `FLEXIBLE_DIALOGUE_DETECTION.md` - Flexible detection notes -- ✅ `DEBUGGING_GUIDE.md` - Development debugging guide - -**Reason:** These files documented temporary issues fixed during development. The actual code is now fixed and in production. These notes should be archived in git history, not maintained in the working directory. - -## Current Directory Structure - -### Root Level Files (Primary Directory) -``` -C:\Users\mackk\Arduino-Source\SerialPrograms\Source\PokemonBDSP\Programs\Story\ -├── PokemonBDSP_AutoStory.h - Main program header -├── PokemonBDSP_AutoStory.cpp - Main program implementation -├── BDSP_RouteBuilder.h - Route composition interface -├── BDSP_RouteBuilder.cpp - Route composition logic -└── BDSP_RouteConfig.h - Route configuration enum/types -``` - -### Routes Subdirectory -``` -Routes/ -├── BaseRoute.h/cpp - Initial story progression (title → starter selection) -├── Starter_Chimchar.h/cpp - Chimchar selection and confirmation -├── Starter_Piplup.h/cpp - Piplup selection and confirmation -├── Starter_Turtwig.h/cpp - Turtwig selection and confirmation -├── OutsideHelp_Modifiers.h/cpp - Story path using NPC assistance -├── NoOutsideHelp_Modifiers.h/cpp - Solo story path -├── Legendary_Segment.h/cpp - Box legendary Pokemon catching -├── DateTimeSetup.h/cpp - ⚠️ Unused placeholder (see below) -├── ARCHITECTURE.md - System design and file organization ✓ -└── STARTER_SELECTION_GUIDE.md - Starter selection mechanics ✓ -``` - -## Findings: Unused Files - -### DateTimeSetup.h/cpp (Candidates for Removal) -These files are **currently unused** and can be safely removed: - -- **Status:** Declared but not included in any route builder or main program -- **Purpose:** Was intended to handle date/time setup, but this functionality is now handled by the framework's `DateSpam` and `DateManip` modules -- **Evidence:** - - Not included in `BDSP_RouteBuilder.cpp` - - Not referenced in `PokemonBDSP_AutoStory.cpp` - - Comment in route builder explicitly states: "Date/time setting happens in the main program before route execution. This is handled in PokemonBDSP_AutoStory::program()" via framework modules - -**Recommendation:** Delete `Routes/DateTimeSetup.h` and `Routes/DateTimeSetup.cpp` in the next cleanup phase if confirmed to be unused in the broader codebase. - -## File Organization Assessment - -### ✅ Strengths -1. **Clear separation of concerns** - Each route type has dedicated files -2. **Logical naming** - File names clearly indicate purpose (Starter_*, Modifiers_*, Segment) -3. **Good documentation** - ARCHITECTURE.md and STARTER_SELECTION_GUIDE.md provide context -4. **Modular design** - Route builder composes segments from multiple sources cleanly - -### Consolidation Opportunities - -The current file structure is well-organized and follows a logical module pattern. No consolidation is recommended because: - -1. **Starter files** (Chimchar, Piplup, Turtwig) are similar in structure but represent distinct game choices - separate files aid maintainability -2. **Route modifier files** (OutsideHelp vs. NoOutsideHelp) represent strategic divergence points - worth keeping separate -3. **Legendary catching** is a distinct feature - appropriate as separate module -4. **BaseRoute** handles a critical common path - appropriate as standalone - -## Recommendations - -### Immediate -- ✅ **COMPLETED:** Remove temporary fix documentation files - -### Short-term (Next Phase) -1. **Remove DateTimeSetup files** - Confirm no other project uses them, then delete `Routes/DateTimeSetup.h/cpp` -2. **Archive development notes** - The deleted documentation should be in git commit history for future reference - -### Long-term -- Monitor for other unused placeholder segments -- Consider creating a test suite to verify all route compositions work end-to-end -- Update ARCHITECTURE.md when adding new features - -## File Statistics - -| Category | Count | Notes | -|----------|-------|-------| -| Active Code Files | 15 | Header + Implementation pairs (+ config header) | -| Documentation Files | 2 | ARCHITECTURE.md, STARTER_SELECTION_GUIDE.md | -| Unused Code Files | 2 | DateTimeSetup.h/cpp (flagged for removal) | -| Deleted Files | 9 | Temporary fix documentation | - -## Verification - -- ✅ No compilation errors in Story directory files -- ✅ All active code files are properly included in route builder -- ✅ Documentation files are current and relevant -- ✅ Project structure matches documented architecture - ---- - -**Last Updated:** April 12, 2026 -**By:** Code Cleanup Agent diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md deleted file mode 100644 index d032dc15e7..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md +++ /dev/null @@ -1,817 +0,0 @@ -these are the counts and direction inputs from my segement beginning in the player's room to the entrance of the 3rd gym (fighting gym) - - -U2 -R6 -U1 -R2 -Amash -D5 -L4 -Amash -D1 -L8 -U10 -L4 -Amash -U2 -U3 -L5 -U4 -R1 -Amash -U1 -L1 -D8 -R5 -D2 -R5 -U19 -Amash -U3 -L30 -U9 -Starter select program -A mash -D8 -R20 -Amash -D3 -L4 -u33 -r34 -u8 -r18 -amash -b -a -amash -pokedex received -d9 -amash -l36 -d6 -l38 -d34 -r3 -u4 -r1 -amash -b -amash -l1 -d3 -amash -l3 -u31 -r25 -u2 -r8 -u7 -r35 -u9 -r3 -u10 -l5 -amash -catch a pokemon -l13 -u9 -r1 -youngster Tristan -r10 -d2 -r6 -lass Natalie -r6 -u11 -l3 -youngster logan -l11 -u12 -amash -u19 -r5 -u7 -heal pokecenter -a -bmash -r4 -u3 -deposit starter -home transfer -withdraw starter -d3 -l4 -d6 -l13 -u9 -l1 -a -bmash -d8 -r6 -u2 -amash -poketech challenge begins -r3 -u9 -r7 -d2 -amash -u2 -l10 -u6 -l1 -u10 -r1 -amash -d5 -l22 -u2 -amash -d2 -r30 -d6 -r1 -d8 -r1 -amash -poketech acquired -b -u11 -r23 -barry 1 -bmash -r19 -u8 -r11 -d4 -r17 -u7 -r6 -rock smash acquired -bmash -r9 -d4 -r2 -u3 -r5 -u4 -r2 -d2 -r10 -oreburg tour -d2 -r20 -u37 -catch machop -d42 -r3 -d61 -r2 -d2 -l1 -d6 -l11 -d2 -l2 -d4 -r3 -d4 -l1 (pick up escape rope) -r2 -d1 -r11 -u1 -a -bmash -escape rope -u33 -l20 -u8 -l3 -u4 -r7 -u10 -l4 -u3 -save -roark -d20 -r4 -u10 -l1 -u5 -purchases -d4 -l7 -u3 -machop trade -d3 -l12 -d2 -l4 -barry text bmash -l3 -d1 -l4 -u2 -l2 -d4 -l5 -d2 -l2 -u3 -l8 -u1 -l4 -u3 -l17 -d9 -l4 -u3 -l11 -d4 -l8 -d1 -l32 -u13 -l1 -u2 -double battle -bmash -u19 -l1 -u15 -l2 -u8 -r2 -amash -r7 -d2 -l8 -u5 -r9 -u10 -box all but 1 pokemon -l11 -u24 -r32 -u1 -a -bmash -r1 -u1 -a -bmash -r8 -bmash -l37 -u15 -l11 -u9 -bmash -battle -amash -battle -bmash -d25 -r60 -d2 -r19 -u6 -r1 -u1 -battle -amash -u1 -amash -u1 -amash -l10 -u7 -battle -u1 -r6 -d1 -r8 -u1 -r3 -mars1 -bmash -l6 -d1 -l8 -u1 -l2 -d7 -r9 -d6 -l1 -d3 -l24 -u9 -l4 -u11 -l6 -u24 -l2 -u9 -r2 -u7 -l2 -u21 -Cheryl -Bmash -R3 -U1 -R4 -U11 -R4 -U5 -Double battle -U3 -R4 -U6 -R7 -D7 -battle -R1 -D1 -Battle -D12 -R12 -U17 -R22 -U16 -L7 -U9 -R5 -Cheryl -R7 -D5 -R10 -D4 -R38 -U6 -R1 -Undergournd man -A -Bmash -Y -D1 -A -A -Bmash -D3 -L3 -D8 -R1 -D21 -R5 -U1 -Gardenia gym -U7 -A -Bmash -U6 -L1 -U9 -R1 -U6 -L2 -U1 -l4 -d3 -battle -u5 -l5 -d7 -r1 -d5 -l1 -d3 -l1 -d5 -r4 -a -battle -bmash -l4 -u5 -r1 -u4 -r1 -u4 -l1 -u7 -r6 -d1 -r4 -d5 -r1 -d2 -l1 -d4 -r5 -u2 -r1 -l1 -d2 -l5 -d5 -r4 -d1 -r6 -u1 -r1 -u16 -l5 -battle -r5 -d16 -l4 -d1 -l7 -u10 -r1 -u6 -l1 -u6 -a -gardenia -bmash -d6 -r1 -d6 -l1 -d13 -r1 -d9 -l8 -u32 -l4 -u7 -r3 -Cynthia -Bmash -R2 -U2 -Amash -U7 -R4 -U2 -Battle -U1 -L1 -D2 -R7 -U1 -L1 -D2 -R2 -Battle -R5 -U1 -L1 -D4 -R3 -D1 -A -Jupiter1 -Bmash -U3 -L2 -U1 -R1 -D2 -L7 -U1 -R1d2 -L7 -U1 -R1 -D9 -L4 -D1 -Amash -D5 -L4 -D7 -R5 -D9 -R4 -U6 -L4 -U1 -Bike obtained -A -Amash -Bmash -Register bike -+ -L6 -D54 -Battle -Wait -R1 -Battle -Battle -Battle -L1 -L3 -D1 -+ -D28 -Item finder -Bmash -D2 -R3 -A -D6 -R4 -D8 -R13 -D2 -battle -U1 -R7 -U3 -R4 -Battle -D1 -R7 -U4 -L4 -U1 -Battle -L1 -U2 -R7 -U2 -R1 -Repel -D15 -R10 -Boss -Bmash -U6 -L1 -A -U1 -R1 -U3 -R4 -D1 -R5 -U2 -R3 -D7 -R17 -Battle -R11 -U2 -r1 -a -battle -d1 -r3 -u8 -r5 -d13 -r37 -buneary -bmash -r24 -u12 -l16 -u16 -l4 -u7 -a -a -a -a -a -bmash -u6 -r14 -u5 -a -Fatima -a -Bmash -U3 -Keira -Bmash -D10 -R19 -d10 -l21 -u5 -buy repels -bmash -d9 -r8 -d2 -barry 2 -d9 -r35 -u3 -r11 -u1 -r21 -u6 -r6 -u3 -r7 -u13 -l4 -u2 -l1 -u9 -r10 -u12 -l2 -u5 -l1 -u1 -r1 -d8 -l2 -d2 -l8 -u7 -r2 -u2 -r2 -d5 -r1 -d2 -l1 -d4 -l4 -u2 -l1 -u3 -l5 -u3 -r2 -u2 -r1 -d5 -l7 -u2 -l1 -u2 -r1 -d3 -l4 -d2 -a -a -a -a -a -bmash -escape rope -d3 -l4 -u8 -l3 -u45 -l7 -u11 -r10 -u18 -r9 -u4 -r19 -u11 -l4 -d4 -r1 -battle -r10 -amash -r6 -d8 -r6 -u1 -amash -bmash -r3 -u16 -l13 -u4 -r6 -d4 -a -battle -r1 -d7 -r16 -u7 -r10 -d2 -r28 -u6 -r1 -amash -d6 -l6 -d3 -r1 -amash -r5 -u1 -amash -l7 -u2 -l2 -d2 -l6 -d6 -r1 -a -battle -d1 -r1 -battle -r48 -d10 -r11 -d10 -r7 -u6 -r5 -u7 -a -a -a -a -bmash -d6 -l5 -u6 -l8 -u2 -l3 -u3 -r4 -u7 -r1 -l1 -a -a -d2 -a -a -a -r1 -d3 -l9 -d4 -l2 -d1 -buy TM29 psychic -buy TM21 dazzling gleam -sell tms -r3 -u1 -r1 -u4 -l9 -buy x items -d2 -l1 -buy vitamins -d2 -l1 -get counter app -r3 -d4 -l3 -d2 -r7 -d9 -r2 -d4 -r12 -d2 -r4 -get metronome -a -bmash -d10 -l30 -d2 -l12 -u16 -dawn -bmash -u4 - - - diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md deleted file mode 100644 index d032dc15e7..0000000000 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md +++ /dev/null @@ -1,817 +0,0 @@ -these are the counts and direction inputs from my segement beginning in the player's room to the entrance of the 3rd gym (fighting gym) - - -U2 -R6 -U1 -R2 -Amash -D5 -L4 -Amash -D1 -L8 -U10 -L4 -Amash -U2 -U3 -L5 -U4 -R1 -Amash -U1 -L1 -D8 -R5 -D2 -R5 -U19 -Amash -U3 -L30 -U9 -Starter select program -A mash -D8 -R20 -Amash -D3 -L4 -u33 -r34 -u8 -r18 -amash -b -a -amash -pokedex received -d9 -amash -l36 -d6 -l38 -d34 -r3 -u4 -r1 -amash -b -amash -l1 -d3 -amash -l3 -u31 -r25 -u2 -r8 -u7 -r35 -u9 -r3 -u10 -l5 -amash -catch a pokemon -l13 -u9 -r1 -youngster Tristan -r10 -d2 -r6 -lass Natalie -r6 -u11 -l3 -youngster logan -l11 -u12 -amash -u19 -r5 -u7 -heal pokecenter -a -bmash -r4 -u3 -deposit starter -home transfer -withdraw starter -d3 -l4 -d6 -l13 -u9 -l1 -a -bmash -d8 -r6 -u2 -amash -poketech challenge begins -r3 -u9 -r7 -d2 -amash -u2 -l10 -u6 -l1 -u10 -r1 -amash -d5 -l22 -u2 -amash -d2 -r30 -d6 -r1 -d8 -r1 -amash -poketech acquired -b -u11 -r23 -barry 1 -bmash -r19 -u8 -r11 -d4 -r17 -u7 -r6 -rock smash acquired -bmash -r9 -d4 -r2 -u3 -r5 -u4 -r2 -d2 -r10 -oreburg tour -d2 -r20 -u37 -catch machop -d42 -r3 -d61 -r2 -d2 -l1 -d6 -l11 -d2 -l2 -d4 -r3 -d4 -l1 (pick up escape rope) -r2 -d1 -r11 -u1 -a -bmash -escape rope -u33 -l20 -u8 -l3 -u4 -r7 -u10 -l4 -u3 -save -roark -d20 -r4 -u10 -l1 -u5 -purchases -d4 -l7 -u3 -machop trade -d3 -l12 -d2 -l4 -barry text bmash -l3 -d1 -l4 -u2 -l2 -d4 -l5 -d2 -l2 -u3 -l8 -u1 -l4 -u3 -l17 -d9 -l4 -u3 -l11 -d4 -l8 -d1 -l32 -u13 -l1 -u2 -double battle -bmash -u19 -l1 -u15 -l2 -u8 -r2 -amash -r7 -d2 -l8 -u5 -r9 -u10 -box all but 1 pokemon -l11 -u24 -r32 -u1 -a -bmash -r1 -u1 -a -bmash -r8 -bmash -l37 -u15 -l11 -u9 -bmash -battle -amash -battle -bmash -d25 -r60 -d2 -r19 -u6 -r1 -u1 -battle -amash -u1 -amash -u1 -amash -l10 -u7 -battle -u1 -r6 -d1 -r8 -u1 -r3 -mars1 -bmash -l6 -d1 -l8 -u1 -l2 -d7 -r9 -d6 -l1 -d3 -l24 -u9 -l4 -u11 -l6 -u24 -l2 -u9 -r2 -u7 -l2 -u21 -Cheryl -Bmash -R3 -U1 -R4 -U11 -R4 -U5 -Double battle -U3 -R4 -U6 -R7 -D7 -battle -R1 -D1 -Battle -D12 -R12 -U17 -R22 -U16 -L7 -U9 -R5 -Cheryl -R7 -D5 -R10 -D4 -R38 -U6 -R1 -Undergournd man -A -Bmash -Y -D1 -A -A -Bmash -D3 -L3 -D8 -R1 -D21 -R5 -U1 -Gardenia gym -U7 -A -Bmash -U6 -L1 -U9 -R1 -U6 -L2 -U1 -l4 -d3 -battle -u5 -l5 -d7 -r1 -d5 -l1 -d3 -l1 -d5 -r4 -a -battle -bmash -l4 -u5 -r1 -u4 -r1 -u4 -l1 -u7 -r6 -d1 -r4 -d5 -r1 -d2 -l1 -d4 -r5 -u2 -r1 -l1 -d2 -l5 -d5 -r4 -d1 -r6 -u1 -r1 -u16 -l5 -battle -r5 -d16 -l4 -d1 -l7 -u10 -r1 -u6 -l1 -u6 -a -gardenia -bmash -d6 -r1 -d6 -l1 -d13 -r1 -d9 -l8 -u32 -l4 -u7 -r3 -Cynthia -Bmash -R2 -U2 -Amash -U7 -R4 -U2 -Battle -U1 -L1 -D2 -R7 -U1 -L1 -D2 -R2 -Battle -R5 -U1 -L1 -D4 -R3 -D1 -A -Jupiter1 -Bmash -U3 -L2 -U1 -R1 -D2 -L7 -U1 -R1d2 -L7 -U1 -R1 -D9 -L4 -D1 -Amash -D5 -L4 -D7 -R5 -D9 -R4 -U6 -L4 -U1 -Bike obtained -A -Amash -Bmash -Register bike -+ -L6 -D54 -Battle -Wait -R1 -Battle -Battle -Battle -L1 -L3 -D1 -+ -D28 -Item finder -Bmash -D2 -R3 -A -D6 -R4 -D8 -R13 -D2 -battle -U1 -R7 -U3 -R4 -Battle -D1 -R7 -U4 -L4 -U1 -Battle -L1 -U2 -R7 -U2 -R1 -Repel -D15 -R10 -Boss -Bmash -U6 -L1 -A -U1 -R1 -U3 -R4 -D1 -R5 -U2 -R3 -D7 -R17 -Battle -R11 -U2 -r1 -a -battle -d1 -r3 -u8 -r5 -d13 -r37 -buneary -bmash -r24 -u12 -l16 -u16 -l4 -u7 -a -a -a -a -a -bmash -u6 -r14 -u5 -a -Fatima -a -Bmash -U3 -Keira -Bmash -D10 -R19 -d10 -l21 -u5 -buy repels -bmash -d9 -r8 -d2 -barry 2 -d9 -r35 -u3 -r11 -u1 -r21 -u6 -r6 -u3 -r7 -u13 -l4 -u2 -l1 -u9 -r10 -u12 -l2 -u5 -l1 -u1 -r1 -d8 -l2 -d2 -l8 -u7 -r2 -u2 -r2 -d5 -r1 -d2 -l1 -d4 -l4 -u2 -l1 -u3 -l5 -u3 -r2 -u2 -r1 -d5 -l7 -u2 -l1 -u2 -r1 -d3 -l4 -d2 -a -a -a -a -a -bmash -escape rope -d3 -l4 -u8 -l3 -u45 -l7 -u11 -r10 -u18 -r9 -u4 -r19 -u11 -l4 -d4 -r1 -battle -r10 -amash -r6 -d8 -r6 -u1 -amash -bmash -r3 -u16 -l13 -u4 -r6 -d4 -a -battle -r1 -d7 -r16 -u7 -r10 -d2 -r28 -u6 -r1 -amash -d6 -l6 -d3 -r1 -amash -r5 -u1 -amash -l7 -u2 -l2 -d2 -l6 -d6 -r1 -a -battle -d1 -r1 -battle -r48 -d10 -r11 -d10 -r7 -u6 -r5 -u7 -a -a -a -a -bmash -d6 -l5 -u6 -l8 -u2 -l3 -u3 -r4 -u7 -r1 -l1 -a -a -d2 -a -a -a -r1 -d3 -l9 -d4 -l2 -d1 -buy TM29 psychic -buy TM21 dazzling gleam -sell tms -r3 -u1 -r1 -u4 -l9 -buy x items -d2 -l1 -buy vitamins -d2 -l1 -get counter app -r3 -d4 -l3 -d2 -r7 -d9 -r2 -d4 -r12 -d2 -r4 -get metronome -a -bmash -d10 -l30 -d2 -l12 -u16 -dawn -bmash -u4 - - - From 554ebb0bc186d9c7326fa25667c705bd39e5f080 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 24 Oct 2026 21:25:39 -0500 Subject: [PATCH 31/39] Fix Hearthome checkpoint completion and recovery Handle Fantina camera fades before dialogue, recover the lower stair lane visually before gate travel, and retain the shorter Center approach. Finish the Hall checkpoint at its watched outdoor exit, remove the unvalidated approach-view confirmation, and clear all source indentation tabs. Validation: 18 latest Hall control checks pass; CI-equivalent tab scan passes across 3159 tracked source files. Earlier route/image checks and application build pass; latest Hall completion changes are source-only. The full suite retains baseline totals of 29463 passes and 928 failures with missing-fixture errors. Required resource branch commit: 1b093c5a91540a3fecce0091879312a2f6bcc58d --- .../PokemonBDSP_AutoStory_Segment_00.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 155 ++++++++++++++---- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 2 +- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 6 +- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 42 +++++ .../Utils/PokemonBDSP_OverworldMatchers.h | 1 + 7 files changed, 169 insertions(+), 41 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 7422087fa2..f567aa7f63 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -303,7 +303,7 @@ namespace PokemonAutomation { } // --------------------------------------------------------------------------- // End of Segment 00 - 01 - // Standing in front lake varity ready to enter lake Verity for starter selection. + // Standing in front lake varity ready to enter lake Verity for starter selection. // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Segment 00-02 begins diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index b2f179ebe6..d8232d1a8e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -244,7 +244,7 @@ namespace PokemonAutomation { walk_through_grass(stream, context, 1, 0, 5, "right through grass"); wait_for_dialogue(stream, context, "Dallas fight dialog)"); pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Dallas + Barry_1_fight(env, stream, context);//fighting Dallas context.wait_for_all_requests(); walk_through_grass(stream, context, 1, 0, 5, "right through grass"); pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index e561d53249..f357935813 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -144,6 +144,48 @@ namespace PokemonBDSP{ // remaining movement budget inside the next map. // Recorded Cycling Road movement may cross a gate before its budget ends. // Cancel at either fade color and resume only after that fade has cleared. + // Recover the successful descent endpoint before spending eastward travel. + // Correct height first, while still west of the honey-tree collision column. + static void segment03_align_lower_stair_lane(VideoStream& stream, ProControllerContext& context) { + size_t confirmations=0,missing=0; + for (size_t observation=0;observation<40;observation++) { + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + if (!frame) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: No video frame.",stream); + const auto match=match_route208_lower_stair_lane(*frame.frame); + stream.log("[Route 208 lower stair lane] located="+std::to_string(match.located)+ + ", target="+std::to_string(match.at_target)+ + ", offset=("+std::to_string(match.dx)+","+std::to_string(match.dy)+")"); + if (!match.located) { + confirmations=0; + if (++missing>6) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: Cliff/tree anchors missing; no blind search.",stream); + pbf_wait(context,500ms); + continue; + } + missing=0; + if (match.at_target && !match.coarse) { + if (++confirmations>=3) return; + pbf_wait(context,250ms); + continue; + } + confirmations=0; + const bool vertical=match.coarse || std::abs(match.dy)>1.0/54; + const double error=vertical ? match.dy*9.0/16 : match.dx; + const auto duration=std::abs(error)>1.0/24 ? 80ms : + std::abs(error)>1.0/64 ? 40ms : 16ms; + pbf_move_left_joystick(context, + {vertical ? 0.0 : (error>0 ? 1.0 : -1.0), + vertical ? (error<0 ? 1.0 : -1.0) : 0.0},duration,0ms); + pbf_wait(context,500ms); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: Could not confirm the safe descent endpoint.",stream); + } + static bool segment03_cycling_move(VideoStream& stream, ProControllerContext& context, double x, double y, Milliseconds duration, Milliseconds rest, const std::string& label, bool require_transition = false) { @@ -994,6 +1036,7 @@ namespace PokemonBDSP{ pbf_move_left_joystick(ctx,{0,-1},1650ms,0ms); pbf_wait(ctx,643ms); ctx.wait_for_all_requests(); + segment03_align_lower_stair_lane(stream,ctx); },{{fallback_black},{fallback_white},{fallback_battle}}); if (fallback_interrupted>=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, @@ -1009,18 +1052,18 @@ namespace PokemonBDSP{ // inside the gatehouse. The existing watcher cancels at black/white, // waits for GONE500ms, then settles for2s before the next input. bool hearthome_gate_entered=segment03_cycling_move(stream,context, - 1,0,rock_acquisition_timed_out ?3422ms:3500ms,443ms,"Hearthome west gate approach"); + 1,0,3500ms,443ms,"Hearthome west gate approach"); if (!hearthome_gate_entered) hearthome_gate_entered=segment03_cycling_move(stream,context, - 0,1,rock_acquisition_timed_out ?241ms:250ms,405ms,"Hearthome west gate doorway alignment"); + 0,1,250ms,405ms,"Hearthome west gate doorway alignment"); if (!hearthome_gate_entered) hearthome_gate_entered=segment03_cycling_move(stream,context, - 1,0,rock_acquisition_timed_out ?633ms:600ms,1617ms,"Hearthome west gate entrance",true); + 1,0,600ms,1617ms,"Hearthome west gate entrance",true); if (!hearthome_gate_entered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome west gate: Interior transition not confirmed.",stream); if (!segment03_cycling_move(stream,context, - 1,0,rock_acquisition_timed_out ?1743ms:1800ms,1588ms,"Hearthome west gate exit",true)) + 1,0,1800ms,1588ms,"Hearthome west gate exit",true)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome west gate: Exterior transition not confirmed.",stream); OverworldWatcher hearthome_outside; @@ -1066,7 +1109,7 @@ namespace PokemonBDSP{ BlackScreenWatcher center_transition; const int center_entry=run_until(stream,context, [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3500ms, 0ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3350ms, 0ms); pbf_wait(ctx, 437ms); // Clear the northbound lamp column before crossing the plaza. pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 250ms, 0ms); @@ -1120,20 +1163,38 @@ namespace PokemonBDSP{ context.wait_for_all_requests(); align_to_overworld_landmarks(stream,context,match_hearthome_contest_fantina, "Hearthome Contest Hall Fantina",false,true); - // Confirm the actual first interaction before clearing its text. Do not - // spend the old blind north/west holds against the side of the Hall. - pbf_move_left_joystick(context,{0,1},16ms,100ms); - pbf_press_button(context,BUTTON_A,194ms,277ms); - context.wait_for_all_requests(); + // Repeat the existing short Up/A interaction within the 15s window. + // Dialogue cancels the remaining inputs before any text cleanup. ShortDialogWatcher fantina_dialogue; - if (wait_until(stream,context,15s,{{fantina_dialogue}})!=0) + BlackScreenWatcher fantina_black; + WhiteScreenWatcher fantina_white; + const int fantina_started=run_until(stream,context, + [](ProControllerContext& ctx) { + constexpr auto pulse=16ms+100ms+194ms+277ms; + Milliseconds elapsed=0ms; + for (;elapsed+pulse<=15s;elapsed+=pulse) { + pbf_move_left_joystick(ctx,{0,1},16ms,100ms); + pbf_press_button(ctx,BUTTON_A,194ms,277ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx,15s-elapsed); + },{{fantina_dialogue},{fantina_black},{fantina_white}}); + if (fantina_started>0) { + // Fantina's opening camera cut fades to black before the text box. + // The watcher has cancelled Up/A; wait without further movement. + stream.log("[Hearthome Fantina] Opening fade detected; waiting for dialogue."); + segment03_finish_transition(stream,context,"Hearthome Fantina opening"); + ShortDialogWatcher opening_dialogue; + if (wait_until(stream,context,15s,{{opening_dialogue}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Fantina: Opening fade was not followed by dialogue.",stream); + } else if (fantina_started!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Contest Hall: Fantina dialogue did not start.",stream); clear_post_battle_dialogue(stream,context,BUTTON_B); pbf_wait(context,2000ms); clear_post_battle_dialogue(stream,context,BUTTON_B); - // Preserve the separate north1179 Hall-entry phase and initial choice - // inputs. A failed doorway or absent conversation must not finish18. + // Keep Hall entry separate from Fantina's outdoor conversation. segment03_cycling_move(stream,context,0,1,1179ms,4268ms, "Hearthome Contest Hall entrance",true); pbf_press_button(context,BUTTON_A,181ms,219ms); @@ -1142,20 +1203,44 @@ namespace PokemonBDSP{ if (wait_until(stream,context,15s,{{contest_dialogue}})!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Contest Hall: Interior dialogue did not start.",stream); - pbf_press_button(context,BUTTON_A,195ms,123ms); - pbf_press_button(context,BUTTON_A,185ms,121ms); - pbf_press_button(context,BUTTON_A,170ms,805ms); - pbf_press_button(context,BUTTON_A,195ms,140ms); - pbf_press_button(context,BUTTON_A,102ms,218ms); - clear_post_battle_dialogue(stream,context,BUTTON_B); - pbf_wait(context,2000ms); - clear_post_battle_dialogue(stream,context,BUTTON_B); - context.wait_for_all_requests(); + // Use Buneary's resumed-text and scene-clear checks, with B to clear. + bool contest_cleared=false; + for (size_t pass=0;pass<6 && !contest_cleared;pass++) { + mash_until_dialogue_ends(stream,context,BUTTON_B,60s); + context.wait_for_all_requests(); + ShortDialogWatcher resumed_dialogue; + if (wait_until(stream,context,2000ms,{{resumed_dialogue}})==0) continue; + pbf_wait(context,6000ms); + context.wait_for_all_requests(); + const auto frame=stream.video().snapshot(); + OverworldDetector overworld; + ShortDialogDetector dialogue; + contest_cleared=frame && overworld.detect(*frame.frame) && !dialogue.detect(*frame.frame); + } + if (!contest_cleared) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Interior scene did not clear.",stream); + // Own the exit before handing off the checkpoint. The transition watcher + // cancels southward movement at the fade, leaving the fixed outdoor spawn. + if (!segment03_cycling_move(stream,context,0,-1,903ms,1455ms, + "Hearthome Contest Hall exit",true)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Exit transition not confirmed.",stream); + OverworldWatcher hall_outside; + if (wait_until(stream,context,10s,{{hall_outside}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Overworld not ready after exit.",stream); + // The approach landmark view is not the post-exit doorway view. + // The watched south exit plus restored overworld completes this boundary. + stream.log("[Hearthome Contest Hall] Checkpoint complete: outside doorway, facing south."); + // End 03.04 / start 03.05: just outside the Hall, south-facing, Repel off. } void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, {0.000000, -1.000000}, 903ms, 0ms); - pbf_wait(context, 1455ms); + OverworldWatcher hall_start; + if (wait_until(stream,context,10s,{{hall_start}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall checkpoint start: Overworld not ready.",stream); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1830ms, 0ms); pbf_wait(context, 144ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 1880ms, 0ms); @@ -2071,7 +2156,7 @@ void Checkpoint_0303( } std::string AutoStory_Checkpoint_0304::name() const { return "03.04 - Coronet to Hearthome"; } std::string AutoStory_Checkpoint_0304::start_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } -std::string AutoStory_Checkpoint_0304::end_text() const { return "Outside the Hearthome Contest Hall after the Keira/Johanna introduction."; } +std::string AutoStory_Checkpoint_0304::end_text() const { return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } void AutoStory_Checkpoint_0304::run_checkpoint( SingleSwitchProgramEnvironment& env, @@ -2100,7 +2185,7 @@ void Checkpoint_0304( ); } std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - Hearthome and Lost Tower"; } -std::string AutoStory_Checkpoint_0305::start_text() const { return "Outside the Hearthome Contest Hall after the Keira/Johanna introduction."; } +std::string AutoStory_Checkpoint_0305::start_text() const { return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } std::string AutoStory_Checkpoint_0305::end_text() const { return "At the Lost Tower departure position after the tower gifts and recorded return path."; } void AutoStory_Checkpoint_0305::run_checkpoint( @@ -2114,16 +2199,16 @@ void AutoStory_Checkpoint_0305::run_checkpoint( void Checkpoint_0305( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // Recorded route with feedback battle handling. - heartholme_to_lost_tower(env.console, context); - lost_tower(env.console, context); + checkpoint_reattempt_loop(env, context, options, stats, + [&](size_t /*attempt*/) { + // Recorded route with feedback battle handling. + heartholme_to_lost_tower(env.console, context); + lost_tower(env.console, context); }, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index ebd37d0225..65d63e11e0 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1838,7 +1838,7 @@ namespace PokemonAutomation { } // --------------------------------------------------------------------------- - // Generic Movement Helper - Walk through grass in specified direction for + // Generic Movement Helper - Walk through grass in specified direction for // specified steps, attempting to run from any battles that are encountered. // --------------------------------------------------------------------------- void walk_through_grass( diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index 247a70d04f..3c0b2e40a1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -129,8 +129,8 @@ namespace PokemonAutomation { VideoStream& stream, ProControllerContext& context ); - // --------------------------------------------------------------------------- - // Helper function to navigate the poketech challenge clown dialogue. + // --------------------------------------------------------------------------- + // Helper function to navigate the poketech challenge clown dialogue. // Example usage // Move up and talk to clown. // clown_dialog_helper( @@ -152,7 +152,7 @@ namespace PokemonAutomation { // context, // DPAD_RIGHT // ); - // ---------------------------------------------------------------------------- + // ---------------------------------------------------------------------------- void clown_dialog_helper( VideoStream& stream, ProControllerContext& context, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index a9f4c6cfde..c85d8734c3 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -494,6 +494,48 @@ OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame){ } return best; } +OverworldMatchResult match_route208_lower_stair_lane(const ImageRGB32& frame){ + // Successful 1825ms descent endpoint at video 230s (2026-10-24 15-38-06). + // Independent cliff/tree anchors must agree before movement is authorized. + OverworldMatchResult best; + double best_score=-1; + for (double scale : {.90,.95,1.0,1.05,1.10}){ + std::vector configs={ + {"Lower stair cliff corner","PokemonBDSP/Autostory/Route208LowerLane/CliffCorner.png", + {690.0/1920,540.0/1080,110.0/1920,150.0/1080},{0,0,1,1},.93,.20}, + {"Honey tree","PokemonBDSP/Autostory/Route208LowerLane/HoneyTree.png", + {1510.0/1920,600.0/1080,100.0/1920,170.0/1080},{0,0,1,1},.93,.20} + }; + for (auto& config:configs){ + const double w=config.target.width,h=config.target.height; + config.target.width*=scale;config.target.height*=scale; + config.target.x+=(w-config.target.width)/2; + config.target.y+=(h-config.target.height)/2; + } + auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + if (candidate.landmarks.size()!=2) continue; + const auto& cliff=candidate.landmarks[0]; + const auto& tree=candidate.landmarks[1]; + // Perspective changes anchor spacing farther down the field. Two strong + // anchors can authorize north-only recovery while bracketing the player. + // This cue never confirms arrival or authorizes east travel. + if (!candidate.located && cliff.accepted && tree.accepted && + cliff.dy < -1.0/24 && tree.dy < -1.0/24 && + std::abs(cliff.dy-tree.dy)<=AGREEMENT_Y && + cliff.found.x+cliff.found.width<.5 && tree.found.x>.65){ + candidate.located=true;candidate.coarse=true;candidate.at_target=false; + candidate.dx=0;candidate.dy=(cliff.dy+tree.dy)/2; + } + double score=1; + for (const auto& landmark:candidate.landmarks) score=std::min(score,landmark.score); + if ((candidate.located && !best.located) || + (candidate.located && best.located && best.coarse && !candidate.coarse) || + (candidate.located==best.located && candidate.coarse==best.coarse && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + return best; +} OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ OverworldMatchResult best; double best_score=-1; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index bd39058b8e..3ba034aa12 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -55,6 +55,7 @@ OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame); OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame); OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame); +OverworldMatchResult match_route208_lower_stair_lane(const ImageRGB32& frame); OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame); From 2e5ddcde31aea1bcba29d3233b5c750936fc760f Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 24 Oct 2026 21:29:32 -0500 Subject: [PATCH 32/39] Restore shared upstream automation patterns --- .gitignore | 3 +- .../AutomationProgramPatterns.md | 160 ++++++++++++++++++ 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 CodingAgentContext/AutomationProgramPatterns.md diff --git a/.gitignore b/.gitignore index 5bc3147fce..febf4a74d4 100644 --- a/.gitignore +++ b/.gitignore @@ -79,7 +79,8 @@ Packages/* # Local agent context and working notes /.github/copilot-instructions.md -/CodingAgentContext/ +/CodingAgentContext/* +!/CodingAgentContext/AutomationProgramPatterns.md /SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md /SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md /SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md diff --git a/CodingAgentContext/AutomationProgramPatterns.md b/CodingAgentContext/AutomationProgramPatterns.md new file mode 100644 index 0000000000..fee4a67712 --- /dev/null +++ b/CodingAgentContext/AutomationProgramPatterns.md @@ -0,0 +1,160 @@ +# Automation Program Patterns + +## Basic Program Structure (for Single-Switch Programs) + +### Required Files +``` +GameName_ProgramName.h // Header with class declarations +GameName_ProgramName.cpp // Implementation +``` +Example game names: `PokemonLA`, `PokemonSwSh`, `PokemonSV`, `PokemonBDSP` + +### Core Classes +```cpp +class ProgramName_Descriptor : public SingleSwitchProgramDescriptor +class ProgramName : public SingleSwitchProgramInstance +``` + +## Essential Includes + +### Header (.h) +```cpp +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +``` + +### Implementation (.cpp) +```cpp +#include "CommonFramework/Exceptions/ProgramFinishedException.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +``` + +## Example Program Options + +### Common Option Types +```cpp +SimpleIntegerOption NUM_BOXES( + "Number of Boxes:", + LockMode::LOCK_WHILE_RUNNING, + 5, 1, 30 // default, min, max +); + +BooleanCheckBoxOption ENABLE_FEATURE( + "Enable Feature:", + LockMode::LOCK_WHILE_RUNNING, + true +); +``` + +### Register Options +```cpp +PA_ADD_OPTION(NUM_BOXES); +PA_ADD_OPTION(ENABLE_FEATURE); +``` + +## Notifications + +### Declaration (in .h) +```cpp +EventNotificationOption NOTIFICATION_STATUS_UPDATE; +EventNotificationsOption NOTIFICATIONS; +``` + +### Initialization (in constructor) +```cpp +NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) +, NOTIFICATIONS({ + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_FATAL, +}) +``` + +### Sending Notifications +```cpp +send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); +send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); +send_program_fatal_error_notification(env, NOTIFICATION_ERROR_FATAL, "Error message"); +``` + +## Controller Input + +### Button Presses +```cpp +pbf_press_button(context, BUTTON_A, 10, 100); // button, hold_time, release_time +pbf_press_dpad(context, DPAD_DOWN, 20, 50); +context.wait_for_all_requests(); // wait for button to finish execution +``` + +### Delays +```cpp +context.wait_for(std::chrono::milliseconds(1000)); +``` + +## Main Program Function Pattern + +### Simplest Program +```cpp +void ProgramName::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // button presses and inference routines +} +``` + +## Looping Program with Ability to Recover from Minor Error + +```cpp +while(true){ + try{ + // Initialization logic + + env.update_stats(); + send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); + + // Main program logic + + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + }catch(ProgramFinishedException&){ // an exception indicating the program has finished + env.update_stats(); + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + throw; + }catch(OperationFailedException& e){ // an exception indicating an error somewhere in the loop + if(...){ // if we can recover from it + // execute recover logic (usually is restarting the game) + continue; + } + // we cannot recover from the exception. Throw the exception to the higher level to + // trigger fatal error noticiation and cleanup. + throw; + } +} +``` + + +## Program Registration + +### In GameName_Panels.cpp +```cpp +// Add include +#include "Programs/Category/GameName_ProgramName.h" + +// Add to make_panels() +ret.emplace_back(make_single_switch_program()); +``` +Examples: `PokemonLA_Panels.cpp`, `PokemonSwSh_Panels.cpp`, `PokemonSV_Panels.cpp` + +## Descriptor Configuration + +```cpp +ProgramName_Descriptor::ProgramName_Descriptor() + : SingleSwitchProgramDescriptor( + "GameName:ProgramName", // Unique ID + STRING_POKEMON + " [GameName]", "Display Name", // Category, Name + "ComputerControl/blob/master/Wiki/...", // Wiki URL + "Brief description.", // Description + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, // or NONE + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} +``` \ No newline at end of file From c99e9455fc486f50efec1eb95bb957e7c56f724a Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 24 Oct 2026 21:35:25 -0500 Subject: [PATCH 33/39] Keep personal ignore rules local --- .gitignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitignore b/.gitignore index febf4a74d4..053e1db970 100644 --- a/.gitignore +++ b/.gitignore @@ -77,10 +77,3 @@ build_*/ .cache/* Packages/* -# Local agent context and working notes -/.github/copilot-instructions.md -/CodingAgentContext/* -!/CodingAgentContext/AutomationProgramPatterns.md -/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/DIRECTORY_CLEANUP_SUMMARY.md -/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/butters segment data.md -/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/butters segment data.md From ec3fe4603704df10e148a04d33134fdeacc9f725 Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Sat, 24 Oct 2026 22:10:29 -0500 Subject: [PATCH 34/39] Clean up BDSP resources and register offline tests Replace Qt file, JSON, and resource lookup in BDSP code with project utilities; use UTF-8 paths for lighting output. Register the Bag/menu suites and resolve their fixtures through UNIT_TEST_RESOURCE_PATH(). Debug build and 504 BDSP checks pass; full suite reports 29,465 passed and 928 existing resource-related failures. Fixture repositories: CommandLineTests fb26d1734253d5530a91c8bc6492c06912152421; Packages ad4647b23ea6208c088e045283d53305dcd58275. --- .../ComputerPrograms_UnitTestRunner.cpp | 2 + .../Inference/PokemonBDSP_MenuDetector.cpp | 38 +- .../Source/PokemonBDSP/PokemonBDSP_Tests.cpp | 413 +++++++++--------- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 5 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 21 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 3 - .../PokemonBDSP_OverworldLightingTest.cpp | 27 +- 7 files changed, 249 insertions(+), 260 deletions(-) diff --git a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp index 1c19573466..502c22e7e1 100644 --- a/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp @@ -27,6 +27,7 @@ #include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" #include "NintendoSwitch/Programs/Typewriter/NintendoSwitch_Typewriter_Tests.h" +#include "PokemonBDSP/PokemonBDSP_Tests.h" #include "VideoGames/PokemonHome/PokemonHome_Tests.h" #include "VideoGames/PokemonSwSh/PokemonSwSh_Tests.h" #include "VideoGames/PokemonLA/PokemonLA_Tests.h" @@ -62,6 +63,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ NintendoSwitch::add_tests_FailedToConnectDetector(ret); NintendoSwitch::add_tests_UpdatePopupDetector(ret); NintendoSwitch::Typewriter::add_tests_TypewriterTextMapping(ret); + NintendoSwitch::PokemonBDSP::add_tests(ret); NintendoSwitch::PokemonFRLG::add_tests(ret); NintendoSwitch::PokemonRSE::add_tests(ret); NintendoSwitch::PokemonHome::add_tests(ret); diff --git a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp index eb66d7ff64..3df61e3b68 100644 --- a/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Inference/PokemonBDSP_MenuDetector.cpp @@ -4,11 +4,11 @@ * */ +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Filesystem/Filesystem.h" #include "CommonFramework/GlobalAutoPaths.h" #include #include -#include -#include #include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/Globals.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" @@ -256,21 +256,17 @@ bool MenuWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp std::string menu_detector_test_folder(){ - QDir directory(QCoreApplication::applicationDirPath()); - for (size_t level = 0; level < 6; level++){ - const QString folder = directory.filePath("Packages/Resources/PokemonBDSP/menu_detector"); - if (QDir(folder).exists()){ - return QDir(folder).absolutePath().toStdString(); - } - if (!directory.cdUp()) break; - } - return RESOURCE_PATH() + "PokemonBDSP/menu_detector"; + return UNIT_TEST_RESOURCE_PATH() + "PokemonBDSP/menu_detector"; } bool run_menu_detector_tests(Logger& logger, const std::string& folder){ - const QDir directory(QString::fromStdString(folder)); - const auto files = directory.entryList({"*.png"}, QDir::Files, QDir::Name); - if (files.isEmpty()){ + std::vector files; + for (const auto& path : Filesystem::list_directory(folder)){ + if (Filesystem::is_regular_file(path) && path.extension().string() == ".png"){ + files.push_back(path); + } + } + if (files.empty()){ logger.log("[Menu tests] No PNG screenshots in " + folder, COLOR_RED); return false; } @@ -283,19 +279,23 @@ bool run_menu_detector_tests(Logger& logger, const std::string& folder){ ": detected=" + (actual ? "true" : "false") + ", expected=" + (expected ? "true" : "false"), success ? COLOR_GREEN : COLOR_RED); }; - for (const QString& filename : files){ - const bool expected = !filename.startsWith("non_menu_"); + for (const auto& path : files){ + const std::string filename = path.filename().string(); + const bool expected = !filename.starts_with("non_menu_"); try{ - const ImageRGB32 image(directory.filePath(filename).toStdString()); + const ImageRGB32 image(path.string()); for (size_t width : {size_t(1920), size_t(1280), size_t(960)}){ const ImageRGB32 scaled = image.scale_to(width, width * 9 / 16); MenuDetector detector; check(detector.detect(scaled), expected, - filename.toStdString() + " at " + std::to_string(width) + "px"); + filename + " at " + std::to_string(width) + "px"); } + }catch (const FileException& error){ + failed++; + logger.log("[FAIL] " + filename + ": " + error.to_str(), COLOR_RED); }catch (const std::exception& error){ failed++; - logger.log("[FAIL] " + filename.toStdString() + ": " + error.what(), COLOR_RED); + logger.log("[FAIL] " + filename + ": " + error.what(), COLOR_RED); } } // Uniform bright/dark frames must not count as an open menu. diff --git a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp index b2ec5602d8..569d479276 100644 --- a/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/PokemonBDSP_Tests.cpp @@ -7,11 +7,10 @@ #include #include #include -#include -#include -#include -#include -#include +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "Common/Cpp/Json/JsonValue.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/Json/JsonArray.h" #include "Common/Cpp/Color.h" #include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/Globals.h" @@ -58,234 +57,232 @@ class Test_BagScreenshots : public UnitTest{ public: Test_BagScreenshots() : UnitTest("PokemonBDSP::Bag screenshot tests"){} UnitTestResult run(Logger& logger, CancellableScope&) const override{ - return run_bag_screenshot_tests(logger, RESOURCE_PATH() + "PokemonBDSP/itembagtests"); + return run_bag_screenshot_tests(logger, UNIT_TEST_RESOURCE_PATH() + "PokemonBDSP/itembagtests"); } }; } bool run_bag_screenshot_tests(Logger& logger, const std::string& folder){ - const std::string prefix = folder + "/"; - QFile file(QString::fromStdString(prefix + "bag_tests.json")); - if (!file.open(QIODevice::ReadOnly)){ - logger.log("[Bag tests] Cannot open " + prefix + "bag_tests.json", COLOR_RED); - return false; - } - QJsonParseError error; - const auto doc = QJsonDocument::fromJson(file.readAll(), &error); - const auto manifest = doc.object(); - const auto template_json = manifest["templates"].toArray(); - const auto cases = manifest["cases"].toArray(); - if (error.error != QJsonParseError::NoError || template_json.isEmpty() || cases.isEmpty()){ - logger.log("[Bag tests] Invalid or empty test manifest.", COLOR_RED); - return false; - } - std::map> images; - auto load_image = [&](const std::string& name){ - auto it = images.find(name); - if (it == images.end()){ - it = images.emplace(name, std::make_shared(prefix + name)).first; - } - return it->second; - }; - std::vector templates; - for (const auto& value : template_json){ - const auto entry = value.toObject(); - TestItemTemplate t; - t.item.label = entry["label"].toString().toStdString(); - t.item.reference_row = entry["row"].toInt(); - const auto name = entry["image"].toString().toStdString(); - if (t.item.label.empty() || t.item.reference_row < 1 || t.item.reference_row > 7 || - !QFileInfo::exists(QString::fromStdString(prefix + name))){ - logger.log("[Bag tests] Invalid or missing item reference: " + name, COLOR_RED); + try{ + const std::string prefix = folder + "/"; + const auto doc = load_json_file(prefix + "bag_tests.json"); + const auto& manifest = doc.to_object_throw(); + const auto& template_json = manifest.get_array_throw("templates"); + const auto& cases = manifest.get_array_throw("cases"); + if (template_json.empty() || cases.empty()){ + logger.log("[Bag tests] Invalid or empty test manifest.", COLOR_RED); return false; } - t.reference = load_image(name); - templates.emplace_back(std::move(t)); - } - - size_t passed = 0; - size_t failed = 0; - auto check = [&](bool result, const std::string& detail){ - (result ? passed : failed)++; - logger.log(std::string(result ? "[PASS] " : "[FAIL] ") + detail, result ? COLOR_GREEN : COLOR_RED); - }; - // All 64 routes, including wraparound and Right for opposite pockets. - for (int current = 1; current <= 8; current++){ - for (int target = 1; target <= 8; target++){ - const auto plan = plan_bag_pocket_move(current, target); - const int delta = target - current; - const int distance = std::abs(delta); - const size_t expected = static_cast(std::min(distance, 8 - distance)); - const auto direction = distance == 0 ? BagPocketAction::CONFIRM : - distance == 4 || (delta > 0 && distance < 4) || (delta < 0 && distance > 4) - ? BagPocketAction::RIGHT : BagPocketAction::LEFT; - check(plan.presses == expected && plan.action == direction, - "Pocket route " + std::to_string(current) + " to " + std::to_string(target)); + std::map> images; + auto load_image = [&](const std::string& name){ + auto it = images.find(name); + if (it == images.end()){ + it = images.emplace(name, std::make_shared(prefix + name)).first; + } + return it->second; + }; + std::vector templates; + for (const auto& value : template_json){ + const auto& entry = value.to_object_throw(); + TestItemTemplate t; + t.item.label = entry.get_string_throw("label"); + const int64_t reference_row = entry.get_integer_throw("row"); + t.item.reference_row = reference_row >= 1 && reference_row <= 7 ? static_cast(reference_row) : 0; + const auto name = entry.get_string_throw("image"); + if (t.item.label.empty() || t.item.reference_row < 1 || t.item.reference_row > 7 || + !Filesystem::is_regular_file(prefix + name)){ + logger.log("[Bag tests] Invalid or missing item reference: " + name, COLOR_RED); + return false; + } + t.reference = load_image(name); + templates.emplace_back(std::move(t)); } - } - check(plan_bag_pocket_move(4, 5).action == BagPocketAction::RIGHT && - plan_bag_pocket_move(4, 5).presses == 1, "Dropped press: recalculate from pocket 4 to 5"); - check(plan_bag_pocket_move(0, 5).action == BagPocketAction::UNKNOWN && - plan_bag_pocket_move(1, 9).action == BagPocketAction::UNKNOWN, - "Unknown or invalid pocket gives no movement"); - // Independent expected values, including paging and unknown-cursor handling. - const std::array pages{13, 11, 10, 9, 8, 7, 1}; - for (int cursor = 1; cursor <= 7; cursor++){ - const auto plan = plan_bag_item_move(cursor, 0); - check(plan.presses == pages[cursor - 1] && plan.action == - (cursor == 7 ? BagMoveAction::REWIND : BagMoveAction::PAGE_DOWN), - "Absent item, cursor " + std::to_string(cursor) + ": " + action_name(plan.action) + - " " + std::to_string(plan.presses)); - } - check(plan_bag_item_move(0, 3).action == BagMoveAction::UNKNOWN, "Unknown cursor gives no movement"); - check(plan_bag_item_move(2, 8).action == BagMoveAction::UNKNOWN, "Invalid item row gives no movement"); - check(bag_cursor_position(ImageRGB32()) == 0, "Missing image gives unknown cursor"); - // Exact expected displacements for every cursor/target combination. - const int displacement[7][7] = { - { 0, 1, 2, 3, 4, 5, 6}, - {-1, 0, 1, 2, 3, 4, 5}, - {-2, -1, 0, 1, 2, 3, 4}, - {-3, -2, -1, 0, 1, 2, 3}, - {-4, -3, -2, -1, 0, 1, 2}, - {-5, -4, -3, -2, -1, 0, 1}, - {-6, -5, -4, -3, -2, -1, 0} - }; - for (int cursor = 1; cursor <= 7; cursor++){ - for (int target = 1; target <= 7; target++){ - const int delta = displacement[cursor - 1][target - 1]; - const auto plan = plan_bag_item_move(cursor, target); - check(plan.action == (delta == 0 ? BagMoveAction::CONFIRM : - delta < 0 ? BagMoveAction::UP : BagMoveAction::DOWN) && - plan.presses == static_cast(delta < 0 ? -delta : delta), - "Cursor " + std::to_string(cursor) + " to item " + std::to_string(target) + - ": " + action_name(plan.action) + " " + std::to_string(plan.presses)); + size_t passed = 0; + size_t failed = 0; + auto check = [&](bool result, const std::string& detail){ + (result ? passed : failed)++; + logger.log(std::string(result ? "[PASS] " : "[FAIL] ") + detail, result ? COLOR_GREEN : COLOR_RED); + }; + // All 64 routes, including wraparound and Right for opposite pockets. + for (int current = 1; current <= 8; current++){ + for (int target = 1; target <= 8; target++){ + const auto plan = plan_bag_pocket_move(current, target); + const int delta = target - current; + const int distance = std::abs(delta); + const size_t expected = static_cast(std::min(distance, 8 - distance)); + const auto direction = distance == 0 ? BagPocketAction::CONFIRM : + distance == 4 || (delta > 0 && distance < 4) || (delta < 0 && distance > 4) + ? BagPocketAction::RIGHT : BagPocketAction::LEFT; + check(plan.presses == expected && plan.action == direction, + "Pocket route " + std::to_string(current) + " to " + std::to_string(target)); + } } - } - ImageRGB32 blank(1920, 1080); - for (size_t y = 0; y < blank.height(); y++){ - for (size_t x = 0; x < blank.width(); x++){ - blank.pixel(x, y) = 0xffffffff; + check(plan_bag_pocket_move(4, 5).action == BagPocketAction::RIGHT && + plan_bag_pocket_move(4, 5).presses == 1, "Dropped press: recalculate from pocket 4 to 5"); + check(plan_bag_pocket_move(0, 5).action == BagPocketAction::UNKNOWN && + plan_bag_pocket_move(1, 9).action == BagPocketAction::UNKNOWN, + "Unknown or invalid pocket gives no movement"); + // Independent expected values, including paging and unknown-cursor handling. + const std::array pages{13, 11, 10, 9, 8, 7, 1}; + for (int cursor = 1; cursor <= 7; cursor++){ + const auto plan = plan_bag_item_move(cursor, 0); + check(plan.presses == pages[cursor - 1] && plan.action == + (cursor == 7 ? BagMoveAction::REWIND : BagMoveAction::PAGE_DOWN), + "Absent item, cursor " + std::to_string(cursor) + ": " + action_name(plan.action) + + " " + std::to_string(plan.presses)); } - } - check(bag_cursor_position(blank) == 0, "Blank screenshot has no cursor"); - for (const auto& t : templates){ - const auto matches = match_bag_item_rows(blank, *t.reference, t.item); - bool found = false; - for (const auto& m : matches){ - found |= m.matched; - } - check(!found, "Blank screenshot does not match " + t.item.label); - } + check(plan_bag_item_move(0, 3).action == BagMoveAction::UNKNOWN, "Unknown cursor gives no movement"); + check(plan_bag_item_move(2, 8).action == BagMoveAction::UNKNOWN, "Invalid item row gives no movement"); + check(bag_cursor_position(ImageRGB32()) == 0, "Missing image gives unknown cursor"); - for (const auto& case_value : cases){ - const auto test = case_value.toObject(); - const auto filename = test["image"].toString().toStdString(); - const auto expected = test["items"].toArray(); - const int expected_cursor = test["cursor"].toInt(-1); - if (expected.size() != 7 || expected_cursor < 0 || expected_cursor > 7 || - !QFileInfo::exists(QString::fromStdString(prefix + filename))){ - check(false, "Invalid or missing screenshot case: " + filename); - continue; + // Exact expected displacements for every cursor/target combination. + const int displacement[7][7] = { + { 0, 1, 2, 3, 4, 5, 6}, + {-1, 0, 1, 2, 3, 4, 5}, + {-2, -1, 0, 1, 2, 3, 4}, + {-3, -2, -1, 0, 1, 2, 3}, + {-4, -3, -2, -1, 0, 1, 2}, + {-5, -4, -3, -2, -1, 0, 1}, + {-6, -5, -4, -3, -2, -1, 0} + }; + for (int cursor = 1; cursor <= 7; cursor++){ + for (int target = 1; target <= 7; target++){ + const int delta = displacement[cursor - 1][target - 1]; + const auto plan = plan_bag_item_move(cursor, target); + check(plan.action == (delta == 0 ? BagMoveAction::CONFIRM : + delta < 0 ? BagMoveAction::UP : BagMoveAction::DOWN) && + plan.presses == static_cast(delta < 0 ? -delta : delta), + "Cursor " + std::to_string(cursor) + " to item " + std::to_string(target) + + ": " + action_name(plan.action) + " " + std::to_string(plan.presses)); + } } - const auto image = load_image(filename); - const int cursor = bag_cursor_position(*image); - check(cursor == expected_cursor, filename + ": cursor " + std::to_string(cursor) + - ", expected " + std::to_string(expected_cursor)); - // Scale the same screen to the minimum supported capture resolution. - const auto small = image->scale_to(1280, 720); - check(bag_cursor_position(small) == expected_cursor, filename + ": cursor at 720p"); - - std::array labels; - std::array best; - std::array second; - best.fill(std::numeric_limits::infinity()); - second = best; - for (const auto& t : templates){ - const auto matches = match_bag_item_rows(*image, *t.reference, t.item); - for (size_t row = 0; row < 7; row++){ - const auto& m = matches[row]; - if (!m.matched){ - continue; - } - const double score = m.icon_rmsd + m.name_rmsd; - if (score < best[row]){ - second[row] = best[row]; - best[row] = score; - labels[row] = t.item.label; - }else if (score < second[row]){ - second[row] = score; - } + ImageRGB32 blank(1920, 1080); + for (size_t y = 0; y < blank.height(); y++){ + for (size_t x = 0; x < blank.width(); x++){ + blank.pixel(x, y) = 0xffffffff; } } - for (size_t row = 0; row < 7; row++){ - if (labels[row].empty()){ - labels[row] = empty_bag_row(*image, row + 1) ? "" : "Unknown"; - }else if (second[row] - best[row] < 2.0){ - labels[row] = "Unknown"; + check(bag_cursor_position(blank) == 0, "Blank screenshot has no cursor"); + for (const auto& t : templates){ + const auto matches = match_bag_item_rows(blank, *t.reference, t.item); + bool found = false; + for (const auto& m : matches){ + found |= m.matched; } - check(labels[row] == expected[row].toString().toStdString(), - filename + ": row " + std::to_string(row + 1) + " = " + - (labels[row].empty() ? "Empty" : labels[row]) + ", expected " + - (expected[row].toString().isEmpty() ? "Empty" : expected[row].toString().toStdString())); + check(!found, "Blank screenshot does not match " + t.item.label); } - for (const auto& query_value : test["queries"].toArray()){ - const auto query = query_value.toObject(); - const auto wanted = query["item"].toString().toStdString(); - int item_row = 0; - for (size_t row = 0; row < 7; row++){ - if (labels[row] == wanted){ - item_row = static_cast(row + 1); + + for (const auto& case_value : cases){ + const auto& test = case_value.to_object_throw(); + const auto filename = test.get_string_throw("image"); + const auto& expected = test.get_array_throw("items"); + const int64_t expected_cursor = test.get_integer_throw("cursor"); + if (expected.size() != 7 || expected_cursor < 0 || expected_cursor > 7 || + !Filesystem::is_regular_file(prefix + filename)){ + check(false, "Invalid or missing screenshot case: " + filename); + continue; + } + const auto image = load_image(filename); + const int cursor = bag_cursor_position(*image); + check(cursor == expected_cursor, filename + ": cursor " + std::to_string(cursor) + + ", expected " + std::to_string(expected_cursor)); + // Scale the same screen to the minimum supported capture resolution. + const auto small = image->scale_to(1280, 720); + check(bag_cursor_position(small) == expected_cursor, filename + ": cursor at 720p"); + + std::array labels; + std::array best; + std::array second; + best.fill(std::numeric_limits::infinity()); + second = best; + for (const auto& t : templates){ + const auto matches = match_bag_item_rows(*image, *t.reference, t.item); + for (size_t row = 0; row < 7; row++){ + const auto& m = matches[row]; + if (!m.matched){ + continue; + } + const double score = m.icon_rmsd + m.name_rmsd; + if (score < best[row]){ + second[row] = best[row]; + best[row] = score; + labels[row] = t.item.label; + }else if (score < second[row]){ + second[row] = score; + } } } - const auto plan = plan_bag_item_move(cursor, item_row); - check(action_name(plan.action) == query["action"].toString().toStdString() && - plan.presses == static_cast(query["presses"].toInt(-1)), - filename + ": looking for " + wanted + ", item row " + std::to_string(item_row) + - ", cursor row " + std::to_string(cursor) + ": " + action_name(plan.action) + - " " + std::to_string(plan.presses) + " (no input sent)"); - } - } - // Optional pocket fixtures extend the same offline/dev-mode suite. - QFile pocket_file(QString::fromStdString(prefix + "pocket_tests.json")); - if (pocket_file.exists()){ - if (!pocket_file.open(QIODevice::ReadOnly)){ - check(false, "Cannot open pocket test manifest"); - }else{ - QJsonParseError pocket_error; - const auto pocket_doc = QJsonDocument::fromJson(pocket_file.readAll(), &pocket_error); - const auto pocket_cases = pocket_doc.object()["cases"].toArray(); - check(pocket_error.error == QJsonParseError::NoError && !pocket_cases.isEmpty(), - "Pocket test manifest is valid"); - for (const auto& value : pocket_cases){ - const auto entry = value.toObject(); - const auto name = entry["image"].toString().toStdString(); - const int expected_pocket = entry["pocket"].toInt(-1); - if (expected_pocket < 0 || expected_pocket > 8 || !entry["empty"].isBool() || - !QFileInfo::exists(QString::fromStdString(prefix + name))){ - check(false, "Invalid or missing pocket case: " + name); - continue; + for (size_t row = 0; row < 7; row++){ + if (labels[row].empty()){ + labels[row] = empty_bag_row(*image, row + 1) ? "" : "Unknown"; + }else if (second[row] - best[row] < 2.0){ + labels[row] = "Unknown"; } - const auto image = load_image(name); - for (size_t width : {1920, 1280, 960}){ - const auto scaled = image->scale_to(width, width * 9 / 16); - check(bag_pocket_position(scaled) == expected_pocket && - bag_list_is_empty(scaled) == entry["empty"].toBool(), - name + ": pocket " + std::to_string(expected_pocket) + - ", empty=" + (entry["empty"].toBool() ? "true" : "false") + - " at width " + std::to_string(width)); + check(labels[row] == expected[row].to_string_throw(), + filename + ": row " + std::to_string(row + 1) + " = " + + (labels[row].empty() ? "Empty" : labels[row]) + ", expected " + + (expected[row].to_string_throw().empty() ? "Empty" : expected[row].to_string_throw())); + } + for (const auto& query_value : test.get_array_throw("queries")){ + const auto& query = query_value.to_object_throw(); + const auto wanted = query.get_string_throw("item"); + int item_row = 0; + for (size_t row = 0; row < 7; row++){ + if (labels[row] == wanted){ + item_row = static_cast(row + 1); + } } + const auto plan = plan_bag_item_move(cursor, item_row); + check(action_name(plan.action) == query.get_string_throw("action") && + plan.presses == static_cast(query.get_integer_throw("presses")), + filename + ": looking for " + wanted + ", item row " + std::to_string(item_row) + + ", cursor row " + std::to_string(cursor) + ": " + action_name(plan.action) + + " " + std::to_string(plan.presses) + " (no input sent)"); } } + // Pocket fixtures are part of this suite and must be present. + const auto pocket_doc = load_json_file(prefix + "pocket_tests.json"); + const auto& pocket_cases = pocket_doc.to_object_throw().get_array_throw("cases"); + check(!pocket_cases.empty(), "Pocket test manifest is nonempty"); + for (const auto& value : pocket_cases){ + const auto& entry = value.to_object_throw(); + const auto& name = entry.get_string_throw("image"); + const int64_t expected_pocket = entry.get_integer_throw("pocket"); + const bool expected_empty = entry.get_boolean_throw("empty"); + if (expected_pocket < 0 || expected_pocket > 8 || + !Filesystem::is_regular_file(prefix + name)){ + check(false, "Invalid or missing pocket case: " + name); + continue; + } + const auto image = load_image(name); + for (size_t width : {size_t(1920), size_t(1280), size_t(960)}){ + const auto scaled = image->scale_to(width, width * 9 / 16); + check(bag_pocket_position(scaled) == expected_pocket && + bag_list_is_empty(scaled) == expected_empty, + name + ": pocket " + std::to_string(expected_pocket) + + ", empty=" + (expected_empty ? "true" : "false") + + " at width " + std::to_string(width)); + } + } + check(bag_pocket_position(blank) == 0 && !bag_list_is_empty(blank), + "Blank screenshot has no pocket and is not an empty Bag"); + check(bag_pocket_position(ImageRGB32()) == 0 && !bag_list_is_empty(ImageRGB32()), + "Missing screenshot has no pocket and is not an empty Bag"); + logger.log("[Bag tests] " + std::to_string(passed) + " passed, " + std::to_string(failed) + " failed.", + failed == 0 ? COLOR_GREEN : COLOR_RED); + return failed == 0; + }catch (const ParseException& error){ + logger.log("[Bag tests] " + error.to_str(), COLOR_RED); + return false; + }catch (const FileException& error){ + logger.log("[Bag tests] " + error.to_str(), COLOR_RED); + return false; + }catch (const std::exception& error){ + logger.log("[Bag tests] " + std::string(error.what()), COLOR_RED); + return false; } - check(bag_pocket_position(blank) == 0 && !bag_list_is_empty(blank), - "Blank screenshot has no pocket and is not an empty Bag"); - check(bag_pocket_position(ImageRGB32()) == 0 && !bag_list_is_empty(ImageRGB32()), - "Missing screenshot has no pocket and is not an empty Bag"); - logger.log("[Bag tests] " + std::to_string(passed) + " passed, " + std::to_string(failed) + " failed.", - failed == 0 ? COLOR_GREEN : COLOR_RED); - return failed == 0; } void add_tests(UnitTestDatabase& database){ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 7ed2d3e940..944aeac473 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1,6 +1,6 @@ #include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/GlobalAutoPaths.h" -#include +#include "Common/Cpp/Filesystem/Filesystem.h" /* BDSP AutoStory Tools * * From: https://github.com/PokemonAutomation/ @@ -1005,8 +1005,7 @@ void open_menu( std::string path = ref_dir + "/" + ICON_NAMES[i] + ".png"; // These templates are optional. The dark-pixel cursor detector // remains available when a template is not packaged. - std::error_code file_error; - if (!std::filesystem::is_regular_file(path, file_error)){ + if (!Filesystem::is_regular_file(path)){ continue; } // Previously constructed the image before checking its availability. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index d8232d1a8e..3ad6b48d94 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -11,9 +11,6 @@ #include #include #include -#include -#include -#include #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTools/ImageDiff.h" @@ -35,24 +32,8 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - // Prefer the source Packages resources when running a development build. - // Installed builds continue to use their bundled Resources directory. static const std::string& mine_navigation_resource_path() { - static const std::string path = [] { - QDir directory(QCoreApplication::applicationDirPath()); - for (size_t level = 0; level < 5; level++) { - const QString candidate = directory.filePath( - "Packages/Resources/PokemonBDSP/Autostory/Oreburg_mine_nav/" - ); - if (QFileInfo(candidate).isDir()) { - return QDir::cleanPath(candidate).toStdString() + "/"; - } - if (!directory.cdUp()) { - break; - } - } - return RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; - }(); + static const std::string path = RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; return path; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 8beb88ca77..797fe2d530 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -10,9 +10,6 @@ #include #include #include -#include -#include -#include #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/ImageTools/ImageDiff.h" diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp index 335d02cb30..8a31155a33 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp @@ -1,6 +1,13 @@ +/* Overworld Matcher Lighting Test + * + * From: https://github.com/PokemonAutomation/ + */ + #include -#include -#include +#include +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "Common/Cpp/Filesystem/FileIO.h" +#include "CommonFramework/GlobalAutoPaths.h" #include #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" @@ -32,11 +39,12 @@ OverworldLightingTest::OverworldLightingTest() {PA_ADD_OPTION(MATCHER);PA_ADD_OPTION(START_HOUR);PA_ADD_OPTION(SETTLE_SECONDS);} void OverworldLightingTest::program(SingleSwitchProgramEnvironment& env,ProControllerContext& context){ const auto& matcher=overworld_matchers()[MATCHER.index()]; - std::filesystem::path output=std::filesystem::path("Screenshots/OverworldLightingTest")/ + Filesystem::Path output=Filesystem::Path(USER_FILE_PATH())/"Screenshots/OverworldLightingTest"/ (matcher.slug+"-"+std::to_string(std::time(nullptr))); - std::filesystem::create_directories(output); - std::ofstream csv(output/"results.csv"); - if(!csv)OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); + Filesystem::create_directories(output); + FileIO results(output/"results.csv",FileMode::WRITE); + std::ostringstream csv; + if(!results.is_open())OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); csv<<"sample,hour,frame,landmark,score,margin,accepted,dx_screen,dy_screen,located,at_target,consensus_dx_screen,consensus_dy_screen,coarse\n"; csv<save((output/name).string()))OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: Screenshot save failed.",env.console); - csv.flush(); + const std::string rows=csv.str(); + if(results.write(rows)!=rows.size() || !results.flush()){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: CSV write failed.",env.console); + } + csv.str(""); + csv.clear(); pbf_wait(context,250ms);context.wait_for_all_requests(); } } From 81caccfe1f30acd04b715a2d41373f3cd599bb8f Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 29 Oct 2026 13:09:02 -0500 Subject: [PATCH 35/39] Improve BDSP AutoStory routes through Veilstone Rework Hearthome, Lost Tower, and Veilstone pathing using recorded routes and existing battle helpers. Add deliberate Lost Tower D-pad movement, watched stair transitions, reward cleanup, and Escape Rope settling. Improve Repel renewal positioning, Route 215 lane recovery, and Veilstone landmark alignment. Group lighting-test matchers by segment and expand diagnostic results. Remove lead-specific move names from battle comments and labels; retain slot-based selection. Clear Maylene's reward dialogue before ending Segment 03; leave the Gym exit and Dawn scene to Segment 04. Add the 04.18 Pastoria Gym Puzzle checkpoint with an explicit unimplemented stop. Correct checkpoint handoff descriptions. --- .../Data/PokemonBDSP_AutoStory_BDSPData.h | 2 +- .../AutoStory/PokemonBDSP_AutoStory.cpp | 1 + .../AutoStory/PokemonBDSP_AutoStoryTools.h | 2 +- .../PokemonBDSP_AutoStory_Segment_00.cpp | 4 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 16 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 1452 ++++++++++------- .../PokemonBDSP_AutoStory_Segment_04.cpp | 34 +- .../PokemonBDSP_AutoStory_Segment_04.h | 13 + .../PokemonBDSP_AutoStory_Segment_05.cpp | 6 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 4 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 6 +- .../PokemonBDSP_AutoStory_Segment_08.cpp | 6 +- .../PokemonBDSP_AutoStory_Segment_09.cpp | 6 +- .../Utils/PokemonBDSP_AutoStory_Battle.cpp | 2 +- .../Utils/PokemonBDSP_AutoStory_Battle.h | 2 +- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 61 +- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 2 +- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 558 ++++++- .../Utils/PokemonBDSP_OverworldMatchers.h | 10 + .../PokemonBDSP_OverworldLightingTest.cpp | 277 +++- .../PokemonBDSP_OverworldLightingTest.h | 12 +- 21 files changed, 1749 insertions(+), 727 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h index 0872c25479..3fa46fabee 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h @@ -33,7 +33,7 @@ struct BDSPMoveData{ uint8_t accuracy; // 0 = never-miss (e.g. Swift); otherwise 1-100 }; -// Returns move data for the given slug (e.g. "earthquake"). +// Returns move data for the given slug. // Throws InternalProgramError if the slug is not found. const BDSPMoveData& bdsp_move(const std::string& slug); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 373777c222..18fd199b29 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -103,6 +103,7 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); // Segment 05 checkpoint_list.emplace_back(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index d312a63495..bf0acc6ad9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -87,7 +87,7 @@ enum class TorterraMove{ ROCK_CLIMB, }; -// Returns the JSON slug for a TorterraMove (e.g. "earthquake"). +// Returns the JSON slug for a configured helper move. const char* torterra_move_slug(TorterraMove move); enum class Trainer { diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index f567aa7f63..97edbe7bea 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -636,8 +636,8 @@ namespace PokemonAutomation { stream.log("[Tutorial] Checking starter HP after the first Potion pickup."); use_potion_first_pokemon(stream, context); context.wait_for_all_requests(); - // Start with one training encounter toward level 6 / Ember. The helper - // waits through XP/automatic move learning and heals after the fight. + // Complete one training encounter before Tristan. The helper waits + // through XP/automatic move learning and heals after the fight. battle_1_pokemon(stream, context); // Stop on the verified Route 201 reference instead of overshooting diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 1025201993..746fedd610 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -1149,9 +1149,9 @@ namespace PokemonAutomation{ }, "Aroma Lady Teighler timed ledge approach")) { recover_teighler_trigger(stream, context); } - stream.log("[Segment 02 battle] Aroma Lady Teighler: Flamethrower (move 2)."); + stream.log("[Segment 02 battle] Aroma Lady Teighler: move slot 2."); wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_move_slot(stream,context,2,"Aroma Lady Teighler (Flamethrower)"); + fight_trainer_using_move_slot(stream,context,2,"Aroma Lady Teighler"); clear_post_battle_dialogue(stream,context); // Omit the Awakening. The grass north of Teighler can trigger @@ -1698,7 +1698,7 @@ namespace PokemonAutomation{ move(0,1,20ms); stream.log("[Route 205] Forest entrance sign confirmed; checkpoint complete.",COLOR_GREEN); } - // Cheryl controls her own Pokemon. Select Flamethrower visually, + // Cheryl controls her own Pokemon. Select move slot 2 visually, // then A advances target selection and battle text, as in the recording. void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { (void)env; @@ -2098,7 +2098,7 @@ namespace PokemonAutomation{ pbf_press_button(context,BUTTON_A,150ms,300ms); wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Lass Caroline (Flamethrower)",false); + fight_trainer_using_moves(stream,context,2,"Lass Caroline",false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. @@ -2112,7 +2112,7 @@ namespace PokemonAutomation{ pbf_press_button(context,BUTTON_A,150ms,300ms); wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Aroma Lady Jenna (Flamethrower)",false); + fight_trainer_using_moves(stream,context,2,"Aroma Lady Jenna",false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. @@ -2126,7 +2126,7 @@ namespace PokemonAutomation{ pbf_press_button(context,BUTTON_A,150ms,300ms); wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Aroma Lady Angela (Flamethrower)",false); + fight_trainer_using_moves(stream,context,2,"Aroma Lady Angela",false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2140,7 +2140,7 @@ namespace PokemonAutomation{ pbf_press_button(context,BUTTON_A,150ms,300ms); wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Beauty Lindsay (Flamethrower)",false); + fight_trainer_using_moves(stream,context,2,"Beauty Lindsay",false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2154,7 +2154,7 @@ namespace PokemonAutomation{ pbf_press_button(context,BUTTON_A,150ms,300ms); wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Gardenia (Flamethrower)",true); + fight_trainer_using_moves(stream,context,2,"Gardenia",true); // The shared battle handler already advances reward dialogue // while confirming the overworld. Do not require the badge diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index f357935813..89a3329420 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -9,7 +9,9 @@ #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include +#include #include +#include #include "CommonFramework/Globals.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" @@ -20,6 +22,7 @@ #include "../Detect/PokemonBDSP_AutoStory_OverworldDetector.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "../Utils/PokemonBDSP_OverworldMatchers.h" #include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" @@ -32,11 +35,6 @@ using namespace std::chrono_literals; namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ - static void segment03_route_battle(VideoStream& stream, ProControllerContext& context, - const std::string& label, bool post_battle_dialogue_cleanup = false) { - fight_trainer_using_moves(stream,context,2,label,post_battle_dialogue_cleanup); - } - static void segment03_finish_transition(VideoStream& stream, ProControllerContext& context, const std::string& label) { BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, @@ -104,7 +102,7 @@ namespace PokemonBDSP{ label+": Expected trainer encounter did not start.",stream); } stream.log("["+label+"] Encounter detected; route movement cancelled."); - segment03_route_battle(stream,context,label,false); + fight_trainer_using_moves(stream, context, 2, label, false); } static void segment03_wait_image(VideoStream& stream, ProControllerContext& context, @@ -199,7 +197,7 @@ namespace PokemonBDSP{ pbf_wait(ctx,rest); },{{black},{white},{battle}}); if (result==2) { - segment03_route_battle(stream,context,label+" encounter"); + fight_trainer_using_moves(stream, context, 2, label+" encounter", false); return false; } if (result<0) { @@ -342,8 +340,7 @@ namespace PokemonBDSP{ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Expected at least three battles before the south gate.",stream); } - segment03_route_battle(stream,context, - "Cycling Road encounter "+std::to_string(completed+1),false); + fight_trainer_using_moves(stream, context, 2, "Cycling Road encounter "+std::to_string(completed+1), false); ++completed; stream.log("[Cycling Road] Completed battles: "+std::to_string(completed)+"/4."); } @@ -387,7 +384,7 @@ namespace PokemonBDSP{ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context) { auto fight=[&](const std::string& label, bool post_battle_dialogue_cleanup = false) { - segment03_route_battle(stream,context,label+" (Flamethrower)",post_battle_dialogue_cleanup); + fight_trainer_using_moves(stream, context, 2, label, post_battle_dialogue_cleanup); }; pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1060ms, 0ms); pbf_wait(context, 225ms); @@ -988,7 +985,7 @@ namespace PokemonBDSP{ continue; } } - segment03_route_battle(stream,context,"Route 208 Hiker"); + fight_trainer_using_moves(stream, context, 2, "Route 208 Hiker", false); if (++bridge_battles>2) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Route 208 bridge: Unexpected additional Hiker battle.",stream); @@ -1241,559 +1238,829 @@ namespace PokemonBDSP{ if (wait_until(stream,context,10s,{{hall_start}})!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Contest Hall checkpoint start: Overworld not ready.",stream); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1830ms, 0ms); - pbf_wait(context, 144ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1880ms, 0ms); - pbf_wait(context, 81ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 609ms, 0ms); - pbf_wait(context, 73ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2014ms, 0ms); - pbf_wait(context, 186ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1744ms, 0ms); - pbf_wait(context, 14ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 411ms, 0ms); - pbf_wait(context, 164ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1944ms, 0ms); - pbf_wait(context, 226ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1142ms, 0ms); - pbf_wait(context, 539ms); - segment03_route_battle(stream,context,"Barry in Hearthome"); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 877ms, 0ms); - pbf_wait(context, 55ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2561ms, 0ms); - pbf_wait(context, 25ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 344ms, 0ms); - pbf_wait(context, 203ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 518ms, 0ms); - pbf_wait(context, 1655ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1906ms, 0ms); - pbf_wait(context, 1761ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 878ms, 0ms); - pbf_wait(context, 1967ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1366ms, 0ms); - pbf_wait(context, 167ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 450ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 250ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 244ms, 0ms); - pbf_wait(context, 77ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 143ms, 0ms); - pbf_wait(context, 71ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 232ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 261ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 206ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 142ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 278ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 225ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3015ms, 0ms); + // butters' recorded Contest Hall-to-rival route. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1294ms, 0ms); + pbf_wait(context, 132ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3255ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2666ms, 0ms); + pbf_wait(context, 62ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2068ms, 0ms); + pbf_wait(context, 456ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1065ms, 0ms); + fight_trainer_using_moves(stream, context, std::vector{2, 1}, "Barry in Hearthome", true); + // butters' recorded rival-to-Lost Tower route. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1088ms, 0ms); pbf_wait(context, 95ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1122ms, 0ms); - pbf_wait(context, 9ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1523ms, 0ms); - pbf_wait(context, 14ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 626ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 28ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 300ms, 0ms); - pbf_wait(context, 110ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1079ms, 0ms); - pbf_wait(context, 189ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 322ms, 0ms); - pbf_wait(context, 344ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 90ms, 0ms); - pbf_wait(context, 777ms); context.wait_for_all_requests(); - } - static void lost_tower(VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, {0.000000, 1.000000}, 745ms, 0ms); + // Cancel at each gate fade, then allow a defined settling wait. + BlackScreenWatcher east_gate_entry; + if (run_until(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{1,0},3446ms,0ms); + pbf_wait(ctx,983ms); + },{{east_gate_entry}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Entrance transition did not start.",stream); + BlackScreenWatcher east_gate_entry_finished(COLOR_RED,{.1,.1,.8,.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{east_gate_entry_finished}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Entrance transition did not finish.",stream); + pbf_wait(context,983ms); + context.wait_for_all_requests(); + + BlackScreenWatcher east_gate_exit; + if (run_until(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{1,0},2095ms,0ms); + pbf_wait(ctx,1660ms); + },{{east_gate_exit}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Exit transition did not start.",stream); + BlackScreenWatcher east_gate_exit_finished(COLOR_RED,{.1,.1,.8,.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{east_gate_exit_finished}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Exit transition did not finish.",stream); + pbf_wait(context,1660ms); + context.wait_for_all_requests(); + stream.log("[Hearthome east gate] Entry and exit transitions finished.",COLOR_GREEN); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1128ms, 0ms); + pbf_wait(context, 7266ms); + // Use the existing Bag helper instead of replaying recorded menu inputs. + if (!activate_repel(stream, context, 8, 0)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 209: Repel activation failed.", stream); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1412ms, 0ms); + pbf_wait(context, 77ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1878ms, 0ms); pbf_wait(context, 100ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 994ms, 0ms); - pbf_wait(context, 94ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1179ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 193ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 884ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1382ms, 0ms); - pbf_wait(context, 524ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1750ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 145ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 116ms, 0ms); - pbf_wait(context, 373ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 321ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 212ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 202ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 132ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 212ms, 0ms); - pbf_wait(context, 1507ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 403ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 210ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 244ms, 0ms); - pbf_wait(context, 56ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 567ms, 0ms); - pbf_wait(context, 855ms); - pbf_press_button(context, BUTTON_X, 212ms, 0ms); - pbf_wait(context, 683ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 184ms, 0ms); - pbf_wait(context, 415ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 129ms, 0ms); - pbf_wait(context, 190ms); - pbf_press_button(context, BUTTON_A, 196ms, 0ms); - pbf_wait(context, 1326ms); - pbf_press_button(context, BUTTON_A, 144ms, 0ms); - pbf_wait(context, 591ms); - pbf_press_button(context, BUTTON_A, 142ms, 0ms); - pbf_wait(context, 1017ms); - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - pbf_wait(context, 411ms); - pbf_press_button(context, BUTTON_B, 190ms, 0ms); - pbf_wait(context, 594ms); - pbf_press_button(context, BUTTON_B, 172ms, 0ms); - pbf_wait(context, 190ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 317ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 266ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 76ms, 0ms); - pbf_wait(context, 424ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 265ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 61ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 280ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 144ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 814ms, 0ms); - pbf_wait(context, 1386ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 944ms, 0ms); - pbf_wait(context, 390ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 347ms, 0ms); - pbf_wait(context, 236ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 450ms, 0ms); - pbf_wait(context, 400ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 581ms, 0ms); - pbf_wait(context, 8ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 964ms, 0ms); - pbf_wait(context, 129ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 215ms, 0ms); - pbf_wait(context, 390ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1092ms, 0ms); - pbf_wait(context, 129ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1353ms, 0ms); - pbf_wait(context, 276ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 193ms, 0ms); - pbf_wait(context, 535ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 711ms, 0ms); - pbf_wait(context, 123ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 746ms, 0ms); - pbf_wait(context, 1670ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1114ms, 0ms); - pbf_wait(context, 474ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 175ms, 0ms); - pbf_wait(context, 377ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 828ms, 0ms); - pbf_wait(context, 366ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 165ms, 0ms); - pbf_wait(context, 274ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1012ms, 0ms); - pbf_wait(context, 1860ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 900ms, 0ms); - pbf_wait(context, 1078ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1089ms, 0ms); - pbf_wait(context, 711ms); - segment03_route_battle(stream,context,"Lost Tower trainers"); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 245ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 167ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 228ms, 0ms); - pbf_wait(context, 455ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 817ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 47ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 853ms, 0ms); - pbf_wait(context, 13884ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1010ms, 0ms); - pbf_wait(context, 2229ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 261ms, 0ms); - pbf_wait(context, 887ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 60ms, 0ms); - pbf_wait(context, 420ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1567ms, 0ms); - pbf_wait(context, 173ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 578ms, 0ms); - pbf_wait(context, 53ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 308ms, 0ms); - pbf_wait(context, 54ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 451ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 126ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 33ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 85ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 883ms, 0ms); - pbf_wait(context, 1309ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 419ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -1.000000}, 44ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 300ms, 0ms); - pbf_wait(context, 371ms); - pbf_press_button(context, BUTTON_A, 181ms, 0ms); - pbf_wait(context, 136ms); - pbf_press_button(context, BUTTON_A, 177ms, 0ms); - pbf_wait(context, 137ms); - pbf_press_button(context, BUTTON_A, 146ms, 0ms); - pbf_wait(context, 286ms); - pbf_press_button(context, BUTTON_A, 150ms, 0ms); - pbf_wait(context, 175ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 310ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -1.000000}, 124ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 377ms, 0ms); - pbf_wait(context, 320ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 327ms, 0ms); - pbf_wait(context, 118ms); - pbf_press_button(context, BUTTON_A, 213ms, 0ms); - pbf_wait(context, 173ms); - pbf_press_button(context, BUTTON_A, 205ms, 0ms); - pbf_wait(context, 106ms); - pbf_press_button(context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 115ms); - pbf_press_button(context, BUTTON_A, 177ms, 0ms); - pbf_wait(context, 145ms); - pbf_press_button(context, BUTTON_A, 194ms, 0ms); - pbf_wait(context, 334ms); - pbf_press_button(context, BUTTON_A, 217ms, 0ms); - pbf_wait(context, 174ms); - pbf_press_button(context, BUTTON_A, 190ms, 0ms); - pbf_wait(context, 234ms); - pbf_press_button(context, BUTTON_A, 233ms, 0ms); - pbf_wait(context, 119ms); - pbf_press_button(context, BUTTON_A, 201ms, 0ms); - pbf_wait(context, 133ms); - pbf_press_button(context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 123ms); - pbf_press_button(context, BUTTON_A, 214ms, 0ms); - pbf_wait(context, 109ms); - pbf_press_button(context, BUTTON_B, 217ms, 0ms); - pbf_wait(context, 157ms); - pbf_press_button(context, BUTTON_B, 162ms, 0ms); - pbf_wait(context, 523ms); - pbf_press_button(context, BUTTON_B, 192ms, 0ms); - pbf_wait(context, 79ms); - pbf_press_button(context, BUTTON_B, 132ms, 0ms); - pbf_wait(context, 115ms); - pbf_press_button(context, BUTTON_B, 126ms, 0ms); - pbf_wait(context, 211ms); - pbf_press_button(context, BUTTON_B, 156ms, 0ms); - pbf_wait(context, 528ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 156ms, 0ms); - pbf_wait(context, 559ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 171ms, 0ms); - pbf_wait(context, 65ms); - pbf_press_button(context, BUTTON_A, 204ms, 0ms); - pbf_wait(context, 172ms); - pbf_press_button(context, BUTTON_A, 177ms, 0ms); - pbf_wait(context, 113ms); - pbf_press_button(context, BUTTON_A, 173ms, 0ms); - pbf_wait(context, 92ms); - pbf_press_button(context, BUTTON_A, 158ms, 0ms); - pbf_wait(context, 102ms); - pbf_press_button(context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 104ms); - pbf_press_button(context, BUTTON_A, 179ms, 0ms); - pbf_wait(context, 106ms); - pbf_press_button(context, BUTTON_A, 194ms, 0ms); - pbf_wait(context, 402ms); - pbf_press_button(context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 390ms); - pbf_press_button(context, BUTTON_A, 193ms, 0ms); - pbf_wait(context, 571ms); - pbf_press_button(context, BUTTON_A, 213ms, 0ms); - pbf_wait(context, 2265ms); - pbf_press_button(context, BUTTON_A, 218ms, 0ms); - pbf_wait(context, 934ms); - pbf_press_button(context, BUTTON_A, 232ms, 0ms); - pbf_wait(context, 171ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1246ms, 0ms); + pbf_wait(context, 522ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3110ms, 0ms); + pbf_wait(context, 322ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1245ms, 0ms); + pbf_wait(context, 89ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3634ms, 0ms); + pbf_wait(context, 966ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2378ms, 0ms); + pbf_wait(context, 310ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 612ms, 0ms); + pbf_wait(context, 1001ms); + context.wait_for_all_requests(); + // butters' marked road pose: sign frame and rock pair. + align_to_overworld_landmarks(stream, context, match_route209_repel_point, + "Route 209 Repel point before expiry", false, true); + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + align_to_overworld_landmarks(stream, context, match_route209_repel_point, + "Route 209 Repel point after renewal", false, true); + + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1345ms, 0ms); + pbf_wait(context, 309ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 789ms, 0ms); + pbf_wait(context, 456ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1100ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1040ms, 0ms); + pbf_wait(context, 93ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1157ms, 0ms); pbf_wait(context, 488ms); - pbf_press_button(context, BUTTON_A, 229ms, 0ms); - pbf_wait(context, 154ms); - pbf_press_button(context, BUTTON_A, 193ms, 0ms); - pbf_wait(context, 287ms); - pbf_press_button(context, BUTTON_A, 199ms, 0ms); - pbf_wait(context, 855ms); - pbf_press_button(context, BUTTON_A, 249ms, 0ms); - pbf_wait(context, 594ms); - pbf_press_button(context, BUTTON_A, 218ms, 0ms); - pbf_wait(context, 172ms); - pbf_press_button(context, BUTTON_A, 172ms, 0ms); - pbf_wait(context, 363ms); - pbf_press_button(context, BUTTON_A, 207ms, 0ms); - pbf_wait(context, 1485ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 400ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 49ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 652ms, 0ms); - pbf_wait(context, 69ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 89ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 126ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 149ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 101ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 104ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 24ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 152ms, 0ms); + // Repel was renewed at the marked road pose; no delayed prompt inputs. + pbf_move_left_joystick(context, {0.000000, 1.000000}, 835ms, 0ms); + pbf_wait(context, 549ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1451ms, 0ms); + pbf_wait(context, 901ms); + context.wait_for_all_requests(); + // Normalize the checkpoint boundary with a complete enter/exit cycle. + // Cancel movement at each fade so it cannot continue onto the next map. + BlackScreenWatcher tower_entry; + if (run_until(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,1},1311ms,0ms); + pbf_wait(ctx,3000ms); + },{{tower_entry}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Entry transition did not start.",stream); + BlackScreenWatcher tower_entry_finished(COLOR_RED,{.1,.1,.8,.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{tower_entry_finished}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Entry transition did not finish.",stream); + OverworldWatcher tower_inside; + if (wait_until(stream,context,10s,{{tower_inside}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Indoor overworld not ready.",stream); + + BlackScreenWatcher tower_exit; + if (run_until(stream,context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx,{0,-1},1311ms,0ms); + pbf_wait(ctx,3000ms); + },{{tower_exit}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Exit transition did not start.",stream); + BlackScreenWatcher tower_exit_finished(COLOR_RED,{.1,.1,.8,.8}, + 100,10,BlackScreenWatcher::FinderType::GONE,500ms); + if (wait_until(stream,context,30s,{{tower_exit_finished}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Exit transition did not finish.",stream); + OverworldWatcher tower_outside; + if (wait_until(stream,context,10s,{{tower_outside}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Outdoor overworld not ready.",stream); + context.wait_for_all_requests(); + stream.log("[Lost Tower handoff] Outside doorway, facing south; checkpoint complete.",COLOR_GREEN); + } + void lost_tower(VideoStream& stream, ProControllerContext& context) { + // Checkpoint 03.06 starts outside the doorway, facing south, with Repel active. + // Turn north, then enter using the same single-step timing as Segment 02. + context.wait_for_all_requests(); + BlackScreenWatcher entering_tower; + if (run_until(stream, context, + [](ProControllerContext& ctx) { + for (int i = 0; i < 2; i++) { + pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); + } + pbf_wait(ctx, 2389ms); + }, {{entering_tower}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Transition did not start.", stream); + BlackScreenWatcher tower_entry_finished(COLOR_RED, {.1,.1,.8,.8}, + 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{tower_entry_finished}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Transition did not finish.", stream); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + OverworldWatcher tower_entry_ready; + if (wait_until(stream, context, 10s, {{tower_entry_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Overworld not ready.", stream); + + // butters' route, using the same counted D-pad steps as the Eterna Gym. + // Counts include one facing press whenever the commanded direction changes. + // Keep the same direction across a dialogue stop without another facing press. + struct Move { + DpadPosition dir; + int count; + }; + const Move to_floor2[] = { + {DPAD_UP, 4}, + {DPAD_LEFT, 4}, + {DPAD_UP, 6}, + {DPAD_LEFT, 2}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, + }; + BlackScreenWatcher stairs_to_floor2; + if (run_until(stream, context, + [&](ProControllerContext& ctx) { + for (const auto& move : to_floor2) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, {{stairs_to_floor2}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 2: Stair transition did not start.", stream); + segment03_finish_transition(stream, context, "Lost Tower floor 2"); + OverworldWatcher floor2_ready; + if (wait_until(stream, context, 10s, {{floor2_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 2: Overworld not ready after stairs.", stream); + + const Move to_floor3[] = { + {DPAD_DOWN, 12}, + {DPAD_LEFT, 6}, + {DPAD_UP, 2}, + // Includes the extra left step to clear the gravestone column. + {DPAD_LEFT, 8}, + {DPAD_UP, 8}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 4}, + }; + BlackScreenWatcher stairs_to_floor3; + if (run_until(stream, context, + [&](ProControllerContext& ctx) { + for (const auto& move : to_floor3) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, {{stairs_to_floor3}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 3: Stair transition did not start.", stream); + segment03_finish_transition(stream, context, "Lost Tower floor 3"); + OverworldWatcher floor3_ready; + if (wait_until(stream, context, 10s, {{floor3_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 3: Overworld not ready after stairs.", stream); + + const Move to_repel[] = { + {DPAD_DOWN, 6}, + }; + bool repel_renewed = false; + ShortDialogDetector pending_repel_dialogue(COLOR_YELLOW); + for (const auto& move : to_repel) { + for (int i = 0; i < move.count; i++) { + // Never queue another step while an expiry prompt is already visible. + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (!frame) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower Repel: No frame before the next down step.", stream); + if (pending_repel_dialogue.detect(*frame.frame)) { + stream.log("[Lost Tower Repel] Pending renewal before down press " + + std::to_string(i + 1) + "; clearing before movement."); + clear_post_battle_dialogue(stream, context, BUTTON_A); + repel_renewed = true; + } + + // Watch only one press at a time. Its 80ms hold finishes before the + // fresh dialogue watcher's 250ms confirmation can cancel the rest. + // The triggering step counts; all subsequent steps remain unqueued. + ShortDialogWatcher step_repel_dialogue(COLOR_YELLOW); + const int expiry = run_until(stream, context, + [&](ProControllerContext& ctx) { + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + ctx.wait_for_all_requests(); + pbf_wait(ctx, 500ms); + }, {{step_repel_dialogue}}); + if (expiry == 0) { + stream.log("[Lost Tower Repel] Renewal after down press " + + std::to_string(i + 1) + "/" + std::to_string(move.count) + + "; clearing before the remaining steps."); + clear_post_battle_dialogue(stream, context, BUTTON_A); + repel_renewed = true; + } + context.wait_for_all_requests(); + } + } + // Preserve the bounded final observation if expiry arrives after the last step. + if (!repel_renewed) { + ShortDialogWatcher late_repel_dialogue(COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{late_repel_dialogue}}) == 0) { + stream.log("[Lost Tower Repel] Late renewal dialogue detected; accepting renewal."); + clear_post_battle_dialogue(stream, context, BUTTON_A); + } else { + stream.log("[Lost Tower Repel] No renewal dialogue at the recorded stop; continuing."); + } + } + + const Move to_biker[] = { + {DPAD_DOWN, 1}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 3}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 6}, + // Clear the gravestone column before beginning the watched up step. + {DPAD_LEFT, 5}, + {DPAD_UP, 2}, + }; + for (const auto& move : to_biker) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + context.wait_for_all_requests(); + // The final up movement is one facing press and one movement press. + // Stop at a trainer prompt before any later path segment is queued. + constexpr int BIKER_UP_PRESSES = 2; + int biker_up_completed = 0; + // A trainer's walk-up can lock movement before the dialogue box appears. + ShortDialogWatcher corner_biker_dialogue(COLOR_YELLOW); + bool biker_challenged = wait_until(stream, context, 5s, {{corner_biker_dialogue}}) == 0; + ShortDialogDetector pending_biker_dialogue(COLOR_YELLOW); + while (!biker_challenged && biker_up_completed < BIKER_UP_PRESSES) { + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (!frame) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower biker: No frame before the next up press.", stream); + if (pending_biker_dialogue.detect(*frame.frame)) { + biker_challenged = true; + break; + } + + // A fresh 250ms dialogue watch cannot cancel the 80ms hold itself. + // Count that press, then preserve any subsequent unqueued presses. + ShortDialogWatcher approaching_biker(COLOR_YELLOW); + const int challenge = run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); + ctx.wait_for_all_requests(); + // Observe the walk-up before another up press can be queued. + pbf_wait(ctx, 5000ms); + }, {{approaching_biker}}); + biker_up_completed++; + if (challenge == 0) { + biker_challenged = true; + break; + } + context.wait_for_all_requests(); + } + if (biker_challenged) { + stream.log("[Lost Tower biker] Dialogue detected after " + + std::to_string(biker_up_completed) + " up presses; handling the trainer battle."); + fight_trainer_using_moves(stream, context, std::vector{2}, + "Lost Tower biker", false); + clear_post_battle_dialogue(stream, context, BUTTON_B); + + // The trainer conversation turns the player toward the biker. + // Restore north if its facing press was already used before the battle. + if (biker_up_completed > 0) { + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + context.wait_for_all_requests(); + } + for (; biker_up_completed < BIKER_UP_PRESSES; biker_up_completed++) { + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } + context.wait_for_all_requests(); + stream.log("[Lost Tower biker] North-facing approach complete; resuming the recorded path."); + } else { + stream.log("[Lost Tower biker] No dialogue; continuing the recorded path."); + } + + const Move to_floor4[] = { + {DPAD_UP, 1}, + {DPAD_LEFT, 2}, + {DPAD_UP, 4}, + {DPAD_LEFT, 6}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 3}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 3}, + }; + BlackScreenWatcher stairs_to_floor4; + if (run_until(stream, context, + [&](ProControllerContext& ctx) { + for (const auto& move : to_floor4) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, {{stairs_to_floor4}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 4: Stair transition did not start.", stream); + segment03_finish_transition(stream, context, "Lost Tower floor 4"); + OverworldWatcher floor4_ready; + if (wait_until(stream, context, 10s, {{floor4_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 4: Overworld not ready after stairs.", stream); + + const Move to_floor5[] = { + {DPAD_DOWN, 6}, + {DPAD_LEFT, 8}, + {DPAD_UP, 3}, + {DPAD_LEFT, 2}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 3}, + }; + BlackScreenWatcher stairs_to_floor5; + if (run_until(stream, context, + [&](ProControllerContext& ctx) { + for (const auto& move : to_floor5) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, {{stairs_to_floor5}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 5: Stair transition did not start.", stream); + segment03_finish_transition(stream, context, "Lost Tower floor 5"); + OverworldWatcher floor5_ready; + if (wait_until(stream, context, 10s, {{floor5_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 5: Overworld not ready after stairs.", stream); + + const Move to_rewards[] = { + {DPAD_DOWN, 4}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 3}, + }; + for (const auto& move : to_rewards) { + for (int i = 0; i < move.count; i++) { + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + context.wait_for_all_requests(); + // Use the Segment 01 conversation pattern: start once, then clear with B. + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 80ms, 100ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Lost Tower reward: First box", 30s); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + stream.log("[Lost Tower reward] Dialogue absent for four seconds; opening the Bag."); + if (!use_escape_rope(stream, context, 8)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower: Escape Rope exit was not confirmed.", stream); + // The exterior is visible before the Rope arrival camera finishes settling. + // Keep the following south/left movement out of that arrival animation. + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + OverworldWatcher rope_exit_ready; + if (wait_until(stream, context, 10s, {{rope_exit_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower: Overworld not ready after Escape Rope settling.", stream); context.wait_for_all_requests(); + stream.log("[Lost Tower] Reward dialogue cleared; Escape Rope exit settled.", COLOR_GREEN); } + void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, {0.000000, -1.000000}, 501ms, 0ms); - pbf_wait(context, 303ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 527ms, 0ms); - pbf_wait(context, 243ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 221ms, 0ms); - pbf_wait(context, 546ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1208ms, 0ms); - pbf_wait(context, 177ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 171ms, 0ms); - pbf_wait(context, 177ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2245ms, 0ms); - pbf_wait(context, 487ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 523ms, 0ms); - pbf_wait(context, 196ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 854ms, 0ms); - pbf_wait(context, 378ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 205ms, 0ms); - pbf_wait(context, 273ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 792ms, 0ms); - pbf_wait(context, 2437ms); - if (!heal_pokemon(stream,context,"Solaceon")) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Solaceon Center exit not confirmed.",stream); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 181ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -1.000000}, 192ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 448ms, 0ms); - pbf_wait(context, 13ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 811ms, 0ms); - pbf_wait(context, 49ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 549ms, 0ms); - pbf_wait(context, 391ms); - pbf_press_button(context, BUTTON_B, 175ms, 0ms); - pbf_wait(context, 222ms); - pbf_press_button(context, BUTTON_B, 141ms, 0ms); - pbf_wait(context, 781ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 180ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 38ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1070ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 17ms, 0ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 186ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 35ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 191ms, 0ms); - pbf_wait(context, 31ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 323ms, 0ms); - pbf_wait(context, 3187ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 795ms, 0ms); - pbf_wait(context, 72ms); - pbf_press_button(context, BUTTON_A, 178ms, 0ms); - pbf_wait(context, 540ms); - pbf_press_button(context, BUTTON_A, 159ms, 0ms); - pbf_wait(context, 655ms); - pbf_press_button(context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 1683ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 163ms, 0ms); - pbf_wait(context, 2372ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 160ms, 0ms); - pbf_wait(context, 454ms); - pbf_press_button(context, BUTTON_A, 165ms, 0ms); - pbf_wait(context, 1591ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 147ms, 0ms); - pbf_wait(context, 853ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 91ms, 0ms); - pbf_wait(context, 389ms); - pbf_press_button(context, BUTTON_A, 162ms, 0ms); - pbf_wait(context, 438ms); - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - pbf_wait(context, 106ms); - pbf_press_button(context, BUTTON_A, 144ms, 0ms); - pbf_wait(context, 287ms); - pbf_press_button(context, BUTTON_A, 169ms, 0ms); - pbf_wait(context, 534ms); - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - pbf_wait(context, 655ms); - pbf_press_button(context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 473ms); - pbf_press_button(context, BUTTON_B, 168ms, 0ms); - pbf_wait(context, 401ms); - pbf_press_button(context, BUTTON_B, 175ms, 0ms); - pbf_wait(context, 439ms); - pbf_press_button(context, BUTTON_B, 78ms, 0ms); - pbf_wait(context, 723ms); - pbf_press_button(context, BUTTON_B, 159ms, 0ms); - pbf_wait(context, 213ms); - pbf_press_button(context, BUTTON_B, 122ms, 0ms); - pbf_wait(context, 544ms); - pbf_press_button(context, BUTTON_B, 144ms, 0ms); - pbf_wait(context, 567ms); - pbf_press_button(context, BUTTON_B, 167ms, 0ms); - pbf_wait(context, 216ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1117ms, 0ms); - pbf_wait(context, 1924ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1610ms, 0ms); - pbf_wait(context, 189ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 5877ms, 0ms); - pbf_wait(context, 2351ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 383ms, 0ms); - pbf_wait(context, 321ms); + // Recorded Tower-to-Gym path; shared helpers handle prompts and battles. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 336ms, 0ms); + pbf_wait(context, 590ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 327ms, 0ms); + pbf_wait(context, 590ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 745ms, 0ms); - pbf_wait(context, 3127ms); - pbf_press_button(context, BUTTON_A, 213ms, 0ms); - pbf_wait(context, 144ms); - pbf_press_button(context, BUTTON_A, 184ms, 0ms); - pbf_wait(context, 550ms); - pbf_press_button(context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 122ms); - pbf_press_button(context, BUTTON_A, 188ms, 0ms); - pbf_wait(context, 130ms); - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - pbf_wait(context, 272ms); - pbf_press_button(context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 5334ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 159ms, 0ms); - pbf_wait(context, 204ms); - pbf_press_button(context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 698ms); - pbf_press_button(context, BUTTON_A, 195ms, 0ms); - pbf_wait(context, 230ms); - pbf_press_button(context, BUTTON_A, 169ms, 0ms); + pbf_wait(context, 379ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 77ms, 0ms); + pbf_wait(context, 17ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1167ms, 0ms); + pbf_wait(context, 1882ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 512ms, 0ms); + pbf_wait(context, 171ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 500ms, 0ms); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1701ms, 0ms); + pbf_press_button(context, BUTTON_PLUS, 181ms, 219ms); + // Stay north/south on the open road beside Lost Tower. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, true, true); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4384ms, 0ms); + pbf_wait(context, 1767ms); + pbf_press_button(context, BUTTON_PLUS, 181ms, 219ms); + pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1701ms, 0ms); + pbf_wait(context, 133ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1379ms, 0ms); + pbf_wait(context, 365ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1701ms, 0ms); + pbf_wait(context, 133ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 3179ms, 0ms); + pbf_wait(context, 76ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3172ms, 0ms); + pbf_wait(context, 28ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 4210ms, 0ms); + pbf_wait(context, 421ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2327ms, 0ms); + pbf_wait(context, 71ms); + // Expiry can interrupt this eastward leg or the following north turn. + // Renew on the clear strip, then repeat the recorded eastward alignment + // against the trees before taking the uninterrupted north leg. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2327ms, 0ms); + pbf_wait(context, 71ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1028ms, 0ms); + pbf_wait(context, 120ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2480ms, 0ms); pbf_wait(context, 773ms); - pbf_press_button(context, BUTTON_X, 212ms, 0ms); - pbf_wait(context, 1555ms); - pbf_press_button(context, BUTTON_X, 233ms, 0ms); - pbf_wait(context, 1255ms); - pbf_press_button(context, BUTTON_A, 173ms, 0ms); - pbf_wait(context, 1640ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 177ms, 0ms); - pbf_wait(context, 278ms); - pbf_press_button(context, BUTTON_A, 189ms, 0ms); - pbf_wait(context, 506ms); - pbf_press_button(context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 700ms); - pbf_press_button(context, BUTTON_A, 167ms, 0ms); - pbf_wait(context, 869ms); - pbf_press_button(context, BUTTON_B, 176ms, 0ms); - pbf_wait(context, 356ms); - pbf_press_button(context, BUTTON_B, 211ms, 0ms); - pbf_wait(context, 257ms); - pbf_press_button(context, BUTTON_B, 158ms, 0ms); - pbf_wait(context, 496ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1677ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, 1.000000}, 14ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2254ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2190ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 1ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 3839ms, 0ms); - pbf_wait(context, 98ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1963ms, 0ms); - pbf_wait(context, 13ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1099ms, 0ms); - pbf_wait(context, 235ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2766ms, 0ms); - pbf_wait(context, 40ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1652ms, 0ms); - pbf_wait(context, 30ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 839ms, 0ms); - pbf_wait(context, 98ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 725ms, 0ms); - pbf_wait(context, 77ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 824ms, 0ms); - pbf_wait(context, 356ms); - segment03_route_battle(stream,context,"Route 215 first trainer"); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2350ms, 0ms); - pbf_wait(context, 56ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2095ms, 0ms); - pbf_wait(context, 69ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 747ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 10ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1434ms, 0ms); - pbf_wait(context, 41ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1993ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 182ms, 0ms); + pbf_wait(context, 567ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2514ms, 0ms); + pbf_wait(context, 86ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 180ms, 0ms); + pbf_wait(context, 308ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 137ms, 0ms); + pbf_wait(context, 510ms); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 500ms, 0ms); + pbf_wait(context, 510ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 383ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 161ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 756ms, 0ms); + pbf_wait(context, 335ms); + // The clear cliff platform permits west/east renewal. Restore its east + // edge afterward so a partial expiry pulse cannot offset the bridge approach. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 756ms, 0ms); + pbf_wait(context, 335ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2176ms, 0ms); + pbf_wait(context, 24ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 826ms, 0ms); + pbf_wait(context, 561ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 957ms, 0ms); + pbf_wait(context, 621ms); + context.wait_for_all_requests(); + ShortDialogWatcher calvin_dialogue; + StartBattleDetector calvin_start(stream.overlay()); + BattleMenuWatcher calvin_menu(BattleType::TRAINER); + if (run_until(stream,context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx,{1.000000,0.000000},794ms,0ms); + pbf_wait(ctx,15000ms); + },{{calvin_dialogue},{calvin_start},{calvin_menu}})<0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Calvin: Trainer encounter did not start.",stream); + fight_trainer_using_moves(stream, context, std::vector{2}, + "Route 215 Ruin Maniac Calvin", false); + // Renew in the clear area after Calvin, before the next walking strip. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + // Cover at most one unfinished 500ms westward expiry pulse. + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1582ms, 0ms); + pbf_wait(context, 406ms); + // The item occupies lane 2. Search only lanes 4 and 3 (numbered from the top). + // Every D-pad press is observed separately: facing changes must not become + // an unverified extra step north. Fixed scenery measures eastward progress. + ShortDialogDetector jogger_dialogue; + BattleMenuDetector jogger_battle(BattleType::TRAINER, COLOR_GREEN); + BlackScreenDetector jogger_transition; + constexpr double lane_pitch=70.0/1080; + constexpr double lane_tolerance=1.0/54; + size_t missing_pose=0, stale_frames=0, stalled=0, lane_changes=0, lane_presses=0, arrival_frames=0, jogger_battles=0; + int changing_to=0; + bool east_pending=false, last_coarse=false, corner_ready=false; + double last_x=0; + Milliseconds bypass_movement=0ms; + WallClock fresh_after=current_time(), last_frame_time=WallClock::min(); + auto bypass_started=std::chrono::steady_clock::now(); + for (size_t observation=0;observation<120;observation++){ + context.throw_if_cancelled(); + context.wait_for_all_requests(); + if (std::chrono::steady_clock::now()-bypass_started>90s) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Lane search timed out.",stream); + const auto frame=stream.video().snapshot(); + if (!frame) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: No video frame.",stream); + if (frame.timestamp<=fresh_after || frame.timestamp<=last_frame_time){ + if (++stale_frames>=8) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Video is not advancing after movement.",stream); + pbf_wait(context,500ms); + continue; + } + last_frame_time=frame.timestamp;stale_frames=0; + if (jogger_dialogue.detect(*frame.frame) || jogger_battle.detect(*frame.frame)){ + if (++jogger_battles>1) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Repeated unexpected encounter.",stream); + // The recorded failure is the jogger's trainer introduction. + // Complete it with the shared move-list handler, then measure + // the current lane again; the trainer may have approached us. + fight_trainer_using_moves(stream,context,std::vector{2}, + "Route 215 jogger",false); + context.wait_for_all_requests();fresh_after=current_time(); + last_frame_time=WallClock::min(); + missing_pose=0;stale_frames=0;stalled=0;arrival_frames=0; + changing_to=0;lane_presses=0;east_pending=false; + bypass_started=std::chrono::steady_clock::now(); + continue; + } + if (jogger_transition.detect(*frame.frame)) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Unexpected transition.",stream); + const auto pose=match_route215_jogger_corner(*frame.frame); + for (const auto& anchor : pose.landmarks) + stream.log("[Route 215 jogger] "+anchor.name+" score="+std::to_string(anchor.score)+ + ", margin="+std::to_string(anchor.margin)+", accepted="+std::to_string(anchor.accepted)+ + ", offset=("+std::to_string(anchor.dx)+","+std::to_string(anchor.dy)+")"); + const int lane=pose.located && std::abs(pose.dy)<=lane_tolerance ? 4 : + pose.located && std::abs(pose.dy-lane_pitch)<=lane_tolerance ? 3 : 0; + stream.log("[Route 215 jogger] lane="+std::to_string(lane)+ + ", coarse="+std::to_string(pose.coarse)+", offset=("+ + std::to_string(pose.dx)+","+std::to_string(pose.dy)+")"); + if (!lane){ + // Wait out rain/clouds or a settling step; never search vertically + // from a missing or ambiguous pose, including after a blocked step. + arrival_frames=0; + if (++missing_pose>=8) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Cannot verify lane 3 or 4.",stream); + pbf_wait(context,500ms); + continue; + } + missing_pose=0; + if (east_pending){ + stalled=pose.coarse==last_coarse && last_x-pose.dx<1.0/256 ? stalled+1 : 0; + east_pending=false; + } + if (changing_to && lane==changing_to){ + changing_to=0;lane_presses=0;stalled=0; + } + const bool near_corner=!pose.coarse && std::abs(pose.dx)<=1.0/128; + if (near_corner && lane==4 && !changing_to){ + if (++arrival_frames<3){pbf_wait(context,250ms);continue;} + force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); + // Expiry preserves lane 4. Cover its possible unfinished 500ms + // west pulse, then settle east against the fence before going south. + pbf_move_left_joystick(context,{1.0,0.0},1000ms,0ms); + context.wait_for_all_requests(); + corner_ready=true; + break; + } + arrival_frames=0; + if (!changing_to && ((near_corner && lane==3) || stalled>=2)){ + if (++lane_changes>6) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Both safe lanes remain blocked.",stream); + changing_to=lane==4 ? 3 : 4; + lane_presses=0;stalled=0; + pbf_wait(context,750ms); + continue; + } + const auto movement_limit=8000ms; + if (changing_to){ + if (lane_presses>=2){ + // A blocked lane change leaves us in the observed lane. + // Wait for the jogger, then try east again without assuming a step. + changing_to=0;lane_presses=0; + pbf_wait(context,750ms); + continue; + } + if (bypass_movement+80ms>movement_limit) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Movement budget exhausted.",stream); + pbf_press_dpad(context,changing_to==3 ? DPAD_UP : DPAD_DOWN,80ms,300ms); + context.wait_for_all_requests();fresh_after=current_time(); + bypass_movement+=80ms;++lane_presses; + continue; + } + if (!pose.coarse && pose.dx < -1.0/32) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Passed the expected east corner.",stream); + const auto duration=!pose.coarse && std::abs(pose.dx)<=1.0/24 ? 40ms : 160ms; + if (bypass_movement+duration>movement_limit) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Movement budget exhausted.",stream); + last_x=pose.dx;last_coarse=pose.coarse; + // Only a small westward correction is allowed at the verified corner. + pbf_move_left_joystick(context,{!pose.coarse && pose.dx<0 ? -1.0 : 1.0,0.0},duration,0ms); + pbf_wait(context,400ms); + context.wait_for_all_requests();fresh_after=current_time(); + bypass_movement+=duration;east_pending=true; + } + if (!corner_ready) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: East corner not confirmed.",stream); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1682ms, 0ms); + pbf_wait(context, 501ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1843ms, 0ms); pbf_wait(context, 55ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1542ms, 0ms); - pbf_wait(context, 192ms); - pbf_press_button(context, BUTTON_A, 174ms, 0ms); - pbf_wait(context, 841ms); - pbf_press_button(context, BUTTON_A, 184ms, 0ms); - pbf_wait(context, 549ms); - pbf_press_button(context, BUTTON_A, 177ms, 0ms); - pbf_wait(context, 261ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 541ms, 0ms); - pbf_wait(context, 6ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 562ms, 0ms); - pbf_wait(context, 42ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 520ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 77ms, 0ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 166ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 37ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 647ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 15ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1062ms, 0ms); - pbf_wait(context, 197ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1426ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2431ms, 0ms); + pbf_wait(context, 723ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2075ms, 0ms); + pbf_wait(context, 243ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 500ms, 0ms); + pbf_wait(context, 75ms); + // The corner-to-bridge leg stays below the regular Repel movement budget. + force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); + pbf_wait(context, 1767ms); + // Expiry stays on the clear upper strip. The recorded east crossing + // takes 2392ms; 3000ms also covers an unfinished 500ms west expiry pulse. + // The eastern wall fixes the starting column for Derek's south approach. + pbf_move_left_joystick(context,{1.0,0.0},3000ms,0ms); + pbf_wait(context, 43ms); + context.wait_for_all_requests(); + ShortDialogWatcher derek_dialogue; + StartBattleDetector derek_start(stream.overlay()); + BattleMenuWatcher derek_menu(BattleType::TRAINER); + if (run_until(stream,context, + [](ProControllerContext& ctx){ + // The uninterrupted 1000ms approach ends one step into grass. + // Stop one recorded step earlier on Derek's sightline while + // the existing watchers wait for him to turn toward the player. + pbf_move_left_joystick(ctx,{0.000000,-1.000000},850ms,0ms); + pbf_wait(ctx,15000ms); + },{{derek_dialogue},{derek_start},{derek_menu}})<0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Derek: Trainer encounter did not start.",stream); + fight_trainer_using_moves(stream, context, std::vector{2}, + "Route 215 Black Belt Derek", false); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1929ms, 0ms); pbf_wait(context, 5ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 676ms, 0ms); - pbf_wait(context, 9ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2523ms, 0ms); - pbf_wait(context, 471ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 378ms, 0ms); - pbf_wait(context, 488ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 943ms, 0ms); - pbf_wait(context, 272ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 127ms, 0ms); - pbf_wait(context, 380ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 920ms, 0ms); - pbf_wait(context, 92ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1115ms, 0ms); - pbf_wait(context, 16ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 144ms, 0ms); - pbf_wait(context, 480ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 613ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 10ms, 0ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 320ms, 0ms); - pbf_wait(context, 56ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 778ms, 0ms); - pbf_wait(context, 476ms); - segment03_route_battle(stream,context,"Route 215 Ace Trainers"); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 4051ms, 0ms); - pbf_wait(context, 1256ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1047ms, 0ms); - pbf_wait(context, 503ms); - pbf_press_button(context, BUTTON_B, 189ms, 0ms); - pbf_wait(context, 222ms); - pbf_press_button(context, BUTTON_B, 183ms, 0ms); - pbf_wait(context, 637ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1824ms, 0ms); - pbf_wait(context, 1256ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2226ms, 0ms); - pbf_wait(context, 891ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1678ms, 0ms); - pbf_wait(context, 70ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1496ms, 0ms); - pbf_wait(context, 54ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1179ms, 0ms); - pbf_wait(context, 21ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2448ms, 0ms); - pbf_wait(context, 54ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1574ms, 0ms); - pbf_wait(context, 58ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 3675ms, 0ms); - pbf_wait(context, 93ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 666ms, 0ms); - pbf_wait(context, 69ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1757ms, 0ms); - pbf_wait(context, 57ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 3227ms, 0ms); - pbf_wait(context, 4788ms); - pbf_press_button(context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 388ms); - pbf_press_button(context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 176ms); - pbf_press_button(context, BUTTON_A, 172ms, 0ms); - pbf_wait(context, 509ms); - pbf_press_button(context, BUTTON_A, 198ms, 0ms); - pbf_wait(context, 255ms); - pbf_press_button(context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 408ms); - pbf_press_button(context, BUTTON_A, 212ms, 0ms); - pbf_wait(context, 189ms); - pbf_press_button(context, BUTTON_A, 166ms, 0ms); - pbf_wait(context, 458ms); - pbf_press_button(context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 3630ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 744ms, 0ms); - // The recording ends outside the gym door, facing north. - segment03_doorway(stream,context,true,1200ms,"Veilstone Gym entrance"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2195ms, 0ms); + pbf_wait(context, 88ms); + // Renew on the clear landing below the stairs, then restore its column. + force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); + align_to_overworld_landmarks(stream,context,match_route215_derek_stairs_renewal, + "Route 215 below Derek stairs after renewal",false,true); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 501ms, 0ms); + pbf_wait(context, 390ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 954ms, 0ms); + pbf_wait(context, 310ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1148ms, 0ms); + pbf_wait(context, 453ms); + // Stay west/east on the clear road row; south would enter tall grass. + force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); + align_to_overworld_landmarks(stream,context,match_route215_derek_stair_top_renewal, + "Route 215 above Derek stairs after renewal",false,true); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1855ms, 0ms); + pbf_wait(context, 171ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1385ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2478ms, 0ms); + pbf_wait(context, 355ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 580ms, 0ms); + pbf_wait(context, 392ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 978ms, 0ms); + pbf_wait(context, 201ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1159ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 535ms, 0ms); + pbf_wait(context, 234ms); + // West/east follows the clear strip south of the grass before the Ace Trainers. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_wait(context, 1767ms); + // Resume from the road/fence pose shown at renewal in the recording. + // A fixed leftover west input compounds the variable expiry offset. + align_to_overworld_landmarks(stream,context,match_route215_ace_renewal, + "Route 215 before Ace Trainers after renewal",false,true); + context.wait_for_all_requests(); + ShortDialogWatcher dennis_dialogue; + StartBattleDetector dennis_start(stream.overlay()); + BattleMenuWatcher dennis_menu(BattleType::TRAINER); + if (run_until(stream,context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx,{0.000000,-1.000000},1323ms,0ms); + pbf_wait(ctx,106ms); + pbf_move_left_joystick(ctx,{1.000000,0.000000},834ms,0ms); + pbf_wait(ctx,206ms); + pbf_move_left_joystick(ctx,{0.000000,1.000000},221ms,0ms); + pbf_wait(ctx,516ms); + pbf_move_left_joystick(ctx,{1.000000,0.000000},667ms,0ms); + pbf_wait(ctx,15000ms); + },{{dennis_dialogue},{dennis_start},{dennis_menu}})<0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Dennis: Trainer encounter did not start.",stream); + // Dennis and Maya are separate consecutive battles. + fight_trainer_using_moves(stream, context, std::vector{2}, + "Route 215 Ace Trainer Dennis", false); + fight_trainer_using_moves(stream, context, std::vector{2}, + "Route 215 Ace Trainer Maya", false); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 10884ms, 0ms); + pbf_wait(context, 48ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2314ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 26ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2251ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 27ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1382ms, 0ms); + pbf_wait(context, 29ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1035ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -1.000000}, 75ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); + // Veilstone needs no Repel. Clear the remaining expiry without renewal + // on the paved north/south strip before normalizing the city approach. + force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); + align_to_overworld_landmarks(stream,context,match_veilstone_city_passage, + "Veilstone passage after expiry",false,true,true); + // Replay the successful street approach, then correct onto the western + // stair lane before climbing. Do not use the NPC pair as a wall stop. + pbf_move_left_joystick(context,{0.0,-1.0},1342ms,0ms); + pbf_move_left_joystick(context,{-1.0,0.0},1404ms,0ms); + pbf_move_left_joystick(context,{0.0,-1.0},368ms,0ms); + pbf_move_left_joystick(context,{-1.0,-1.0},90ms,0ms); + pbf_move_left_joystick(context,{-1.0,0.0},1458ms,0ms); + // Expose the western stair base with the first 200ms of the recorded + // north leg; correct its lane before continuing the remaining ascent. + pbf_move_left_joystick(context,{0.0,1.0},200ms,0ms); + context.wait_for_all_requests(); + align_to_overworld_landmarks(stream,context,match_veilstone_dawn_stairs, + "Veilstone western stair approach",false,true); + stream.log("[Veilstone approach] Following the western stairs toward Dawn."); + ShortDialogWatcher dawn_dialogue; + if (run_until(stream,context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx,{0.0,1.0},3023ms,0ms); + pbf_press_button(ctx,BUTTON_A,80ms,100ms); + // Dawn's turn, walk and camera cut precede the first box. + // Keep watching through the cutscene; cancel as soon as it opens. + pbf_wait(ctx,15000ms); + },{{dawn_dialogue}})!=0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Dawn: Dialogue did not start from the west stair approach.",stream); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + + // The new recording already enters the Gym. Cancel at its first fade. + context.wait_for_all_requests(); + BlackScreenWatcher gym_entry; + if (run_until(stream, context, + [](ProControllerContext& ctx) { + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1954ms, 0ms); + pbf_wait(ctx, 223ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 110ms, 0ms); + pbf_wait(ctx, 190ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 910ms, 0ms); + pbf_wait(ctx, 3000ms); + }, {{gym_entry}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Gym: Entrance transition did not start.", stream); + segment03_finish_transition(stream, context, "Veilstone Gym entrance"); + // The first visit pans across the Gym before returning to the entrance. + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + OverworldWatcher gym_ready; + if (wait_until(stream, context, 10s, {{gym_ready}}) != 0) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Gym: Overworld not ready after the entrance scene.", stream); context.wait_for_all_requests(); } @@ -1920,19 +2187,6 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ {DPAD_UP, 8, 0ms}, }; - const Move sequence6[] = { - {DPAD_DOWN, 8, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 3000ms}, - {DPAD_DOWN, 6, 0ms}, - }; - for (const auto& move : sequence1){ for (int i = 0; i < move.count; i++){ pbf_press_dpad(context, move.dir, 80ms, 300ms); @@ -1945,7 +2199,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 01 (Black Belt Rafael). pbf_press_button(context,BUTTON_A,150ms,300ms); - segment03_route_battle(stream,context,"Black Belt Rafael (Flamethrower)"); + fight_trainer_using_moves(stream, context, 2, "Black Belt Rafael", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -1961,7 +2215,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 02 (Black Belt Jeffrey). pbf_press_button(context,BUTTON_A,150ms,300ms); - segment03_route_battle(stream,context,"Black Belt Jeffrey (Flamethrower)"); + fight_trainer_using_moves(stream, context, 2, "Black Belt Jeffrey", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -1977,7 +2231,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 03 (Black Belt Darren). pbf_press_button(context,BUTTON_A,150ms,300ms); - segment03_route_battle(stream,context,"Black Belt Darren (Flamethrower)"); + fight_trainer_using_moves(stream, context, 2, "Black Belt Darren", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -1993,7 +2247,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 04 (Black Belt Colby). pbf_press_button(context,BUTTON_A,150ms,300ms); - segment03_route_battle(stream,context,"Black Belt Colby (Flamethrower)"); + fight_trainer_using_moves(stream, context, 1, "Black Belt Colby", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2009,26 +2263,19 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate the Gym battle. pbf_press_button(context,BUTTON_A,150ms,300ms); - segment03_route_battle(stream,context,"Maylene (Flamethrower)",true); + fight_trainer_using_moves(stream, context, 1, "Maylene", true); - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + // Clear badge and reward dialogue before handing off inside the Gym. + // Match Gardenia's quiet period so gaps between rewards do not end cleanup. + stream.log("[Veilstone Gym puzzle] Shared battle cleanup returned; clearing remaining Maylene rewards with B."); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); + stream.log("[Veilstone Gym puzzle] Maylene post-battle dialogue cleared; remaining here for Segment 04."); - for (const auto& move : sequence6){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay.count() > 0){ - pbf_wait(context, move.delay); - } - } - } - - //Exits Buiilding - pbf_wait(context, 1000ms); } std::string AutoStory_Segment_03::name() const{ return "03: Second Badge to Third Badge"; } std::string AutoStory_Segment_03::start_text() const{ return "Start: Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Segment_03::end_text() const{ return "End: Third Gym Badge acquired (Veilstone City)."; } +std::string AutoStory_Segment_03::end_text() const{ return "End: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } void AutoStory_Segment_03::run_segment( SingleSwitchProgramEnvironment& env, @@ -2184,9 +2431,9 @@ void Checkpoint_0304( true ); } -std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - Hearthome and Lost Tower"; } +std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - Hearthome to Lost Tower"; } std::string AutoStory_Checkpoint_0305::start_text() const { return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } -std::string AutoStory_Checkpoint_0305::end_text() const { return "At the Lost Tower departure position after the tower gifts and recorded return path."; } +std::string AutoStory_Checkpoint_0305::end_text() const { return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; } void AutoStory_Checkpoint_0305::run_checkpoint( SingleSwitchProgramEnvironment& env, @@ -2206,9 +2453,19 @@ void Checkpoint_0305( ) { checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { + // Restore night lighting on every attempt, including after a reset. + context.wait_for_all_requests(); + env.console.log("[Route 209 locator setup] Setting 22:00; preserving the current date."); + DateTime locator_time; + locator_time.hour = 22; + locator_time.minute = 0; + locator_time.second = 0; + set_time_fast(env, context, locator_time, true); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); // Recorded route with feedback battle handling. heartholme_to_lost_tower(env.console, context); - lost_tower(env.console, context); + // Stop here. Checkpoint 03.06 saves outside and owns the Tower visit. }, @@ -2217,7 +2474,7 @@ void Checkpoint_0305( } std::string AutoStory_Checkpoint_0306::name() const { return "03.06 - Lost Tower to Veilstone"; } -std::string AutoStory_Checkpoint_0306::start_text() const { return "At the Lost Tower departure position after the tower gifts and recorded return path."; } +std::string AutoStory_Checkpoint_0306::start_text() const { return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; } std::string AutoStory_Checkpoint_0306::end_text() const { return "Inside Veilstone Gym at the entrance, facing north."; } void AutoStory_Checkpoint_0306::run_checkpoint( @@ -2238,16 +2495,27 @@ void Checkpoint_0306( ) { checkpoint_reattempt_loop(env, context, options, stats, [&](size_t /*attempt*/) { - // Recorded route with feedback battle handling. + // Restore night before the route on every attempt, including resets. + context.wait_for_all_requests(); + env.console.log("[Route 215 locator setup] Setting 22:00; preserving the current date."); + DateTime locator_time; + locator_time.hour = 22; + locator_time.minute = 0; + locator_time.second = 0; + set_time_fast(env, context, locator_time, true); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + // Save/retry begins outside the Tower, at the end of 03.05. + lost_tower(env.console, context); lost_tower_to_veilstone_gym(env.console, context); }, true ); } -std::string AutoStory_Checkpoint_0307::name() const { return "03.07 - Inside Gym 3"; } -std::string AutoStory_Checkpoint_0307::start_text() const { return "Inside Veilstone Gym at the entrance, facing north."; } -std::string AutoStory_Checkpoint_0307::end_text() const { return "Third Gym Badge acquired (Veilstone City)."; } +std::string AutoStory_Checkpoint_0307::name() const { return "03.07 - Veilstone Gym (start inside at entrance)"; } +std::string AutoStory_Checkpoint_0307::start_text() const { return "Start inside Veilstone Gym at the entrance, facing north."; } +std::string AutoStory_Checkpoint_0307::end_text() const { return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } void AutoStory_Checkpoint_0307::run_checkpoint( SingleSwitchProgramEnvironment& env, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index db8db47593..0ad15a41a7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -28,7 +28,7 @@ namespace PokemonBDSP{ std::string AutoStory_Segment_04::name() const{ return "04: Third Badge to Fourth Badge"; } -std::string AutoStory_Segment_04::start_text() const{ return "Start: Third Gym Badge acquired (Hearthome City)."; } +std::string AutoStory_Segment_04::start_text() const{ return "Start: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } std::string AutoStory_Segment_04::end_text() const{ return "End: Fourth Gym Badge acquired (Pastoria City)."; } @@ -60,6 +60,7 @@ void AutoStory_Segment_04::run_segment( AutoStory_Checkpoint_0415().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0416().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0417().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0418().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); @@ -67,14 +68,14 @@ void AutoStory_Segment_04::run_segment( std::string AutoStory_Checkpoint_0401::name() const{ return "04.01 - leave_gym"; } -std::string AutoStory_Checkpoint_0401::start_text() const{ return "Inside Hearthome City gym after third badge."; } -std::string AutoStory_Checkpoint_0401::end_text() const{ return "Exited Hearthome City gym."; } +std::string AutoStory_Checkpoint_0401::start_text() const{ return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } +std::string AutoStory_Checkpoint_0401::end_text() const{ return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; } void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ checkpoint_0401(env, context, options, stats); } std::string AutoStory_Checkpoint_0402::name() const{ return "04.02 - go_to_veilstone_pokemon_center"; } -std::string AutoStory_Checkpoint_0402::start_text() const{ return "Outside Hearthome City gym."; } +std::string AutoStory_Checkpoint_0402::start_text() const{ return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; } std::string AutoStory_Checkpoint_0402::end_text() const{ return "Entered Veilstone City Pokemon Center."; } void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ checkpoint_0402(env, context, options, stats); @@ -180,11 +181,28 @@ void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& e std::string AutoStory_Checkpoint_0417::name() const{ return "04.17 - go_to_pastoria_gym"; } std::string AutoStory_Checkpoint_0417::start_text() const{ return "Outside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_0417::end_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_Checkpoint_0417::end_text() const{ return "Inside Pastoria Gym at the entrance, facing north."; } void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ checkpoint_0417(env, context, options, stats); } +std::string AutoStory_Checkpoint_0418::name() const{ return "04.18 - Pastoria Gym Puzzle"; } +std::string AutoStory_Checkpoint_0418::start_text() const{ return "Inside Pastoria Gym at the entrance, facing north."; } +std::string AutoStory_Checkpoint_0418::end_text() const{ return "Not implemented: Pastoria Gym puzzle and Crasher Wake battle."; } +void AutoStory_Checkpoint_0418::run_checkpoint( + SingleSwitchProgramEnvironment&, + ProControllerContext& context, + AutoStoryOptions, + AutoStoryStats& +) const{ + context.wait_for_all_requests(); + // Stop before any movement until the puzzle and leader battle are implemented. + throw OperationFailedException( + ErrorReportMode::NO_ERROR_REPORT, + "04.18 - Pastoria Gym Puzzle is not implemented. Stopped inside the Gym." + ); +} + static void move_back_after_unexpected_battle( VideoStream& stream, ProControllerContext& context, @@ -229,19 +247,19 @@ static bool handle_battle( trainerid == "route_213_tuber_jared" or trainerid == "route_213_beauty_cindy" ) { - // Select Razor Leaf + // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( trainerid == "route_214_collector_brady" or trainerid == "route_214_collector_douglas" ){ - // Select Bulldoze + // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( trainerid == "route_214_psychic_abigail" or trainerid == "route_214_psychic_mitchell" ){ - // Select Crunch + // Keep the current battle-move selection. } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h index abbd1503a5..4cece07401 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -267,6 +267,19 @@ class AutoStory_Checkpoint_0417 : public AutoStory_Checkpoint{ void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +class AutoStory_Checkpoint_0418 : public AutoStory_Checkpoint{ +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats + ) const override; +}; + } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index e8f8afa606..456eab9845 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -231,7 +231,7 @@ static bool handle_battle( trainerid == "route_213_beauty_cindy" or trainerid == "route_210_veteran_brian" ) { - // Select Razor leaf + // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( @@ -240,7 +240,7 @@ static bool handle_battle( trainerid == "route_210_breeder_amber" or trainerid == "route_210_blackbelt_adam" ){ - // Select Bulldoze + // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( @@ -248,7 +248,7 @@ static bool handle_battle( trainerid == "route_210_birdkeeper_brianna" or trainerid == "celestic_town_galactic_grunt" ){ - // Select Crunch + // Keep the current battle-move selection. } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index c9d0361ee1..630a572d77 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -171,12 +171,12 @@ static bool handle_battle( if ( trainerid == "route_218_sailor_skyler" ) { - // Select Razor leaf + // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( trainerid == "canalave_clint" ) { - // Select Crunch + // Keep the current battle-move selection. } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index e413755de3..1750d193d7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -246,19 +246,19 @@ static bool handle_battle( trainerid == "verity_lakefront_grunt_1" or trainerid == "verity_lakefront_grunt_3" ) { - // Select Bulldoze + // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( trainerid == "route_217_ace_dalton" ) { - // Select Razor leaf + // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( trainerid == "verity_lakefront_grunt_2" or trainerid == "verity_lakefront_grunt_4" or trainerid == "verity_lakefront_commander_mars" ) { - // Select Crunch + // Keep the current battle-move selection. } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index c567b2bdb9..a50bed240e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -171,14 +171,14 @@ static bool handle_battle( trainerid == "galactic_hq_grunt_4" or trainerid == "galactic_hq_grunt_5" ) { - // Select Bulldoze + // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( trainerid == "galactic_hq_grunt_1" or trainerid == "galactic_hq_double_grunts_1" or trainerid == "route_222_sailor_luther" ) { - // Select Razor leaf + // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( trainerid == "veilstone_warehouse_warehouse_grunt_1" or @@ -189,7 +189,7 @@ static bool handle_battle( trainerid == "galactic_hq_commander_saturn" or trainerid == "route_207_hiker_justin" ) { - // Select Crunch + // Keep the current battle-move selection. } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 206b90bc18..7e45b5a8b4 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -106,7 +106,7 @@ static bool handle_battle( trainerid == "victory_road_dragontamer_andre" or trainerid == "victory_road_veteran_edgar" ) { - // Select Bulldoze + // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); } else if ( trainerid == "route_223_swimmer_miranda" or @@ -117,14 +117,14 @@ static bool handle_battle( trainerid == "victory_road_ace_sydney" or trainerid == "victory_road_psychic_valencia" ) { - // Select Razor leaf + // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); } else if ( trainerid == "victory_road_ace_omar" or trainerid == "victory_road_veteran_clayton" or trainerid == "victory_road_dragontamer_clinton" ) { - // Select Crunch + // Keep the current battle-move selection. } context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp index b039bb81e8..16c75ff79c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -230,7 +230,7 @@ void handle_battle_with_outside_help( bool battle_active = true; while (battle_active){ - // Wait for Torterra's battle menu + // Wait for the lead Pokemon's battle menu { BattleMenuWatcher menu(BattleType::TRAINER); EndBattleWatcher end; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h index 94dacd0804..76850c624d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h @@ -66,7 +66,7 @@ void handle_unexpected_battle( // Outside-help battle handler // --------------------------------------------------------------------------- -// Handle a scripted trainer battle where a human helper (Torterra lv100) joins. +// Handle a scripted trainer battle where the configured human helper joins. // Each turn it: // 1. OCRs current PP for all 4 configured moves. // 2. Calculates damage against all on-field opponents. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 65d63e11e0..9c6cdf9111 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1246,6 +1246,11 @@ namespace PokemonAutomation { ShortDialogDetector dialogue(COLOR_YELLOW); BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); size_t confirmations = 0, missing = 0; + bool ace_bypass = false; + const bool route215_renewal=matcher==match_route215_bridge_renewal || + matcher==match_route215_ace_renewal || matcher==match_route215_derek_stairs_renewal || + matcher==match_route215_derek_stair_top_renewal; + Milliseconds correction_movement=0ms; for (size_t attempt = 0; attempt < 80; attempt++) { const auto frame = stream.video().snapshot(); if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, @@ -1291,6 +1296,18 @@ namespace PokemonAutomation { stream.log("[" + label + "] located=" + std::to_string(result.located) + ", coarse=" + std::to_string(result.coarse) + ", at_target=" + std::to_string(result.at_target) + ", consensus offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); + // The blue-haired NPC patrols the narrow eastern row. Shift + // one tile north on the clear road before crossing west; + // return to the original row only after clearing his column. + if (matcher==match_route215_ace_renewal && result.located){ + if (result.dx< -1.0/12) ace_bypass=true; + else if (result.dx>=-1.0/32) ace_bypass=false; + } + const bool ace_row_recovery=ace_bypass && + std::abs(result.dy-70.0/1080)>1.0/108; + const bool ace_central_row=matcher==match_route215_ace_renewal && + !ace_bypass && result.coarse && std::abs(result.dx)<=1.0/12 && + std::abs(result.dy)>1.0/54; const double entry_confidence=moderate_entry_confidence>0 ? overworld_entry_confidence(result) : 0; const bool moderate_arrival=moderate_entry_confidence>0 && entry_confidence>=moderate_entry_confidence; if(moderate_entry_confidence>0) stream.log("["+label+"] Exterior entry confidence="+ @@ -1356,14 +1373,18 @@ namespace PokemonAutomation { // Fine correction changes axis priority, never arrival gates. const bool stair_fine_correction=matcher==match_route208_stair_top && !result.coarse && std::abs(result.dx)<=1.0/64; - const bool horizontal = !contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && + const bool horizontal = !ace_row_recovery && !ace_central_row && (ace_bypass || + (!contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && ((horizontal_first && !stair_fine_correction && std::abs(result.dx)>1.0/128) || - std::abs(result.dx)>=std::abs(result.dy)*9.0/16))); + std::abs(result.dx)>=std::abs(result.dy)*9.0/16))))); if (stair_fine_correction && horizontal_first && !horizontal && std::abs(result.dx)>1.0/128) stream.log("["+label+"] Near stair column; correcting the larger vertical error first."); - if (coarse_horizontal) stream.log("[" + label + + if (ace_row_recovery) stream.log("["+label+"] Clearing the NPC row before crossing west."); + else if (ace_central_row) stream.log("["+label+"] Both road bends support restoring the arrival row."); + else if (coarse_horizontal && horizontal) stream.log("[" + label + "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); - const double error = horizontal ? result.dx : result.dy*9.0/16; + const double error = horizontal ? result.dx : + (result.dy-(ace_bypass ? 70.0/1080 : 0.0))*9.0/16; auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 1.0/64 ? 40ms : 16ms; // A 16ms turn may not complete a step. Use a bounded // full correction while reacquiring the second plaza anchor. @@ -1381,17 +1402,33 @@ namespace PokemonAutomation { label+": Correction would approach the stair boundary.",stream); } } + if (route215_renewal){ + if (correction_movement+duration>1500ms) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label+": Renewal alignment movement budget exhausted.",stream); + correction_movement+=duration; + } pbf_move_left_joystick(context, {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, - horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0)}, duration, 0ms); + horizontal ? 0.0 : (error < 0 ? 1.0 : -1.0)}, duration, 0ms); pbf_wait(context, 500ms); } else { confirmations = 0; - if (matcher==match_hearthome_center_entrance || matcher==match_hearthome_contest_fantina){ + if (matcher==match_hearthome_center_entrance || matcher==match_hearthome_contest_fantina || + matcher==match_veilstone_city_passage || matcher==match_veilstone_dawn_stairs || + route215_renewal){ // Do not sweep into a different doorway or around the Hall's - // side when a cloud, NPC or fade hides the required anchors. + // side, or onto a Veilstone terrace, while required anchors are hidden. if (++missing>20) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, label+": Scene anchors missing; stationary reacquisition exhausted.",stream); + // Recorded expiry endpoints can be north of the passage. + // Acquire its buildings along the clear south-only strip; + // cap total search motion, then remain stationary. + if (matcher==match_veilstone_city_passage && search_down_first && + correction_movement<1800ms){ + pbf_move_left_joystick(context,{0.0,-1.0},100ms,0ms); + correction_movement+=100ms; + } pbf_wait(context,500ms); context.wait_for_all_requests(); continue; @@ -1473,16 +1510,16 @@ namespace PokemonAutomation { } void fight_tristan(VideoStream& stream, ProControllerContext& context) { - // Chimchar learns Ember into slot 3 at level 6. Confirm the move - // cursor visually; missing slot 3 fails instead of using Scratch. - fight_using_move_slot(stream, context, BattleType::TRAINER, 3, "Tristan (Ember)"); + // All starters use move slot 3. Confirm its cursor position + // visually without reading the move name; fail if slot 3 is absent. + fight_trainer_using_move_slot(stream, context, 3, "Tristan (move slot 3)"); } void battle_1_pokemon(VideoStream& stream, ProControllerContext& context) { stream.log("[Training] Finding one wild Pokemon using the catch encounter wiggle.", COLOR_BLUE); encounter_one_wild_pokemon(stream, context); - // Use the same battle loop as Tristan, but Scratch is slot 1. - fight_using_move_slot(stream, context, BattleType::STANDARD, 1, "Training (Scratch)"); + // Use the same battle loop as Tristan with move slot 1. + fight_using_move_slot(stream, context, BattleType::STANDARD, 1, "Training (move slot 1)"); stream.log("[Training] Fight and level-up cleanup complete; verifying Potion healing."); use_potion_first_pokemon(stream, context); context.wait_for_all_requests(); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index 3c0b2e40a1..b7c1f7e5ca 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -81,7 +81,7 @@ namespace PokemonAutomation { ProControllerContext& context ); - // One wild encounter using Scratch, followed by verified Potion healing. + // One wild encounter using move slot 1, followed by verified Potion healing. void battle_1_pokemon(VideoStream& stream, ProControllerContext& context); // Burst wiggle variant of catch_1_pokemon for testing. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index c85d8734c3..bb9219551d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -1,5 +1,6 @@ #include "CommonFramework/GlobalAutoPaths.h" #include +#include #include #include #include "CommonFramework/Globals.h" @@ -536,6 +537,119 @@ OverworldMatchResult match_route208_lower_stair_lane(const ImageRGB32& frame){ } return best; } +OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame){ + // butters' marked sign frame and rock pair; the cloud-sensitive pillar is excluded. + // Only the sign's left frame/post is used; lettering is excluded. + OverworldMatchResult best; + double best_score=-1; + // Lighting samples are translated back to the original marked target. + // Appearance 1 remains acquisition-only; all others describe that target. + static const std::array appearances[]={ + {"PokemonBDSP/Autostory/Route209Repel/SignFrame.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair.png"}, + {"PokemonBDSP/Autostory/Route209Repel/EastSignFrame.png", + "PokemonBDSP/Autostory/Route209Repel/EastRockPair.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H04F1.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H04F1.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H18F4.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H18F4.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F1.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F1.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F3.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F3.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F4.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F4.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F4.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F4.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F5.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F5.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F2.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F2.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F3.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F3.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F4.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F4.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F5.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F1.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F1.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F2.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F2.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F3.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F3.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F4.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F4.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H03F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H03F5.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F1.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F1.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F5.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H13F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H13F5.png"}, + {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H14F5.png", + "PokemonBDSP/Autostory/Route209Repel/RockPair-H14F5.png"}, + }; + for (double scale : {1.0,.95,1.05,.90,1.10}){ + std::vector lighting_landmarks(2); + for (size_t appearance=0;appearance configs={ + {"Sign frame","PokemonBDSP/Autostory/Route209Repel/SignFrame.png", + {1242.0/1920,580.0/1080,28.0/1920,80.0/1080},{0,0,1,1},.93,.15}, + {"Rock pair","PokemonBDSP/Autostory/Route209Repel/RockPair.png", + {580.0/1920,690.0/1080,155.0/1920,165.0/1080},{0,0,1,1},.93,.15} + }; + for (size_t i=0;ilighting_landmarks[i].score))lighting_landmarks[i]=landmark; + } + } + double score=1; + // Both remaining anchors must pass confidence and agree spatially. + for (const auto& landmark : candidate.landmarks) + score=std::min(score,landmark.score); + candidate.at_target=appearance!=1 && candidate.located && std::abs(candidate.dx)<=1.0/128 && + std::abs(candidate.dy)<=POSITION_TOLERANCE_Y; + // East-side appearance is acquisition-only; the marked pose owns arrival. + if (appearance==1 && std::abs(candidate.dx)<=1.0/128 && + std::abs(candidate.dy)<=POSITION_TOLERANCE_Y) candidate.located=false; + if (candidate.located)return candidate; + if ((candidate.at_target && !best.at_target) || + (candidate.located && !best.located) || + (candidate.at_target==best.at_target && candidate.located==best.located && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + // Combine lighting references only when the sign and rock agree. + OverworldMatchResult combined; + combined.landmarks=std::move(lighting_landmarks); + const auto& sign=combined.landmarks[0]; + const auto& rock=combined.landmarks[1]; + combined.located=sign.accepted && rock.accepted && + std::abs(sign.dx-rock.dx)<=AGREEMENT_X && + std::abs(sign.dy-rock.dy)<=AGREEMENT_Y; + if(combined.located){ + combined.dx=(sign.dx+rock.dx)/2; + combined.dy=(sign.dy+rock.dy)/2; + combined.at_target=std::abs(combined.dx)<=1.0/128 && + std::abs(combined.dy)<=POSITION_TOLERANCE_Y; + return combined; + } + } + return best; +} OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ OverworldMatchResult best; double best_score=-1; @@ -582,7 +696,7 @@ OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ } OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame){ // Real stationary hourly views cover daylight, night, dusk and cloud edges. - // The target is Kevin's street pose below the door; entry remains watched. + // The target is butters' street pose below the door; entry remains watched. struct Reference { const char* ball; const char* awning; double ball_width,ball_height; }; const Reference references[]={ {"PokemonBDSP/Autostory/HearthomeCenter/Lighting00Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting00Awning.png",144.0/1920,128.0/1080}, @@ -671,6 +785,48 @@ OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame){ return match_lighting_pair(frame,variants,"Contest Hall sign frame", "Contest Hall roof emblem",1.0/128); } +// Fixed city landmarks preserve the recorded passage and western stair lane. +OverworldMatchResult match_veilstone_city_passage(const ImageRGB32& frame){ + auto fine=match_landmarks(frame,{ + {"Passage house","PokemonBDSP/Autostory/VeilstoneDawn/PassageHouse.png", + {42.0/960,222.0/540,210.0/960,110.0/540},{0,0,.55,1},.93,.12}, + {"Game Corner facade","PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", + {592.0/960,227.0/540,230.0/960,110.0/540},{.45,0,.55,1},.93,.12} + },2,1.0/128,1.0/108,true); + if (fine.located) return fine; + // The former reference column lies just inside the left cliff in the + // live corridor. Recenter eight pixels horizontally and two vertically in the + // matcher image; keep both arrival tolerances unchanged. + // A farther-east expiry may expose only the house's right window. + std::vector variants; + for (double scale : {.85,.90,.95,1.0,1.05,1.10}){ + const double hw=95.0/960*scale,hh=80.0/540*scale; + const double gw=230.0/960*scale,gh=110.0/540*scale; + variants.push_back({"House window","PokemonBDSP/Autostory/VeilstoneDawn/PassageHouseWindow.png", + {(165.0+95.0/2-8)/960-hw/2,(250.0+80.0/2+2)/540-hh/2,hw,hh},{0,0,.55,1},.94,.15}); + variants.push_back({"Game Corner facade","PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", + {(600.0+230.0/2-8)/960-gw/2,(225.0+110.0/2+2)/540-gh/2,gw,gh},{.25,0,.75,1},.93,.12}); + } + // At the east-side roof obstruction, perspective changes the facade. + // Calibrate this acquisition-only view to the simultaneously visible house + // window. It guides west off that roof corner; fine arrival stays mandatory. + variants.push_back({"Game Corner facade","PokemonBDSP/Autostory/VeilstoneDawn/GameCornerEastApproach.png", + {644.0/960,228.0/540,240.0/960,100.0/540},{.25,0,.75,1},.93,.12}); + auto approach=match_lighting_pair(frame,variants,"House window","Game Corner facade",1.0/128); + approach.coarse=true;approach.at_target=false; + return approach; +} +OverworldMatchResult match_veilstone_dawn_stairs(const ImageRGB32& frame){ + // The residential terrace to the east has similar stairs. Require its + // distinct west corner together with the western staircase, not NPCs. + return match_landmarks(frame,{ + {"Western stairs","PokemonBDSP/Autostory/VeilstoneDawn/GymStairs.png", + {445.0/960,40.0/540,165.0/960,55.0/540},{.10,0,.65,.70},.93,.12}, + {"East terrace corner","PokemonBDSP/Autostory/VeilstoneDawn/EastTerraceCorner.png", + {620.0/960,155.0/540,200.0/960,110.0/540},{.35,0,.65,.85},.93,.12} + },2,1.0/128,1.0/108,true); +} + OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame){ // Wider fixed facade distinguishes the window bays at night. Real hourly // references cover the illuminated windows; cursor, NPCs and text stay out. @@ -1681,6 +1837,398 @@ OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame){ if (!boundary.landmarks.empty()) result.landmarks.push_back(boundary.landmarks[0]); return result; } +OverworldMatchResult match_route215_bridge_renewal(const ImageRGB32& frame){ + // Restore the successful west bridge approach after variable expiry movement. + if (!frame) return {}; + cv::Mat gray,smoothed; + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); + ImageRGB32 filtered(960,540); + cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); + cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + auto result=match_lighting_pair(filtered,{ + {"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeEdge.png", + {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, + {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeCliff.png", + {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} + ,{"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeFarEdge.png", + {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, + {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeFarCliff.png", + {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} + ,{"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeNightEdge.png", + {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, + {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeNightCliff.png", + {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} + ,{"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarEdge.png", + {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, + {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarCliff.png", + {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} + },"Bridge west edge","Landing cliff corner",1.0/128); + result.at_target=result.located && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/108; + return result; +} +OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame){ + // The road bend and fence corner exclude the patrolling trainers. + if (!frame) return {}; + cv::Mat gray,smoothed; + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); + ImageRGB32 filtered(960,540); + cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); + cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + auto result=match_landmarks(filtered,{ + {"Road bend","PokemonBDSP/Autostory/Route215Renewal/AceRoad.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{.10,.15,.85,.80},.92,.08}, + {"Fence corner","PokemonBDSP/Autostory/Route215Renewal/AceFence.png", + {165.0/960,389.0/540,110.0/960,78.0/540},{.05,.25,.90,.75},.90,.10,LandmarkFeatures::LOCAL_CONTRAST} + },2,1.0/128,1.0/108,true); + if (result.located) return result; + // Expiry may finish east of the fence's visible range. Two separate road + // bends establish a horizontal recovery direction along this clear row. + // Perspective changes their spacing, so this pair cannot confirm arrival. + // Suppress thin rain streaks and puddle rings in the broader road shapes; + // retain the original input and confidence gates for the fine fence pair. + cv::medianBlur(gray,smoothed,5); + cv::GaussianBlur(smoothed,smoothed,cv::Size(0,0),.8); + cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + auto approach=match_lighting_pair(filtered,{ + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadGoal.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadNear.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFar.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestFar.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNear.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWest.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNearRain.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFarRain.png", + {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, + {"Lower road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoadEast.png", + {500.0/960,420.0/540,80.0/960,70.0/540},{0,.70,1,.30},.93,.10}, + {"Lower road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoad.png", + {500.0/960,420.0/540,80.0/960,70.0/540},{0,.70,1,.30},.93,.10} + },"Upper road bend","Lower road bend",1.0/128); + // At the eastern extreme, the original bends leave the screen. The two + // corners of the same grass bed remain visible above the clear road. + auto east=match_lighting_pair(filtered,{ + {"Grass west corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassWest.png", + {665.0/960,145.0/540,110.0/960,75.0/540},{0,.20,1,.40},.92,.10}, + {"Grass east corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassEast.png", + {890.0/960,175.0/540,65.0/960,62.0/540},{0,.20,1,.40},.92,.10}, + {"Grass west corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", + {665.0/960,145.0/540,110.0/960,75.0/540},{0,.20,1,.40},.92,.10}, + {"Grass east corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEast.png", + {890.0/960,175.0/540,65.0/960,62.0/540},{0,.20,1,.40},.92,.10}, + {"Grass east corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEastShoulder.png", + {850.0/960,145.0/540,105.0/960,80.0/540},{0,.20,1,.40},.92,.10} + },"Grass west corner","Grass east corner",1.0/128); + const auto& west=east.landmarks[0];const auto& right=east.landmarks[1]; + const double east_nearest=std::min(std::abs(west.dx),std::abs(right.dx)); + east.located=west.accepted && right.accepted && + std::abs(west.dy-right.dy)<=AGREEMENT_Y && + west.dx< -1.0/12 && right.dx< -1.0/12 && + east_nearest>2*std::abs(west.dx-right.dx) && + east.dy>=-1.0/27 && east.dy<=1.0/9; + if (east.located) { east.coarse=true;east.at_target=false;return east; } + // From the eastern end, the fixed route sign remains unique while the + // road bends are off-screen. Its virtual goal lies beyond the right edge; + // this pair guides acquisition only and can never confirm arrival. + auto far_east=match_lighting_pair(filtered,{ + {"Grass west corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", + {665.0/960,145.0/540,110.0/960,75.0/540},{0,.20,1,.40},.92,.10}, + {"Route 215 sign","PokemonBDSP/Autostory/Route215Renewal/AceRoute215Sign.png", + {1128.0/960,359.0/540,90.0/960,45.0/540},{.50,.40,.50,.50},.94,.20}, + {"Route 215 sign","PokemonBDSP/Autostory/Route215Renewal/AceRoute215SignRain.png", + {1128.0/960,359.0/540,90.0/960,45.0/540},{.50,.40,.50,.50},.94,.20} + },"Grass west corner","Route 215 sign",1.0/128); + const auto& corner=far_east.landmarks[0];const auto& sign=far_east.landmarks[1]; + const double far_nearest=std::min(std::abs(corner.dx),std::abs(sign.dx)); + far_east.located=corner.accepted && sign.accepted && + std::abs(corner.dy-sign.dy)<=AGREEMENT_Y && + corner.dx< -1.0/4 && sign.dx< -1.0/4 && + far_nearest>2*std::abs(corner.dx-sign.dx) && + far_east.dy>=-1.0/27 && far_east.dy<=1.0/9; + if (far_east.located) {far_east.coarse=true;far_east.at_target=false;return far_east;} + const auto& upper=approach.landmarks[0];const auto& lower=approach.landmarks[1]; + // Keep the shared row when both bends already agree. In the recorded + // perspective-shifted rows the nearer bend moves 1.35 times as far; + // compare those acquisition errors in the farther bend's plane. + // Neither model changes the strict fine-arrival checks above. + const double lower_plane_dy=std::abs(upper.dy-lower.dy)<=AGREEMENT_Y ? + lower.dy : lower.dy/1.35; + approach.dy=(upper.dy+lower_plane_dy)/2; + const double nearest=std::min(std::abs(upper.dx),std::abs(lower.dx)); + const bool horizontal_support=upper.dx*lower.dx>0 && nearest>1.0/64 && + nearest>2*std::abs(upper.dx-lower.dx); + // Expiry can also finish one row south, already near the goal column. + // Both accepted bends must agree before correcting that row vertically. + const bool vertical_support=std::abs(upper.dx-lower.dx)<=AGREEMENT_X && + std::max(std::abs(upper.dx),std::abs(lower.dx))<=1.0/12 && + std::abs(upper.dy-lower_plane_dy)<=1.0/108 && + upper.dy*lower_plane_dy>0 && std::abs(approach.dy)>1.0/54; + approach.located=upper.accepted && lower.accepted && + std::abs(upper.dy-lower_plane_dy)<=AGREEMENT_Y && + std::abs(approach.dy)<=1.0/9 && (horizontal_support || vertical_support); + approach.coarse=true;approach.at_target=false; + return approach; +} +OverworldMatchResult match_route215_derek_stairs_renewal(const ImageRGB32& frame){ + if (!frame) return {}; + cv::Mat gray,smoothed; + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); + ImageRGB32 filtered(960,540); + cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); + cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + // Clear landing below the short stairs; neither tree nor player is an anchor. + auto result=match_lighting_pair(filtered,{ + {"White stair railing","PokemonBDSP/Autostory/Route215Renewal/DerekStairsRailing.png", + {500.0/960,78.0/540,90.0/960,110.0/540},{.10,.05,.85,.85},.93,.12}, + {"Lower stair corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairsCorner.png", + {589.0/960,315.0/540,95.0/960,105.0/540},{.10,.20,.85,.80},.93,.12} + ,{"White stair railing","PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightRailing.png", + {500.0/960,78.0/540,90.0/960,110.0/540},{.10,.05,.85,.85},.93,.12}, + {"Lower stair corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightCorner.png", + {589.0/960,315.0/540,95.0/960,105.0/540},{.10,.20,.85,.80},.93,.12} + },"White stair railing","Lower stair corner",1.0/128); + result.at_target=result.located && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/108; + if (result.located) return result; + // Far west of this landing, perspective shifts the two structures by + // different amounts. Use that view only to acquire the horizontal column; + // the original close pair above remains the sole arrival check. + auto approach=match_landmarks(filtered,{ + {"White stair railing","PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestRailing.png", + {500.0/960,78.0/540,90.0/960,110.0/540},{.10,.05,.85,.85},.93,.12}, + {"Lower stair corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestCorner.png", + {589.0/960,315.0/540,95.0/960,105.0/540},{.10,.20,.85,.80},.93,.12} + },2,1.0/128,1.0/108,true); + const auto& rail=approach.landmarks[0];const auto& stair=approach.landmarks[1]; + const double nearest=std::min(std::abs(rail.dx),std::abs(stair.dx)); + approach.located=rail.accepted && stair.accepted && + std::abs(rail.dy-stair.dy)<=AGREEMENT_Y && + std::abs((rail.dy+stair.dy)/2)<=1.0/108 && + rail.dx*stair.dx>0 && nearest>1.0/64 && + nearest>2*std::abs(rail.dx-stair.dx); + approach.dx=(rail.dx+stair.dx)/2;approach.dy=(rail.dy+stair.dy)/2; + approach.coarse=true;approach.at_target=false; + return approach; +} +OverworldMatchResult match_route215_derek_stair_top_renewal(const ImageRGB32& frame){ + if (!frame) return {}; + cv::Mat gray,smoothed; + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); + ImageRGB32 filtered(960,540); + cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); + cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + // The road row above the stairs is clear west/east, below the tall grass. + auto result=match_lighting_pair(filtered,{ + {"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrier.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, + {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrierEast.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, + {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFenceEast.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightBarrier.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, + {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastBarrier.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, + {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestBarrier.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, + {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNorthFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthBarrier.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12} + ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastBarrier.png", + {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12} + ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + // Raw-luminance alternatives preserve fence structure when local + // contrast normalization amplifies a passing rain streak. + ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthGrayFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12} + ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastGrayFence.png", + {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12} + },"Stair-top barrier","West fence corner",1.0/128); + result.at_target=result.located && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/108; + if (!result.located && result.landmarks.size()==2){ + const auto& barrier=result.landmarks[0];const auto& fence=result.landmarks[1]; + const double nearest=std::min(std::abs(barrier.dx),std::abs(fence.dx)); + // Far-east perspective changes spacing: acquire the column first, + // then use the original strict two-anchor arrival check. + if (barrier.accepted && fence.accepted && + std::abs(barrier.dy-fence.dy)<=AGREEMENT_Y && std::abs(result.dy)<=1.0/20 && + barrier.dx*fence.dx>0 && nearest>1.0/64 && + nearest>2*std::abs(barrier.dx-fence.dx)){ + result.located=true;result.coarse=true;result.at_target=false; + } + } + return result; +} +OverworldMatchResult match_route215_jogger_corner(const ImageRGB32& frame){ + if (!frame) return {}; + // These references describe the game capture, without the recording canvas. + // Smooth only this locator before contrast normalization to suppress rain + // streaks and capture resampling noise; other detectors retain their inputs. + cv::Mat gray,smoothed; + cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); + ImageRGB32 filtered(960,540); + cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); + cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + // Fixed fence cap and road bend; neither the moving jogger nor the item + // supplies position evidence. Each view maps back to the same lane-4 corner. + // Search only the cap/bend height of lanes 4 and 3. This excludes similar + // lower fence sections; a missing cap never authorizes a vertical search. + // Low-frequency cap/cliff alternatives require higher correlation (.95/.97) + // as well as a unique peak and an independently agreeing structural anchor. + const std::vector appearances={ + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceGoal.png", + {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceRainWest.png", + {0.535416666667,0.311111111111,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceCloudWest.png", + {0.535416666667,0.311111111111,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceLane3.png", + {0.535416666667,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceMid.png", + {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceNear.png", + {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachFar.png", + {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachMiddle.png", + {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachNear.png", + {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceGrayLane3.png", + {0.535416666667,0.312962962963,32.0/960,24.0/540},{0,.24,1,.26},.95,.05}, + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperDayGoal.png", + {422.0/960,164.0/540,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, + // In lane 3 the cap overlaps the lower part of the full road patch. + // Upper-bend alternatives keep position evidence clear of the player. + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperGoal.png", + {0.439583333333,0.303703703704,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperRainWest.png", + {0.439583333333,0.303703703704,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperCloudWest.png", + {0.447916666667,0.307407407407,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadGoal.png", + {0.439583333333,0.303703703704,80.0/960,41.0/540},{0,.24,1,.26},.90,.15}, + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadRainWest.png", + {0.439583333333,0.303703703704,80.0/960,41.0/540},{0,.24,1,.26},.90,.15}, + {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadCloudWest.png", + {0.447916666667,0.307407407407,80.0/960,41.0/540},{0,.24,1,.26},.90,.15}, + }; + std::vector variants; + for (const auto& appearance : appearances){ + if (std::string(appearance.name)=="Road bend"){ + variants.push_back(appearance); + continue; + } + // Horizontal perspective changes as the fence crosses the screen. + for (double scale : {.96,1.0,1.04}){ + auto variant=appearance; + variant.target.width*=scale; + variant.target.x+=(appearance.target.width-variant.target.width)/2; + variants.push_back(variant); + } + } + // A road bend can disambiguate repeated fence sections. Choose complete + // agreeing pairs, rather than rejecting one anchor before considering the other. + const auto candidates=match_landmarks(filtered,variants,variants.size()+1, + 1.0/128,POSITION_TOLERANCE_Y,true); + OverworldMatchResult best; + double best_score=-1; + bool supported=false,ambiguous=false; + double first_x=0,first_y=0; + for (const auto& fence : candidates.landmarks){ + if (fence.name!="Fence top") continue; + for (const auto& road : candidates.landmarks){ + if (road.name!="Road bend") continue; + const double score=std::min(fence.score,road.score); + const bool agrees=std::abs(fence.dx-road.dx)<=AGREEMENT_X && + std::abs(fence.dy-road.dy)<=AGREEMENT_Y; + const bool valid=fence.accepted && road.accepted && agrees; + const double dx=(fence.dx+road.dx)/2,dy=(fence.dy+road.dy)/2; + if (valid){ + if (supported && (std::abs(dx-first_x)>AGREEMENT_X || + std::abs(dy-first_y)>AGREEMENT_Y)) ambiguous=true; + if (!supported){first_x=dx;first_y=dy;} + } + if ((valid && !supported) || (valid==supported && score>best_score)){ + best.landmarks={fence,road};best.dx=dx;best.dy=dy;best_score=score; + } + supported|=valid; + } + } + best.located=supported && !ambiguous; + best.at_target=best.located && std::abs(best.dx)<=1.0/128 && + std::abs(best.dy)<=POSITION_TOLERANCE_Y; + if (best.located) return best; + // West of the visible fence, bridge/cliff pairs establish the walking row. + // Their x coordinates are acquisition-only and can never confirm arrival. + const std::vector> approaches={ + { + // The observed lane-4 to lane-3 step changes the bridge-foot view. + // Both corners must agree; this pair never confirms arrival. + {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestNightLane3.png", + {106.0/960-.435625,297.0/540-70.0/1080,60.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastNightLane3.png", + {260.0/960-.435625,295.0/540-70.0/1080,65.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + },{ + // When the east fence is off-screen, these two full bridge-foot + // corners establish lane 4. They never authorize arrival. + {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestCurrent.png", + {106.0/960-.435,257.0/540,60.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastCurrent.png", + {260.0/960-.435,255.0/540,65.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + },{ + + {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeFar.png", + {240.0/960-.61,246.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastBridgeFar.png", + {388.0/960-.61,246.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} + },{ + {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeMid.png", + {108.0/960-.445,245.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, + {"South cliff corner","PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeMid.png", + {77.0/960-.445,295.0/540,90.0/960,80.0/540},{0,.2,1,.6},.97,.05} + },{ + {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeNear.png", + {68.0/960-.405,245.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, + {"South cliff corner","PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeNear.png", + {37.0/960-.405,295.0/540,90.0/960,80.0/540},{0,.2,1,.6},.97,.05} + } + }; + for (const auto& approach : approaches){ + auto candidate=match_landmarks(filtered,approach,2,1.0/128,1.0/54,true); + const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); + if (best.located && candidate.located && std::abs(best.dy-candidate.dy)>AGREEMENT_Y){ + best.located=false;best.at_target=false;best.coarse=true;return best; + } + candidate.coarse=true;candidate.at_target=false; + if ((candidate.located && !best.located) || (candidate.located==best.located && score>best_score)){ + best=std::move(candidate);best_score=score; + } + } + return best; +} const std::vector& overworld_matchers(){ static const std::vector matchers={ {"route204-north-approach","Route 204 left of sign after Tyler",match_route204_north_approach}, {"teighler-alcove-corner","Teighler alcove bottom-left corner",match_teighler_alcove_corner}, @@ -1703,8 +2251,16 @@ const std::vector& overworld_matchers(){ static const std:: {"hearthome-center","Hearthome Pokemon Center doorway",match_hearthome_center_entrance}, {"hearthome-center-interior","Hearthome Pokemon Center interior",match_hearthome_center_interior}, {"hearthome-fantina","Hearthome Contest Hall sign + roof emblem (Fantina)",match_hearthome_contest_fantina}, + {"veilstone-city-passage","Veilstone city passage before the west turn",match_veilstone_city_passage}, + {"veilstone-dawn-stairs","Veilstone western stairs toward Dawn",match_veilstone_dawn_stairs}, {"hearthome-gym","Hearthome Gym doorway (arch + facade)",match_hearthome_gym_entrance}, {"route208-stair-top","Route 208 stair-top rocks",match_route208_stair_top}, + {"route209-repel-point","Route 209 Repel renewal point",match_route209_repel_point}, + {"route215-jogger-corner","Route 215 east corner beyond the jogger",match_route215_jogger_corner}, + {"route215-bridge-renewal","Route 215 west bridge renewal",match_route215_bridge_renewal}, + {"route215-ace-renewal","Route 215 renewal before Ace Trainers",match_route215_ace_renewal}, + {"route215-derek-stairs-renewal","Route 215 renewal below Derek stairs",match_route215_derek_stairs_renewal}, + {"route215-derek-stair-top-renewal","Route 215 renewal above Derek stairs",match_route215_derek_stair_top_renewal}, {"route208-hannah-departure","Route 208 field after Hannah",match_route208_hannah_departure}, {"eterna-gym","Eterna Gym doorway",match_eterna_gym_entrance}, {"galactic-hq-jupiter","Galactic HQ Jupiter interaction",match_galactic_hq_jupiter}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 3ba034aa12..8dfb889fe7 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -53,9 +53,19 @@ OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame); OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame); OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame); OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame); +OverworldMatchResult match_veilstone_city_passage(const ImageRGB32& frame); +OverworldMatchResult match_veilstone_dawn_stairs(const ImageRGB32& frame); OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame); OverworldMatchResult match_route208_lower_stair_lane(const ImageRGB32& frame); +// User-marked sign frame and rock pair at the Route 209 renewal point. +OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame); +// Lane 4 east corner; coarse bridge references only guide approach acquisition. +OverworldMatchResult match_route215_jogger_corner(const ImageRGB32& frame); +OverworldMatchResult match_route215_bridge_renewal(const ImageRGB32& frame); +OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame); +OverworldMatchResult match_route215_derek_stairs_renewal(const ImageRGB32& frame); +OverworldMatchResult match_route215_derek_stair_top_renewal(const ImageRGB32& frame); OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame); OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp index 8a31155a33..d6bec8469d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp @@ -3,86 +3,197 @@ * From: https://github.com/PokemonAutomation/ */ -#include -#include -#include "Common/Cpp/Filesystem/Filesystem.h" -#include "Common/Cpp/Filesystem/FileIO.h" -#include "CommonFramework/GlobalAutoPaths.h" -#include -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP_OverworldLightingTest.h" -#include "../AutoStory/Utils/PokemonBDSP_OverworldMatchers.h" -#include "../AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" -namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ -using namespace std::chrono_literals; -static const StringSelectDatabase& lighting_matcher_database(){ - static const StringSelectDatabase db=[](){ - StringSelectDatabase result; - for(const auto& matcher:overworld_matchers())result.add_entry({matcher.slug,matcher.name}); - return result; - }();return db; -} -OverworldLightingTest_Descriptor::OverworldLightingTest_Descriptor() - : SingleSwitchProgramDescriptor("PokemonBDSP:OverworldLightingTest","Pokemon BDSP","Overworld Matcher Lighting Test","", - "Place the player at the selected matcher's reference location. Tests 24 hourly lighting settings without moving the player. " - "Changes console date/time; saves screenshots and CSV results under Screenshots/OverworldLightingTest.", - ProgramControllerClass::StandardController_NoRestrictions,FeedbackType::REQUIRED,AllowCommandsWhenRunning::DISABLE_COMMANDS) -{} -OverworldLightingTest::OverworldLightingTest() - : MATCHER("Matcher:",lighting_matcher_database(),LockMode::LOCK_WHILE_RUNNING,"poketch-landmarks") - , START_HOUR("Starting hour:
Establishes the initial time, then advances one hour for each of 24 samples.",LockMode::LOCK_WHILE_RUNNING,0,0,23) - , SETTLE_SECONDS("Wait after returning to game (seconds):",LockMode::LOCK_WHILE_RUNNING,5,1,60) -{PA_ADD_OPTION(MATCHER);PA_ADD_OPTION(START_HOUR);PA_ADD_OPTION(SETTLE_SECONDS);} -void OverworldLightingTest::program(SingleSwitchProgramEnvironment& env,ProControllerContext& context){ - const auto& matcher=overworld_matchers()[MATCHER.index()]; - Filesystem::Path output=Filesystem::Path(USER_FILE_PATH())/"Screenshots/OverworldLightingTest"/ - (matcher.slug+"-"+std::to_string(std::time(nullptr))); - Filesystem::create_directories(output); - FileIO results(output/"results.csv",FileMode::WRITE); - std::ostringstream csv; - if(!results.is_open())OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); - csv<<"sample,hour,frame,landmark,score,margin,accepted,dx_screen,dy_screen,located,at_target,consensus_dx_screen,consensus_dy_screen,coarse\n"; - csv<save((output/name).string()))OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: Screenshot save failed.",env.console); - const std::string rows=csv.str(); - if(results.write(rows)!=rows.size() || !results.flush()){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: CSV write failed.",env.console); - } - csv.str(""); - csv.clear(); - pbf_wait(context,250ms);context.wait_for_all_requests(); - } - } - env.log("[Lighting test] Complete: "+std::to_string(target_frames)+"/120 frames at target. Results: "+output.string()); -} -}}} +#include +#include +#include +#include "Common/Cpp/Json/JsonObject.h" +#include +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "Common/Cpp/Filesystem/FileIO.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP_OverworldLightingTest.h" +#include "../AutoStory/Utils/PokemonBDSP_OverworldMatchers.h" +#include "../AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +using namespace std::chrono_literals; +static const StringSelectDatabase& lighting_matcher_database(size_t segment){ + static const std::array databases=[](){ + // Include shared locations in each segment that visits them. + const std::array,10> slugs{{ + {"poketch-landmarks","jubilife-center","route201-potion-exit", + "sandgem-junction","route202-grass-exit"}, + {"oreburgh-mart"}, + {"oreburgh-mart","jubilife-street-approach","jubilife-plaza-approach", + "route204-north-approach","teighler-alcove-corner","route204-north-stairs", + "floaroma-center-entrance","ravaged-path-south-entrance","route204-sign", + "windworks-sign","route205-bridge","route205-bank-tree","route205-zackary", + "route205-kelsey","route205-forest","eterna-center","eterna-gym", + "eterna-gym-statues","eterna-underground-house","eterna-underground-man"}, + {"eterna-center","galactic-hq-jupiter","hearthome-center","hearthome-center-interior", + "hearthome-fantina","hearthome-gym","route208-stair-top", + "route208-hannah-departure","route209-repel-point","route215-jogger-corner", + "route215-bridge-renewal","route215-ace-renewal", + "route215-derek-stairs-renewal","route215-derek-stair-top-renewal", + "veilstone-city-passage","veilstone-dawn-stairs"}, + {}, + {"hearthome-gym"}, + {}, {}, {}, {} + }}; + std::array result; + for(size_t i=0;iSegment:",{ + {0,"00","00: Intro to Poketch"}, + {1,"01","01: Jubilife City to First Badge"}, + {2,"02","02: First Badge to Second Badge"}, + {3,"03","03: Second Badge to Third Badge"}, + {4,"04","04: Third Badge to Fourth Badge"}, + {5,"05","05: Fourth Badge to Fifth Badge"}, + {6,"06","06: Fifth Badge to Sixth Badge"}, + {7,"07","07: Sixth Badge to Seventh Badge"}, + {8,"08","08: Seventh Badge to Eighth Badge"}, + {9,"09","09: Eighth Badge to Champion"} + },LockMode::LOCK_WHILE_RUNNING,0) + , START_HOUR("Starting hour:
Establishes the initial time, then advances one hour for each of 24 samples.",LockMode::LOCK_WHILE_RUNNING,0,0,23) + , SETTLE_SECONDS("Wait after returning to game (seconds):",LockMode::LOCK_WHILE_RUNNING,5,1,60) +{ + PA_ADD_OPTION(SEGMENT); + for(size_t i=0;i("Landmark matcher:", + database,LockMode::LOCK_WHILE_RUNNING,i==0 ? "poketch-landmarks" : database[0].slug); + add_option(*MATCHERS[i],"MATCHER_"+std::to_string(i)); + } + PA_ADD_OPTION(START_HOUR); + PA_ADD_OPTION(SETTLE_SECONDS); + SEGMENT.add_listener(*this); + on_config_value_changed(nullptr); +} +void OverworldLightingTest::on_config_value_changed(void*){ + for(size_t i=0;iset_visibility(i!=SEGMENT.current_value() ? ConfigOptionState::HIDDEN : + MATCHERS[i]->slug()=="none" ? ConfigOptionState::DISABLED : ConfigOptionState::ENABLED); + } +} +std::string OverworldLightingTest::check_validity() const{ + const auto error=SingleSwitchProgramInstance::check_validity(); + if(!error.empty())return error; + if(MATCHERS[SEGMENT.current_value()]->slug()=="none") + return "The selected segment has no landmark matchers available."; + return {}; +} +void OverworldLightingTest::load_json(const JsonValue& json){ + SingleSwitchProgramInstance::load_json(json); + // Preserve the selected matcher from settings saved before segment filtering. + const auto* object=json.to_object(); + if(!object || object->get_value("SEGMENT"))return; + const auto* legacy=object->get_string("MATCHER"); + if(!legacy)return; + for(size_t i=0;iset_by_slug(*legacy); + SEGMENT.set_value(i); + return; + } + } +} +void OverworldLightingTest::program(SingleSwitchProgramEnvironment& env,ProControllerContext& context){ + const auto& slug=MATCHERS[SEGMENT.current_value()]->slug(); + const auto& matchers=overworld_matchers(); + const auto selected=std::find_if(matchers.begin(),matchers.end(), + [&slug](const OverworldMatcher& entry){return entry.slug==slug;}); + if(selected==matchers.end()) + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT, + "The selected segment has no landmark matcher available.",env.console); + const auto& matcher=*selected; + Filesystem::Path output=Filesystem::Path(USER_FILE_PATH())/"Screenshots/OverworldLightingTest"/ + (matcher.slug+"-"+std::to_string(std::time(nullptr))); + Filesystem::create_directories(output); + FileIO results(output/"results.csv",FileMode::WRITE); + std::ostringstream csv; + if(!results.is_open())OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Cannot create lighting-test results file.",env.console); + csv<<"sample,hour,frame,landmark,score,margin,accepted,dx_screen,dy_screen,located,at_target,consensus_dx_screen,consensus_dy_screen,coarse\n"; + csv<save((output/name).string()))OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: Screenshot save failed.",env.console); + const std::string rows=csv.str(); + if(results.write(rows)!=rows.size() || !results.flush()){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::NO_ERROR_REPORT,"Lighting test: CSV write failed.",env.console); + } + csv.str(""); + csv.clear(); + pbf_wait(context,250ms);context.wait_for_all_requests(); + } + env.log("[Lighting test] Hour "+std::to_string(hour)+": "+ + std::to_string(located_this_hour)+"/5 located; "+std::to_string(target_this_hour)+ + "/5 at target; "+std::to_string(located_this_hour-target_this_hour)+ + "/5 off target; "+std::to_string(5-located_this_hour)+"/5 missing landmarks."); + } + env.log("[Lighting test] Complete: "+std::to_string(located_frames)+"/120 located; "+ + std::to_string(target_frames)+"/120 at target; "+ + std::to_string(located_frames-target_frames)+"/120 off target; "+ + std::to_string(120-located_frames)+"/120 missing landmarks. Results: "+output.string()); +} +}}} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h index dc77fe8770..1a9b6456c2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.h @@ -1,4 +1,7 @@ #pragma once +#include +#include +#include "Common/Cpp/Options/EnumDropdownOption.h" #include "Common/Cpp/Options/SimpleIntegerOption.h" #include "CommonTools/Options/StringSelectOption.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" @@ -6,13 +9,18 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ class OverworldLightingTest_Descriptor : public SingleSwitchProgramDescriptor{ public: OverworldLightingTest_Descriptor(); }; -class OverworldLightingTest : public SingleSwitchProgramInstance{ +class OverworldLightingTest : public SingleSwitchProgramInstance, public ConfigOption::Listener{ public: using Descriptor = OverworldLightingTest_Descriptor; + ~OverworldLightingTest(); OverworldLightingTest(); + std::string check_validity() const override; + void load_json(const JsonValue& json) override; void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; private: - StringSelectOption MATCHER; + void on_config_value_changed(void* object) override; + IntegerEnumDropdownOption SEGMENT; + std::array,10> MATCHERS; SimpleIntegerOption START_HOUR; SimpleIntegerOption SETTLE_SECONDS; }; From c876b1e3584d09d2a39e13a72b71ba0162b87cca Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 29 Oct 2026 13:50:51 -0500 Subject: [PATCH 36/39] Align BDSP AutoStory formatting and descriptors with upstream Apply four-space indentation and multiline braced control flow across AutoStory. Sort includes, remove duplicates, and replace backward-relative include paths. Preserve gameplay logic and existing file line endings. Migrate AutoStory and the lighting test to PanelQualityStanding. Validation: formatting checks and Windows Debug build, linking, and deployment passed. --- .../Data/PokemonBDSP_AutoStory_BDSPData.cpp | 102 +- .../Data/PokemonBDSP_AutoStory_BDSPData.h | 61 +- ...kemonBDSP_AutoStory_MenuCursorDetector.cpp | 289 +- ...PokemonBDSP_AutoStory_MenuCursorDetector.h | 41 +- ...okemonBDSP_AutoStory_OverworldDetector.cpp | 53 +- .../PokemonBDSP_AutoStory_OverworldDetector.h | 28 +- .../AutoStory/PokemonBDSP_AutoStory.cpp | 520 +- .../AutoStory/PokemonBDSP_AutoStory.h | 42 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 1539 +++--- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 266 +- .../PokemonBDSP_AutoStory_Segment_00.cpp | 2446 ++++----- .../PokemonBDSP_AutoStory_Segment_00.h | 134 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 3428 ++++++------ .../PokemonBDSP_AutoStory_Segment_01.h | 188 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 4737 +++++++++-------- .../PokemonBDSP_AutoStory_Segment_02.h | 141 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 4499 ++++++++-------- .../PokemonBDSP_AutoStory_Segment_03.h | 164 +- .../PokemonBDSP_AutoStory_Segment_04.cpp | 1098 ++-- .../PokemonBDSP_AutoStory_Segment_04.h | 325 +- .../PokemonBDSP_AutoStory_Segment_05.cpp | 1419 ++--- .../PokemonBDSP_AutoStory_Segment_05.h | 311 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 628 ++- .../PokemonBDSP_AutoStory_Segment_06.h | 209 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 1489 +++--- .../PokemonBDSP_AutoStory_Segment_07.h | 345 +- .../PokemonBDSP_AutoStory_Segment_08.cpp | 2277 ++++---- .../PokemonBDSP_AutoStory_Segment_08.h | 501 +- .../PokemonBDSP_AutoStory_Segment_09.cpp | 1454 +++-- .../PokemonBDSP_AutoStory_Segment_09.h | 330 +- .../Utils/PokemonBDSP_AutoStory_Battle.cpp | 622 ++- .../Utils/PokemonBDSP_AutoStory_Battle.h | 46 +- .../Utils/PokemonBDSP_AutoStory_Dialog.cpp | 32 +- .../Utils/PokemonBDSP_AutoStory_Dialog.h | 25 +- .../PokemonBDSP_AutoStory_Navigation.cpp | 48 +- .../Utils/PokemonBDSP_AutoStory_Navigation.h | 34 +- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 3567 ++++++------- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 265 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 422 +- .../PokemonBDSP_Autostory_Segment_01_tools.h | 21 +- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 4408 +++++++++------ .../Utils/PokemonBDSP_OverworldMatchers.h | 15 +- .../PokemonBDSP_OverworldLightingTest.cpp | 1 + 43 files changed, 19452 insertions(+), 19118 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp index 1066596daf..d6509f1345 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp @@ -4,18 +4,21 @@ * */ -#include "CommonFramework/GlobalAutoPaths.h" #include + #include "Common/Cpp/Exceptions.h" -#include "Common/Cpp/Json/JsonValue.h" #include "Common/Cpp/Json/JsonArray.h" #include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/Json/JsonValue.h" + +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/Globals.h" + #include "PokemonBDSP_AutoStory_BDSPData.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { static const std::string RESOURCE_DIR = "PokemonBDSP/AutoStory/"; @@ -27,24 +30,12 @@ static const std::string RESOURCE_DIR = "PokemonBDSP/AutoStory/"; static PokemonType parse_type(const std::string& slug, const std::string& path){ static const std::map TABLE{ - {"normal", PokemonType::NORMAL}, - {"fire", PokemonType::FIRE}, - {"fighting", PokemonType::FIGHTING}, - {"water", PokemonType::WATER}, - {"flying", PokemonType::FLYING}, - {"grass", PokemonType::GRASS}, - {"poison", PokemonType::POISON}, - {"electric", PokemonType::ELECTRIC}, - {"ground", PokemonType::GROUND}, - {"psychic", PokemonType::PSYCHIC}, - {"rock", PokemonType::ROCK}, - {"ice", PokemonType::ICE}, - {"bug", PokemonType::BUG}, - {"dragon", PokemonType::DRAGON}, - {"ghost", PokemonType::GHOST}, - {"dark", PokemonType::DARK}, - {"steel", PokemonType::STEEL}, - {"fairy", PokemonType::FAIRY}, + {"normal", PokemonType::NORMAL}, {"fire", PokemonType::FIRE}, {"fighting", PokemonType::FIGHTING}, + {"water", PokemonType::WATER}, {"flying", PokemonType::FLYING}, {"grass", PokemonType::GRASS}, + {"poison", PokemonType::POISON}, {"electric", PokemonType::ELECTRIC}, {"ground", PokemonType::GROUND}, + {"psychic", PokemonType::PSYCHIC}, {"rock", PokemonType::ROCK}, {"ice", PokemonType::ICE}, + {"bug", PokemonType::BUG}, {"dragon", PokemonType::DRAGON}, {"ghost", PokemonType::GHOST}, + {"dark", PokemonType::DARK}, {"steel", PokemonType::STEEL}, {"fairy", PokemonType::FAIRY}, }; auto iter = TABLE.find(slug); if (iter == TABLE.end()){ @@ -54,9 +45,15 @@ static PokemonType parse_type(const std::string& slug, const std::string& path){ } static MoveCategory parse_category(const std::string& slug, const std::string& path){ - if (slug == "physical") return MoveCategory::PHYSICAL; - if (slug == "special") return MoveCategory::SPECIAL; - if (slug == "status") return MoveCategory::STATUS; + if (slug == "physical"){ + return MoveCategory::PHYSICAL; + } + if (slug == "special"){ + return MoveCategory::SPECIAL; + } + if (slug == "status"){ + return MoveCategory::STATUS; + } throw FileException(nullptr, PA_CURRENT_FUNCTION, "Unknown move category: " + slug, path); } @@ -65,7 +62,7 @@ static MoveCategory parse_category(const std::string& slug, const std::string& p // Move database // --------------------------------------------------------------------------- -struct BDSPMoveDatabase{ +struct BDSPMoveDatabase { std::map data; static const BDSPMoveDatabase& instance(){ @@ -83,12 +80,12 @@ struct BDSPMoveDatabase{ JsonObject& obj = item.second.to_object_throw(path); BDSPMoveData& move = data[slug]; - move.category = parse_category(obj.get_string_throw("category", path), path); - move.type = parse_type(obj.get_string_throw("type", path), path); + move.category = parse_category(obj.get_string_throw("category", path), path); + move.type = parse_type(obj.get_string_throw("type", path), path); move.base_power = (uint8_t)obj.get_integer_throw("base_power", path); - move.pp = (uint8_t)obj.get_integer_throw("pp", path); - move.spread = obj.get_boolean_throw("spread", path); - move.accuracy = (uint8_t)obj.get_integer_default("accuracy", 100); + move.pp = (uint8_t)obj.get_integer_throw("pp", path); + move.spread = obj.get_boolean_throw("spread", path); + move.accuracy = (uint8_t)obj.get_integer_default("accuracy", 100); } } }; @@ -107,7 +104,7 @@ const BDSPMoveData& bdsp_move(const std::string& slug){ // Pokemon database // --------------------------------------------------------------------------- -struct BDSPPokemonDatabase{ +struct BDSPPokemonDatabase { std::map data; static const BDSPPokemonDatabase& instance(){ @@ -129,19 +126,21 @@ struct BDSPPokemonDatabase{ mon.type[1] = PokemonType::NONE; { JsonArray& type_arr = obj.get_array_throw("type", path); - if (type_arr.size() >= 1) + if (type_arr.size() >= 1){ mon.type[0] = parse_type(type_arr[0].to_string_throw(path), path); - if (type_arr.size() >= 2) + } + if (type_arr.size() >= 2){ mon.type[1] = parse_type(type_arr[1].to_string_throw(path), path); + } } { JsonObject& stats = obj.get_object_throw("base_stats", path); - mon.hp = (uint8_t)stats.get_integer_throw("hp", path); - mon.attack = (uint8_t)stats.get_integer_throw("attack", path); + mon.hp = (uint8_t)stats.get_integer_throw("hp", path); + mon.attack = (uint8_t)stats.get_integer_throw("attack", path); mon.defense = (uint8_t)stats.get_integer_throw("defense", path); - mon.spatk = (uint8_t)stats.get_integer_throw("spatk", path); - mon.spdef = (uint8_t)stats.get_integer_throw("spdef", path); - mon.speed = (uint8_t)stats.get_integer_throw("speed", path); + mon.spatk = (uint8_t)stats.get_integer_throw("spatk", path); + mon.spdef = (uint8_t)stats.get_integer_throw("spdef", path); + mon.speed = (uint8_t)stats.get_integer_throw("speed", path); } } } @@ -161,7 +160,7 @@ const BDSPPokemonData& bdsp_pokemon(const std::string& slug){ // Trainer database // --------------------------------------------------------------------------- -struct BDSPTrainerDatabase{ +struct BDSPTrainerDatabase { std::map data; static const BDSPTrainerDatabase& instance(){ @@ -181,10 +180,11 @@ struct BDSPTrainerDatabase{ BDSPTrainerData& battle = data[battle_id]; { const std::string& type_str = obj.get_string_throw("battle_type", path); - if (type_str == "2v2") + if (type_str == "2v2"){ battle.battle_type = BDSPBattleType::DOUBLES; - else + }else{ battle.battle_type = BDSPBattleType::SINGLES; + } } { JsonArray& trainers_arr = obj.get_array_throw("trainers", path); @@ -196,17 +196,19 @@ struct BDSPTrainerDatabase{ JsonObject& p_obj = p_val.to_object_throw(path); BDSPTrainerPokemon tp; tp.species = p_obj.get_string_throw("species", path); - tp.level = (uint8_t)p_obj.get_integer_throw("level", path); + tp.level = (uint8_t)p_obj.get_integer_throw("level", path); auto parse_stat_set = [&](BDSPStatSet& set, const char* key){ JsonObject* s = p_obj.get_object(key); - if (!s) return; - set.hp = (uint8_t)s->get_integer_default("hp", 0); - set.attack = (uint8_t)s->get_integer_default("attack", 0); + if (!s){ + return; + } + set.hp = (uint8_t)s->get_integer_default("hp", 0); + set.attack = (uint8_t)s->get_integer_default("attack", 0); set.defense = (uint8_t)s->get_integer_default("defense", 0); - set.spatk = (uint8_t)s->get_integer_default("spatk", 0); - set.spdef = (uint8_t)s->get_integer_default("spdef", 0); - set.speed = (uint8_t)s->get_integer_default("speed", 0); + set.spatk = (uint8_t)s->get_integer_default("spatk", 0); + set.spdef = (uint8_t)s->get_integer_default("spdef", 0); + set.speed = (uint8_t)s->get_integer_default("speed", 0); }; parse_stat_set(tp.ivs, "ivs"); parse_stat_set(tp.evs, "evs"); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h index 3fa46fabee..a8a6bce913 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h @@ -11,11 +11,12 @@ #include #include + #include "Pokemon/Pokemon_Types.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { using namespace Pokemon; @@ -24,13 +25,13 @@ using namespace Pokemon; // Move data // --------------------------------------------------------------------------- -struct BDSPMoveData{ +struct BDSPMoveData { MoveCategory category; - PokemonType type; - uint8_t base_power; - uint8_t pp; - bool spread; - uint8_t accuracy; // 0 = never-miss (e.g. Swift); otherwise 1-100 + PokemonType type; + uint8_t base_power; + uint8_t pp; + bool spread; + uint8_t accuracy; // 0 = never-miss (e.g. Swift); otherwise 1-100 }; // Returns move data for the given slug. @@ -42,14 +43,14 @@ const BDSPMoveData& bdsp_move(const std::string& slug); // Pokemon base stats // --------------------------------------------------------------------------- -struct BDSPPokemonData{ - PokemonType type[2]; // type[1] == NONE for mono-type pokemon - uint8_t hp; - uint8_t attack; - uint8_t defense; - uint8_t spatk; - uint8_t spdef; - uint8_t speed; +struct BDSPPokemonData { + PokemonType type[2]; // type[1] == NONE for mono-type pokemon + uint8_t hp; + uint8_t attack; + uint8_t defense; + uint8_t spatk; + uint8_t spdef; + uint8_t speed; }; // Returns base stats for the given species slug (e.g. "beautifly"). @@ -61,34 +62,34 @@ const BDSPPokemonData& bdsp_pokemon(const std::string& slug); // Trainer database // --------------------------------------------------------------------------- -enum class BDSPBattleType{ +enum class BDSPBattleType { SINGLES, DOUBLES, }; -struct BDSPStatSet{ - uint8_t hp = 0; - uint8_t attack = 0; +struct BDSPStatSet { + uint8_t hp = 0; + uint8_t attack = 0; uint8_t defense = 0; - uint8_t spatk = 0; - uint8_t spdef = 0; - uint8_t speed = 0; + uint8_t spatk = 0; + uint8_t spdef = 0; + uint8_t speed = 0; }; -struct BDSPTrainerPokemon{ +struct BDSPTrainerPokemon { std::string species; - uint8_t level; + uint8_t level; BDSPStatSet ivs; BDSPStatSet evs; - bool has_intimidate = false; + bool has_intimidate = false; }; -struct BDSPTrainer{ +struct BDSPTrainer { std::vector pokemon; }; -struct BDSPTrainerData{ - BDSPBattleType battle_type; +struct BDSPTrainerData { + BDSPBattleType battle_type; std::vector trainers; }; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp index 3b288bb241..0759014d39 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp @@ -6,126 +6,144 @@ #include #include + #include "CommonFramework/VideoPipeline/VideoFeed.h" + #include "CommonTools/Images/ImageFilter.h" + #include "PokemonBDSP_AutoStory_MenuCursorDetector.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { std::string menu_cursor_to_string(MenuCursorPosition pos){ switch (pos){ - case MenuCursorPosition::POKEDEX: return "POKEDEX"; - case MenuCursorPosition::POKEMON: return "POKEMON"; - case MenuCursorPosition::BAG: return "BAG"; - case MenuCursorPosition::CARD: return "CARD"; - case MenuCursorPosition::OPTIONS: return "OPTIONS"; - case MenuCursorPosition::MAP: return "MAP"; - case MenuCursorPosition::CAPSULES: return "CAPSULES"; - case MenuCursorPosition::MYSTERYGIFT: return "MYSTERYGIFT"; - default: return "UNKNOWN"; + case MenuCursorPosition::POKEDEX: + return "POKEDEX"; + case MenuCursorPosition::POKEMON: + return "POKEMON"; + case MenuCursorPosition::BAG: + return "BAG"; + case MenuCursorPosition::CARD: + return "CARD"; + case MenuCursorPosition::OPTIONS: + return "OPTIONS"; + case MenuCursorPosition::MAP: + return "MAP"; + case MenuCursorPosition::CAPSULES: + return "CAPSULES"; + case MenuCursorPosition::MYSTERYGIFT: + return "MYSTERYGIFT"; + default: + return "UNKNOWN"; } } MenuCursorDetector::MenuCursorDetector(Color color) : m_color(color) - // ------------------------------------------------------------------------- - // Top row — left to right - // ------------------------------------------------------------------------- - // TODO: replace each ImageFloatBox(0,0,0,0) with the real normalized - // (x, y, width, height) for a small region that gains dark pixels when - // that icon is hovered. Use the test program overlays to tune coordinates. - // ------------------------------------------------------------------------- - , m_pokedex_box{ - ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), - } - , m_pokemon_box{ - ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), - } - , m_bag_box{ - ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), - } - , m_card_box{ - ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), - } - // ------------------------------------------------------------------------- - // Bottom row — left to right - // 5-icon layout: OPTIONS is the only bottom icon (far-left position). - // 8-icon layout: MAP, CAPSULES, OPTIONS, MYSTERYGIFT from left to right. - // OPTIONS sits at a different x-position in each layout — use a separate - // MenuCursorDetector instance if you need to detect OPTIONS in both. - // ------------------------------------------------------------------------- - , m_options_box{ - ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), - } - , m_map_box{ - ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), - } - , m_capsules_box{ - ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), - } - , m_mysterygift_box{ - ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), - } - // ------------------------------------------------------------------------- - // Approach 2 search boxes — one per icon, same area as the dark-pixel box. - // The template PNG is slid across this area at its native size. - // ------------------------------------------------------------------------- - , m_icon_match_box{ - ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), // 0 POKEDEX - ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), // 1 POKEMON - ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), // 2 BAG - ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), // 3 CARD - ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), // 4 OPTIONS (8-icon layout) - ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), // 5 MAP - ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), // 6 CAPSULES - ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), // 7 MYSTERYGIFT - } -{} + // ------------------------------------------------------------------------- + // Top row — left to right + // ------------------------------------------------------------------------- + // TODO: replace each ImageFloatBox(0,0,0,0) with the real normalized + // (x, y, width, height) for a small region that gains dark pixels when + // that icon is hovered. Use the test program overlays to tune coordinates. + // ------------------------------------------------------------------------- + , + m_pokedex_box{ + ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), + }, + m_pokemon_box{ + ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), + }, + m_bag_box{ + ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), + }, + m_card_box{ + ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), + } // ------------------------------------------------------------------------- + // Bottom row — left to right + // 5-icon layout: OPTIONS is the only bottom icon (far-left position). + // 8-icon layout: MAP, CAPSULES, OPTIONS, MYSTERYGIFT from left to right. + // OPTIONS sits at a different x-position in each layout — use a separate + // MenuCursorDetector instance if you need to detect OPTIONS in both. + // ------------------------------------------------------------------------- + , + m_options_box{ + ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), + }, + m_map_box{ + ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), + }, + m_capsules_box{ + ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), + }, + m_mysterygift_box{ + ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), + } // ------------------------------------------------------------------------- + // Approach 2 search boxes — one per icon, same area as the dark-pixel box. + // The template PNG is slid across this area at its native size. + // ------------------------------------------------------------------------- + , + m_icon_match_box{ + ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), // 0 POKEDEX + ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), // 1 POKEMON + ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), // 2 BAG + ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), // 3 CARD + ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), // 4 OPTIONS (8-icon layout) + ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), // 5 MAP + ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), // 6 CAPSULES + ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), // 7 MYSTERYGIFT + } { +} -void MenuCursorDetector::make_overlays(VideoOverlaySet& items) const{ +void MenuCursorDetector::make_overlays(VideoOverlaySet& items) const { for (const ImageFloatBox* icon_boxes : { - m_pokedex_box, m_pokemon_box, m_bag_box, m_card_box, - m_options_box, m_map_box, m_capsules_box, m_mysterygift_box, - }){ + m_pokedex_box, + m_pokemon_box, + m_bag_box, + m_card_box, + m_options_box, + m_map_box, + m_capsules_box, + m_mysterygift_box, + }){ for (int i = 0; i < BOXES_PER_ICON; i++){ - if (icon_boxes[i].width > 0.0 && icon_boxes[i].height > 0.0) + if (icon_boxes[i].width > 0.0 && icon_boxes[i].height > 0.0){ items.add(m_color, icon_boxes[i]); + } } } for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ - if (m_icon_match_box[i].width > 0.0 && m_icon_match_box[i].height > 0.0) + if (m_icon_match_box[i].width > 0.0 && m_icon_match_box[i].height > 0.0){ items.add(m_color, m_icon_match_box[i]); + } } } -namespace{ +namespace { // Count how many of `n` boxes contain >= MIN_DARK_PIXEL_COUNT pixels with // R, G, B all <= BLACK_CHANNEL_MAX. Boxes with zero width are skipped. -int count_dark_boxes( - const ImageViewRGB32& screen, - const ImageFloatBox* boxes, int n -){ - const uint32_t ch = uint32_t(MenuCursorDetector::BLACK_CHANNEL_MAX); +int count_dark_boxes(const ImageViewRGB32& screen, const ImageFloatBox* boxes, int n){ + const uint32_t ch = uint32_t(MenuCursorDetector::BLACK_CHANNEL_MAX); const uint32_t maxs = 0xff000000u | (ch << 16) | (ch << 8) | ch; int count = 0; for (int i = 0; i < n; i++){ - if (boxes[i].width == 0.0) continue; + if (boxes[i].width == 0.0){ + continue; + } size_t dark_pixels = 0; - filter_rgb32_range( - dark_pixels, - extract_box_reference(screen, boxes[i]), - 0xff000000u, maxs, - Color(0xffffffff), false - ); - if (dark_pixels >= MenuCursorDetector::MIN_DARK_PIXEL_COUNT) count++; + filter_rgb32_range(dark_pixels, extract_box_reference(screen, boxes[i]), 0xff000000u, maxs, Color(0xffffffff), + false); + if (dark_pixels >= MenuCursorDetector::MIN_DARK_PIXEL_COUNT){ + count++; + } } return count; } @@ -133,16 +151,18 @@ int count_dark_boxes( // Pixel-wise RMSD between two same-size images over RGB channels only. double compute_rmsd(const ImageViewRGB32& a, const ImageViewRGB32& b){ size_t n = a.width() * a.height(); - if (n == 0) return 0.0; + if (n == 0){ + return 0.0; + } double sum_sq = 0.0; for (size_t y = 0; y < a.height(); y++){ for (size_t x = 0; x < a.width(); x++){ uint32_t pa = a.pixel(x, y); uint32_t pb = b.pixel(x, y); int dr = int((pa >> 16) & 0xff) - int((pb >> 16) & 0xff); - int dg = int((pa >> 8) & 0xff) - int((pb >> 8) & 0xff); - int db = int( pa & 0xff) - int( pb & 0xff); - sum_sq += double(dr*dr + dg*dg + db*db); + int dg = int((pa >> 8) & 0xff) - int((pb >> 8) & 0xff); + int db = int(pa & 0xff) - int(pb & 0xff); + sum_sq += double(dr * dr + dg * dg + db * db); } } return std::sqrt(sum_sq / (3.0 * n)); @@ -150,22 +170,21 @@ double compute_rmsd(const ImageViewRGB32& a, const ImageViewRGB32& b){ // Slide `tmpl` (at its native pixel size) across the region of `screen` // defined by `box`. Returns the minimum RMSD found over all window positions. -double template_match_rmsd( - const ImageViewRGB32& screen, - const ImageFloatBox& box, - const ImageViewRGB32& tmpl -){ +double template_match_rmsd(const ImageViewRGB32& screen, const ImageFloatBox& box, const ImageViewRGB32& tmpl){ ImageViewRGB32 region = extract_box_reference(screen, box); - size_t rw = tmpl.width(), rh = tmpl.height(); + size_t rw = tmpl.width(), rh = tmpl.height(); size_t lw = region.width(), lh = region.height(); - if (rw == 0 || rh == 0 || rw > lw || rh > lh) + if (rw == 0 || rh == 0 || rw > lw || rh > lh){ return 0.0; + } double min_rmsd = std::numeric_limits::max(); for (size_t dy = 0; dy <= lh - rh; dy++){ for (size_t dx = 0; dx <= lw - rw; dx++){ double r = compute_rmsd(region.sub_image(dx, dy, rw, rh), tmpl); - if (r < min_rmsd) min_rmsd = r; + if (r < min_rmsd){ + min_rmsd = r; + } } } return min_rmsd; @@ -174,32 +193,26 @@ double template_match_rmsd( } // anonymous namespace -MenuCursorPosition MenuCursorDetector::detect_dark_pixels( - const ImageViewRGB32& screen -) const { - struct IconEntry{ +MenuCursorPosition MenuCursorDetector::detect_dark_pixels(const ImageViewRGB32& screen) const { + struct IconEntry { MenuCursorPosition position; const ImageFloatBox* boxes; }; const IconEntry icons[] = { - { MenuCursorPosition::POKEDEX, m_pokedex_box }, - { MenuCursorPosition::POKEMON, m_pokemon_box }, - { MenuCursorPosition::BAG, m_bag_box }, - { MenuCursorPosition::CARD, m_card_box }, - { MenuCursorPosition::OPTIONS, m_options_box }, - { MenuCursorPosition::MAP, m_map_box }, - { MenuCursorPosition::CAPSULES, m_capsules_box }, - { MenuCursorPosition::MYSTERYGIFT, m_mysterygift_box }, + {MenuCursorPosition::POKEDEX, m_pokedex_box}, {MenuCursorPosition::POKEMON, m_pokemon_box}, + {MenuCursorPosition::BAG, m_bag_box}, {MenuCursorPosition::CARD, m_card_box}, + {MenuCursorPosition::OPTIONS, m_options_box}, {MenuCursorPosition::MAP, m_map_box}, + {MenuCursorPosition::CAPSULES, m_capsules_box}, {MenuCursorPosition::MYSTERYGIFT, m_mysterygift_box}, }; - MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; - int best_count = 0; + MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; + int best_count = 0; for (const auto& entry : icons){ int count = count_dark_boxes(screen, entry.boxes, BOXES_PER_ICON); if (count > best_count){ best_count = count; - best_pos = entry.position; + best_pos = entry.position; } } @@ -207,59 +220,63 @@ MenuCursorPosition MenuCursorDetector::detect_dark_pixels( } -MenuCursorPosition MenuCursorDetector::detect_image_match( - const ImageViewRGB32& screen -) const { - MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; - double best_rmsd = MATCH_RMSD_THRESHOLD; // must exceed this +MenuCursorPosition MenuCursorDetector::detect_image_match(const ImageViewRGB32& screen) const { + MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; + double best_rmsd = MATCH_RMSD_THRESHOLD; // must exceed this for (int i = 0; i < MENU_CURSOR_NUM_ICONS; i++){ - if (!m_icon_references[i].has_value()) continue; - if (m_icon_match_box[i].width == 0.0) continue; + if (!m_icon_references[i].has_value()){ + continue; + } + if (m_icon_match_box[i].width == 0.0){ + continue; + } double r = template_match_rmsd(screen, m_icon_match_box[i], *m_icon_references[i]); if (r > best_rmsd){ best_rmsd = r; - best_pos = static_cast(i); + best_pos = static_cast(i); } } return best_pos; } -MenuCursorPosition MenuCursorDetector::detect( - const ImageViewRGB32& screen -) const { +MenuCursorPosition MenuCursorDetector::detect(const ImageViewRGB32& screen) const { MenuCursorPosition p1 = detect_dark_pixels(screen); MenuCursorPosition p2 = detect_image_match(screen); - if (p1 == p2) return p1; // both agree (or both UNKNOWN) - if (p1 == MenuCursorPosition::UNKNOWN) return p2; // only approach 2 fired - if (p2 == MenuCursorPosition::UNKNOWN) return p1; // only approach 1 fired - return p1; // conflict: prefer approach 1 + if (p1 == p2){ + return p1; // both agree (or both UNKNOWN) + } + if (p1 == MenuCursorPosition::UNKNOWN){ + return p2; // only approach 2 fired + } + if (p2 == MenuCursorPosition::UNKNOWN){ + return p1; // only approach 1 fired + } + return p1; // conflict: prefer approach 1 } -void MenuCursorDetector::set_icon_reference( - MenuCursorPosition icon, - ImageRGB32 reference -){ +void MenuCursorDetector::set_icon_reference(MenuCursorPosition icon, ImageRGB32 reference){ m_icon_references[static_cast(icon)].emplace(std::move(reference)); } -double MenuCursorDetector::icon_rmsd( - const ImageViewRGB32& screen, MenuCursorPosition icon -) const { +double MenuCursorDetector::icon_rmsd(const ImageViewRGB32& screen, MenuCursorPosition icon) const { int idx = static_cast(icon); - if (!m_icon_references[idx].has_value() || m_icon_match_box[idx].width == 0.0) + if (!m_icon_references[idx].has_value() || m_icon_match_box[idx].width == 0.0){ return -1.0; + } return template_match_rmsd(screen, m_icon_match_box[idx], *m_icon_references[idx]); } MenuCursorPosition detect_menu_cursor(VideoStream& stream, ProControllerContext& /*context*/){ VideoSnapshot screen = stream.video().snapshot(); - if (!screen) return MenuCursorPosition::UNKNOWN; + if (!screen){ + return MenuCursorPosition::UNKNOWN; + } MenuCursorDetector detector; return detector.detect(screen); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h index c5ac2626c1..e7090deafb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h @@ -40,28 +40,31 @@ #include #include #include + #include "Common/Cpp/Color.h" + #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/Tools/VideoStream.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" + #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -enum class MenuCursorPosition{ - UNKNOWN = -1, - POKEDEX = 0, - POKEMON = 1, - BAG = 2, - CARD = 3, - OPTIONS = 4, - MAP = 5, - CAPSULES = 6, +enum class MenuCursorPosition { + UNKNOWN = -1, + POKEDEX = 0, + POKEMON = 1, + BAG = 2, + CARD = 3, + OPTIONS = 4, + MAP = 5, + CAPSULES = 6, MYSTERYGIFT = 7, }; @@ -70,20 +73,20 @@ static constexpr int MENU_CURSOR_NUM_ICONS = 8; std::string menu_cursor_to_string(MenuCursorPosition pos); -class MenuCursorDetector{ +class MenuCursorDetector { public: // ── Approach 1: dark pixel detection ───────────────────────────────────── - static constexpr int BOXES_PER_ICON = 3; - static constexpr int REQUIRED_VOTES = 1; // out of BOXES_PER_ICON - static constexpr uint8_t BLACK_CHANNEL_MAX = 50; // R,G,B <= this → dark pixel - static constexpr size_t MIN_DARK_PIXEL_COUNT = 1; // dark pixels per box to fire + static constexpr int BOXES_PER_ICON = 3; + static constexpr int REQUIRED_VOTES = 1; // out of BOXES_PER_ICON + static constexpr uint8_t BLACK_CHANNEL_MAX = 50; // R,G,B <= this → dark pixel + static constexpr size_t MIN_DARK_PIXEL_COUNT = 1; // dark pixels per box to fire // ── Approach 2: template match ─────────────────────────────────────────── // Reference PNGs are the UN-hovered state of each icon. When hovered, the // icon gains dark pixels and diverges from the reference, so RMSD increases. // detect_image_match() selects the icon with the HIGHEST RMSD >= threshold. // Tune upward to reduce false positives; downward if the correct icon is missed. - static constexpr double MATCH_RMSD_THRESHOLD = 53.0; + static constexpr double MATCH_RMSD_THRESHOLD = 53.0; MenuCursorDetector(Color color = COLOR_RED); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp index ce23be29b7..aacb67bfeb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp @@ -6,27 +6,27 @@ #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/Images/SolidColorTest.h" + #include "PokemonBDSP_AutoStory_OverworldDetector.h" using namespace std::chrono_literals; -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // --------------------------------------------------------------------------- // OverworldDetector // --------------------------------------------------------------------------- -OverworldDetector::OverworldDetector(Color color) - : m_color(color) - , m_battle_menu(BattleType::STANDARD, color) -{} +OverworldDetector::OverworldDetector(Color color) : m_color(color), m_battle_menu(BattleType::STANDARD, color){ +} -void OverworldDetector::make_overlays(VideoOverlaySet& items) const{ +void OverworldDetector::make_overlays(VideoOverlaySet& items) const { m_dialog.make_overlays(items); m_battle_menu.make_overlays(items); m_menu.make_overlays(items); @@ -34,9 +34,15 @@ void OverworldDetector::make_overlays(VideoOverlaySet& items) const{ bool OverworldDetector::detect(const ImageViewRGB32& screen){ // We are in the overworld when none of the overlay screens are present. - if (m_dialog.detect(screen)) return false; - if (m_battle_menu.detect(screen)) return false; - if (m_menu.detect(screen)) return false; + if (m_dialog.detect(screen)){ + return false; + } + if (m_battle_menu.detect(screen)){ + return false; + } + if (m_menu.detect(screen)){ + return false; + } return true; } @@ -46,11 +52,10 @@ bool OverworldDetector::detect(const ImageViewRGB32& screen){ // --------------------------------------------------------------------------- OverworldWatcher::OverworldWatcher(Color color) - : OverworldDetector(color) - , VisualInferenceCallback("OverworldWatcher") -{} + : OverworldDetector(color), VisualInferenceCallback("OverworldWatcher"){ +} -void OverworldWatcher::make_overlays(VideoOverlaySet& items) const{ +void OverworldWatcher::make_overlays(VideoOverlaySet& items) const { OverworldDetector::make_overlays(items); } @@ -63,12 +68,10 @@ bool OverworldWatcher::process_frame(const ImageViewRGB32& frame, WallClock /*ti // PoketchButtonDetector // --------------------------------------------------------------------------- -PoketchButtonDetector::PoketchButtonDetector(Color color) - : m_color(color) - , m_button(0.968, 0.170, 0.022, 0.120) -{} +PoketchButtonDetector::PoketchButtonDetector(Color color) : m_color(color), m_button(0.968, 0.170, 0.022, 0.120){ +} -void PoketchButtonDetector::make_overlays(VideoOverlaySet& items) const{ +void PoketchButtonDetector::make_overlays(VideoOverlaySet& items) const { items.add(m_color, m_button); } @@ -79,11 +82,10 @@ bool PoketchButtonDetector::detect(const ImageViewRGB32& screen){ } PoketchButtonWatcher::PoketchButtonWatcher(Color color) - : PoketchButtonDetector(color) - , VisualInferenceCallback("PoketchButtonWatcher") -{} + : PoketchButtonDetector(color), VisualInferenceCallback("PoketchButtonWatcher"){ +} -void PoketchButtonWatcher::make_overlays(VideoOverlaySet& items) const{ +void PoketchButtonWatcher::make_overlays(VideoOverlaySet& items) const { PoketchButtonDetector::make_overlays(items); } @@ -103,7 +105,8 @@ bool ensure_poketch_visible(VideoStream& stream, ProControllerContext& context){ if (look_for_poketch_button(stream, context)){ return true; } - stream.log("[AutoStory] Poketch button not found. Attempt " + std::to_string(attempt + 1) + " of 3.", COLOR_ORANGE); + stream.log("[AutoStory] Poketch button not found. Attempt " + std::to_string(attempt + 1) + " of 3.", + COLOR_ORANGE); if (attempt == 0){ // Most likely hidden: one tap shows it. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h index 044f75a8cf..4741325711 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h @@ -19,21 +19,25 @@ #define PokemonAutomation_PokemonBDSP_AutoStory_OverworldDetector_H #include "Common/Cpp/Color.h" + +#include "CommonFramework/Tools/VideoStream.h" + #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" -#include "CommonFramework/Tools/VideoStream.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // Static (single-frame) version — use when you already have a frame. -class OverworldDetector : public StaticScreenDetector{ +class OverworldDetector : public StaticScreenDetector { public: OverworldDetector(Color color = COLOR_CYAN); @@ -45,14 +49,14 @@ class OverworldDetector : public StaticScreenDetector{ private: Color m_color; - ShortDialogDetector m_dialog; - BattleMenuDetector m_battle_menu; - MenuDetector m_menu; + ShortDialogDetector m_dialog; + BattleMenuDetector m_battle_menu; + MenuDetector m_menu; }; // Watcher (callback) version — use with run_until<> or wait_until(). -class OverworldWatcher : public OverworldDetector, public VisualInferenceCallback{ +class OverworldWatcher : public OverworldDetector, public VisualInferenceCallback { public: OverworldWatcher(Color color = COLOR_CYAN); @@ -63,7 +67,7 @@ class OverworldWatcher : public OverworldDetector, public VisualInferenceCallbac // Detects the big red button on the right edge of the Poketch overlay. // Present only while the Poketch is shown at its normal size. -class PoketchButtonDetector : public StaticScreenDetector{ +class PoketchButtonDetector : public StaticScreenDetector { public: PoketchButtonDetector(Color color = COLOR_RED); @@ -75,7 +79,7 @@ class PoketchButtonDetector : public StaticScreenDetector{ ImageFloatBox m_button; }; -class PoketchButtonWatcher : public PoketchButtonDetector, public VisualInferenceCallback{ +class PoketchButtonWatcher : public PoketchButtonDetector, public VisualInferenceCallback { public: PoketchButtonWatcher(Color color = COLOR_RED); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 18fd199b29..0d27724388 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -1,12 +1,15 @@ + + #include #include -#include "PokemonBDSP_AutoStory.h" - #include "CommonFramework/Globals.h" #include "CommonFramework/Notifications/ProgramNotifications.h" + #include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/Options/StringSelectOption.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "StorySegments/PokemonBDSP_AutoStory_Segment_00.h" @@ -20,11 +23,11 @@ #include "StorySegments/PokemonBDSP_AutoStory_Segment_08.h" #include "StorySegments/PokemonBDSP_AutoStory_Segment_09.h" -#include "CommonTools/Options/StringSelectOption.h" +#include "PokemonBDSP_AutoStory.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { using namespace std::chrono_literals; @@ -137,7 +140,7 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - // Segment 07 + // Segment 07 checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); @@ -201,7 +204,6 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - //checkpoint_list.emplace_back(std::make_unique()); /*checkpoint_list.emplace_back(std::make_unique()); @@ -256,31 +258,34 @@ static const std::vector>& ALL_CHECKPOINTS // tutorial/main-story boundary: the starter must be returned from HOME first. static constexpr size_t TUTORIAL_CHECKPOINT_COUNT = 6; static const StringSelectDatabase& section_database(bool checkpoints, bool tutorial){ - static const StringSelectDatabase tutorial_segments = []{ + static const StringSelectDatabase tutorial_segments = [] { StringSelectDatabase db; db.add_entry(StringSelectEntry("0", ALL_SEGMENTS()[0]->name())); return db; }(); - static const StringSelectDatabase main_segments = []{ + static const StringSelectDatabase main_segments = [] { StringSelectDatabase db; - for (size_t i = 1; i < ALL_SEGMENTS().size(); i++) + for (size_t i = 1; i < ALL_SEGMENTS().size(); i++){ db.add_entry(StringSelectEntry(std::to_string(i), ALL_SEGMENTS()[i]->name())); + } return db; }(); - static const StringSelectDatabase tutorial_checkpoints = []{ + static const StringSelectDatabase tutorial_checkpoints = [] { StringSelectDatabase db; - for (size_t i = 0; i < TUTORIAL_CHECKPOINT_COUNT; i++) + for (size_t i = 0; i < TUTORIAL_CHECKPOINT_COUNT; i++){ db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + } return db; }(); - static const StringSelectDatabase main_checkpoints = []{ + static const StringSelectDatabase main_checkpoints = [] { StringSelectDatabase db; - for (size_t i = TUTORIAL_CHECKPOINT_COUNT; i < ALL_CHECKPOINTS().size(); i++) + for (size_t i = TUTORIAL_CHECKPOINT_COUNT; i < ALL_CHECKPOINTS().size(); i++){ db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + } return db; }(); return checkpoints ? (tutorial ? tutorial_checkpoints : main_checkpoints) - : (tutorial ? tutorial_segments : main_segments); + : (tutorial ? tutorial_segments : main_segments); } // --------------------------------------------------------------------------- @@ -289,23 +294,14 @@ static const StringSelectDatabase& section_database(bool checkpoints, bool tutor BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() : SingleSwitchProgramDescriptor( - "PokemonBDSP:AutoStory", - "Pokemon BDSP", - "Auto Story", - "Programs/PokemonBDSP/AutoStory.html", - "Automatically completes BDSP story segments.", - ProgramControllerClass::StandardController_NoRestrictions, - FeedbackType::REQUIRED, - AllowCommandsWhenRunning::DISABLE_COMMANDS, - PanelDeprecation::NOT_DEPRECATED, - {"Tesseract"} - ) -{} - - - -std::unique_ptr -BDSPAutoStory_Descriptor::make_stats() const{ + "PokemonBDSP:AutoStory", "Pokemon BDSP", "Auto Story", "Programs/PokemonBDSP/AutoStory.html", + "Automatically completes BDSP story segments.", PanelQualityStanding::THIRD_PARTY_UNVETTED, + ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS, {"Tesseract"}){ +} + + +std::unique_ptr BDSPAutoStory_Descriptor::make_stats() const { return std::make_unique(); } @@ -327,8 +323,6 @@ BDSPAutoStory::~BDSPAutoStory(){ START_SEGMENT.remove_listener(*this); END_SEGMENT.remove_listener(*this); ENABLE_ADVANCED_MODE.remove_listener(*this); - - } @@ -338,227 +332,179 @@ BDSPAutoStory::~BDSPAutoStory(){ BDSPAutoStory::BDSPAutoStory() : SingleSwitchProgramInstance() - // DIAGNOSTIC ONLY: Remove before pushing to production. - , DIAGNOSTIC_POST_CATCH_ONLY( + // DIAGNOSTIC ONLY: Remove before pushing to production. + , + DIAGNOSTIC_POST_CATCH_ONLY( "Diagnostic only: Run 00.04 after catch through 00.05
" "Start at the saved position immediately after catching the second Pokemon. " "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " "Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, - false - ) - // DIAGNOSTIC ONLY: Remove before pushing to production. - , DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( + LockMode::LOCK_WHILE_RUNNING, false) + // DIAGNOSTIC ONLY: Remove before pushing to production. + , + DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( "Diagnostic only: Fake checkpoint saves
" "Use the fake save helper instead of normal checkpoint saving. " "Use an existing in-game save at the selected starting checkpoint or segment. " "Any failure reloads that save and reruns the entire selected range from its start. " "Checkpoint saves are not verified in this mode. Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, false) - , STORY_SECTION( + LockMode::LOCK_WHILE_RUNNING, false), + STORY_SECTION( "Story Section:", - {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, - {StorySection::MAIN_STORY, "main-story", "Main Story"}}, - LockMode::LOCK_WHILE_RUNNING, StorySection::TUTORIAL - ) - , MAINSTORY_NOTE("", "Return your maxed starter from Pokemon HOME before starting Main Story. " - "Use the required moveset and the starting position shown below.") - , ENABLE_ADVANCED_MODE( - "Advanced mode:
" - "Use checkpoints instead of segments.", - LockMode::UNLOCK_WHILE_RUNNING, - false - ) - , START_CHECKPOINT_TUTORIAL( - "Start Checkpoint:", - section_database(true, true), - LockMode::LOCK_WHILE_RUNNING, - "0" - ) - , END_CHECKPOINT_TUTORIAL( - "End Checkpoint:", - section_database(true, true), - LockMode::UNLOCK_WHILE_RUNNING, - "5" - ) - - , START_DESCRIPTION_SEGMENT("Start:
", "") - , END_DESCRIPTION_SEGMENT("End:
", "") - - , START_DESCRIPTION_CHECKPOINT("Start:
", "") - , END_DESCRIPTION_CHECKPOINT("End:
", "") - - , START_CHECKPOINT_MAINSTORY( - "Start Checkpoint (Main):", - section_database(true, false), - LockMode::LOCK_WHILE_RUNNING, - "6" - ) - - , END_CHECKPOINT_MAINSTORY( - "End Checkpoint (Main):", - section_database(true, false), - LockMode::UNLOCK_WHILE_RUNNING, - std::to_string(ALL_CHECKPOINTS().size() - 1) - ) - - , START_SEGMENT( - "Start Segment:", - section_database(false, true), - LockMode::LOCK_WHILE_RUNNING, - "0" - ) - - , END_SEGMENT( - "End Segment:", - section_database(false, true), - LockMode::UNLOCK_WHILE_RUNNING, - "0" - ) - - , START_SEGMENT_MAINSTORY( - "Start Segment:", section_database(false, false), - LockMode::LOCK_WHILE_RUNNING, "1") - , END_SEGMENT_MAINSTORY( - "End Segment:", section_database(false, false), - LockMode::LOCK_WHILE_RUNNING, std::to_string(ALL_SEGMENTS().size() - 1)) - , STARTERCHOICE( - "Starter Pokemon:", - { - {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, - {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, - {StarterChoice::PIPLUP, "piplup", "Piplup"}, - }, - LockMode::LOCK_WHILE_RUNNING, - StarterChoice::CHIMCHAR - ) - - , STARTER_SHINY( - "Shiny Starter:", - LockMode::LOCK_WHILE_RUNNING, - false - ) - - , CATCH_LEGENDARY( - "Catch Box Legendary:", - LockMode::LOCK_WHILE_RUNNING, - false - ) - - , HELP_MODE( - "Help Mode:", - { - {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, - {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, - }, - LockMode::LOCK_WHILE_RUNNING, - HelpMode::NO_OUTSIDE_HELP - ) - - , OUTSIDE_HELP_POKEMON( - "Pokemon:", - { - {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, - }, - LockMode::LOCK_WHILE_RUNNING, - OutsideHelpPokemon::TORTERRA - ) - - , OUTSIDE_HELP_ATTACK_1( - "Attack 1:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::EARTHQUAKE - ) - - , OUTSIDE_HELP_ATTACK_2( - "Attack 2:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::ROCK_SLIDE - ) - - , OUTSIDE_HELP_ATTACK_3( - "Attack 3:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::WOOD_HAMMER - ) - - , OUTSIDE_HELP_ATTACK_4( - "Attack 4:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, - TorterraMove::CRUNCH - ) - - , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)) - , NOTIFICATIONS({ - &NOTIFICATION_STATUS_UPDATE, - &NOTIFICATION_PROGRAM_FINISH, - &NOTIFICATION_ERROR_RECOVERABLE, - &NOTIFICATION_ERROR_FATAL, - }) + {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, {StorySection::MAIN_STORY, "main-story", "Main Story"}}, + LockMode::LOCK_WHILE_RUNNING, StorySection::TUTORIAL), + MAINSTORY_NOTE("", "Return your maxed starter from Pokemon HOME before starting Main Story. " + "Use the required moveset and the starting position shown below."), + ENABLE_ADVANCED_MODE("Advanced mode:
" + "Use checkpoints instead of segments.", + LockMode::UNLOCK_WHILE_RUNNING, false), + START_CHECKPOINT_TUTORIAL("Start Checkpoint:", section_database(true, true), LockMode::LOCK_WHILE_RUNNING, + "0"), + END_CHECKPOINT_TUTORIAL("End Checkpoint:", section_database(true, true), LockMode::UNLOCK_WHILE_RUNNING, + "5") + + , + START_DESCRIPTION_SEGMENT("Start:
", ""), END_DESCRIPTION_SEGMENT("End:
", "") + + , + START_DESCRIPTION_CHECKPOINT("Start:
", ""), END_DESCRIPTION_CHECKPOINT("End:
", "") + + , + START_CHECKPOINT_MAINSTORY("Start Checkpoint (Main):", section_database(true, false), + LockMode::LOCK_WHILE_RUNNING, "6") + + , + END_CHECKPOINT_MAINSTORY("End Checkpoint (Main):", section_database(true, false), + LockMode::UNLOCK_WHILE_RUNNING, std::to_string(ALL_CHECKPOINTS().size() - 1)) + + , + START_SEGMENT("Start Segment:", section_database(false, true), LockMode::LOCK_WHILE_RUNNING, "0") + + , + END_SEGMENT("End Segment:", section_database(false, true), LockMode::UNLOCK_WHILE_RUNNING, "0") + + , + START_SEGMENT_MAINSTORY("Start Segment:", section_database(false, false), LockMode::LOCK_WHILE_RUNNING, + "1"), + END_SEGMENT_MAINSTORY("End Segment:", section_database(false, false), LockMode::LOCK_WHILE_RUNNING, + std::to_string(ALL_SEGMENTS().size() - 1)), + STARTERCHOICE("Starter Pokemon:", + { + {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, + {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, + {StarterChoice::PIPLUP, "piplup", "Piplup"}, + }, + LockMode::LOCK_WHILE_RUNNING, StarterChoice::CHIMCHAR) + + , + STARTER_SHINY("Shiny Starter:", LockMode::LOCK_WHILE_RUNNING, false) + + , + CATCH_LEGENDARY("Catch Box Legendary:", LockMode::LOCK_WHILE_RUNNING, false) + + , + HELP_MODE("Help Mode:", + { + {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, + {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, + }, + LockMode::LOCK_WHILE_RUNNING, HelpMode::NO_OUTSIDE_HELP) + + , + OUTSIDE_HELP_POKEMON("Pokemon:", + { + {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, + }, + LockMode::LOCK_WHILE_RUNNING, OutsideHelpPokemon::TORTERRA) + + , + OUTSIDE_HELP_ATTACK_1("Attack 1:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, TorterraMove::EARTHQUAKE) + + , + OUTSIDE_HELP_ATTACK_2("Attack 2:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, TorterraMove::ROCK_SLIDE) + + , + OUTSIDE_HELP_ATTACK_3("Attack 3:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, TorterraMove::WOOD_HAMMER) + + , + OUTSIDE_HELP_ATTACK_4("Attack 4:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, TorterraMove::CRUNCH) + + , + NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)), + NOTIFICATIONS({ + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_RECOVERABLE, + &NOTIFICATION_ERROR_FATAL, + }) { // ----------------------------------------------------------------------- @@ -640,9 +586,7 @@ void BDSPAutoStory::update_ui_state(){ bool show_help = ((HelpMode)HELP_MODE == HelpMode::OUTSIDE_HELP); - OUTSIDE_HELP_POKEMON.set_visibility( - show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN - ); + OUTSIDE_HELP_POKEMON.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); OUTSIDE_HELP_ATTACK_1.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); OUTSIDE_HELP_ATTACK_2.set_visibility(show_help ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN); @@ -655,34 +599,26 @@ void BDSPAutoStory::on_config_value_changed(void*){ const auto& checkpoints = ALL_CHECKPOINTS(); size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); if (start < checkpoints.size()){ - START_DESCRIPTION_CHECKPOINT.set_text( - "Start: " + checkpoints[start]->start_text() - ); + START_DESCRIPTION_CHECKPOINT.set_text("Start: " + checkpoints[start]->start_text()); } if (end < checkpoints.size()){ - END_DESCRIPTION_CHECKPOINT.set_text( - "End: " + checkpoints[end]->end_text() - ); + END_DESCRIPTION_CHECKPOINT.set_text("End: " + checkpoints[end]->end_text()); } }else{ const auto& segments = ALL_SEGMENTS(); size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); if (start < segments.size()){ - START_DESCRIPTION_SEGMENT.set_text( - "Start: " + segments[start]->start_text() - ); + START_DESCRIPTION_SEGMENT.set_text("Start: " + segments[start]->start_text()); } if (end < segments.size()){ - END_DESCRIPTION_SEGMENT.set_text( - "End: " + segments[end]->end_text() - ); + END_DESCRIPTION_SEGMENT.set_text("End: " + segments[end]->end_text()); } } @@ -692,36 +628,33 @@ void BDSPAutoStory::on_config_value_changed(void*){ // Program logic // --------------------------------------------------------------------------- -void BDSPAutoStory::program( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -){ +void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ AutoStoryStats& stats = env.current_stats(); - AutoStoryOptions options{ - Language::English, - STARTERCHOICE, - NOTIFICATION_STATUS_UPDATE, - (HelpMode)HELP_MODE, - OutsideHelpConfig{ - (OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, - { - (TorterraMove)OUTSIDE_HELP_ATTACK_1, - (TorterraMove)OUTSIDE_HELP_ATTACK_2, - (TorterraMove)OUTSIDE_HELP_ATTACK_3, - (TorterraMove)OUTSIDE_HELP_ATTACK_4, - } - }, - DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL - }; + AutoStoryOptions options{Language::English, + STARTERCHOICE, + NOTIFICATION_STATUS_UPDATE, + (HelpMode)HELP_MODE, + OutsideHelpConfig{(OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, + { + (TorterraMove)OUTSIDE_HELP_ATTACK_1, + (TorterraMove)OUTSIDE_HELP_ATTACK_2, + (TorterraMove)OUTSIDE_HELP_ATTACK_3, + (TorterraMove)OUTSIDE_HELP_ATTACK_4, + }}, + DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL}; const bool fake_saves = options.checkpoint_save_mode == CheckpointSaveMode::FAKE; - if (fake_saves) options.checkpoint_retry_scope = CheckpointRetryScope::DEFER_TO_SELECTED_RANGE; + if (fake_saves){ + options.checkpoint_retry_scope = CheckpointRetryScope::DEFER_TO_SELECTED_RANGE; + } auto run_selected_range = [&](size_t attempt){ - if (attempt > 0) env.log("[Fake saves] Reloaded initial save; restarting the entire selected range from its start."); + if (attempt > 0){ + env.log("[Fake saves] Reloaded initial save; restarting the entire selected range from its start."); + } // DIAGNOSTIC ONLY: Remove this branch before pushing to production. - if (DIAGNOSTIC_POST_CATCH_ONLY && ENABLE_ADVANCED_MODE && - STORY_SECTION == StorySection::TUTORIAL && START_CHECKPOINT_TUTORIAL.slug() == "4"){ + if (DIAGNOSTIC_POST_CATCH_ONLY && ENABLE_ADVANCED_MODE && STORY_SECTION == StorySection::TUTORIAL && + START_CHECKPOINT_TUTORIAL.slug() == "4"){ env.log("Diagnostic only: Running checkpoint 00.04 after catch."); diagnostic_checkpoint_0004_after_catch(env, context, options, stats); env.log("Diagnostic continuation: Running checkpoint 00.05."); @@ -734,10 +667,9 @@ void BDSPAutoStory::program( if (ENABLE_ADVANCED_MODE){ size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); - env.log("Starting AutoStory from checkpoint " + std::to_string(start) + - " to " + std::to_string(end)); + env.log("Starting AutoStory from checkpoint " + std::to_string(start) + " to " + std::to_string(end)); const auto& checkpoints = ALL_CHECKPOINTS(); @@ -748,7 +680,7 @@ void BDSPAutoStory::program( }else{ size_t start = std::stoul((tutorial ? START_SEGMENT : START_SEGMENT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); + size_t end = std::stoul((tutorial ? END_SEGMENT : END_SEGMENT_MAINSTORY).slug()); for (size_t i = start; i <= end && i < segments.size(); i++){ env.log("Running Segment: " + std::to_string(i)); @@ -757,9 +689,8 @@ void BDSPAutoStory::program( } }; if (fake_saves){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - std::move(run_selected_range), true, CheckpointSaveMode::FAKE, - CheckpointRetryScope::SELECTED_RANGE); + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, std::move(run_selected_range), true, + CheckpointSaveMode::FAKE, CheckpointRetryScope::SELECTED_RANGE); }else{ run_selected_range(0); } @@ -768,7 +699,6 @@ void BDSPAutoStory::program( } - } } } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h index da56201aa9..541918b1c0 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h @@ -2,21 +2,29 @@ #include #include + #include "Common/Cpp/Options/BooleanCheckBoxOption.h" #include "Common/Cpp/Options/EnumDropdownOption.h" #include "Common/Cpp/Options/StaticTextOption.h" + #include "CommonFramework/Notifications/EventNotificationsTable.h" + #include "CommonTools/Options/StringSelectOption.h" + #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" + #include "PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -enum class StorySection { TUTORIAL, MAIN_STORY }; +enum class StorySection { + TUTORIAL, + MAIN_STORY +}; -class BDSPAutoStory_Descriptor : public SingleSwitchProgramDescriptor{ +class BDSPAutoStory_Descriptor : public SingleSwitchProgramDescriptor { public: BDSPAutoStory_Descriptor(); @@ -24,21 +32,19 @@ class BDSPAutoStory_Descriptor : public SingleSwitchProgramDescriptor{ }; -class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::Listener{ +class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::Listener { public: using Descriptor = BDSPAutoStory_Descriptor; ~BDSPAutoStory(); BDSPAutoStory(); - virtual void program( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context - ) override; + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; private: virtual void on_config_value_changed(void* object) override; void update_ui_state(); + private: // DIAGNOSTIC ONLY: Remove before pushing to production. BooleanCheckBoxOption DIAGNOSTIC_POST_CATCH_ONLY; @@ -63,15 +69,15 @@ class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::L StringSelectOption START_SEGMENT_MAINSTORY; StringSelectOption END_SEGMENT_MAINSTORY; - EnumDropdownOption STARTERCHOICE; - BooleanCheckBoxOption STARTER_SHINY; - BooleanCheckBoxOption CATCH_LEGENDARY; - EnumDropdownOption HELP_MODE; + EnumDropdownOption STARTERCHOICE; + BooleanCheckBoxOption STARTER_SHINY; + BooleanCheckBoxOption CATCH_LEGENDARY; + EnumDropdownOption HELP_MODE; EnumDropdownOption OUTSIDE_HELP_POKEMON; - EnumDropdownOption OUTSIDE_HELP_ATTACK_1; - EnumDropdownOption OUTSIDE_HELP_ATTACK_2; - EnumDropdownOption OUTSIDE_HELP_ATTACK_3; - EnumDropdownOption OUTSIDE_HELP_ATTACK_4; + EnumDropdownOption OUTSIDE_HELP_ATTACK_1; + EnumDropdownOption OUTSIDE_HELP_ATTACK_2; + EnumDropdownOption OUTSIDE_HELP_ATTACK_3; + EnumDropdownOption OUTSIDE_HELP_ATTACK_4; EventNotificationOption NOTIFICATION_STATUS_UPDATE; EventNotificationsOption NOTIFICATIONS; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 944aeac473..229eba3076 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -1,81 +1,101 @@ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" -#include "CommonFramework/GlobalAutoPaths.h" -#include "Common/Cpp/Filesystem/Filesystem.h" + + + /* BDSP AutoStory Tools * * From: https://github.com/PokemonAutomation/ * */ + +#include #include -#include "CommonFramework/ImageTools/ImageDiff.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include +#include + +#include "Common/Cpp/Filesystem/Filesystem.h" + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/Globals.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageDiff.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/Notifications/ProgramNotifications.h" #include "CommonFramework/Tools/VideoStream.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" + #include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "CommonTools/Images/ImageFilter.h" #include "CommonTools/Images/SolidColorTest.h" -#include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" #include "CommonTools/OCR/OCR_Routines.h" -#include -#include "CommonTools/Images/ImageFilter.h" -#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" + #include "Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "PokemonBDSP_AutoStoryTools.h" -#include -#include +#include "PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { const char* torterra_move_slug(TorterraMove move){ switch (move){ - case TorterraMove::EARTHQUAKE: return "earthquake"; - case TorterraMove::WOOD_HAMMER: return "wood-hammer"; - case TorterraMove::RAZOR_LEAF: return "razor-leaf"; - case TorterraMove::CRUNCH: return "crunch"; - case TorterraMove::GIGA_DRAIN: return "giga-drain"; - case TorterraMove::IRON_TAIL: return "iron-tail"; - case TorterraMove::ROCK_TOMB: return "rock-tomb"; - case TorterraMove::FACADE: return "facade"; - case TorterraMove::STONE_EDGE: return "stone-edge"; - case TorterraMove::ROCK_SLIDE: return "rock-slide"; - case TorterraMove::BULLDOZE: return "bulldoze"; - case TorterraMove::STRENGTH: return "strength"; - case TorterraMove::ROCK_SMASH: return "rock-smash"; - case TorterraMove::ROCK_CLIMB: return "rock-climb"; + case TorterraMove::EARTHQUAKE: + return "earthquake"; + case TorterraMove::WOOD_HAMMER: + return "wood-hammer"; + case TorterraMove::RAZOR_LEAF: + return "razor-leaf"; + case TorterraMove::CRUNCH: + return "crunch"; + case TorterraMove::GIGA_DRAIN: + return "giga-drain"; + case TorterraMove::IRON_TAIL: + return "iron-tail"; + case TorterraMove::ROCK_TOMB: + return "rock-tomb"; + case TorterraMove::FACADE: + return "facade"; + case TorterraMove::STONE_EDGE: + return "stone-edge"; + case TorterraMove::ROCK_SLIDE: + return "rock-slide"; + case TorterraMove::BULLDOZE: + return "bulldoze"; + case TorterraMove::STRENGTH: + return "strength"; + case TorterraMove::ROCK_SMASH: + return "rock-smash"; + case TorterraMove::ROCK_CLIMB: + return "rock-climb"; } return "unknown"; } const char* trainer_slug(Trainer t){ switch (t){ - case Trainer::Veilstone_GalacticGrunts_2v2: return "Veilstone_GalacticGrunts_2v2"; + case Trainer::Veilstone_GalacticGrunts_2v2: + return "Veilstone_GalacticGrunts_2v2"; } return "unknown"; } -bool fake_save_game( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -){ +bool fake_save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ pbf_mash_button(context, BUTTON_L, 1000ms); context.wait_for_all_requests(); @@ -87,21 +107,16 @@ bool fake_save_game( return true; } -bool save_game( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -) { +bool save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ static constexpr size_t MAX_RETRIES = 3; - for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++) { + for (size_t attempt = 0; attempt < MAX_RETRIES; attempt++){ try { - env.console.log( - "Checkpoint save attempt: " + std::to_string(attempt + 1) - ); + env.console.log("Checkpoint save attempt: " + std::to_string(attempt + 1)); // Only mash B on retries - no need to on first attempt // if already cleanly in the overworld. - if (attempt > 0) { + if (attempt > 0){ pbf_mash_button(context, BUTTON_B, 1000ms); } @@ -118,41 +133,26 @@ bool save_game( // Verify overworld. Generous timeout: save animation + fade can be slow. OverworldWatcher overworld; - int ret = wait_until( - env.console, - context, - std::chrono::seconds(15), - { overworld } - ); - if (ret < 0) { - throw OperationFailedExceptionWithScreenshot( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to detect overworld after save.", - env.console - ); + int ret = wait_until(env.console, context, std::chrono::seconds(15), {overworld}); + if (ret < 0){ + throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, + "Failed to detect overworld after save.", env.console); } env.console.log("Checkpoint save verified."); return true; - } - catch (OperationFailedException& e) { - env.console.log( - "Checkpoint save failed: " + e.message(), - COLOR_RED - ); + } catch (OperationFailedException& e){ + env.console.log("Checkpoint save failed: " + e.message(), COLOR_RED); context.wait_for(1000ms); } } // Blind fallback: reuse the non-feedback save_game(context) from GameNavigation // rather than duplicating the button sequence manually. - env.console.log( - "All save retries failed. Using blind fallback.", - COLOR_ORANGE - ); + env.console.log("All save retries failed. Using blind fallback.", COLOR_ORANGE); pbf_mash_button(context, BUTTON_B, 1000ms); - save_game(context); // PokemonBDSP_GameNavigation.h - non-feedback overload + save_game(context); // PokemonBDSP_GameNavigation.h - non-feedback overload pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); @@ -160,13 +160,9 @@ bool save_game( } -void checkpoint_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - CheckpointSaveMode save_mode -){ +void checkpoint_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats, + CheckpointSaveMode save_mode){ const bool fake = save_mode == CheckpointSaveMode::FAKE; env.console.log(fake ? "[Checkpoint save] DIAGNOSTIC FAKE mode: save is not verified." : "[Checkpoint save] REAL mode."); @@ -175,20 +171,14 @@ void checkpoint_save( stats.m_checkpoint++; env.update_stats(); - send_program_status_notification( - env, - notif_status_update, - fake ? "Diagnostic fake checkpoint save (not verified)." : success - ? "Saved at checkpoint (verified)." - : "Saved at checkpoint (blind fallback)." - ); + send_program_status_notification(env, notif_status_update, + fake ? "Diagnostic fake checkpoint save (not verified)." + : success ? "Saved at checkpoint (verified)." + : "Saved at checkpoint (blind fallback)."); } -void reload_backup_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -){ +void reload_backup_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // TODO: implement backup save reload. // This is called when a checkpoint with save=false fails. // Load the game's automatic backup save rather than resetting from HOME. @@ -197,20 +187,14 @@ void reload_backup_save( } -void checkpoint_reattempt_loop( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - std::function&& action, - bool save, - CheckpointSaveMode save_mode, - CheckpointRetryScope retry_scope -){ +void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats, + std::function&& action, bool save, + CheckpointSaveMode save_mode, CheckpointRetryScope retry_scope){ static constexpr size_t MAX_ATTEMPTS = 20; for (size_t i = 0;; i++){ - try{ + try { if (i == 0 && retry_scope != CheckpointRetryScope::SELECTED_RANGE){ if (save || stats.m_first_run){ checkpoint_save(env, context, notif_status_update, stats, save_mode); @@ -221,9 +205,12 @@ void checkpoint_reattempt_loop( action(i); return; // success - }catch (OperationFailedException& e){ - if (retry_scope == CheckpointRetryScope::DEFER_TO_SELECTED_RANGE) throw; - env.console.log("checkpoint_reattempt_loop: attempt " + std::to_string(i) + " failed: " + e.message(), COLOR_RED); + } catch (OperationFailedException& e){ + if (retry_scope == CheckpointRetryScope::DEFER_TO_SELECTED_RANGE){ + throw; + } + env.console.log("checkpoint_reattempt_loop: attempt " + std::to_string(i) + " failed: " + e.message(), + COLOR_RED); stats.m_reset++; env.update_stats(); @@ -236,12 +223,10 @@ void checkpoint_reattempt_loop( bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); if (!ok){ throw OperationFailedExceptionWithScreenshot( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to reset game from HOME during checkpoint retry.", - env.console - ); + ErrorReportMode::SEND_ERROR_REPORT, "Failed to reset game from HOME during checkpoint retry.", + env.console); } - } else { + }else{ env.console.log("Reloading from backup save before retry...", COLOR_ORANGE); reload_backup_save(env, context); } @@ -250,7 +235,6 @@ void checkpoint_reattempt_loop( } - void repeat(std::initializer_list> actions, size_t times){ for (size_t i = 0; i < times; i++){ for (const auto& action : actions){ @@ -260,79 +244,59 @@ void repeat(std::initializer_list> actions, size_t times){ } -void wait_for_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label, - Milliseconds timeout -){ - auto log_detected = [&]{ +void wait_for_dialogue(VideoStream& stream, ProControllerContext& context, const std::string& label, + Milliseconds timeout){ + auto log_detected = [&] { if (!label.empty()){ stream.log(label + " dialogue detected."); } }; ShortDialogWatcher watcher_cyan(COLOR_CYAN); - int ret = run_until( - stream, context, - [timeout](ProControllerContext& context){ - context.wait_for(timeout); - }, - {{watcher_cyan}} - ); - if (ret == 0){ log_detected(); return; } + int ret = run_until(stream, context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_cyan}}); + if (ret == 0){ + log_detected(); + return; + } ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until( - stream, context, - [timeout](ProControllerContext& context){ - context.wait_for(timeout); - }, - {{watcher_red}} - ); - if (ret == 0){ log_detected(); return; } + ret = run_until(stream, context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_red}}); + if (ret == 0){ + log_detected(); + return; + } OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", - stream - ); + (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", stream); } -void mash_until_dialogue_ends( - VideoStream& stream, - ProControllerContext& context, - Button button, - Milliseconds timeout -){ +void mash_until_dialogue_ends(VideoStream& stream, ProControllerContext& context, Button button, Milliseconds timeout){ OverworldWatcher overworld; - int ret = run_until( - stream, context, - [button, timeout](ProControllerContext& context){ - pbf_mash_button(context, button, timeout); - }, - {{overworld}} - ); + int ret = run_until(stream, context, + [button, timeout](ProControllerContext& context){ + pbf_mash_button(context, button, timeout); + }, + {{overworld}}); if (ret < 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "mash_until_dialogue_ends: overworld not reached within timeout.", - stream - ); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "mash_until_dialogue_ends: overworld not reached within timeout.", + stream); } } -void repeat_dpad( - ProControllerContext& context, - DpadState& state, - DpadPosition dir, - Milliseconds press, - Milliseconds hold, - size_t times, - bool include_last_dir -){ +void repeat_dpad(ProControllerContext& context, DpadState& state, DpadPosition dir, Milliseconds press, + Milliseconds hold, size_t times, bool include_last_dir){ if (include_last_dir && state.last_dir != DPAD_NONE && state.last_dir != dir){ times += 1; } @@ -344,38 +308,64 @@ void repeat_dpad( static const char* shop_item_name(ShopItem item){ switch (item){ - case ShopItem::FullRestore: return "Full restore"; - case ShopItem::MaxPotion: return "Max potion"; - case ShopItem::HyperPotion: return "Hyper potion"; - case ShopItem::SuperPotion: return "Super potion"; - case ShopItem::Potion: return "Potion"; - case ShopItem::Antidote: return "Antidote"; - case ShopItem::ParalyzHeal: return "Paralyz heal"; - case ShopItem::Awakening: return "Awakening"; - case ShopItem::Repel: return "Repel"; - case ShopItem::SuperRepel: return "Super repel"; - case ShopItem::MaxRepel: return "Max repel"; - case ShopItem::EscapeRope: return "Escape rope"; - default: return "Unknown"; + case ShopItem::FullRestore: + return "Full restore"; + case ShopItem::MaxPotion: + return "Max potion"; + case ShopItem::HyperPotion: + return "Hyper potion"; + case ShopItem::SuperPotion: + return "Super potion"; + case ShopItem::Potion: + return "Potion"; + case ShopItem::Antidote: + return "Antidote"; + case ShopItem::ParalyzHeal: + return "Paralyz heal"; + case ShopItem::Awakening: + return "Awakening"; + case ShopItem::Repel: + return "Repel"; + case ShopItem::SuperRepel: + return "Super repel"; + case ShopItem::MaxRepel: + return "Max repel"; + case ShopItem::EscapeRope: + return "Escape rope"; + default: + return "Unknown"; } } // Returns the OCR slug used as the dictionary key in ShopItemNameOCR.json. static const char* shop_item_slug(ShopItem item){ switch (item){ - case ShopItem::FullRestore: return "full-restore"; - case ShopItem::MaxPotion: return "max-potion"; - case ShopItem::HyperPotion: return "hyper-potion"; - case ShopItem::SuperPotion: return "super-potion"; - case ShopItem::Potion: return "potion"; - case ShopItem::Antidote: return "antidote"; - case ShopItem::ParalyzHeal: return "paralyze-heal"; - case ShopItem::Awakening: return "awakening"; - case ShopItem::Repel: return "repel"; - case ShopItem::SuperRepel: return "super-repel"; - case ShopItem::MaxRepel: return "max-repel"; - case ShopItem::EscapeRope: return "escape-rope"; - default: return "unknown"; + case ShopItem::FullRestore: + return "full-restore"; + case ShopItem::MaxPotion: + return "max-potion"; + case ShopItem::HyperPotion: + return "hyper-potion"; + case ShopItem::SuperPotion: + return "super-potion"; + case ShopItem::Potion: + return "potion"; + case ShopItem::Antidote: + return "antidote"; + case ShopItem::ParalyzHeal: + return "paralyze-heal"; + case ShopItem::Awakening: + return "awakening"; + case ShopItem::Repel: + return "repel"; + case ShopItem::SuperRepel: + return "super-repel"; + case ShopItem::MaxRepel: + return "max-repel"; + case ShopItem::EscapeRope: + return "escape-rope"; + default: + return "unknown"; } } @@ -383,10 +373,12 @@ static const char* shop_item_slug(ShopItem item){ static std::string make_buy_label(const std::vector& items){ std::string label; for (size_t i = 0; i < items.size(); i++){ - if (i > 0) label += ", "; + if (i > 0){ + label += ", "; + } if (items[i].quantity == BUY_MAX){ label += "max "; - } else { + }else{ label += std::to_string(items[i].quantity) + " "; } label += shop_item_name(items[i].item); @@ -394,12 +386,8 @@ static std::string make_buy_label(const std::vector& items){ return label; } -bool buy_items( - VideoStream& stream, - ProControllerContext& context, - const std::vector& items, - Language language -){ +bool buy_items(VideoStream& stream, ProControllerContext& context, const std::vector& items, + Language language){ if (items.empty()){ return true; } @@ -424,157 +412,152 @@ bool buy_items( // The hover detector scans this region to dynamically locate the red outline. static const ImageFloatBox ITEM_SCAN_AREA{0.417000, 0.100000, 0.540000, 0.625000}; - static constexpr size_t MAX_OUTER = 50; - static constexpr size_t MAX_ITEM_SEARCH = 50; // max iterations per item before giving up + static constexpr size_t MAX_OUTER = 50; + static constexpr size_t MAX_ITEM_SEARCH = 50; // max iterations per item before giving up // ── Outer state machine ────────────────────────────────────────────────── for (size_t outer = 0; outer < MAX_OUTER; outer++){ - SelectionArrowFinder arrow_vendor(stream.overlay(), VENDOR_ARROW_BOX, COLOR_GREEN); + SelectionArrowFinder arrow_vendor(stream.overlay(), VENDOR_ARROW_BOX, COLOR_GREEN); ShopBuyingWindowWatcher buying_window; - ShortDialogWatcher dialog(COLOR_RED); + ShortDialogWatcher dialog(COLOR_RED); // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. stream.log("Starting to loop over items"); - int state = run_until( - stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 5; i++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); - } - }, - {{arrow_vendor}, {buying_window}, {dialog}} - ); + int state = run_until(stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 5; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + } + }, + {{arrow_vendor}, {buying_window}, {dialog}}); context.wait_for_all_requests(); pbf_wait(context, 500ms); switch (state){ - case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" + case 0: // case 1: SelectionArrow at vendor menu → press A to choose "Buy" stream.log("Found the selection arrow, pressing A to enter the buying window...", COLOR_GREEN); pbf_press_button(context, BUTTON_A, 80ms, 500ms); break; - case 1: // case 2: buying window open → buy each requested item - stream.log("Found the buying window, searching for the right item...", COLOR_GREEN); - { - bool buying_failed = false; - - for (const ItemToBuy& item : items){ - stream.log("Buying " + std::to_string(item.quantity) + "x " + shop_item_slug(item.item)); - if (buying_failed) break; - - // Detector fires only when the red outline is found AND both - // name and price OCR match the expected item slug. - ShopItemHoverDetector hover(stream.logger(), ITEM_SCAN_AREA, - shop_item_slug(item.item), language, COLOR_CYAN); - ShopQuantityWindowWatcher qty_window; - SelectionArrowFinder confirm_arrow(stream.overlay(), CONFIRM_ARROW_BOX, COLOR_GREEN); - - bool item_bought = false; - bool hover_confirmed = false; - for (size_t inner = 0; inner < MAX_ITEM_SEARCH && !item_bought; inner++){ - context.wait_for_all_requests(); + case 1: // case 2: buying window open → buy each requested item + stream.log("Found the buying window, searching for the right item...", COLOR_GREEN); + { + bool buying_failed = false; - int sub = run_until( - stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(1000ms); - }, - {{confirm_arrow}, {qty_window}, {hover}} - ); + for (const ItemToBuy& item : items){ + stream.log("Buying " + std::to_string(item.quantity) + "x " + shop_item_slug(item.item)); + if (buying_failed){ + break; + } - context.wait_for_all_requests(); + // Detector fires only when the red outline is found AND both + // name and price OCR match the expected item slug. + ShopItemHoverDetector hover(stream.logger(), ITEM_SCAN_AREA, shop_item_slug(item.item), language, + COLOR_CYAN); + ShopQuantityWindowWatcher qty_window; + SelectionArrowFinder confirm_arrow(stream.overlay(), CONFIRM_ARROW_BOX, COLOR_GREEN); - stream.log("buy_items: sub=" + std::to_string(sub), COLOR_WHITE); + bool item_bought = false; + bool hover_confirmed = false; + for (size_t inner = 0; inner < MAX_ITEM_SEARCH && !item_bought; inner++){ + context.wait_for_all_requests(); - switch (sub){ + int sub = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(1000ms); + }, + {{confirm_arrow}, {qty_window}, {hover}}); - case 0: // YES/NO confirmation arrow → confirm purchase - stream.log("Found the confirmation to buy arrow, pressing A to buy", COLOR_GREEN); - { context.wait_for_all_requests(); - ShopBuyingWindowWatcher back_in_shop; - run_until( - stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 10; i++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); + + stream.log("buy_items: sub=" + std::to_string(sub), COLOR_WHITE); + + switch (sub){ + + case 0: // YES/NO confirmation arrow → confirm purchase + stream.log("Found the confirmation to buy arrow, pressing A to buy", COLOR_GREEN); + { + context.wait_for_all_requests(); + ShopBuyingWindowWatcher back_in_shop; + run_until(stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 10; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); + } + }, + {{back_in_shop}}); + item_bought = true; + break; + } + + case 1: // quantity window + if (!hover_confirmed){ + // Quantity window appeared before we confirmed the right item — + // an earlier A press accidentally opened the wrong item's selector. + // Press B to go back to the list and continue searching. + stream.log("Unexpected quantity window (wrong item), pressing B...", COLOR_YELLOW); + pbf_press_button(context, BUTTON_B, 80ms, 800ms); + break; + } + stream.log("Found the quantity window", COLOR_GREEN); + if (item.quantity == BUY_MAX){ + stream.log("Buying max quantity"); + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + }else{ + stream.log("Buying " + std::to_string(item.quantity) + " of the item"); + for (int q = 1; q < item.quantity; q++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); } - }, - {{back_in_shop}} - ); - item_bought = true; - break; - } + } + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 1000ms); + break; - case 1: // quantity window - if (!hover_confirmed){ - // Quantity window appeared before we confirmed the right item — - // an earlier A press accidentally opened the wrong item's selector. - // Press B to go back to the list and continue searching. - stream.log("Unexpected quantity window (wrong item), pressing B...", COLOR_YELLOW); - pbf_press_button(context, BUTTON_B, 80ms, 800ms); + case 2: // correct item highlighted → press A to open quantity selector + stream.log("Found the right item, pressing A to buy a certain quantity...", COLOR_GREEN); + hover_confirmed = true; + pbf_press_button(context, BUTTON_A, 80ms, 300ms); + pbf_wait(context, 1000ms); break; - } - stream.log("Found the quantity window", COLOR_GREEN); - if (item.quantity == BUY_MAX){ - stream.log("Buying max quantity"); + + default: // outline not found or wrong item → advance to next row + stream.log("Not on the right item, moving...", COLOR_GREEN); pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } else { - stream.log("Buying " + std::to_string(item.quantity) + " of the item"); - for (int q = 1; q < item.quantity; q++){ - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - } + pbf_wait(context, 300ms); + break; } - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 1000ms); - break; - - case 2: // correct item highlighted → press A to open quantity selector - stream.log("Found the right item, pressing A to buy a certain quantity...", COLOR_GREEN); - hover_confirmed = true; - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 1000ms); - break; + } - default: // outline not found or wrong item → advance to next row - stream.log("Not on the right item, moving...", COLOR_GREEN); - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - pbf_wait(context, 300ms); - break; + if (!item_bought){ + stream.log(label + ": buy_items: could not locate " + shop_item_name(item.item) + + " in shop list.", + COLOR_RED); + buying_failed = true; } } - if (!item_bought){ - stream.log(label + ": buy_items: could not locate " + - shop_item_name(item.item) + " in shop list.", COLOR_RED); - buying_failed = true; + if (!buying_failed){ + stream.log("All items bought, exiting shop...", COLOR_GREEN); + pbf_mash_button(context, BUTTON_B, 5000ms); + return true; } + break; } - if (!buying_failed){ - stream.log("All items bought, exiting shop...", COLOR_GREEN); - pbf_mash_button(context, BUTTON_B, 5000ms); - return true; - } - break; - } - - case 2: // case 3: unexpected ShortDialog → press B until back in buying window + case 2: // case 3: unexpected ShortDialog → press B until back in buying window { ShopBuyingWindowWatcher back_in_shop; - run_until( - stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 5000ms); - }, - {{back_in_shop}} - ); + run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 5000ms); + }, + {{back_in_shop}}); break; } - default: // outer default: unrecognised state → press B to recover + default: // outer default: unrecognised state → press B to recover pbf_press_button(context, BUTTON_B, 80ms, 3000ms); break; } @@ -585,11 +568,7 @@ bool buy_items( } -bool heal_pokemon( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -){ +bool heal_pokemon(VideoStream& stream, ProControllerContext& context, const std::string& label){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -606,13 +585,11 @@ bool heal_pokemon( // Exiting the Pokemon Center BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_pokemon(" + label + "): black screen not detected", COLOR_RED); return false; @@ -621,35 +598,49 @@ bool heal_pokemon( return true; } -std::string fly_point_name(FlyPoint place) { - switch (place) { - case FlyPoint::CanalavCity: return "canalave_city"; - case FlyPoint::CelesticTown: return "celestic_town"; - case FlyPoint::EternaCity: return "eterna_city"; - case FlyPoint::FloaromaTown: return "floaroma_town"; - case FlyPoint::HearthomeCity: return "hearthome_city"; - case FlyPoint::JubilifeCity: return "jubilife_city"; - case FlyPoint::OreburghCity: return "oreburgh_city"; - case FlyPoint::PastoriaCity: return "pastoria_city"; - case FlyPoint::PokemonLeagueLower: return "pokemon_league_lower"; - case FlyPoint::PokemonLeagueUpper: return "pokemon_league_upper"; - case FlyPoint::Route221: return "route_221"; - case FlyPoint::SandgemTown: return "sandgem_town"; - case FlyPoint::SolaceonTown: return "solaceon_town"; - case FlyPoint::SnowpointCity: return "snowpoint_city"; - case FlyPoint::SunyshoreCity: return "sunyshore_city"; - case FlyPoint::TwinleafTown: return "twinleaf_town"; - case FlyPoint::VeilstoneCity: return "veilstone_city"; - case FlyPoint::CurrentCity: return "current_city"; +std::string fly_point_name(FlyPoint place){ + switch (place){ + case FlyPoint::CanalavCity: + return "canalave_city"; + case FlyPoint::CelesticTown: + return "celestic_town"; + case FlyPoint::EternaCity: + return "eterna_city"; + case FlyPoint::FloaromaTown: + return "floaroma_town"; + case FlyPoint::HearthomeCity: + return "hearthome_city"; + case FlyPoint::JubilifeCity: + return "jubilife_city"; + case FlyPoint::OreburghCity: + return "oreburgh_city"; + case FlyPoint::PastoriaCity: + return "pastoria_city"; + case FlyPoint::PokemonLeagueLower: + return "pokemon_league_lower"; + case FlyPoint::PokemonLeagueUpper: + return "pokemon_league_upper"; + case FlyPoint::Route221: + return "route_221"; + case FlyPoint::SandgemTown: + return "sandgem_town"; + case FlyPoint::SolaceonTown: + return "solaceon_town"; + case FlyPoint::SnowpointCity: + return "snowpoint_city"; + case FlyPoint::SunyshoreCity: + return "sunyshore_city"; + case FlyPoint::TwinleafTown: + return "twinleaf_town"; + case FlyPoint::VeilstoneCity: + return "veilstone_city"; + case FlyPoint::CurrentCity: + return "current_city"; } return "unknown"; } -bool fly_to( - VideoStream& stream, - ProControllerContext& context, - FlyPoint place -){ +bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ // Each city branch below should update this to the city icon's position on the fly map. // Detection: when the cursor brackets overlap the icon, its normally uniform // color becomes non-uniform (stddev rises). Box stays as a rough fallback @@ -661,13 +652,14 @@ bool fly_to( pbf_wait(context, 1000ms); open_menu(stream, context, MenuCursorPosition::MAP, 8); - if (place == FlyPoint::CurrentCity) { + if (place == FlyPoint::CurrentCity){ ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; struct FlyAButtonDetector : VisualInferenceCallback { ImageFloatBox m_box; - FlyAButtonDetector(const ImageFloatBox& box) - : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} - void make_overlays(VideoOverlaySet&) const override {} + FlyAButtonDetector(const ImageFloatBox& box) : VisualInferenceCallback("FlyAButtonDetector"), m_box(box){ + } + void make_overlays(VideoOverlaySet&) const override { + } bool process_frame(const ImageViewRGB32& frame, WallClock) override { ImageStats s = image_stats(extract_box_reference(frame, m_box)); return s.stddev.sum() > 30.0; @@ -675,17 +667,17 @@ bool fly_to( }; FlyAButtonDetector watcher(button_a_box); int ret = wait_until(stream, context, 3000ms, {{watcher}}); - if (ret < 0) { + if (ret < 0){ stream.log("CurrentCity: A button not found.", COLOR_RED); return false; } BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int fly_ret = run_until( - stream, context, - [](ProControllerContext& context){ pbf_mash_button(context, BUTTON_A, 200000ms); }, - {{flying_black_screen}} - ); - if (fly_ret < 0) { + int fly_ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{flying_black_screen}}); + if (fly_ret < 0){ stream.log("CurrentCity: black screen not detected!", COLOR_RED); return false; } @@ -693,31 +685,31 @@ bool fly_to( return true; } - switch (place) { - case FlyPoint::CelesticTown: - pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); - break; - case FlyPoint::HearthomeCity: - case FlyPoint::OreburghCity: - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); - break; - case FlyPoint::PastoriaCity: - case FlyPoint::PokemonLeagueLower: - case FlyPoint::PokemonLeagueUpper: - case FlyPoint::SolaceonTown: - case FlyPoint::SunyshoreCity: - case FlyPoint::VeilstoneCity: - pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); - break; - case FlyPoint::SnowpointCity: - pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); - break; - case FlyPoint::TwinleafTown: - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); - break; - default: - pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); - break; + switch (place){ + case FlyPoint::CelesticTown: + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + break; + case FlyPoint::HearthomeCity: + case FlyPoint::OreburghCity: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; + case FlyPoint::PastoriaCity: + case FlyPoint::PokemonLeagueLower: + case FlyPoint::PokemonLeagueUpper: + case FlyPoint::SolaceonTown: + case FlyPoint::SunyshoreCity: + case FlyPoint::VeilstoneCity: + pbf_move_left_joystick(context, {+1, -1}, 4000ms, 500ms); + break; + case FlyPoint::SnowpointCity: + pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); + break; + case FlyPoint::TwinleafTown: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; + default: + pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); + break; } context.wait_for_all_requests(); @@ -725,9 +717,10 @@ bool fly_to( struct FlyAButtonDetector : VisualInferenceCallback { ImageFloatBox m_box; - FlyAButtonDetector(const ImageFloatBox& box) - : VisualInferenceCallback("FlyAButtonDetector"), m_box(box) {} - void make_overlays(VideoOverlaySet&) const override {} + FlyAButtonDetector(const ImageFloatBox& box) : VisualInferenceCallback("FlyAButtonDetector"), m_box(box){ + } + void make_overlays(VideoOverlaySet&) const override { + } bool process_frame(const ImageViewRGB32& frame, WallClock) override { ImageStats s = image_stats(extract_box_reference(frame, m_box)); // Blue stripe is uniform when no button shown; A button disrupts it @@ -736,76 +729,77 @@ bool fly_to( }; FlyAButtonDetector watcher_1(button_a_box); - switch (place) { - case FlyPoint::CanalavCity: - break; - case FlyPoint::CelesticTown: - city_icon_box = {0.660000, 0.470000, 0.012000, 0.020000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 15, false); - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 13, false); - break; - case FlyPoint::EternaCity: - break; - case FlyPoint::FloaromaTown: - break; - case FlyPoint::HearthomeCity: - city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); - break; - case FlyPoint::JubilifeCity: - city_icon_box = {0.453000, 0.702000, 0.016000, 0.054000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); - break; - case FlyPoint::OreburghCity: - city_icon_box = {0.566000, 0.702000, 0.012000, 0.054000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 10, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); - break; - case FlyPoint::PastoriaCity: - break; - case FlyPoint::PokemonLeagueLower: - break; - case FlyPoint::PokemonLeagueUpper: - break; - case FlyPoint::Route221: - break; - case FlyPoint::SandgemTown: - break; - case FlyPoint::SolaceonTown: - city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); - break; - case FlyPoint::SnowpointCity: - city_icon_box = {0.602000, 0.070000, 0.013000, 0.054000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 12, false); - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, false); - break; - case FlyPoint::SunyshoreCity: - break; - case FlyPoint::TwinleafTown: - city_icon_box = {0.415000, 0.870000, 0.013000, 0.022000}; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, false); - break; - case FlyPoint::VeilstoneCity: - city_icon_box = {0.829000, 0.537000, 0.017000, 0.054000}; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8, false); - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 10, false); - break; - case FlyPoint::CurrentCity: - break; + switch (place){ + case FlyPoint::CanalavCity: + break; + case FlyPoint::CelesticTown: + city_icon_box = {0.660000, 0.470000, 0.012000, 0.020000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 15, false); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 13, false); + break; + case FlyPoint::EternaCity: + break; + case FlyPoint::FloaromaTown: + break; + case FlyPoint::HearthomeCity: + city_icon_box = {0.660000, 0.637000, 0.029000, 0.052000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 16, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 7, false); + break; + case FlyPoint::JubilifeCity: + city_icon_box = {0.453000, 0.702000, 0.016000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); + break; + case FlyPoint::OreburghCity: + city_icon_box = {0.566000, 0.702000, 0.012000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 10, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 5, false); + break; + case FlyPoint::PastoriaCity: + break; + case FlyPoint::PokemonLeagueLower: + break; + case FlyPoint::PokemonLeagueUpper: + break; + case FlyPoint::Route221: + break; + case FlyPoint::SandgemTown: + break; + case FlyPoint::SolaceonTown: + city_icon_box = {0.735000, 0.603000, 0.030000, 0.022000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 9, false); + break; + case FlyPoint::SnowpointCity: + city_icon_box = {0.602000, 0.070000, 0.013000, 0.054000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 12, false); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, false); + break; + case FlyPoint::SunyshoreCity: + break; + case FlyPoint::TwinleafTown: + city_icon_box = {0.415000, 0.870000, 0.013000, 0.022000}; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, false); + break; + case FlyPoint::VeilstoneCity: + city_icon_box = {0.829000, 0.537000, 0.017000, 0.054000}; + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 8, false); + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 10, false); + break; + case FlyPoint::CurrentCity: + break; } context.wait_for_all_requests(); struct CityIconDetector : VisualInferenceCallback { ImageFloatBox m_box; - CityIconDetector(const ImageFloatBox& box) - : VisualInferenceCallback("CityIconDetector"), m_box(box) {} - void make_overlays(VideoOverlaySet&) const override {} + CityIconDetector(const ImageFloatBox& box) : VisualInferenceCallback("CityIconDetector"), m_box(box){ + } + void make_overlays(VideoOverlaySet&) const override { + } bool process_frame(const ImageViewRGB32& frame, WallClock) override { ImageStats s = image_stats(extract_box_reference(frame, m_box)); // City icon is normally a uniform color; cursor brackets disrupt it @@ -817,27 +811,25 @@ bool fly_to( const std::string name = fly_point_name(place); int ret_1 = wait_until(stream, context, 3000ms, {{watcher_1}}); int ret_2 = wait_until(stream, context, 4000ms, {{watcher_2}}); - if (ret_1 < 0) { + if (ret_1 < 0){ stream.log("A button not found, retrying...", COLOR_RED); }; - if (ret_2 < 0) { + if (ret_2 < 0){ stream.log(name + " not found, retrying...", COLOR_RED); }; if (ret_1 == 0 and ret_2 == 0){ stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{flying_white_screen}, {flying_black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{flying_white_screen}, {flying_black_screen}}); if (ret < 0){ stream.log("Flying to " + name + ": white / black screen not detected!", COLOR_RED); return false; - } else if (ret >= 0) { + }else if (ret >= 0){ stream.log("Flying to " + name + ": white / black screen detected!", COLOR_GREEN); return true; } @@ -865,13 +857,15 @@ bool fly_to( // --------------------------------------------------------------------------- // Slot 1's colored HP bar in the Bag. Read fill position rather than OCR. -static double potion_hp_fraction(const ImageViewRGB32& frame) { - if (bag_pocket_position(frame) == 0) return -1; +static double potion_hp_fraction(const ImageViewRGB32& frame){ + if (bag_pocket_position(frame) == 0){ + return -1; + } const ImageViewRGB32 bar = extract_box_reference(frame, ImageFloatBox{0.137, 0.160, 0.202, 0.016}); size_t filled = 0; - for (size_t x = 0; x < bar.width(); x++) { + for (size_t x = 0; x < bar.width(); x++){ size_t color = 0; - for (size_t y = 0; y < bar.height(); y++) { + for (size_t y = 0; y < bar.height(); y++){ uint32_t p = bar.pixel(x, y); int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; const bool green = g > r + 30 && g > b + 20; @@ -884,42 +878,50 @@ static double potion_hp_fraction(const ImageViewRGB32& frame) { return bar.width() ? (double)filled / bar.width() : -1; } -static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& context) { +static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& context){ double previous = -1; size_t stable = 0; - for (size_t check = 0; check < 20; check++) { + for (size_t check = 0; check < 20; check++){ context.wait_for_all_requests(); auto frame = stream.video().snapshot(); double hp = frame ? potion_hp_fraction(frame) : -1; - if (hp >= 0 && previous >= 0 && std::abs(hp - previous) <= 0.015) { - if (++stable >= 3) return hp; - } else stable = 0; + if (hp >= 0 && previous >= 0 && std::abs(hp - previous) <= 0.015){ + if (++stable >= 3){ + return hp; + } + }else{ + stable = 0; + } previous = hp; pbf_wait(context, 150ms); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 HP bar could not be confirmed.", stream); + "Potion: Slot 1 HP bar could not be confirmed.", stream); } -void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context) { +void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context){ OverlayBoxScope hp_overlay(stream.overlay(), COLOR_GREEN, {0.137, 0.160, 0.202, 0.016}, ""); BagItemTemplate potion; potion.reference_path = "PokemonBDSP/Autostory/Bag_Potion.png"; potion.label = "Potion"; potion.reference_row = 1; bool healed = false; - for (size_t dose = 0; dose < 3; dose++) { + for (size_t dose = 0; dose < 3; dose++){ stream.log("[Potion] Finding Bag/Medicine/Potion; dose " + std::to_string(dose + 1) + "/3."); - if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Could not verify Potion in the Medicine pocket.", stream); + if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Potion: Could not verify Potion in the Medicine pocket.", stream); } const double before = confirmed_potion_hp(stream, context); stream.log("[Potion] Slot 1 HP bar before use=" + std::to_string(before * 100) + "%."); - if (before >= 0.98) { healed = true; break; } - if (before <= 0.005) { + if (before >= 0.98){ + healed = true; + break; + } + if (before <= 0.005){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 appears fainted; a Potion cannot revive it.", stream); + "Potion: Slot 1 appears fainted; a Potion cannot revive it.", + stream); } pbf_press_button(context, BUTTON_A, 80ms, 500ms); pbf_press_button(context, BUTTON_A, 80ms, 500ms); @@ -927,9 +929,9 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); OverworldWatcher overworld; - if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) { + if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Did not return to the overworld after use.", stream); + "Potion: Did not return to the overworld after use.", stream); } // Reopen Bag to verify the result, including when the last Potion was used. open_menu(stream, context, MenuCursorPosition::BAG, 5); @@ -937,53 +939,66 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context context.wait_for_all_requests(); const double after = confirmed_potion_hp(stream, context); stream.log("[Potion] Slot 1 HP bar after use=" + std::to_string(after * 100) + "%."); - if (after <= before + 0.005) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + if (after <= before + 0.005){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Potion: Slot 1 HP did not increase; refusing to continue movement.", stream); } - if (after >= 0.98) { healed = true; break; } + if (after >= 0.98){ + healed = true; + break; + } pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); } - if (!healed) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Full HP not confirmed after three uses.", stream); + if (!healed){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Full HP not confirmed after three uses.", stream); + } pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); OverworldWatcher overworld; - if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0) + if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Final overworld return not confirmed.", stream); + "Potion: Final overworld return not confirmed.", stream); + } stream.log("[Potion] Slot 1 HP bar full and overworld confirmed."); } -namespace{ +namespace { -struct IconGrid { int row, col; }; +struct IconGrid { + int row, col; +}; IconGrid icon_to_grid(MenuCursorPosition pos, int num_icons){ switch (pos){ - case MenuCursorPosition::POKEDEX: return {0, 0}; - case MenuCursorPosition::POKEMON: return {0, 1}; - case MenuCursorPosition::BAG: return {0, 2}; - case MenuCursorPosition::CARD: return {0, 3}; - case MenuCursorPosition::OPTIONS: return {1, num_icons <= 5 ? 0 : 2}; - case MenuCursorPosition::MAP: return {1, 0}; - case MenuCursorPosition::CAPSULES: return {1, 1}; - case MenuCursorPosition::MYSTERYGIFT: return {1, 3}; - default: return {-1, -1}; + case MenuCursorPosition::POKEDEX: + return {0, 0}; + case MenuCursorPosition::POKEMON: + return {0, 1}; + case MenuCursorPosition::BAG: + return {0, 2}; + case MenuCursorPosition::CARD: + return {0, 3}; + case MenuCursorPosition::OPTIONS: + return {1, num_icons <= 5 ? 0 : 2}; + case MenuCursorPosition::MAP: + return {1, 0}; + case MenuCursorPosition::CAPSULES: + return {1, 1}; + case MenuCursorPosition::MYSTERYGIFT: + return {1, 3}; + default: + return {-1, -1}; } } } // anonymous namespace -void open_menu( - VideoStream& stream, - ProControllerContext& context, - MenuCursorPosition target, - int num_icons -){ +void open_menu(VideoStream& stream, ProControllerContext& context, MenuCursorPosition target, int num_icons){ using namespace std::chrono_literals; // Previous single opening attempt: @@ -996,8 +1011,7 @@ void open_menu( MenuCursorDetector detector; { static const char* ICON_NAMES[MENU_CURSOR_NUM_ICONS] = { - "POKEDEX", "POKEMON", "BAG", "CARD", - "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", + "POKEDEX", "POKEMON", "BAG", "CARD", "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", }; // Previous optional template directory: PokemonBDSP/AutoStory. std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/Autostory"; @@ -1011,10 +1025,7 @@ void open_menu( // Previously constructed the image before checking its availability. ImageRGB32 img(path); if (img){ - detector.set_icon_reference( - static_cast(i), - std::move(img) - ); + detector.set_icon_reference(static_cast(i), std::move(img)); } } } @@ -1063,31 +1074,26 @@ void open_menu( } } if (cur != MenuCursorPosition::UNKNOWN){ - stream.log("[Open menu] Menu and cursor detected on attempt " + std::to_string(attempt) + ".", COLOR_GREEN); + stream.log("[Open menu] Menu and cursor detected on attempt " + std::to_string(attempt) + ".", + COLOR_GREEN); break; } - stream.log("[Open menu] Menu cursor not detected on attempt " + std::to_string(attempt) + ".", COLOR_ORANGE); - } - catch (OperationFailedException& error){ + stream.log("[Open menu] Menu cursor not detected on attempt " + std::to_string(attempt) + ".", + COLOR_ORANGE); + } catch (OperationFailedException& error){ stream.log("[Open menu] Attempt " + std::to_string(attempt) + " failed: " + error.message(), COLOR_ORANGE); } } if (cur == MenuCursorPosition::UNKNOWN){ OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Menu or cursor detection failed after 3 opening attempts.", - stream - ); + ErrorReportMode::SEND_ERROR_REPORT, "Menu or cursor detection failed after 3 opening attempts.", stream); } - IconGrid cur_g = icon_to_grid(cur, num_icons); + IconGrid cur_g = icon_to_grid(cur, num_icons); IconGrid tgt_g = icon_to_grid(target, num_icons); if (tgt_g.row == -1){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Unknown target menu icon.", - stream - ); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Unknown target menu icon.", + stream); } if (cur_g.row != tgt_g.row){ @@ -1130,12 +1136,12 @@ void open_menu( // ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); // const ImageFloatBox selected_header(0.428, 0.754, 0.382, 0.046); // const auto deadline = std::chrono::steady_clock::now() + search_timeout; -// +// // stream.log("[Bag search] Looking for " + item.label + "."); // open_menu(stream, context, MenuCursorPosition::BAG, num_icons); // pbf_wait(context, 1000ms); // context.wait_for_all_requests(); -// +// // for (size_t pocket = 0; pocket < 8; pocket++){ // ImageRGB32 initial_header; // ImageRGB32 previous_header; @@ -1143,7 +1149,7 @@ void open_menu( // size_t matching_frames = 0; // size_t scrolls = 0; // bool scanning_up = false; -// +// // while (std::chrono::steady_clock::now() < deadline){ // context.wait_for_all_requests(); // const VideoSnapshot frame = stream.video().snapshot(); @@ -1169,7 +1175,7 @@ void open_menu( // continue; // } // matching_frames = 0; -// +// // const ImageRGB32 header = extract_box_reference(frame, selected_header).scale_to(382, 46); // if (!initial_header){ // initial_header = header.copy(); @@ -1216,7 +1222,7 @@ void open_menu( // stream.log("[Bag search] Item not found in any pocket.", COLOR_ORANGE); // return false; // } -// +// // One narrow strip on the left side of the red selection border. Divide its // height into the seven row bands; the item's art and NEW badge are outside it. @@ -1224,9 +1230,7 @@ int bag_cursor_position(const ImageViewRGB32& frame){ if (!frame){ return 0; } - const ImageViewRGB32 strip = extract_box_reference( - frame, ImageFloatBox(0.431, 0.229, 0.005, 0.470) - ); + const ImageViewRGB32 strip = extract_box_reference(frame, ImageFloatBox(0.431, 0.229, 0.005, 0.470)); double best = 0.0; double second = 0.0; int position = 0; @@ -1267,14 +1271,8 @@ static ImageFloatBox bag_list_name_box(size_t row){ return ImageFloatBox(0.492, 0.213 + (row - 1) * 0.075, 0.335, 0.050); } -static double bag_row_patch_score( - const ImageRGB32& reference, - const ImageViewRGB32& frame, - const ImageFloatBox& box -){ - const ImageRGB32 live = extract_box_reference(frame, box).scale_to( - reference.width(), reference.height() - ); +static double bag_row_patch_score(const ImageRGB32& reference, const ImageViewRGB32& frame, const ImageFloatBox& box){ + const ImageRGB32 live = extract_box_reference(frame, box).scale_to(reference.width(), reference.height()); return ImageMatch::pixel_RMSD(reference, live); } @@ -1295,31 +1293,43 @@ static double bag_row_patch_score( // } size_t bag_clear_tab_count(const ImageViewRGB32& frame){ - if (!frame || bag_pocket_position(frame)==0) return 0; - static const std::vector> references=[] { + if (!frame || bag_pocket_position(frame) == 0){ + return 0; + } + static const std::vector> references = [] { std::vector> refs; - for (const char* name : {"Normal","NormalMedium","NormalSmall","Alternate","AlternateMedium","AlternateSmall","Selected","SelectedMedium","SelectedSmall","SelectedBlue","SelectedBlueMedium","SelectedBlueSmall"}) - refs.emplace_back(std::make_shared( - RESOURCE_PATH()+"PokemonBDSP/Autostory/BagTabs/"+name+".png")); + for (const char* name : + {"Normal", "NormalMedium", "NormalSmall", "Alternate", "AlternateMedium", "AlternateSmall", "Selected", + "SelectedMedium", "SelectedSmall", "SelectedBlue", "SelectedBlueMedium", "SelectedBlueSmall"}){ + refs.emplace_back( + std::make_shared(RESOURCE_PATH() + "PokemonBDSP/Autostory/BagTabs/" + name + ".png")); + } return refs; }(); - size_t clear=0; - for (size_t tab=0;tab<8;tab++) { - const double x=.570+tab*.0421; + size_t clear = 0; + for (size_t tab = 0; tab < 8; tab++){ + const double x = .570 + tab * .0421; // A selected tab has a thin red outline. The NEW marker fills the // interior of this upper-left patch, which the outline leaves white. - auto badge=extract_box_reference(frame,ImageFloatBox(x+.007,.074,.006,.014)); - size_t red=0; - for (size_t y=0;y>16)&255,gg=(pixel>>8)&255,bb=pixel&255; - red+=rr>150 && rr>gg+60 && rr>bb+60; + auto badge = extract_box_reference(frame, ImageFloatBox(x + .007, .074, .006, .014)); + size_t red = 0; + for (size_t y = 0; y < badge.height(); y++){ + for (size_t bx = 0; bx < badge.width(); bx++){ + const uint32_t pixel = badge.pixel(bx, y); + int rr = (pixel >> 16) & 255, gg = (pixel >> 8) & 255, bb = pixel & 255; + red += rr > 150 && rr > gg + 60 && rr > bb + 60; + } } - if (badge.width()*badge.height() && (double)red/(badge.width()*badge.height())>.45) continue; - const ImageFloatBox box{x,.06,.038,.072}; - for (const auto& reference : references) { - ImageMatchDetector detector(reference,box,50.0); - if (detector.detect(frame)) {clear++;break;} + if (badge.width() * badge.height() && (double)red / (badge.width() * badge.height()) > .45){ + continue; + } + const ImageFloatBox box{x, .06, .038, .072}; + for (const auto& reference : references){ + ImageMatchDetector detector(reference, box, 50.0); + if (detector.detect(frame)){ + clear++; + break; + } } } return clear; @@ -1327,47 +1337,56 @@ size_t bag_clear_tab_count(const ImageViewRGB32& frame){ bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons){ stream.log("[Bag NEW flags] Opening bag to verify all eight tabs."); - open_menu(stream,context,MenuCursorPosition::BAG,num_icons); - pbf_wait(context,1000ms); - auto all_clear=[&]() { + open_menu(stream, context, MenuCursorPosition::BAG, num_icons); + pbf_wait(context, 1000ms); + auto all_clear = [&](){ context.wait_for_all_requests(); - size_t confirmed=0; - for (size_t check=0;check<3;check++) { - const auto frame=stream.video().snapshot(); - const size_t count=frame?bag_clear_tab_count(*frame.frame):0; - stream.log("[Bag NEW flags] Clear tabs: "+std::to_string(count)+"/8."); - if (count!=8) return false; + size_t confirmed = 0; + for (size_t check = 0; check < 3; check++){ + const auto frame = stream.video().snapshot(); + const size_t count = frame ? bag_clear_tab_count(*frame.frame) : 0; + stream.log("[Bag NEW flags] Clear tabs: " + std::to_string(count) + "/8."); + if (count != 8){ + return false; + } confirmed++; - pbf_wait(context,150ms); + pbf_wait(context, 150ms); context.wait_for_all_requests(); } - return confirmed==3; + return confirmed == 3; }; - bool cleared=all_clear(); - for (size_t pocket=0;pocket<32 && !cleared;pocket++) { - if (pocket) pbf_press_dpad(context,DPAD_RIGHT,80ms,300ms); + bool cleared = all_clear(); + for (size_t pocket = 0; pocket < 32 && !cleared; pocket++){ + if (pocket){ + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); + } // Let the initial selection and each subsequent item clear its NEW // flag. Short taps avoid skipping rows through held-button repeat. - pbf_wait(context,1000ms); - for (size_t tap=0;tap<12;tap++) pbf_press_dpad(context,DPAD_DOWN,80ms,1000ms); - cleared=all_clear(); + pbf_wait(context, 1000ms); + for (size_t tap = 0; tap < 12; tap++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 1000ms); + } + cleared = all_clear(); } - pbf_press_dpad(context,DPAD_UP,3000ms,250ms); - pbf_mash_button(context,BUTTON_B,2000ms); + pbf_press_dpad(context, DPAD_UP, 3000ms, 250ms); + pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); - stream.log(cleared?"[Bag NEW flags] All eight tabs clear; bag closed.": - "[Bag NEW flags] Full pocket pass did not clear all eight tabs; bag closed.",cleared?COLOR_GREEN:COLOR_ORANGE); + stream.log(cleared ? "[Bag NEW flags] All eight tabs clear; bag closed." + : "[Bag NEW flags] Full pocket pass did not clear all eight tabs; bag closed.", + cleared ? COLOR_GREEN : COLOR_ORANGE); return cleared; } int bag_pocket_position(const ImageViewRGB32& frame){ - if (!frame) return 0; + if (!frame){ + return 0; + } int selected = 0; for (int pocket = 1; pocket <= 8; pocket++){ // The lower left edge of each tab is below the red NEW badge. // This strip includes both the normal outline and enlarged selection. - const auto edge = extract_box_reference(frame, - ImageFloatBox(0.570 + (pocket - 1) * 0.0421, 0.098, 0.0045, 0.021)); + const auto edge = + extract_box_reference(frame, ImageFloatBox(0.570 + (pocket - 1) * 0.0421, 0.098, 0.0045, 0.021)); size_t colored = 0; for (size_t y = 0; y < edge.height(); y++){ for (size_t x = 0; x < edge.width(); x++){ @@ -1376,13 +1395,14 @@ int bag_pocket_position(const ImageViewRGB32& frame){ const int g = (pixel >> 8) & 255; const int b = pixel & 255; // Red when focused, blue when the item list has focus. - colored += (r > 160 && r > g + 60 && r > b + 60) || - (b > 160 && b > r + 40 && b > g + 20); + colored += (r > 160 && r > g + 60 && r > b + 60) || (b > 160 && b > r + 40 && b > g + 20); } } const double pixels = (double)edge.width() * edge.height(); if (pixels > 0 && colored / pixels >= 0.18){ - if (selected != 0) return 0; + if (selected != 0){ + return 0; + } selected = pocket; } } @@ -1390,21 +1410,19 @@ int bag_pocket_position(const ImageViewRGB32& frame){ } bool bag_list_is_empty(const ImageViewRGB32& frame){ - if (!frame || bag_pocket_position(frame) == 0 || bag_cursor_position(frame) != 0) return false; + if (!frame || bag_pocket_position(frame) == 0 || bag_cursor_position(frame) != 0){ + return false; + } // Empty pockets have no item icons. The central Empty label is outside // this column; counting dark pixels in the name area included that label. const auto icons = extract_box_reference(frame, ImageFloatBox(0.449, 0.213, 0.032, 0.500)); const auto stats = image_stats(icons); - return stats.average.r > 220 && stats.average.g > 220 && stats.average.b > 220 && - stats.stddev.sum() < 20; + return stats.average.r > 220 && stats.average.g > 220 && stats.average.b > 220 && stats.stddev.sum() < 20; } -std::array match_bag_item_rows( - const ImageViewRGB32& frame, - const ImageViewRGB32& reference, - const BagItemTemplate& item -){ +std::array match_bag_item_rows(const ImageViewRGB32& frame, const ImageViewRGB32& reference, + const BagItemTemplate& item){ std::array result; if (!frame || !reference || item.reference_row < 1 || item.reference_row > 7){ return result; @@ -1414,8 +1432,7 @@ std::array match_bag_item_rows( for (size_t row = 1; row <= 7; row++){ auto& match = result[row - 1]; match.icon_rmsd = bag_row_patch_score(icon, frame, bag_list_icon_box(row)); - match.name_rmsd = item.check_name - ? bag_row_patch_score(name, frame, bag_list_name_box(row)) : 0.0; + match.name_rmsd = item.check_name ? bag_row_patch_score(name, frame, bag_list_name_box(row)) : 0.0; match.matched = match.icon_rmsd <= item.max_icon_rmsd && match.name_rmsd <= item.max_name_rmsd; } return result; @@ -1429,10 +1446,8 @@ BagMovePlan plan_bag_item_move(int cursor_row, int item_row){ if (item_row == cursor_row){ return {BagMoveAction::CONFIRM, 0}; } - return { - item_row > cursor_row ? BagMoveAction::DOWN : BagMoveAction::UP, - static_cast(std::abs(item_row - cursor_row)) - }; + return {item_row > cursor_row ? BagMoveAction::DOWN : BagMoveAction::UP, + static_cast(std::abs(item_row - cursor_row))}; } if (cursor_row == 7){ return {BagMoveAction::REWIND, 1}; @@ -1450,7 +1465,9 @@ static int bag_confirm_pocket(VideoStream& stream, ProControllerContext& context const int pocket = frame ? bag_pocket_position(frame) : 0; matches = pocket != 0 && pocket == previous ? matches + 1 : pocket != 0 ? 1 : 0; previous = pocket; - if (matches >= 3) return pocket; + if (matches >= 3){ + return pocket; + } pbf_wait(context, 100ms); } return 0; @@ -1477,62 +1494,61 @@ static int bag_confirm_pocket(VideoStream& stream, ProControllerContext& context // } BagPocketMovePlan plan_bag_pocket_move(int current, int target){ - if (current < 1 || current > 8 || target < 1 || target > 8) return {}; + if (current < 1 || current > 8 || target < 1 || target > 8){ + return {}; + } const int right = (target - current + 8) % 8; const int left = (current - target + 8) % 8; - if (right == 0) return {BagPocketAction::CONFIRM, 0}; + if (right == 0){ + return {BagPocketAction::CONFIRM, 0}; + } return right <= left ? BagPocketMovePlan{BagPocketAction::RIGHT, static_cast(right)} - : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; + : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; } static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& context, int target){ int current = bag_confirm_pocket(stream, context); for (size_t attempt = 0; attempt < 3; attempt++){ const auto plan = plan_bag_pocket_move(current, target); - if (plan.action == BagPocketAction::CONFIRM) return true; + if (plan.action == BagPocketAction::CONFIRM){ + return true; + } if (plan.action == BagPocketAction::UNKNOWN){ stream.log("[Bag search] Selected pocket not detected.", COLOR_ORANGE); return false; } - stream.log("[Bag search] Pocket " + std::to_string(current) + " to " + - std::to_string(target) + ": " + - (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + - std::to_string(plan.presses) + " presses, attempt " + std::to_string(attempt + 1) + "/3."); + stream.log("[Bag search] Pocket " + std::to_string(current) + " to " + std::to_string(target) + ": " + + (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + std::to_string(plan.presses) + + " presses, attempt " + std::to_string(attempt + 1) + "/3."); for (size_t press = 0; press < plan.presses; press++){ - pbf_press_dpad(context, plan.action == BagPocketAction::RIGHT ? DPAD_RIGHT : DPAD_LEFT, - 80ms, 150ms); + pbf_press_dpad(context, plan.action == BagPocketAction::RIGHT ? DPAD_RIGHT : DPAD_LEFT, 80ms, 150ms); } pbf_wait(context, 350ms); current = bag_confirm_pocket(stream, context); - stream.log("[Bag search] Final pocket=" + std::to_string(current) + - ", expected=" + std::to_string(target) + "."); - if (current == target) return true; + stream.log("[Bag search] Final pocket=" + std::to_string(current) + ", expected=" + std::to_string(target) + + "."); + if (current == target){ + return true; + } } stream.log("[Bag search] Target pocket not reached after 3 movement batches.", COLOR_ORANGE); return false; } -bool find_bag_item( - VideoStream& stream, - ProControllerContext& context, - const BagItemTemplate& item, - int target_pocket, - int num_icons, - size_t max_scrolls_per_pocket, - std::chrono::milliseconds search_timeout -){ +bool find_bag_item(VideoStream& stream, ProControllerContext& context, const BagItemTemplate& item, int target_pocket, + int num_icons, size_t max_scrolls_per_pocket, std::chrono::milliseconds search_timeout){ if (target_pocket < 1 || target_pocket > 8 || item.reference_row < 1 || item.reference_row > 7){ stream.log("[Bag search] Invalid template reference row.", COLOR_ORANGE); return false; } auto reference = std::make_shared(RESOURCE_PATH() + item.reference_path); -// const ImageRGB32 reference_icon = extract_box_reference( -// *reference, bag_list_icon_box(item.reference_row) -// ).copy(); -// const ImageRGB32 reference_name = extract_box_reference( -// *reference, bag_list_name_box(item.reference_row) -// ).copy(); + // const ImageRGB32 reference_icon = extract_box_reference( + // *reference, bag_list_icon_box(item.reference_row) + // ).copy(); + // const ImageRGB32 reference_name = extract_box_reference( + // *reference, bag_list_name_box(item.reference_row) + // ).copy(); ImageMatchDetector bag(reference, ImageFloatBox(0.055, 0.012, 0.044, 0.045), 35.0, true); const auto deadline = std::chrono::steady_clock::now() + search_timeout; @@ -1540,7 +1556,9 @@ bool find_bag_item( pbf_wait(context, 1000ms); context.wait_for_all_requests(); - if (!bag_navigate_to_pocket(stream, context, target_pocket)) return false; + if (!bag_navigate_to_pocket(stream, context, target_pocket)){ + return false; + } // Previously searched all eight pockets. Only inspect the requested pocket now. // for (size_t pocket = 0; pocket < 8; pocket++){ { @@ -1553,8 +1571,7 @@ bool find_bag_item( bool empty = false; auto move = [&](DpadPosition direction, size_t count, bool check_rows = false){ for (size_t press = 0; press < count; press++){ - if (commands >= max_scrolls_per_pocket || - std::chrono::steady_clock::now() >= deadline){ + if (commands >= max_scrolls_per_pocket || std::chrono::steady_clock::now() >= deadline){ stream.log("[Bag search] Limit reached; search incomplete.", COLOR_ORANGE); return false; } @@ -1584,10 +1601,8 @@ bool find_bag_item( size_t missing_cursor = 0; size_t unchanged_pages = 0; ImageRGB32 previous_page; - stream.log( - "[Bag search] Looking for " + item.label + ": pocket " + - std::to_string(selected_pocket) + ", pass " + std::to_string(pass + 1) + "/3." - ); + stream.log("[Bag search] Looking for " + item.label + ": pocket " + std::to_string(selected_pocket) + + ", pass " + std::to_string(pass + 1) + "/3."); while (std::chrono::steady_clock::now() < deadline){ const VideoSnapshot frame = stream.video().snapshot(); if (!frame || !bag.detect(frame)){ @@ -1628,11 +1643,9 @@ bool find_bag_item( } } } - stream.log( - "[Bag search] " + item.label + (target == 0 ? " is not detected" : " is detected") + - " on screen; item row=" + std::to_string(target) + - ", cursor row=" + std::to_string(cursor) + "." - ); + stream.log("[Bag search] " + item.label + (target == 0 ? " is not detected" : " is detected") + + " on screen; item row=" + std::to_string(target) + ", cursor row=" + std::to_string(cursor) + + "."); if (ambiguous){ stream.log("[Bag search] Multiple rows match; selection is uncertain.", COLOR_ORANGE); return false; @@ -1659,8 +1672,8 @@ bool find_bag_item( // row is sufficient; do not depend on the detail panel. if (target == cursor){ if (++matching_frames >= 3){ - stream.log("[Bag search] Item and cursor confirmed in row " + - std::to_string(cursor) + ".", COLOR_GREEN); + stream.log("[Bag search] Item and cursor confirmed in row " + std::to_string(cursor) + ".", + COLOR_GREEN); return true; } pbf_wait(context, 150ms); @@ -1670,16 +1683,15 @@ bool find_bag_item( matching_frames = 0; const size_t count = plan_bag_item_move(cursor, target).presses; stream.log("[Bag search] Moving " + std::string(target > cursor ? "down " : "up ") + - std::to_string(count) + " time(s), then checking again."); + std::to_string(count) + " time(s), then checking again."); if (!move(target > cursor ? DPAD_DOWN : DPAD_UP, count)){ return false; } continue; } matching_frames = 0; - const ImageRGB32 page = extract_box_reference( - frame, ImageFloatBox(0.449, 0.212, 0.378, 0.500) - ).scale_to(378, 500); + const ImageRGB32 page = + extract_box_reference(frame, ImageFloatBox(0.449, 0.212, 0.378, 0.500)).scale_to(378, 500); if (previous_page && ImageMatch::pixel_RMSD(previous_page, page) <= 3.0){ unchanged_pages++; }else{ @@ -1716,35 +1728,26 @@ bool find_bag_item( return false; } } -// if (pocket < 7){ -// // pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); -// // context.wait_for_all_requests(); -// if (!bag_advance_pocket(stream, context, selected_pocket)) return false; -// } - + // if (pocket < 7){ + // // pbf_press_dpad(context, DPAD_RIGHT, 80ms, 500ms); + // // context.wait_for_all_requests(); + // if (!bag_advance_pocket(stream, context, selected_pocket)) return false; + // } } stream.log("[Bag search] Item not found after three passes per nonempty pocket.", COLOR_ORANGE); return false; } -bool use_escape_rope( - VideoStream& stream, - ProControllerContext& context, - int num_icons -){ - const BagItemTemplate rope{ - // "PokemonBDSP/AutoStory/Bag_EscapeRope.png", "escape-rope" - "PokemonBDSP/Autostory/Bag_EscapeRope.png", "escape-rope" - }; +bool use_escape_rope(VideoStream& stream, ProControllerContext& context, int num_icons){ + const BagItemTemplate rope{// "PokemonBDSP/AutoStory/Bag_EscapeRope.png", "escape-rope" + "PokemonBDSP/Autostory/Bag_EscapeRope.png", "escape-rope"}; if (!find_bag_item(stream, context, rope, 5, num_icons)){ return false; } pbf_press_button(context, BUTTON_A, 80ms, 350ms); context.wait_for_all_requests(); - SelectionArrowFinder use_menu( - stream.overlay(), ImageFloatBox(0.660, 0.100, 0.310, 0.430), COLOR_GREEN - ); + SelectionArrowFinder use_menu(stream.overlay(), ImageFloatBox(0.660, 0.100, 0.310, 0.430), COLOR_GREEN); if (wait_until(stream, context, 3s, {{use_menu}}) != 0){ stream.log("[Escape Rope] Item action menu not detected.", COLOR_ORANGE); return false; @@ -1755,36 +1758,27 @@ bool use_escape_rope( BlackScreenWatcher transition; // Escape Rope fades white in the supplied clips; support black transitions too. WhiteScreenWatcher white_transition; - const int result = run_until( - stream, context, - [](ProControllerContext& ctx){ - for (size_t press = 0; press < 10; press++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); - } - ctx.wait_for_all_requests(); - pbf_wait(ctx, 10s); - ctx.wait_for_all_requests(); - }, - {transition, white_transition} - ); + const int result = run_until(stream, context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 10; press++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); + } + ctx.wait_for_all_requests(); + pbf_wait(ctx, 10s); + ctx.wait_for_all_requests(); + }, + {transition, white_transition}); if (result < 0){ stream.log("[Escape Rope] No exit transition detected.", COLOR_ORANGE); return false; } - BlackScreenWatcher finished( - COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms - ); - WhiteScreenWatcher white_finished( - COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, - WhiteScreenWatcher::FinderType::GONE, 500ms - ); - stream.log(std::string("[Escape Rope] ") + - (result == 1 ? "White" : "Black") + " exit transition detected."); + BlackScreenWatcher finished(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + WhiteScreenWatcher white_finished(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, + 500ms); + stream.log(std::string("[Escape Rope] ") + (result == 1 ? "White" : "Black") + " exit transition detected."); // if (wait_until(stream, context, 15s, {{finished}}) != 0){ return false; } - const int finish_result = result == 1 - ? wait_until(stream, context, 15s, {{white_finished}}) - : wait_until(stream, context, 15s, {{finished}}); + const int finish_result = result == 1 ? wait_until(stream, context, 15s, {{white_finished}}) + : wait_until(stream, context, 15s, {{finished}}); if (finish_result != 0){ stream.log("[Escape Rope] Exit transition did not finish.", COLOR_ORANGE); return false; @@ -1801,39 +1795,29 @@ bool use_escape_rope( } -bool activate_repel( - VideoStream& stream, - ProControllerContext& context, - int num_icons, - int repel_type -){ - static const char* const slugs[]={"repel","super-repel","max-repel"}; - if (repel_type<0 || repel_type>2) { - stream.log("activate_repel: type must be 0 (Repel), 1 (Super Repel), or 2 (Max Repel).",COLOR_RED); +bool activate_repel(VideoStream& stream, ProControllerContext& context, int num_icons, int repel_type){ + static const char* const slugs[] = {"repel", "super-repel", "max-repel"}; + if (repel_type < 0 || repel_type > 2){ + stream.log("activate_repel: type must be 0 (Repel), 1 (Super Repel), or 2 (Max Repel).", COLOR_RED); return false; } - const std::string slug=slugs[repel_type]; - stream.log("Activating "+slug); + const std::string slug = slugs[repel_type]; + stream.log("Activating " + slug); open_menu(stream, context, MenuCursorPosition::BAG, num_icons); context.wait_for_all_requests(); pbf_wait(context, 1000ms); - auto repel_tab_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTab.png" - ); + auto repel_tab_ref = std::make_shared(RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTab.png"); // A smaller reference covers capture-resize sampling differences without // loosening the match threshold or changing shared image processing. - auto repel_tab_small_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabSmall.png" - ); + auto repel_tab_small_ref = + std::make_shared(RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabSmall.png"); // Obtaining Honey adds a NEW badge to the same selected Other Items tab. // Keep the original references and threshold for the earlier route stages. - auto repel_tab_honey_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabHoney.png" - ); - auto repel_tab_honey_small_ref = std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabHoneySmall.png" - ); + auto repel_tab_honey_ref = + std::make_shared(RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabHoney.png"); + auto repel_tab_honey_small_ref = + std::make_shared(RESOURCE_PATH() + "PokemonBDSP/Autostory/Bag_RepelTabHoneySmall.png"); const ImageFloatBox repel_tab_box{0.735000, 0.055000, 0.045000, 0.080000}; const double repel_tab_rmsd = 50.0; @@ -1847,17 +1831,18 @@ bool activate_repel( ImageMatchWatcher small_watcher(repel_tab_small_ref, repel_tab_box, repel_tab_rmsd); ImageMatchWatcher honey_watcher(repel_tab_honey_ref, repel_tab_box, repel_tab_rmsd); ImageMatchWatcher honey_small_watcher(repel_tab_honey_small_ref, repel_tab_box, repel_tab_rmsd); - int ret = wait_until(stream, context, 400ms, {{watcher}, {small_watcher}, {honey_watcher}, {honey_small_watcher}}); + int ret = + wait_until(stream, context, 400ms, {{watcher}, {small_watcher}, {honey_watcher}, {honey_small_watcher}}); if (ret >= 0){ - stream.log("[AutoStory] activate_repel: repel tab found after " - + std::to_string(press) + " press(es).", COLOR_GREEN); + stream.log("[AutoStory] activate_repel: repel tab found after " + std::to_string(press) + " press(es).", + COLOR_GREEN); found = true; break; } if (press < 8){ - stream.log("[AutoStory] activate_repel: tab not found, pressing right (" - + std::to_string(press + 1) + "/8)."); + stream.log("[AutoStory] activate_repel: tab not found, pressing right (" + std::to_string(press + 1) + + "/8)."); pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); } } @@ -1869,44 +1854,55 @@ bool activate_repel( // Reuse the Bag cursor/name geometry and existing Tesseract backend. A later // save may contain all three Repels and unrelated Other Items in any order. - bool item_found=false; - size_t confirmed=0; - size_t unknown_cursor=0; - for (size_t scan=0;scan<256;scan++) { + bool item_found = false; + size_t confirmed = 0; + size_t unknown_cursor = 0; + for (size_t scan = 0; scan < 256; scan++){ context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if (!frame || bag_list_is_empty(*frame.frame)) break; - const int cursor=bag_cursor_position(*frame.frame); - if (!cursor) { - if (++unknown_cursor>=3) break; - pbf_wait(context,200ms); + const auto frame = stream.video().snapshot(); + if (!frame || bag_list_is_empty(*frame.frame)){ + break; + } + const int cursor = bag_cursor_position(*frame.frame); + if (!cursor){ + if (++unknown_cursor >= 3){ + break; + } + pbf_wait(context, 200ms); continue; } - unknown_cursor=0; + unknown_cursor = 0; // Use the existing Tesseract entry point directly so item selection // does not depend on the optional Paddle/ONNX runtime configuration. - const std::string text=OCR::tesseract_ocr_read(Language::English, - extract_box_reference(*frame.frame,bag_list_name_box(cursor))); + const std::string text = + OCR::tesseract_ocr_read(Language::English, extract_box_reference(*frame.frame, bag_list_name_box(cursor))); std::string normalized; - for (unsigned char ch:text) if (std::isalpha(ch)) normalized.push_back((char)std::tolower(ch)); - static const char* const names[]={"repel","superrepel","maxrepel"}; - if (normalized==names[repel_type]) { - if (++confirmed>=2) {item_found=true;break;} - pbf_wait(context,150ms); - } else { - confirmed=0; + for (unsigned char ch : text){ + if (std::isalpha(ch)){ + normalized.push_back((char)std::tolower(ch)); + } + } + static const char* const names[] = {"repel", "superrepel", "maxrepel"}; + if (normalized == names[repel_type]){ + if (++confirmed >= 2){ + item_found = true; + break; + } + pbf_wait(context, 150ms); + }else{ + confirmed = 0; // Search both directions so a non-wrapping list opened halfway // down still reaches items above the initial cursor position. - pbf_press_dpad(context,scan<128 ? DPAD_DOWN : DPAD_UP,80ms,300ms); + pbf_press_dpad(context, scan < 128 ? DPAD_DOWN : DPAD_UP, 80ms, 300ms); } } - if (!item_found) { - stream.log("activate_repel: requested item not confirmed: "+slug,COLOR_RED); - pbf_mash_button(context,BUTTON_B,2000ms); + if (!item_found){ + stream.log("activate_repel: requested item not confirmed: " + slug, COLOR_RED); + pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); return false; } - stream.log("[AutoStory] activate_repel: selected item confirmed: "+slug,COLOR_GREEN); + stream.log("[AutoStory] activate_repel: selected item confirmed: " + slug, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 400ms); @@ -1929,29 +1925,22 @@ bool activate_repel( } -void use_HM( - VideoStream& stream, - ProControllerContext& context, - const std::string& label +void use_HM(VideoStream& stream, ProControllerContext& context, const std::string& label ){ stream.log("Using " + label); const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 30000ms); - }, - {{arrow}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 30000ms); + }, + {{arrow}}); if (ret != 0){ OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - "Using " + label + ": HM confirmation arrow not found; stopping before further movement.", - stream - ); + "Using " + label + ": HM confirmation arrow not found; stopping before further movement.", stream); } stream.log("Using " + label + ": selection arrow found, confirming.", COLOR_GREEN); @@ -1962,7 +1951,7 @@ void use_HM( bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLevel /*gear*/){ static const ImageFloatBox FRONT_BOX{0.440000, 0.470000, 0.035000, 0.054000}; - static const ImageFloatBox BACK_BOX {0.520000, 0.470000, 0.022000, 0.054000}; + static const ImageFloatBox BACK_BOX{0.520000, 0.470000, 0.022000, 0.054000}; // Spark color range: white (R=232,G=255,B=255) down to light green (R=163,G=217,B=181) static const uint32_t SPARK_MIN = 0xFF000000u | (163u << 16) | (217u << 8) | 181u; static const uint32_t SPARK_MAX = 0xFFFFFFFFu; @@ -1983,33 +1972,37 @@ bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLeve while (std::chrono::steady_clock::now() < deadline){ context.wait_for(50ms); VideoSnapshot screen = stream.video().snapshot(); - if (!screen) continue; + if (!screen){ + continue; + } size_t front_pixels = 0, back_pixels = 0; - filter_rgb32_range(front_pixels, extract_box_reference(screen, FRONT_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); - filter_rgb32_range(back_pixels, extract_box_reference(screen, BACK_BOX), SPARK_MIN, SPARK_MAX, Color(0), false); + filter_rgb32_range(front_pixels, extract_box_reference(screen, FRONT_BOX), SPARK_MIN, SPARK_MAX, Color(0), + false); + filter_rgb32_range(back_pixels, extract_box_reference(screen, BACK_BOX), SPARK_MIN, SPARK_MAX, Color(0), + false); - if (front_pixels > max_front) max_front = front_pixels; - if (back_pixels > max_back) max_back = back_pixels; + if (front_pixels > max_front){ + max_front = front_pixels; + } + if (back_pixels > max_back){ + max_back = back_pixels; + } if (front_pixels >= SPARK_THRESHOLD || back_pixels >= SPARK_THRESHOLD){ - stream.log( - "[AutoStory] get_on_bicycle: sparks detected " - "(front=" + std::to_string(front_pixels) + - " back=" + std::to_string(back_pixels) + - " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", - COLOR_GREEN - ); + stream.log("[AutoStory] get_on_bicycle: sparks detected " + "(front=" + + std::to_string(front_pixels) + " back=" + std::to_string(back_pixels) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_GREEN); return true; } } - stream.log( - "[AutoStory] get_on_bicycle: no sparks detected " - "(peak front=" + std::to_string(max_front) + - " back=" + std::to_string(max_back) + - " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", - COLOR_ORANGE - ); + stream.log("[AutoStory] get_on_bicycle: no sparks detected " + "(peak front=" + + std::to_string(max_front) + " back=" + std::to_string(max_back) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_ORANGE); return false; }; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index bf0acc6ad9..9d5021c6cb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -7,39 +7,41 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStoryTools_H #define PokemonAutomation_PokemonBDSP_AutoStoryTools_H -#include "CommonFramework/GlobalAutoPaths.h" #include +#include #include #include -#include #include + #include "Common/Cpp/Options/EnumDropdownOption.h" + +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Tools/VideoStream.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" + #include "CommonTools/VisualDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" + #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" + +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" + #include "Detect/PokemonBDSP_AutoStory_MenuCursorDetector.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // --------------------------------------------------------------------------- // Stats // --------------------------------------------------------------------------- -struct AutoStoryStats : public StatsTracker{ - AutoStoryStats() - : m_checkpoint(m_stats["Checkpoint"]) - , m_segment(m_stats["Segment"]) - , m_reset(m_stats["Reset"]) - { +struct AutoStoryStats : public StatsTracker { + AutoStoryStats() : m_checkpoint(m_stats["Checkpoint"]), m_segment(m_stats["Segment"]), m_reset(m_stats["Reset"]){ m_display_order.emplace_back("Checkpoint"); m_display_order.emplace_back("Segment"); m_display_order.emplace_back("Reset"); @@ -55,22 +57,22 @@ struct AutoStoryStats : public StatsTracker{ // Options passed to every segment/checkpoint // --------------------------------------------------------------------------- -enum class StarterChoice{ +enum class StarterChoice { TURTWIG, CHIMCHAR, PIPLUP, }; -enum class HelpMode{ +enum class HelpMode { NO_OUTSIDE_HELP, OUTSIDE_HELP, }; -enum class OutsideHelpPokemon{ +enum class OutsideHelpPokemon { TORTERRA, }; -enum class TorterraMove{ +enum class TorterraMove { EARTHQUAKE, WOOD_HAMMER, RAZOR_LEAF, @@ -98,17 +100,24 @@ enum class Trainer { // Returns the trainers.json battle-ID string for a Trainer value. const char* trainer_slug(Trainer t); -struct OutsideHelpConfig{ +struct OutsideHelpConfig { OutsideHelpPokemon pokemon; TorterraMove attacks[4]; }; -enum class CheckpointSaveMode { REAL, FAKE }; +enum class CheckpointSaveMode { + REAL, + FAKE +}; // Real saves retry one checkpoint. Fake-save runs own one retry loop around // the selected range; inner checkpoints propagate failures to that owner. -enum class CheckpointRetryScope { CHECKPOINT, SELECTED_RANGE, DEFER_TO_SELECTED_RANGE }; +enum class CheckpointRetryScope { + CHECKPOINT, + SELECTED_RANGE, + DEFER_TO_SELECTED_RANGE +}; -struct AutoStoryOptions{ +struct AutoStoryOptions { Language language; StarterChoice starter_choice; EventNotificationOption& notif_status_update; @@ -123,32 +132,24 @@ struct AutoStoryOptions{ // Base classes // --------------------------------------------------------------------------- -class AutoStory_Segment{ +class AutoStory_Segment { public: virtual ~AutoStory_Segment() = default; - virtual std::string name() const = 0; + virtual std::string name() const = 0; virtual std::string start_text() const = 0; - virtual std::string end_text() const = 0; - virtual void run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const = 0; + virtual std::string end_text() const = 0; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const = 0; }; -class AutoStory_Checkpoint{ +class AutoStory_Checkpoint { public: virtual ~AutoStory_Checkpoint() = default; - virtual std::string name() const = 0; + virtual std::string name() const = 0; virtual std::string start_text() const = 0; - virtual std::string end_text() const = 0; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const = 0; + virtual std::string end_text() const = 0; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const = 0; }; @@ -158,34 +159,21 @@ class AutoStory_Checkpoint{ // Save the game from the overworld with retries. Returns true if the save was // verified via overworld detection, false if the blind fallback was used. -bool save_game( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -); +bool save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& context); // Fake save game to be replaced with save_game when program runs smoothly -void checkpoint_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -); +void checkpoint_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context); // Save the game from the overworld, increment the checkpoint counter, // update stats display, and send a status notification. -void checkpoint_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - CheckpointSaveMode save_mode = CheckpointSaveMode::REAL -); +void checkpoint_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL); // Reload the game from the most recent backup save (auto-save). // Called on retry when a checkpoint has no hard save (save=false). // TODO: implement this function. -void reload_backup_save( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context -); +void reload_backup_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context); // If save=true (default): save the game before the first attempt, then run // `action(attempt_number)`. On failure, reset from HOME and retry. @@ -193,25 +181,18 @@ void reload_backup_save( // SELECTED_RANGE skips the entry save and owns HOME resets for the range. // DEFER_TO_SELECTED_RANGE lets an inner failure reach that outer retry loop. // Throws a fatal error after too many consecutive failures. -void checkpoint_reattempt_loop( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats, - std::function&& action, - bool save = true, - CheckpointSaveMode save_mode = CheckpointSaveMode::REAL, - CheckpointRetryScope retry_scope = CheckpointRetryScope::CHECKPOINT -); +void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats, + std::function&& action, bool save = true, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL, + CheckpointRetryScope retry_scope = CheckpointRetryScope::CHECKPOINT); // Forward the explicit checkpoint save mode selected by AutoStory's options. -inline void checkpoint_reattempt_loop( - SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats, - std::function&& action, bool save = true -){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, - std::move(action), save, options.checkpoint_save_mode, options.checkpoint_retry_scope); +inline void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats, + std::function&& action, bool save = true){ + checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, std::move(action), save, + options.checkpoint_save_mode, options.checkpoint_retry_scope); } // Execute every action in `actions` in order, repeated `times` times. @@ -225,35 +206,20 @@ struct DpadState { // Press `dir` on the D-pad `times` times (plus one extra if the direction // changed from the previous call, since the first press turns the character). -void repeat_dpad( - ProControllerContext& context, - DpadState& state, - DpadPosition dir, - Milliseconds press, - Milliseconds hold, - size_t times, - bool include_last_dir = true -); +void repeat_dpad(ProControllerContext& context, DpadState& state, DpadPosition dir, Milliseconds press, + Milliseconds hold, size_t times, bool include_last_dir = true); // Wait (without pressing any buttons) until a short dialogue box appears. // Logs `label + " dialogue detected."` when found (omit label to skip logging). // Throws OperationFailedException if no dialogue appears within `timeout`. -void wait_for_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label = "", - Milliseconds timeout = std::chrono::seconds(60) -); +void wait_for_dialogue(VideoStream& stream, ProControllerContext& context, const std::string& label = "", + Milliseconds timeout = std::chrono::seconds(60)); // Mash `button` (default B) until the game returns to the free-roaming overworld // (no dialogue, menu, or battle UI visible). // Throws OperationFailedException if the overworld is not reached within `timeout`. -void mash_until_dialogue_ends( - VideoStream& stream, - ProControllerContext& context, - Button button = BUTTON_B, - Milliseconds timeout = std::chrono::seconds(60) -); +void mash_until_dialogue_ends(VideoStream& stream, ProControllerContext& context, Button button = BUTTON_B, + Milliseconds timeout = std::chrono::seconds(60)); // Scripted trainer battle: choose a 1-based move position each turn, then // confirm the default target with A and clear narration with B. No target steering. @@ -265,10 +231,10 @@ void mash_until_dialogue_ends( // fight_trainer_using_moves(stream, context, 2, "Grunt", false); // const std::vector moves{2, 1, 3}; // fight_trainer_using_moves(stream, context, moves, "Gym leader", true); -void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, - size_t move, const std::string& label, bool post_battle_dialogue_cleanup); -void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, - const std::vector& moves, const std::string& label, bool post_battle_dialogue_cleanup); +void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, size_t move, + const std::string& label, bool post_battle_dialogue_cleanup); +void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, const std::vector& moves, + const std::string& label, bool post_battle_dialogue_cleanup); // A full Bag screenshot with the desired item highlighted. Paths are relative // to RESOURCE_PATH(); quantities and selection borders are excluded. @@ -301,16 +267,20 @@ size_t bag_clear_tab_count(const ImageViewRGB32& frame); // with 80ms Down taps / 1s rest until all 8 tabs match, // then restore the top of the item list and close the bag. // Returns false after four bounded pocket passes. -bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons=8); +bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons = 8); bool bag_list_is_empty(const ImageViewRGB32& frame); int bag_cursor_position(const ImageViewRGB32& frame); -std::array match_bag_item_rows( - const ImageViewRGB32& frame, - const ImageViewRGB32& reference, - const BagItemTemplate& item -); - -enum class BagMoveAction { UNKNOWN, CONFIRM, UP, DOWN, PAGE_DOWN, REWIND }; +std::array match_bag_item_rows(const ImageViewRGB32& frame, const ImageViewRGB32& reference, + const BagItemTemplate& item); + +enum class BagMoveAction { + UNKNOWN, + CONFIRM, + UP, + DOWN, + PAGE_DOWN, + REWIND +}; struct BagMovePlan { BagMoveAction action = BagMoveAction::UNKNOWN; size_t presses = 0; @@ -320,7 +290,12 @@ struct BagMovePlan { // recheck until row 1; a screenshot cannot tell the total distance to the top. BagMovePlan plan_bag_item_move(int cursor_row, int item_row); -enum class BagPocketAction { UNKNOWN, CONFIRM, LEFT, RIGHT }; +enum class BagPocketAction { + UNKNOWN, + CONFIRM, + LEFT, + RIGHT +}; struct BagPocketMovePlan { BagPocketAction action = BagPocketAction::UNKNOWN; size_t presses = 0; @@ -335,22 +310,12 @@ BagPocketMovePlan plan_bag_pocket_move(int current, int target); // item in the green header. Makes up to three passes per pocket. // Limits report an incomplete search, not absence. // Returns false on missing frames, timeout, or no match; does not use an item. -bool find_bag_item( - VideoStream& stream, - ProControllerContext& context, - const BagItemTemplate& item, - int target_pocket, - int num_icons = 8, - size_t max_scrolls_per_pocket = 512, - std::chrono::milliseconds search_timeout = std::chrono::minutes(10) -); +bool find_bag_item(VideoStream& stream, ProControllerContext& context, const BagItemTemplate& item, int target_pocket, + int num_icons = 8, size_t max_scrolls_per_pocket = 512, + std::chrono::milliseconds search_timeout = std::chrono::minutes(10)); // Find Escape Rope, choose Use, and verify a transition back to the overworld. -bool use_escape_rope( - VideoStream& stream, - ProControllerContext& context, - int num_icons = 8 -); +bool use_escape_rope(VideoStream& stream, ProControllerContext& context, int num_icons = 8); // Reuse the tutorial's verified Jubilife Center alignment and entry. void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context); @@ -359,28 +324,15 @@ void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& // Returns false if the repel compartment tab could not be found. // num_icons: early/late menu layout (preserved for existing callers). // repel_type: 0=Repel, 1=Super Repel, 2=Max Repel. English item names. -bool activate_repel( - VideoStream& stream, - ProControllerContext& context, - int num_icons = 8, - int repel_type = 0 -); +bool activate_repel(VideoStream& stream, ProControllerContext& context, int num_icons = 8, int repel_type = 0); // Press A until the any HM selection arrow appears, confirm with A, then mash B for 3 s. -void use_HM( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -); +void use_HM(VideoStream& stream, ProControllerContext& context, const std::string& label); // Walk to the nurse, heal the party, and exit the Pokemon Center. // `label` names the center for log messages (e.g. "Veilstone"). // Returns false if the exit black screen was not detected. -bool heal_pokemon( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -); +bool heal_pokemon(VideoStream& stream, ProControllerContext& context, const std::string& label); enum class FlyPoint { CanalavCity, @@ -400,7 +352,7 @@ enum class FlyPoint { SunyshoreCity, TwinleafTown, VeilstoneCity, - CurrentCity, // cursor already starts on the city; skip all map navigation + CurrentCity, // cursor already starts on the city; skip all map navigation }; std::string fly_point_name(FlyPoint place); @@ -408,25 +360,13 @@ std::string fly_point_name(FlyPoint place); // Open the map, move the cursor to either bottom left, bottom right or // top left, then move the cursor a number of steps, then checks for // correctness, and mashes A until a black screen is detected -bool fly_to( - VideoStream& stream, - ProControllerContext& context, - FlyPoint place -); +bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place); // Opens the X-menu from the overworld, navigates the cursor to `target`, and presses A. // num_icons: 5 (early-game layout) or 8 (late-game layout). -void open_menu( - VideoStream& stream, - ProControllerContext& context, - MenuCursorPosition target, - int num_icons -); - -void use_potion_first_pokemon( - VideoStream& stream, - ProControllerContext& context -); +void open_menu(VideoStream& stream, ProControllerContext& context, MenuCursorPosition target, int num_icons); + +void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context); enum class GearLevel { Fast, @@ -457,19 +397,15 @@ static constexpr int BUY_MAX = -1; struct ItemToBuy { ShopItem item; - int quantity; // positive count to buy, or BUY_MAX + int quantity; // positive count to buy, or BUY_MAX }; // Walk up to a vendor (already talking to them), navigate through the Buy menu, // and purchase each item in `items` in order. The function presses DPAD_DOWN // until the matching item name is found via OCR, so items may appear in any // list order. Returns true on success. -bool buy_items( - VideoStream& stream, - ProControllerContext& context, - const std::vector& items, - Language language = Language::English -); +bool buy_items(VideoStream& stream, ProControllerContext& context, const std::vector& items, + Language language = Language::English); // Mount the bicycle (press +) and confirm it is in the requested gear. @@ -477,11 +413,7 @@ bool buy_items( // determine which gear was entered. If the wrong gear is entered, presses B // again and re-checks. Player must be on foot when called. // Returns false if gear state cannot be confirmed after two toggles. -bool get_on_bicycle( - VideoStream& stream, - ProControllerContext& context, - GearLevel gear -); +bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLevel gear); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 97edbe7bea..4f11f6ab34 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -5,1276 +5,1294 @@ * */ -#include "CommonFramework/GlobalAutoPaths.h" -#include "PokemonBDSP_AutoStory_Segment_00.h" -#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" -#include "../Utils/PokemonBDSP_OverworldMatchers.h" - #include #include + #include + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" - -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" - -#include "Pokemon/Pokemon_Strings.h" -#include "Pokemon/Pokemon_Notification.h" -#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -#include "PokemonBDSP/Programs/ShinyHunting/PokemonBDSP_StarterReset.h" #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" +#include "Pokemon/Pokemon_Notification.h" +#include "Pokemon/Pokemon_Strings.h" + +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h" #include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" +#include "PokemonBDSP/Programs/ShinyHunting/PokemonBDSP_StarterReset.h" + +#include "PokemonBDSP_AutoStory_Segment_00.h" using namespace std::chrono_literals; namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - // --------------------------------------------------------------------------- - // Starter selection - // --------------------------------------------------------------------------- - - // Watch before movement starts, release on the fade, and hold the - // following recorded wait until the destination is visible again. - static void recorded_move_until_transition( - VideoStream& stream, ProControllerContext& context, - double x, double y, Milliseconds duration, const std::string& label - ) { - context.wait_for_all_requests(); - stream.log("[Tutorial transition] " + label + ": approaching fade."); - BlackScreenWatcher black; - int ret = run_until(stream, context, - [x, y, duration](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); - // Keep observing a delayed fade after the recorded hold. - pbf_wait(ctx, 5000ms); - }, {{black}}); - if (ret != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Black transition not detected.", stream); - } - BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, - 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); - if (wait_until(stream, context, 15s, {{visible}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not finish.", stream); - } - stream.log("[Tutorial transition] " + label + ": destination visible."); - } - - static void select_turtwig(VideoStream& stream, ProControllerContext& context) { - stream.log("[DEBUG] Turtwig Selection: Starting starter selection"); - - pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); - context.wait_for_all_requests(); - - ShortDialogWatcher watcher(COLOR_CYAN); - int ret = run_until( - stream, context, - [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, - { watcher } - ); - if (ret != 0) { - ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until( - stream, context, - [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, - { watcher_red } - ); - } - stream.log(ret == 0 ? "[DEBUG] Turtwig Selection: Confirmation dialogue detected" - : "[WARNING] Turtwig Selection: Dialogue detection failed, proceeding anyway"); - - pbf_wait(context, 400ms); - pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); - pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); - context.wait_for_all_requests(); - stream.log("[DEBUG] Turtwig Selection: Selection complete"); - } - - static void select_chimchar(VideoStream& stream, ProControllerContext& context) { - stream.log("[DEBUG] Chimchar Selection: Starting starter selection"); - - pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); - pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); - context.wait_for_all_requests(); +namespace NintendoSwitch { +namespace PokemonBDSP { +// --------------------------------------------------------------------------- +// Starter selection +// --------------------------------------------------------------------------- + +// Watch before movement starts, release on the fade, and hold the +// following recorded wait until the destination is visible again. +static void recorded_move_until_transition(VideoStream& stream, ProControllerContext& context, double x, double y, + Milliseconds duration, const std::string& label){ + context.wait_for_all_requests(); + stream.log("[Tutorial transition] " + label + ": approaching fade."); + BlackScreenWatcher black; + int ret = run_until(stream, context, + [x, y, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + // Keep observing a delayed fade after the recorded hold. + pbf_wait(ctx, 5000ms); + }, + {{black}}); + if (ret != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Black transition not detected.", stream); + } + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Transition did not finish.", stream); + } + stream.log("[Tutorial transition] " + label + ": destination visible."); +} - ShortDialogWatcher watcher(COLOR_CYAN); - int ret = run_until( - stream, context, - [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, - { watcher } - ); - if (ret != 0) { - ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until( - stream, context, - [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, - { watcher_red } - ); - } - stream.log(ret == 0 ? "[DEBUG] Chimchar Selection: Confirmation dialogue detected" - : "[WARNING] Chimchar Selection: Dialogue detection failed, proceeding anyway"); - - pbf_wait(context, 400ms); - pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); - pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); - context.wait_for_all_requests(); - stream.log("[DEBUG] Chimchar Selection: Selection complete"); - } +static void select_turtwig(VideoStream& stream, ProControllerContext& context){ + stream.log("[DEBUG] Turtwig Selection: Starting starter selection"); + + pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); + context.wait_for_all_requests(); + + ShortDialogWatcher watcher(COLOR_CYAN); + int ret = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher}); + if (ret != 0){ + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher_red}); + } + stream.log(ret == 0 ? "[DEBUG] Turtwig Selection: Confirmation dialogue detected" + : "[WARNING] Turtwig Selection: Dialogue detection failed, proceeding anyway"); + + pbf_wait(context, 400ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); + context.wait_for_all_requests(); + stream.log("[DEBUG] Turtwig Selection: Selection complete"); +} - static void select_piplup(VideoStream& stream, ProControllerContext& context) { - stream.log("[DEBUG] Piplup Selection: Starting starter selection"); +static void select_chimchar(VideoStream& stream, ProControllerContext& context){ + stream.log("[DEBUG] Chimchar Selection: Starting starter selection"); + + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); + pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); + context.wait_for_all_requests(); + + ShortDialogWatcher watcher(COLOR_CYAN); + int ret = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher}); + if (ret != 0){ + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher_red}); + } + stream.log(ret == 0 ? "[DEBUG] Chimchar Selection: Confirmation dialogue detected" + : "[WARNING] Chimchar Selection: Dialogue detection failed, proceeding anyway"); + + pbf_wait(context, 400ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); + context.wait_for_all_requests(); + stream.log("[DEBUG] Chimchar Selection: Selection complete"); +} - pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); - pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); - pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); - context.wait_for_all_requests(); +static void select_piplup(VideoStream& stream, ProControllerContext& context){ + stream.log("[DEBUG] Piplup Selection: Starting starter selection"); + + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); + pbf_press_dpad(context, DPAD_RIGHT, 160ms, 840ms); + pbf_press_button(context, BUTTON_ZL, 160ms, 240ms); + context.wait_for_all_requests(); + + ShortDialogWatcher watcher(COLOR_CYAN); + int ret = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher}); + if (ret != 0){ + ShortDialogWatcher watcher_red(COLOR_RED); + ret = run_until(stream, context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher_red}); + } + stream.log(ret == 0 ? "[DEBUG] Piplup Selection: Confirmation dialogue detected" + : "[WARNING] Piplup Selection: Dialogue detection failed, proceeding anyway"); + + pbf_wait(context, 400ms); + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); + context.wait_for_all_requests(); + stream.log("[DEBUG] Piplup Selection: Selection complete"); +} - ShortDialogWatcher watcher(COLOR_CYAN); - int ret = run_until( - stream, context, - [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, - { watcher } - ); - if (ret != 0) { - ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until( - stream, context, - [](ProControllerContext& ctx) { ctx.wait_for(std::chrono::milliseconds(5000)); }, - { watcher_red } - ); - } - stream.log(ret == 0 ? "[DEBUG] Piplup Selection: Confirmation dialogue detected" - : "[WARNING] Piplup Selection: Dialogue detection failed, proceeding anyway"); - - pbf_wait(context, 400ms); - pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); - pbf_press_button(context, BUTTON_ZL, 80ms, 5000ms); - context.wait_for_all_requests(); - stream.log("[DEBUG] Piplup Selection: Selection complete"); - } +// Navigation functions for each part of the tutorial. These functions are meant to be used +// in order and rely on the state of the game being what it is at the end of the previous function. +// They may also be modified or removed without warning. + +// --------------------------------------------------------------------------- +// Segment 00-01 begins +// Navigates from player's room to Route 201 through Mom/Barry dialogues. +// --------------------------------------------------------------------------- + +static void tutorial_part_1_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + DateTime t{2026, 1, 1, 0, 0, 0}; + set_time_fast(env, context, t); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 527ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 456ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 915ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 642ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 557ms, 0ms); + pbf_wait(context, 978ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1112ms, 0ms); + pbf_wait(context, 4967ms); + + wait_for_dialogue(stream, context, "Phase 1 (Mom)"); + + pbf_mash_button(context, BUTTON_A, 5000ms); + pbf_wait(context, 300ms); + + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1662ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -1.000000}, 49ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1215ms, 0ms); + pbf_wait(context, 1419ms); + + wait_for_dialogue(stream, context, "Phase 2 (Mom Warning)"); + + pbf_mash_button(context, BUTTON_A, 2500ms); + pbf_wait(context, 300ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0.000000, -1.000000}, 680ms, 0ms); + + pbf_wait(context, 4000ms); + + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1748ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 18ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2763ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 58ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1364ms, 0ms); + pbf_wait(context, 4000ms); + context.wait_for_all_requests(); + + wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); + pbf_mash_button(context, BUTTON_A, 6000ms); + pbf_wait(context, 800ms); + wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); + pbf_mash_button(context, BUTTON_A, 6000ms); + pbf_wait(context, 800ms); + + pbf_wait(context, 1306ms); + context.wait_for_all_requests(); + stream.log("[Barry house] Entering; waiting for entry fade."); + BlackScreenWatcher entry_black; + int entry_ret = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 5000ms, 0ms); + }, + {{entry_black}}); + if (entry_ret != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Barry house: Entry fade not detected.", stream); + } + // Release the joystick during loading; resume indoor movement only + // after the screen has stayed non-black for half a second. + BlackScreenWatcher entry_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 15s, {{entry_visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Barry house: Entry fade did not finish.", stream); + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + stream.log("[Barry house] Entry settled; starting stair approach."); + // Align left after entry before the next upward stair approach. + pbf_move_left_joystick(context, {0.000000, 1.000000}, 400ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 0.000000}, 1600ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2722ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1600ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 501ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 652ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1817ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 975ms, 0ms); + pbf_wait(context, 1422ms); + + wait_for_dialogue(stream, context, "Phase 8 (Barry 2nd dialog)"); + pbf_mash_button(context, BUTTON_A, 10000ms); + + pbf_wait(context, 856ms); + + + // Recorded route from the top of Barry's stairs. Moves rounded to + // 100ms, waits to 50ms; transition waits gate both floor changes. + recorded_move_until_transition(stream, context, -1.0, 0.0, 900ms, "Barry house downstairs"); + pbf_wait(context, 750ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 2000ms, 0ms); + pbf_wait(context, 250ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 2000ms, 0ms); + pbf_wait(context, 150ms); + pbf_move_left_joystick(context, {1.0, 0.0}, 1400ms, 0ms); + pbf_wait(context, 500ms); + recorded_move_until_transition(stream, context, 0.0, -1.0, 1200ms, "Barry house exit"); + // First-run video placed the player east of the northbound path. + // Shorten the rightward approach; tune further from the next recording. + stream.log("[Route 201] Aligning east for 2300ms before northbound approach."); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2300ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 5861ms, 0ms); + pbf_wait(context, 134ms); + + wait_for_dialogue(stream, context, "Phase 9 (Barry Route 201)"); + + pbf_mash_button(context, BUTTON_B, 6000ms); + pbf_wait(context, 800ms); + + pbf_move_left_joystick(context, {0.000000, 1.000000}, 700ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 279ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 0.000000}, 10ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 145ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 0.000000}, 7703ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 140ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1700ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 39ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 0.000000}, 544ms, 1000ms); + + pbf_wait(context, 1000ms); + + // save_game(stream, context); // Disabled: checkpoint save mode controls saving. + context.wait_for_all_requests(); +} +// --------------------------------------------------------------------------- +// End of Segment 00 - 01 +// Standing in front lake varity ready to enter lake Verity for starter selection. +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Segment 00-02 begins +// Navigates into lake Verity to recieve starter +// --------------------------------------------------------------------------- +static void starter_nav(VideoStream& stream, ProControllerContext& context){ + // re-sync control + pbf_press_button(context, BUTTON_B, 20ms, 200ms); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + + // continue movement + pbf_move_left_joystick(context, {0.000000, 1.000000}, 300ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 300ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 10 (Barry before the starter selection)"); + + pbf_mash_button(context, BUTTON_A, 1000ms); + + pbf_wait(context, 500ms); + std::shared_ptr briefcase = + std::make_shared(RESOURCE_PATH() + "PokemonBDSP/StarterBriefcase.png"); + + // Mash B until we see the briefcase. + ImageMatchWatcher detector(briefcase, {0.5, 0.1, 0.5, 0.7}, 100, true); + + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 120000ms); + }, + {{detector}}); + + if (ret == 0){ + stream.log("Detected briefcase!"); + }else{ + stream.log("Timed out waiting for briefcase."); + throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, + "Failed to detect briefcase in Segment 00.", stream); + } - // Navigation functions for each part of the tutorial. These functions are meant to be used - // in order and rely on the state of the game being what it is at the end of the previous function. - // They may also be modified or removed without warning. - - // --------------------------------------------------------------------------- - // Segment 00-01 begins - // Navigates from player's room to Route 201 through Mom/Barry dialogues. - // --------------------------------------------------------------------------- - - static void tutorial_part_1_navigation(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - DateTime t{ 2026, 1, 1, 0, 0, 0 }; - set_time_fast(env, context, t); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 527ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 456ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 915ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, 1.000000 }, 642ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 557ms, 0ms); - pbf_wait(context, 978ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1112ms, 0ms); - pbf_wait(context, 4967ms); - - wait_for_dialogue(stream, context, "Phase 1 (Mom)"); - - pbf_mash_button(context, BUTTON_A, 5000ms); - pbf_wait(context, 300ms); + // Wait for briefcase to fully open. + stream.log("Mashing B for briefcase to fully open."); + pbf_mash_button(context, BUTTON_B, 2000ms); +} +static void post_starly_clenaup(VideoStream& stream, ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 60000ms); +} +// --------------------------------------------------------------------------- +// End of Segment 00 - 02 +// Standing in front lake varity after starter selection. +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Segment 00-03 begins +// Navigates from Exit of lake Verity to receipt of the Pokedex +// --------------------------------------------------------------------------- + +static void tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + + pbf_wait(context, 563ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1700ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 510ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 50ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 310ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 5500ms, 0ms); + pbf_wait(context, 3963ms); + context.wait_for_all_requests(); + + wait_for_dialogue(stream, context, "Phase 10 (Professor dialog to mom)"); + pbf_mash_button(context, BUTTON_B, 40000ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 820ms, 0ms); + pbf_wait(context, 4000ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 800ms, 0ms); + pbf_wait(context, 555ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 50ms, 0ms); + pbf_wait(context, 23ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 5020ms, 0ms); + pbf_wait(context, 169ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); + DateTime t{2026, 1, 1, 0, 0, 50}; + set_time_fast(env, context, t); + walk_through_grass(stream, context, 1, 0, 20, "right through grass to path", BattleType::STARTER); + walk_through_grass(stream, context, 0, 1, 18, "up through grass 2", BattleType::STARTER); + walk_through_grass(stream, context, 1, 0, 10, "right through grass 2", BattleType::STARTER); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 730ms, 0ms); + pbf_wait(context, 941ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1950ms, 0ms); + wait_for_dialogue(stream, context, "Phase 11 (dawn conversation)"); + pbf_mash_button(context, BUTTON_B, 50000ms); + wait_for_dialogue(stream, context, "Phase 12 (Nickname reject)"); + pbf_mash_button(context, BUTTON_B, 40000ms); + wait_for_dialogue(stream, context, "Phase 13 (Pokedex accept)"); + pbf_mash_button(context, BUTTON_A, 40000ms); + + + // pokedex received +} - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1662ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -1.000000 }, 49ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1215ms, 0ms); - pbf_wait(context, 1419ms); - wait_for_dialogue(stream, context, "Phase 2 (Mom Warning)"); +// --------------------------------------------------------------------------- +// End of Segment 00 - 03 +// Standing in front of the professor after receiving the pokedex. +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// Begin Segment 00 - 04 +// Will heal at Sandgem pokeCenter to reliably catch a second pokemon for Home +// transfer. +// Collects poketech and stands in front of the PC to end the tutorial section +// and send the starter to pokemon HOME. +// --------------------------------------------------------------------------- + +static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + // Arm exit detection before movement and finish the fade before + // starting the dialogue watcher, which cancels queued commands. + recorded_move_until_transition(stream, context, 0.0, -1.0, 2010ms, "Rowan lab exit"); + context.wait_for_all_requests(); + + wait_for_dialogue(stream, context, "Phase 14 (dawn tour)"); + + pbf_mash_button(context, BUTTON_B, 25000ms); + + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 5125ms, 0ms); + pbf_wait(context, 336ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1700ms, 0ms); + pbf_wait(context, 213ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2325ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, 1.000000}, 950ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2360ms, 0ms); + pbf_wait(context, 253ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 4610ms, 0ms); + pbf_wait(context, 954ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 220ms, 0ms); + pbf_wait(context, 587ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1375ms, 0ms); + pbf_wait(context, 464ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 775ms, 0ms); + pbf_wait(context, 1655ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 640ms, 0ms); + pbf_wait(context, 3128ms); + pbf_press_dpad(context, DPAD_UP, 180ms, 0ms); + pbf_wait(context, 472ms); + pbf_press_dpad(context, DPAD_UP, 130ms, 0ms); + pbf_wait(context, 1621ms); + pbf_press_dpad(context, DPAD_RIGHT, 130ms, 0ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 15 (Mom conversation to accept parcel)"); + + pbf_mash_button(context, BUTTON_B, 40000ms); + + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + pbf_press_dpad(context, DPAD_LEFT, 150ms, 0ms); + pbf_wait(context, 1143ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 875ms, 0ms); + pbf_wait(context, 1169ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 946ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 9092ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 740ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 2998ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 240ms, 0ms); + pbf_wait(context, 893ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 5300ms, 0ms); + pbf_wait(context, 282ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 260ms, 0ms); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + DateTime t{2026, 1, 1, 0, 0, 50}; + set_time_fast(env, context, t); + walk_through_grass(stream, context, 1, 0, 20, "right through grass to path", BattleType::STARTER); + walk_through_grass(stream, context, 0, 1, 18, "up through grass 2", BattleType::STARTER); + walk_through_grass(stream, context, 1, 0, 10, "right through grass 2", BattleType::STARTER); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + pbf_move_left_joystick(context, {0.000000, -1.000000}, 560ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 5000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1855ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 430ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 850ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1120ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 16 (catch tutorial)"); + + pbf_mash_button(context, BUTTON_B, 70000ms); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + catch_1_pokemon(stream, context); + pbf_wait(context, 1500ms); + context.wait_for_all_requests(); +} - pbf_mash_button(context, BUTTON_A, 2500ms); - pbf_wait(context, 300ms); - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 680ms, 0ms); - - pbf_wait(context, 4000ms); - - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1748ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 18ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2763ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 58ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1364ms, 0ms); - pbf_wait(context, 4000ms); - context.wait_for_all_requests(); - - wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); - pbf_mash_button(context, BUTTON_A, 6000ms); - pbf_wait(context, 800ms); - wait_for_dialogue(stream, context, "Phase 3 (Barry 1st)"); - pbf_mash_button(context, BUTTON_A, 6000ms); - pbf_wait(context, 800ms); - - pbf_wait(context, 1306ms); - context.wait_for_all_requests(); - stream.log("[Barry house] Entering; waiting for entry fade."); - BlackScreenWatcher entry_black; - int entry_ret = run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0, 1}, 5000ms, 0ms); - }, {{entry_black}}); - if (entry_ret != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry house: Entry fade not detected.", stream); - } - // Release the joystick during loading; resume indoor movement only - // after the screen has stayed non-black for half a second. - BlackScreenWatcher entry_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, - 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); - if (wait_until(stream, context, 15s, {{entry_visible}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry house: Entry fade did not finish.", stream); +// Align to a route reference before continuing recorded navigation. +// Matcher offsets and thresholds are fractions of screen width/height. +static void align_to_tutorial_landmarks(VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label){ + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmations = 0, coarse_confirmations = 0; + double previous_dx = 0, previous_dy = 0; + for (size_t attempt = 0; attempt < 60; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + } + const auto result = matcher(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); } - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - stream.log("[Barry house] Entry settled; starting stair approach."); - // Align left after entry before the next upward stair approach. - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 400ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 1600ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2722ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1600ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 501ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 652ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1817ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 975ms, 0ms); - pbf_wait(context, 1422ms); - - wait_for_dialogue(stream, context, "Phase 8 (Barry 2nd dialog)"); - pbf_mash_button(context, BUTTON_A, 10000ms); - - pbf_wait(context, 856ms); - - - // Recorded route from the top of Barry's stairs. Moves rounded to - // 100ms, waits to 50ms; transition waits gate both floor changes. - recorded_move_until_transition(stream, context, -1.0, 0.0, 900ms, - "Barry house downstairs"); - pbf_wait(context, 750ms); - pbf_move_left_joystick(context, {-1.0, 0.0}, 2000ms, 0ms); - pbf_wait(context, 250ms); - pbf_move_left_joystick(context, {0.0, -1.0}, 2000ms, 0ms); - pbf_wait(context, 150ms); - pbf_move_left_joystick(context, {1.0, 0.0}, 1400ms, 0ms); - pbf_wait(context, 500ms); - recorded_move_until_transition(stream, context, 0.0, -1.0, 1200ms, - "Barry house exit"); - // First-run video placed the player east of the northbound path. - // Shorten the rightward approach; tune further from the next recording. - stream.log("[Route 201] Aligning east for 2300ms before northbound approach."); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2300ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5861ms, 0ms); - pbf_wait(context, 134ms); - - wait_for_dialogue(stream, context, "Phase 9 (Barry Route 201)"); - - pbf_mash_button(context, BUTTON_B, 6000ms); - pbf_wait(context, 800ms); - - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 700ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 279ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 10ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 145ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 7703ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 140ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1700ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 39ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 0.000000 }, 544ms, 1000ms); - - pbf_wait(context, 1000ms); - - // save_game(stream, context); // Disabled: checkpoint save mode controls saving. - context.wait_for_all_requests(); + stream.log("[" + label + "] " + landmark.name + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + + " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + + ")"); } - // --------------------------------------------------------------------------- - // End of Segment 00 - 01 - // Standing in front lake varity ready to enter lake Verity for starter selection. - // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- - // Segment 00-02 begins - // Navigates into lake Verity to recieve starter - // --------------------------------------------------------------------------- - static void starter_nav(VideoStream& stream, ProControllerContext& context) { - // re-sync control - pbf_press_button(context, BUTTON_B, 20ms, 200ms); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - - // continue movement - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 300ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 300ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 10 (Barry before the starter selection)"); - - pbf_mash_button(context, BUTTON_A, 1000ms); - - pbf_wait(context, 500ms); - std::shared_ptr briefcase = - std::make_shared( - RESOURCE_PATH() + "PokemonBDSP/StarterBriefcase.png" - ); - - // Mash B until we see the briefcase. - ImageMatchWatcher detector(briefcase, { 0.5, 0.1, 0.5, 0.7 }, 100, true); - - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_mash_button(context, BUTTON_B, 120000ms); - }, - { {detector} } - ); - - if (ret == 0) { - stream.log("Detected briefcase!"); + stream.log("[" + label + "] located=" + std::to_string(result.located) + " mode=" + + (result.coarse ? "vertical acquisition" : "fine alignment") + " reference screen-relative offset=(" + + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); + if (result.coarse && result.located){ + if (coarse_confirmations && std::abs(result.dx - previous_dx) <= 0.00625 && + std::abs(result.dy - previous_dy) <= 1.0 / 90){ + coarse_confirmations++; + }else{ + coarse_confirmations = 1; } - else { - stream.log("Timed out waiting for briefcase."); - throw OperationFailedExceptionWithScreenshot( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to detect briefcase in Segment 00.", - stream - ); + previous_dx = result.dx; + previous_dy = result.dy; + }else{ + coarse_confirmations = 0; + } + if (!result.located){ + confirmations = 0; + pbf_wait(context, 250ms); + }else if (result.coarse && coarse_confirmations < 3){ + confirmations = 0; + stream.log("[" + label + "] Waiting for consistent vertical acquisition."); + pbf_wait(context, 250ms); + }else if (result.at_target){ + if (++confirmations >= 3){ + stream.log("[" + label + "] Reference position confirmed."); + return; } + pbf_wait(context, 300ms); + }else{ + confirmations = 0; + const bool horizontal = !result.coarse && std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16; + const double error = horizontal ? result.dx : result.dy * 9.0 / 16; + const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + stream.log("[" + label + "] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + " axis " + + (horizontal ? (result.dx > 0 ? "right" : "left") : (result.dy < 0 ? "up" : "down")) + " for " + + std::to_string(duration.count()) + "ms."); + // Reuse confirmed-Run encounter handling if correction enters grass. + walk_through_grass(stream, context, horizontal ? (result.dx > 0 ? 1 : -1) : 0, + horizontal ? 0 : (result.dy < 0 ? 1 : -1), 1, label, BattleType::STANDARD, duration); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Reference position not confirmed; route movement stopped.", + stream); +} - // Wait for briefcase to fully open. - stream.log("Mashing B for briefcase to fully open."); - pbf_mash_button(context, BUTTON_B, 2000ms); +static void align_to_sandgem_junction(VideoStream& stream, ProControllerContext& context){ + align_to_tutorial_landmarks(stream, context, match_sandgem_junction, "Sandgem junction"); +} + +// Resumes from the save made after catching the second Pokemon. +static void tutorial_part_3_after_catch_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + pbf_wait(context, 1500ms); + //-------------------------------------------------------- + // To get potions + // ------------------------------------------------------- + walk_through_grass(stream, context, 1, 0, 10, "Route 202 grass exit", BattleType::STANDARD, 200ms, + match_route202_grass_exit); + // Recording: the exit reference was reached after two 200ms steps. + // Eight further steps (1600ms) reached the eastern tree boundary. + // Continue outside grass to preserve the next down movement's start. + stream.log("[Tutorial] Route 202 grass exit confirmed; continuing right to the tree boundary."); + pbf_move_left_joystick(context, {1.0, 0.0}, 1600ms, 0ms); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1421ms, 0ms); + pbf_wait(context, 52ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1013ms, 0ms); + pbf_wait(context, 142ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1112ms, 0ms); + pbf_wait(context, 45ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 3206ms, 0ms); + pbf_wait(context, 912ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 856ms, 0ms); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + walk_through_grass(stream, context, -1, 0, 18, "left through grass 3"); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 17 (accept potions)"); + + pbf_mash_button(context, BUTTON_B, 10000ms); + context.wait_for_all_requests(); + // Catching the second Pokemon can damage the starter. Verify healing + // once Potions are available, before continuing toward Tristan. + stream.log("[Tutorial] Checking starter HP after the first Potion pickup."); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + // Complete one training encounter before Tristan. The helper waits + // through XP/automatic move learning and heals after the fight. + battle_1_pokemon(stream, context); + + // Stop on the verified Route 201 reference instead of overshooting + // with ten fixed steps. The limit also allows small final corrections. + walk_through_grass(stream, context, 1, 0, 20, "Route 201 Potion exit", BattleType::STANDARD, 200ms, + match_route201_potion_exit); + context.wait_for_all_requests(); + pbf_wait(context, 1500ms); + + // --------------------------------------------------------- + // Navigate back to catch tutorial grass + // --------------------------------------------------------- + pbf_move_left_joystick(context, {0.000000, -1.000000}, 560ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 5000ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1000ms, 0ms); + pbf_wait(context, 1500ms); + align_to_sandgem_junction(stream, context); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1855ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 430ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 850ms, 0ms); + pbf_wait(context, 1500ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1120ms, 0ms); + + // Continue on to jubilife + walk_through_grass(stream, context, -1, 0, 18, "left through grass 3"); + walk_through_grass(stream, context, 0, 1, 18, "up through grass 3"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1000ms, 0ms); + wait_for_dialogue(stream, context, "Phase 18 (tristan encounter dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + fight_tristan(stream, context); + + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1366ms, 0ms); + pbf_wait(context, 467ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1018ms, 0ms); + pbf_wait(context, 311ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 353ms, 0ms); + pbf_wait(context, 660ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 580ms, 0ms); + pbf_wait(context, 1672ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1344ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 19 (natalie encounter dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + + fight_Natalie(stream, context); + context.wait_for_all_requests(); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1301ms, 0ms); + walk_through_grass(stream, context, 0, 1, 15, "up through grass"); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 392ms, 0ms); + pbf_wait(context, 1261ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 815ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 20 (logan encounter dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + fight_Natalie(stream, context); + context.wait_for_all_requests(); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + walk_through_grass(stream, context, -1, 0, 8, "left through grass"); + + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2469ms, 0ms); + wait_for_dialogue(stream, context, "Phase 21 (dawn Jubilife dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); +} +// --------------------------------------------------------------------------- +// End of Segment 00 - 04 +// Standing in Jubilife after speaking to dawn. +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Begin Segment 00 - 05 +// Standing at the entrance to jubilife after speaking to dawn, will navigate to the poketech challenge. +// Collects poketech and stands in front of the PC to end the tutorial section +// and send the starter to pokemon HOME. +// --------------------------------------------------------------------------- + +// Stop the approach as soon as buildings can guide it, instead of +// overshooting south and then blindly walking back north. +static void approach_poketch_landmarks(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + Milliseconds walked = 0ms; + while (walked < 1900ms){ + auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Poketch approach: No video frame.", stream); } - static void post_starly_clenaup(VideoStream& stream, ProControllerContext& context) { - pbf_mash_button(context, BUTTON_B, 60000ms); + const auto result = match_poketch_landmarks(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); + } + stream.log("[Poketch approach] " + landmark.name + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin)); } - // --------------------------------------------------------------------------- - // End of Segment 00 - 02 - // Standing in front lake varity after starter selection. - // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- - // Segment 00-03 begins - // Navigates from Exit of lake Verity to receipt of the Pokedex - // --------------------------------------------------------------------------- - - static void tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - - pbf_wait(context, 563ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1700ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 510ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 50ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 310ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5500ms, 0ms); - pbf_wait(context, 3963ms); - context.wait_for_all_requests(); - - wait_for_dialogue(stream, context, "Phase 10 (Professor dialog to mom)"); - pbf_mash_button(context, BUTTON_B, 40000ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 820ms, 0ms); - pbf_wait(context, 4000ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 800ms, 0ms); - pbf_wait(context, 555ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 50ms, 0ms); - pbf_wait(context, 23ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5020ms, 0ms); - pbf_wait(context, 169ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 745ms, 0ms); - DateTime t{ 2026, 1, 1, 0, 0, 50 }; - set_time_fast(env, context, t); - walk_through_grass(stream, context, 1, 0, 20, "right through grass to path", BattleType::STARTER); - walk_through_grass(stream, context, 0, 1, 18, "up through grass 2", BattleType::STARTER); - walk_through_grass(stream, context, 1, 0, 10, "right through grass 2", BattleType::STARTER); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 730ms, 0ms); - pbf_wait(context, 941ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1950ms, 0ms); - wait_for_dialogue(stream, context, "Phase 11 (dawn conversation)"); - pbf_mash_button(context, BUTTON_B, 50000ms); - wait_for_dialogue(stream, context, "Phase 12 (Nickname reject)"); - pbf_mash_button(context, BUTTON_B, 40000ms); - wait_for_dialogue(stream, context, "Phase 13 (Pokedex accept)"); - pbf_mash_button(context, BUTTON_A, 40000ms); - - - // pokedex received + if (result.located){ + stream.log("[Poketch approach] Landmarks acquired after " + std::to_string(walked.count()) + + "ms; ending downward approach."); + return; } + const Milliseconds step = std::min(200ms, 1900ms - walked); + pbf_move_left_joystick(context, {0, -1}, step, 0ms); + walked += step; + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + } + stream.log("[Poketch approach] Reached 1900ms downward limit; handing off to locator."); +} - - // --------------------------------------------------------------------------- - // End of Segment 00 - 03 - // Standing in front of the professor after receiving the pokedex. - // --------------------------------------------------------------------------- - - // --------------------------------------------------------------------------- - // Begin Segment 00 - 04 - // Will heal at Sandgem pokeCenter to reliably catch a second pokemon for Home - // transfer. - // Collects poketech and stands in front of the PC to end the tutorial section - // and send the starter to pokemon HOME. - // --------------------------------------------------------------------------- - - static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - // Arm exit detection before movement and finish the fade before - // starting the dialogue watcher, which cancels queued commands. - recorded_move_until_transition(stream, context, 0.0, -1.0, 2010ms, - "Rowan lab exit"); - context.wait_for_all_requests(); - - wait_for_dialogue(stream, context, "Phase 14 (dawn tour)"); - - pbf_mash_button(context, BUTTON_B, 25000ms); - - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 5125ms, 0ms); - pbf_wait(context, 336ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1700ms, 0ms); - pbf_wait(context, 213ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2325ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, 1.000000 }, 950ms, 0ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2360ms, 0ms); - pbf_wait(context, 253ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4610ms, 0ms); - pbf_wait(context, 954ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 220ms, 0ms); - pbf_wait(context, 587ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1375ms, 0ms); - pbf_wait(context, 464ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 775ms, 0ms); - pbf_wait(context, 1655ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 640ms, 0ms); - pbf_wait(context, 3128ms); - pbf_press_dpad(context, DPAD_UP, 180ms, 0ms); - pbf_wait(context, 472ms); - pbf_press_dpad(context, DPAD_UP, 130ms, 0ms); - pbf_wait(context, 1621ms); - pbf_press_dpad(context, DPAD_RIGHT, 130ms, 0ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 15 (Mom conversation to accept parcel)"); - - pbf_mash_button(context, BUTTON_B, 40000ms); - - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - pbf_press_dpad(context, DPAD_LEFT, 150ms, 0ms); - pbf_wait(context, 1143ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 875ms, 0ms); - pbf_wait(context, 1169ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 946ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 9092ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 740ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 2998ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 240ms, 0ms); - pbf_wait(context, 893ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 5300ms, 0ms); - pbf_wait(context, 282ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 260ms, 0ms); - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - - DateTime t{ 2026, 1, 1, 0, 0, 50 }; - set_time_fast(env, context, t); - walk_through_grass(stream, context, 1, 0, 20, "right through grass to path", BattleType::STARTER); - walk_through_grass(stream, context, 0, 1, 18, "up through grass 2", BattleType::STARTER); - walk_through_grass(stream, context, 1, 0, 10, "right through grass 2", BattleType::STARTER); - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 560ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5000ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1000ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1855ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 430ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 850ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1120ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 16 (catch tutorial)"); - - pbf_mash_button(context, BUTTON_B, 70000ms); - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - catch_1_pokemon(stream, context); - pbf_wait(context, 1500ms); - context.wait_for_all_requests(); +static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmed = 0, coarse_confirmed = 0; + double previous_dx = 0, previous_dy = 0; + size_t acquisition_misses = 0, vertical_probe = 0; + // Two small excursions around the approach position. This is + // visibility recovery, not permission to interact without a match. + const int vertical_probes[] = {-80, 160, -240, 320, -160, -120, 240, -360, 480, -240}; + for (size_t attempt = 0; attempt < 80; attempt++){ + auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Poketch interaction: No video frame.", stream); } - - // Align to a route reference before continuing recorded navigation. - // Matcher offsets and thresholds are fractions of screen width/height. - static void align_to_tutorial_landmarks( - VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label - ) { - context.wait_for_all_requests(); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - size_t confirmations = 0, coarse_confirmations = 0; - double previous_dx = 0, previous_dy = 0; - for (size_t attempt = 0; attempt < 60; attempt++) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); - const auto result = matcher(*frame.frame); - VideoOverlaySet overlays(stream.overlay()); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[" + label + "] " + landmark.name + - " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + - " accepted=" + std::to_string(landmark.accepted) + - " screen-relative offset=(" + std::to_string(landmark.dx) + - "," + std::to_string(landmark.dy) + ")"); - } - stream.log("[" + label + "] located=" + std::to_string(result.located) + - " mode=" + (result.coarse ? "vertical acquisition" : "fine alignment") + - " reference screen-relative offset=(" + std::to_string(result.dx) + - "," + std::to_string(result.dy) + ")"); - if (result.coarse && result.located) { - if (coarse_confirmations && std::abs(result.dx-previous_dx)<=0.00625 && - std::abs(result.dy-previous_dy)<=1.0/90) coarse_confirmations++; - else coarse_confirmations=1; - previous_dx=result.dx; - previous_dy=result.dy; - } else coarse_confirmations=0; - if (!result.located) { - confirmations = 0; - pbf_wait(context, 250ms); - } else if (result.coarse && coarse_confirmations<3) { - confirmations = 0; - stream.log("[" + label + "] Waiting for consistent vertical acquisition."); - pbf_wait(context, 250ms); - } else if (result.at_target) { - if (++confirmations >= 3) { - stream.log("[" + label + "] Reference position confirmed."); - return; - } - pbf_wait(context, 300ms); - } else { - confirmations = 0; - const bool horizontal = !result.coarse && - std::abs(result.dx) >= std::abs(result.dy)*9.0/16; - const double error = horizontal ? result.dx : result.dy*9.0/16; - const auto duration = std::abs(error) > 1.0/24 ? 80ms : - std::abs(error) > 0.015625 ? 40ms : 16ms; - stream.log("[" + label + "] Correcting " + - std::string(horizontal ? "horizontal" : "vertical") + " axis " + - (horizontal ? (result.dx>0 ? "right" : "left") : (result.dy<0 ? "up" : "down")) + - " for " + std::to_string(duration.count()) + "ms."); - // Reuse confirmed-Run encounter handling if correction enters grass. - walk_through_grass(stream, context, - horizontal ? (result.dx > 0 ? 1 : -1) : 0, - horizontal ? 0 : (result.dy < 0 ? 1 : -1), - 1, label, BattleType::STANDARD, duration); - } - context.wait_for_all_requests(); + const OverworldMatchResult result = match_poketch_landmarks(*frame.frame); + stream.log(result.coarse ? "[Poketch landmarks] Coarse acquisition." : "[Poketch landmarks] Fine alignment."); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Reference position not confirmed; route movement stopped.", stream); + stream.log("[Poketch landmarks] " + landmark.name + " score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + + " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + + ")"); } - - static void align_to_sandgem_junction(VideoStream& stream, ProControllerContext& context) { - align_to_tutorial_landmarks(stream, context, match_sandgem_junction, "Sandgem junction"); + if (result.located){ + acquisition_misses = 0; } - - // Resumes from the save made after catching the second Pokemon. - static void tutorial_part_3_after_catch_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 1500ms); - //-------------------------------------------------------- - // To get potions - // ------------------------------------------------------- - walk_through_grass(stream, context, 1, 0, 10, "Route 202 grass exit", - BattleType::STANDARD, 200ms, match_route202_grass_exit); - // Recording: the exit reference was reached after two 200ms steps. - // Eight further steps (1600ms) reached the eastern tree boundary. - // Continue outside grass to preserve the next down movement's start. - stream.log("[Tutorial] Route 202 grass exit confirmed; continuing right to the tree boundary."); - pbf_move_left_joystick(context, {1.0, 0.0}, 1600ms, 0ms); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1421ms, 0ms); - pbf_wait(context, 52ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1013ms, 0ms); - pbf_wait(context, 142ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1112ms, 0ms); - pbf_wait(context, 45ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 3206ms, 0ms); - pbf_wait(context, 912ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 856ms, 0ms); - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - walk_through_grass(stream, context, -1, 0, 18, "left through grass 3"); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 17 (accept potions)"); - - pbf_mash_button(context, BUTTON_B, 10000ms); - context.wait_for_all_requests(); - // Catching the second Pokemon can damage the starter. Verify healing - // once Potions are available, before continuing toward Tristan. - stream.log("[Tutorial] Checking starter HP after the first Potion pickup."); - use_potion_first_pokemon(stream, context); - context.wait_for_all_requests(); - // Complete one training encounter before Tristan. The helper waits - // through XP/automatic move learning and heals after the fight. - battle_1_pokemon(stream, context); - - // Stop on the verified Route 201 reference instead of overshooting - // with ten fixed steps. The limit also allows small final corrections. - walk_through_grass(stream, context, 1, 0, 20, "Route 201 Potion exit", - BattleType::STANDARD, 200ms, match_route201_potion_exit); - context.wait_for_all_requests(); - pbf_wait(context, 1500ms); - - // --------------------------------------------------------- - // Navigate back to catch tutorial grass - // --------------------------------------------------------- - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 560ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 5000ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1000ms, 0ms); - pbf_wait(context, 1500ms); - align_to_sandgem_junction(stream, context); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1855ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 430ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 850ms, 0ms); - pbf_wait(context, 1500ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1120ms, 0ms); - - // Continue on to jubilife - walk_through_grass(stream, context, -1, 0, 18, "left through grass 3"); - walk_through_grass(stream, context, 0, 1, 18, "up through grass 3"); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1000ms, 0ms); - wait_for_dialogue(stream, context, "Phase 18 (tristan encounter dialog)"); - - pbf_mash_button(context, BUTTON_B, 5000ms); - fight_tristan(stream, context); - - use_potion_first_pokemon(stream, context); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1366ms, 0ms); - pbf_wait(context, 467ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1018ms, 0ms); - pbf_wait(context, 311ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 353ms, 0ms); - pbf_wait(context, 660ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 580ms, 0ms); - pbf_wait(context, 1672ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1344ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 19 (natalie encounter dialog)"); - - pbf_mash_button(context, BUTTON_B, 5000ms); - - fight_Natalie(stream, context); - context.wait_for_all_requests(); - use_potion_first_pokemon(stream, context); - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1301ms, 0ms); - walk_through_grass(stream, context, 0, 1, 15, "up through grass"); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 392ms, 0ms); - pbf_wait(context, 1261ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 815ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 20 (logan encounter dialog)"); - - pbf_mash_button(context, BUTTON_B, 5000ms); - fight_Natalie(stream, context); - context.wait_for_all_requests(); - use_potion_first_pokemon(stream, context); - context.wait_for_all_requests(); - walk_through_grass(stream, context, -1, 0, 8, "left through grass"); - - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2469ms, 0ms); - wait_for_dialogue(stream, context, "Phase 21 (dawn Jubilife dialog)"); - - pbf_mash_button(context, BUTTON_B, 5000ms); + if (result.coarse && result.located){ + if (coarse_confirmed && std::abs(result.dx - previous_dx) <= 0.00625 && + std::abs(result.dy - previous_dy) <= 1.0 / 90){ + coarse_confirmed++; + }else{ + coarse_confirmed = 1; + } + previous_dx = result.dx; + previous_dy = result.dy; + }else{ + coarse_confirmed = 0; } - // --------------------------------------------------------------------------- - // End of Segment 00 - 04 - // Standing in Jubilife after speaking to dawn. - // --------------------------------------------------------------------------- - // --------------------------------------------------------------------------- - // Begin Segment 00 - 05 - // Standing at the entrance to jubilife after speaking to dawn, will navigate to the poketech challenge. - // Collects poketech and stands in front of the PC to end the tutorial section - // and send the starter to pokemon HOME. - // --------------------------------------------------------------------------- - - // Stop the approach as soon as buildings can guide it, instead of - // overshooting south and then blindly walking back north. - static void approach_poketch_landmarks(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - Milliseconds walked = 0ms; - while (walked < 1900ms) { - auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Poketch approach: No video frame.", stream); - const auto result = match_poketch_landmarks(*frame.frame); - VideoOverlaySet overlays(stream.overlay()); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Poketch approach] " + landmark.name + " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin)); - } - if (result.located) { - stream.log("[Poketch approach] Landmarks acquired after " + std::to_string(walked.count()) + "ms; ending downward approach."); - return; - } - const Milliseconds step = std::min(200ms, 1900ms - walked); - pbf_move_left_joystick(context, {0, -1}, step, 0ms); - walked += step; - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - } - stream.log("[Poketch approach] Reached 1900ms downward limit; handing off to locator."); + if (!result.located){ + confirmed = 0; + if (++acquisition_misses >= 4 && vertical_probe < sizeof(vertical_probes) / sizeof(vertical_probes[0])){ + int probe = vertical_probes[vertical_probe++]; + acquisition_misses = 0; + stream.log("[Poketch landmarks] Bounded vertical visibility probe " + std::to_string(vertical_probe) + + "/10: " + (probe < 0 ? "down " : "up ") + std::to_string(std::abs(probe)) + "ms."); + pbf_move_left_joystick(context, {0, probe < 0 ? -1.0 : 1.0}, Milliseconds(std::abs(probe)), 0ms); + pbf_wait(context, 700ms); + }else{ + stream.log("[Poketch landmarks] No trustworthy acquisition; waiting for another frame."); + pbf_wait(context, 250ms); } - - static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerContext& context) { + }else if (result.coarse && coarse_confirmed < 3){ + confirmed = 0; + stream.log("[Poketch landmarks] Waiting for consistent coarse offset before movement."); + pbf_wait(context, 250ms); + }else if (result.at_target){ + if (++confirmed >= 3){ + stream.log("[Poketch landmarks] Above-NPC reference confirmed; turning down."); + pbf_press_dpad(context, DPAD_DOWN, 40ms, 400ms); context.wait_for_all_requests(); - pbf_wait(context, 700ms); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); context.wait_for_all_requests(); - size_t confirmed = 0, coarse_confirmed = 0; - double previous_dx = 0, previous_dy = 0; - size_t acquisition_misses = 0, vertical_probe = 0; - // Two small excursions around the approach position. This is - // visibility recovery, not permission to interact without a match. - const int vertical_probes[] = {-80, 160, -240, 320, -160, -120, 240, -360, 480, -240}; - for (size_t attempt = 0; attempt < 80; attempt++) { - auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Poketch interaction: No video frame.", stream); - const OverworldMatchResult result = match_poketch_landmarks(*frame.frame); - stream.log(result.coarse ? "[Poketch landmarks] Coarse acquisition." : "[Poketch landmarks] Fine alignment."); - VideoOverlaySet overlays(stream.overlay()); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Poketch landmarks] " + landmark.name + " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + - " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); - } - if (result.located) acquisition_misses = 0; - if (result.coarse && result.located) { - if (coarse_confirmed && std::abs(result.dx - previous_dx) <= 0.00625 && std::abs(result.dy - previous_dy) <= 1.0/90) - coarse_confirmed++; - else coarse_confirmed = 1; - previous_dx = result.dx; previous_dy = result.dy; - } else coarse_confirmed = 0; - if (!result.located) { - confirmed = 0; - if (++acquisition_misses >= 4 && vertical_probe < sizeof(vertical_probes) / sizeof(vertical_probes[0])) { - int probe = vertical_probes[vertical_probe++]; - acquisition_misses = 0; - stream.log("[Poketch landmarks] Bounded vertical visibility probe " + std::to_string(vertical_probe) + - "/10: " + (probe < 0 ? "down " : "up ") + std::to_string(std::abs(probe)) + "ms."); - pbf_move_left_joystick(context, {0, probe < 0 ? -1.0 : 1.0}, Milliseconds(std::abs(probe)), 0ms); - pbf_wait(context, 700ms); - } else { - stream.log("[Poketch landmarks] No trustworthy acquisition; waiting for another frame."); - pbf_wait(context, 250ms); - } - } else if (result.coarse && coarse_confirmed < 3) { - confirmed = 0; - stream.log("[Poketch landmarks] Waiting for consistent coarse offset before movement."); - pbf_wait(context, 250ms); - } else if (result.at_target) { - if (++confirmed >= 3) { - stream.log("[Poketch landmarks] Above-NPC reference confirmed; turning down."); - pbf_press_dpad(context, DPAD_DOWN, 40ms, 400ms); - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - context.wait_for_all_requests(); - wait_for_dialogue(stream, context, "Phase 27 (poketech challenge completed dialog)", 10s); - return; - } - pbf_wait(context, 300ms); - } else { - confirmed = 0; - const double dx = result.dx, dy = result.dy; - stream.log("[Poketch landmarks] Reference screen-relative offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")."); - const bool horizontal = std::abs(dx) > 7.0/960 && - (std::abs(dy) <= 7.0/540 || std::abs(dx) >= std::abs(dy)*9.0/16); - double error = horizontal ? dx : dy*9.0/16; - auto duration = std::abs(error) > 0.03125 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; - if (std::abs(dx) <= 7.0/960 && std::abs(dy) <= 7.0/540) { - stream.log("[Poketch landmarks] Coarse position near target; waiting for fine confirmation."); - pbf_wait(context, 300ms); - context.wait_for_all_requests(); - continue; - } - stream.log("[Poketch landmarks] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + - " axis for " + std::to_string(duration.count()) + "ms."); - pbf_move_left_joystick(context, - {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, - horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, duration, 0ms); - pbf_wait(context, 700ms); - } - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Poketch interaction: Building landmarks did not confirm the reference position; A not pressed.", stream); + wait_for_dialogue(stream, context, "Phase 27 (poketech challenge completed dialog)", 10s); + return; } - - void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - pbf_wait(context, 700ms); + pbf_wait(context, 300ms); + }else{ + confirmed = 0; + const double dx = result.dx, dy = result.dy; + stream.log("[Poketch landmarks] Reference screen-relative offset=(" + std::to_string(result.dx) + "," + + std::to_string(result.dy) + ")."); + const bool horizontal = + std::abs(dx) > 7.0 / 960 && (std::abs(dy) <= 7.0 / 540 || std::abs(dx) >= std::abs(dy) * 9.0 / 16); + double error = horizontal ? dx : dy * 9.0 / 16; + auto duration = std::abs(error) > 0.03125 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + if (std::abs(dx) <= 7.0 / 960 && std::abs(dy) <= 7.0 / 540){ + stream.log("[Poketch landmarks] Coarse position near target; waiting for fine confirmation."); + pbf_wait(context, 300ms); context.wait_for_all_requests(); - size_t confirmed = 0; - for (size_t attempt = 0; attempt < 60; attempt++) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Jubilife Center alignment: No video frame.", stream); - const auto result = match_jubilife_center_entrance(*frame.frame); - VideoOverlaySet overlays(stream.overlay()); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Jubilife Center alignment] score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + " screen-relative offset=(" + - std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); - } - if (!result.located) { - confirmed = 0; - pbf_wait(context, 250ms); - } else if (result.at_target) { - if (++confirmed >= 3) { - stream.log("[Jubilife Center alignment] Door position confirmed; entering."); - recorded_move_until_transition(stream, context, 0, 1, 2000ms, "Jubilife Pokemon Center entry"); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - return; - } - pbf_wait(context, 300ms); - } else { - confirmed = 0; - bool horizontal = std::abs(result.dx) >= std::abs(result.dy)*9.0/16; - double error = horizontal ? result.dx : result.dy*9.0/16; - const auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; - pbf_move_left_joystick(context, - {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, - horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0)}, duration, 0ms); - pbf_wait(context, 700ms); - } - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Jubilife Center alignment: Door reference not confirmed; entry not attempted.", stream); + continue; } - - static void tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2580ms, 0ms); - pbf_wait(context, 504ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 830ms, 0ms); - pbf_wait(context, 617ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 800ms, 0ms); - pbf_wait(context, 509ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); - pbf_wait(context, 598ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 200ms, 0ms); - pbf_wait(context, 581ms); - pbf_press_button(context, BUTTON_A, 150ms, 0ms); - pbf_wait(context, 795ms); - - wait_for_dialogue(stream, context, "Phase 22 (barry town map dialog)"); - - pbf_mash_button(context, BUTTON_B, 15000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1521ms, 0ms); - pbf_wait(context, 602ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1100ms, 0ms); - pbf_wait(context, 864ms); - pbf_press_dpad(context, DPAD_RIGHT, 138ms, 0ms); - pbf_wait(context, 2398ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1018ms, 0ms); - pbf_wait(context, 3811ms); - - wait_for_dialogue(stream, context, "Phase 23 (poketech challenge dialog)"); - - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 192ms, 0ms); - pbf_wait(context, 820ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 980ms, 0ms); - pbf_wait(context, 543ms); - pbf_press_dpad(context, DPAD_UP, 203ms, 0ms); - pbf_wait(context, 593ms); - pbf_press_dpad(context, DPAD_UP, 115ms, 0ms); - pbf_wait(context, 566ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1567ms, 0ms); - pbf_wait(context, 538ms); - pbf_press_dpad(context, DPAD_LEFT, 157ms, 0ms); - pbf_wait(context, 735ms); - pbf_press_dpad(context, DPAD_LEFT, 164ms, 0ms); - pbf_wait(context, 564ms); - pbf_press_dpad(context, DPAD_LEFT, 250ms, 0ms); - pbf_wait(context, 513ms); - pbf_press_dpad(context, DPAD_LEFT, 250ms, 0ms); - pbf_wait(context, 513ms); - pbf_press_dpad(context, DPAD_DOWN, 163ms, 0ms); - pbf_wait(context, 609ms); - pbf_press_dpad(context, DPAD_DOWN, 193ms, 0ms); - pbf_wait(context, 584ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 151ms, 0ms); - context.wait_for_all_requests(); - - //phase 24 Clown 1 dialog - clown_dialog_helper(stream, context, DPAD_RIGHT); - context.wait_for_all_requests(); - - pbf_press_dpad(context, DPAD_UP, 187ms, 0ms); - pbf_wait(context, 106ms); - pbf_press_dpad(context, DPAD_UP, 32ms, 0ms); - pbf_wait(context, 671ms); - pbf_press_dpad(context, DPAD_UP, 262ms, 0ms); - pbf_wait(context, 684ms); - pbf_press_dpad(context, DPAD_UP, 372ms, 0ms); - pbf_wait(context, 392ms); - pbf_press_dpad(context, DPAD_UP, 419ms, 0ms); - pbf_wait(context, 658ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2013ms, 0ms); - pbf_wait(context, 369ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1448ms, 0ms); - pbf_wait(context, 2435ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1077ms, 0ms); - pbf_wait(context, 1084ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1280ms, 0ms); - pbf_wait(context, 1895ms); - pbf_press_dpad(context, DPAD_LEFT, 88ms, 0ms); - - context.wait_for_all_requests(); - - //phase 25 Clown 2 dialog - - clown_dialog_helper(stream, context, DPAD_UP); - - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 511ms, 0ms); - pbf_wait(context, 1318ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 140ms, 0ms); - pbf_wait(context, 1502ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2093ms, 0ms); - pbf_wait(context, 2327ms); - pbf_press_dpad(context, DPAD_LEFT, 151ms, 0ms); - pbf_wait(context, 549ms); - pbf_press_dpad(context, DPAD_LEFT, 118ms, 0ms); - pbf_wait(context, 999ms); - pbf_press_dpad(context, DPAD_UP, 206ms, 0ms); - pbf_wait(context, 474ms); - pbf_press_dpad(context, DPAD_UP, 249ms, 0ms); - pbf_wait(context, 333ms); - pbf_press_dpad(context, DPAD_UP, 212ms, 0ms); - pbf_wait(context, 427ms); - pbf_press_dpad(context, DPAD_UP, 210ms, 0ms); - context.wait_for_all_requests(); - //phase 26 Clown 3 dialog - - clown_dialog_helper(stream, context, DPAD_LEFT); - - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1098ms, 0ms); - pbf_wait(context, 2779ms); - pbf_press_dpad(context, DPAD_LEFT, 177ms, 0ms); - pbf_wait(context, 488ms); - pbf_press_dpad(context, DPAD_LEFT, 200ms, 0ms); - pbf_wait(context, 649ms); - pbf_press_dpad(context, DPAD_LEFT, 160ms, 0ms); - pbf_wait(context, 4285ms); - pbf_press_dpad(context, DPAD_DOWN, 307ms, 0ms); - pbf_wait(context, 639ms); - pbf_press_dpad(context, DPAD_DOWN, 463ms, 0ms); - pbf_wait(context, 2521ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3600ms, 0ms); - pbf_wait(context, 610ms); - approach_poketch_landmarks(stream, context); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 192ms, 0ms); - pbf_wait(context, 3238ms); - align_and_talk_to_poketch_man(stream, context); - - pbf_mash_button(context, BUTTON_B, 15000ms); - context.wait_for_all_requests(); - - // poketech acquired - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1194ms, 0ms); - pbf_wait(context, 1984ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 832ms, 0ms); - pbf_wait(context, 1522ms); - pbf_press_dpad(context, DPAD_RIGHT, 144ms, 0ms); - pbf_wait(context, 1767ms); - align_and_enter_jubilife_center(stream, context); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1099ms, 0ms); - pbf_wait(context, 1222ms); - pbf_press_dpad(context, DPAD_RIGHT, 228ms, 0ms); - pbf_wait(context, 625ms); - pbf_press_dpad(context, DPAD_RIGHT, 238ms, 0ms); - pbf_wait(context, 537ms); - pbf_press_dpad(context, DPAD_RIGHT, 210ms, 0ms); - pbf_wait(context, 566ms); - pbf_press_dpad(context, DPAD_RIGHT, 179ms, 0ms); - pbf_wait(context, 692ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 642ms, 0ms); - } - // --------------------------------------------------------------------------- - // Segment 00-05 ends - // Standing in front of the Jubilife City Pokemon Center PC. - // Player will need to deposit pokemon in the pc and transfer to Pokemon Home - // and level to 100 before starting the next segment. - // --------------------------------------------------------------------------- - - // --------------------------------------------------------------------------- - // Segment / Checkpoint classes - // --------------------------------------------------------------------------- - - std::string AutoStory_Segment_00::name() const { return "00: Intro to Poketech"; } - std::string AutoStory_Segment_00::start_text() const { return "Start: Game launched, Standing in the player's room after tv stops playing"; } - std::string AutoStory_Segment_00::end_text() const { return "End: Starter Pokemon chosen, received, and Pokedex acquired, standing in front of the PC."; } - - void AutoStory_Segment_00::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0000().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0001().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0002().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0003().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0004().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); - } - - std::string AutoStory_Checkpoint_0000::name() const { return "00.00 - Verify Settings"; } - std::string AutoStory_Checkpoint_0000::start_text() const { return "Standing in the initial bedroom before beginning the tutorial."; } - std::string AutoStory_Checkpoint_0000::end_text() const { return "Required game settings verified and saved; ready for 00.01."; } - void AutoStory_Checkpoint_0000::run_checkpoint( - SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats - ) const { - // Before any route movement or checkpoint save: fail visibly if - // options cannot be read, rather than navigate with unknown settings. - verify_tutorial_settings(env.console, context); - context.wait_for_all_requests(); + stream.log("[Poketch landmarks] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + + " axis for " + std::to_string(duration.count()) + "ms."); + pbf_move_left_joystick(context, + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, + duration, 0ms); + pbf_wait(context, 700ms); } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Poketch interaction: Building landmarks did not confirm the reference position; A not pressed.", stream); +} - static void checkpoint_0001( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // Segment 00-00: Intro and starter selection - tutorial_part_1_navigation(env, env.console, context); - }, - true - ); - } - static void checkpoint_0002( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // Segment 00-01: Starter acquisition - starter_nav(env.console, context); - // Segment 00-00 starter selection - switch (options.starter_choice) { - case StarterChoice::TURTWIG: select_turtwig(env.console, context); break; - case StarterChoice::CHIMCHAR: select_chimchar(env.console, context); break; - case StarterChoice::PIPLUP: select_piplup(env.console, context); break; - } - fight_starly(env.console, context); - post_starly_clenaup(env.console, context); - }, - true - ); - } - static void checkpoint_0003( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // Segment 00-002: Pokedex acquisition - tutorial_part_2_navigation(env, env.console, context); - }, - true - ); +void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmed = 0; + for (size_t attempt = 0; attempt < 60; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Jubilife Center alignment: No video frame.", stream); } - static void checkpoint_0004( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // Segment 00-003: Poketech acquisition - tutorial_part_3_navigation(env, env.console, context); - }, - true - ); - - // Save and advance progress at the catch boundary. Retries of this - // section must start from the caught-Pokemon save, not the old route. - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - tutorial_part_3_after_catch_navigation(env, env.console, context); - }, - true - ); + const auto result = match_jubilife_center_entrance(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); + } + stream.log("[Jubilife Center alignment] score=" + std::to_string(landmark.score) + + " margin=" + std::to_string(landmark.margin) + " screen-relative offset=(" + + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } - // DIAGNOSTIC ONLY: Remove before pushing to production. - void diagnostic_checkpoint_0004_after_catch( - SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - tutorial_part_3_after_catch_navigation(env, env.console, context); - }, - true - ); + if (!result.located){ + confirmed = 0; + pbf_wait(context, 250ms); + }else if (result.at_target){ + if (++confirmed >= 3){ + stream.log("[Jubilife Center alignment] Door position confirmed; entering."); + recorded_move_until_transition(stream, context, 0, 1, 2000ms, "Jubilife Pokemon Center entry"); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + return; + } + pbf_wait(context, 300ms); + }else{ + confirmed = 0; + bool horizontal = std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16; + double error = horizontal ? result.dx : result.dy * 9.0 / 16; + const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + pbf_move_left_joystick( + context, + {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0)}, + duration, 0ms); + pbf_wait(context, 700ms); } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Jubilife Center alignment: Door reference not confirmed; entry not attempted.", stream); +} - static void checkpoint_0005( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // Segment 00-004: Poketech acquisition - tutorial_part_4_navigation(env, env.console, context); - }, - true - ); - } +static void tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2580ms, 0ms); + pbf_wait(context, 504ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 830ms, 0ms); + pbf_wait(context, 617ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 800ms, 0ms); + pbf_wait(context, 509ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2000ms, 0ms); + pbf_wait(context, 598ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 200ms, 0ms); + pbf_wait(context, 581ms); + pbf_press_button(context, BUTTON_A, 150ms, 0ms); + pbf_wait(context, 795ms); + + wait_for_dialogue(stream, context, "Phase 22 (barry town map dialog)"); + + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1521ms, 0ms); + pbf_wait(context, 602ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1100ms, 0ms); + pbf_wait(context, 864ms); + pbf_press_dpad(context, DPAD_RIGHT, 138ms, 0ms); + pbf_wait(context, 2398ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1018ms, 0ms); + pbf_wait(context, 3811ms); + + wait_for_dialogue(stream, context, "Phase 23 (poketech challenge dialog)"); + + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {1.000000, -0.000000}, 192ms, 0ms); + pbf_wait(context, 820ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 980ms, 0ms); + pbf_wait(context, 543ms); + pbf_press_dpad(context, DPAD_UP, 203ms, 0ms); + pbf_wait(context, 593ms); + pbf_press_dpad(context, DPAD_UP, 115ms, 0ms); + pbf_wait(context, 566ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1567ms, 0ms); + pbf_wait(context, 538ms); + pbf_press_dpad(context, DPAD_LEFT, 157ms, 0ms); + pbf_wait(context, 735ms); + pbf_press_dpad(context, DPAD_LEFT, 164ms, 0ms); + pbf_wait(context, 564ms); + pbf_press_dpad(context, DPAD_LEFT, 250ms, 0ms); + pbf_wait(context, 513ms); + pbf_press_dpad(context, DPAD_LEFT, 250ms, 0ms); + pbf_wait(context, 513ms); + pbf_press_dpad(context, DPAD_DOWN, 163ms, 0ms); + pbf_wait(context, 609ms); + pbf_press_dpad(context, DPAD_DOWN, 193ms, 0ms); + pbf_wait(context, 584ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 151ms, 0ms); + context.wait_for_all_requests(); + + //phase 24 Clown 1 dialog + clown_dialog_helper(stream, context, DPAD_RIGHT); + context.wait_for_all_requests(); + + pbf_press_dpad(context, DPAD_UP, 187ms, 0ms); + pbf_wait(context, 106ms); + pbf_press_dpad(context, DPAD_UP, 32ms, 0ms); + pbf_wait(context, 671ms); + pbf_press_dpad(context, DPAD_UP, 262ms, 0ms); + pbf_wait(context, 684ms); + pbf_press_dpad(context, DPAD_UP, 372ms, 0ms); + pbf_wait(context, 392ms); + pbf_press_dpad(context, DPAD_UP, 419ms, 0ms); + pbf_wait(context, 658ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2013ms, 0ms); + pbf_wait(context, 369ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1448ms, 0ms); + pbf_wait(context, 2435ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1077ms, 0ms); + pbf_wait(context, 1084ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1280ms, 0ms); + pbf_wait(context, 1895ms); + pbf_press_dpad(context, DPAD_LEFT, 88ms, 0ms); + + context.wait_for_all_requests(); + + //phase 25 Clown 2 dialog + + clown_dialog_helper(stream, context, DPAD_UP); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 511ms, 0ms); + pbf_wait(context, 1318ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 140ms, 0ms); + pbf_wait(context, 1502ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2093ms, 0ms); + pbf_wait(context, 2327ms); + pbf_press_dpad(context, DPAD_LEFT, 151ms, 0ms); + pbf_wait(context, 549ms); + pbf_press_dpad(context, DPAD_LEFT, 118ms, 0ms); + pbf_wait(context, 999ms); + pbf_press_dpad(context, DPAD_UP, 206ms, 0ms); + pbf_wait(context, 474ms); + pbf_press_dpad(context, DPAD_UP, 249ms, 0ms); + pbf_wait(context, 333ms); + pbf_press_dpad(context, DPAD_UP, 212ms, 0ms); + pbf_wait(context, 427ms); + pbf_press_dpad(context, DPAD_UP, 210ms, 0ms); + context.wait_for_all_requests(); + //phase 26 Clown 3 dialog + + clown_dialog_helper(stream, context, DPAD_LEFT); + + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1098ms, 0ms); + pbf_wait(context, 2779ms); + pbf_press_dpad(context, DPAD_LEFT, 177ms, 0ms); + pbf_wait(context, 488ms); + pbf_press_dpad(context, DPAD_LEFT, 200ms, 0ms); + pbf_wait(context, 649ms); + pbf_press_dpad(context, DPAD_LEFT, 160ms, 0ms); + pbf_wait(context, 4285ms); + pbf_press_dpad(context, DPAD_DOWN, 307ms, 0ms); + pbf_wait(context, 639ms); + pbf_press_dpad(context, DPAD_DOWN, 463ms, 0ms); + pbf_wait(context, 2521ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3600ms, 0ms); + pbf_wait(context, 610ms); + approach_poketch_landmarks(stream, context); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 192ms, 0ms); + pbf_wait(context, 3238ms); + align_and_talk_to_poketch_man(stream, context); + + pbf_mash_button(context, BUTTON_B, 15000ms); + context.wait_for_all_requests(); + + // poketech acquired + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1194ms, 0ms); + pbf_wait(context, 1984ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 832ms, 0ms); + pbf_wait(context, 1522ms); + pbf_press_dpad(context, DPAD_RIGHT, 144ms, 0ms); + pbf_wait(context, 1767ms); + align_and_enter_jubilife_center(stream, context); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1099ms, 0ms); + pbf_wait(context, 1222ms); + pbf_press_dpad(context, DPAD_RIGHT, 228ms, 0ms); + pbf_wait(context, 625ms); + pbf_press_dpad(context, DPAD_RIGHT, 238ms, 0ms); + pbf_wait(context, 537ms); + pbf_press_dpad(context, DPAD_RIGHT, 210ms, 0ms); + pbf_wait(context, 566ms); + pbf_press_dpad(context, DPAD_RIGHT, 179ms, 0ms); + pbf_wait(context, 692ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 642ms, 0ms); +} +// --------------------------------------------------------------------------- +// Segment 00-05 ends +// Standing in front of the Jubilife City Pokemon Center PC. +// Player will need to deposit pokemon in the pc and transfer to Pokemon Home +// and level to 100 before starting the next segment. +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// Segment / Checkpoint classes +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_00::name() const { + return "00: Intro to Poketech"; +} +std::string AutoStory_Segment_00::start_text() const { + return "Start: Game launched, Standing in the player's room after tv stops playing"; +} +std::string AutoStory_Segment_00::end_text() const { + return "End: Starter Pokemon chosen, received, and Pokedex acquired, standing in front of the PC."; +} - // ======================== - // Checkpoint 0001 Wrapper - // ======================== - std::string AutoStory_Checkpoint_0001::name() const { return "00.01 - After TV"; } - std::string AutoStory_Checkpoint_0001::start_text() const { return "Game launched, Standing in the player's room after tv stops playing"; } - std::string AutoStory_Checkpoint_0001::end_text() const { return "Standing outside Lake Verity before picking starter."; } - - void AutoStory_Checkpoint_0001::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0001(env, context, options, stats); - } +void AutoStory_Segment_00::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_0000().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0001().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0002().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0003().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0004().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} - // ======================== - // Checkpoint 0002 Wrapper - // ======================== - - std::string AutoStory_Checkpoint_0002::name() const { return "00.02 - Outside Verity before starter"; } - std::string AutoStory_Checkpoint_0002::start_text() const { return AutoStory_Checkpoint_0001().end_text(); } - std::string AutoStory_Checkpoint_0002::end_text() const { return "Standing outside Lake Verity after picking starter."; } - - void AutoStory_Checkpoint_0002::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0002(env, context, options, stats); - } +std::string AutoStory_Checkpoint_0000::name() const { + return "00.00 - Verify Settings"; +} +std::string AutoStory_Checkpoint_0000::start_text() const { + return "Standing in the initial bedroom before beginning the tutorial."; +} +std::string AutoStory_Checkpoint_0000::end_text() const { + return "Required game settings verified and saved; ready for 00.01."; +} +void AutoStory_Checkpoint_0000::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + // Before any route movement or checkpoint save: fail visibly if + // options cannot be read, rather than navigate with unknown settings. + verify_tutorial_settings(env.console, context); + context.wait_for_all_requests(); +} +static void checkpoint_0001(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // Segment 00-00: Intro and starter selection + tutorial_part_1_navigation(env, env.console, context); + }, + true); +} +static void checkpoint_0002(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // Segment 00-01: Starter acquisition + starter_nav(env.console, context); + // Segment 00-00 starter selection + switch (options.starter_choice){ + case StarterChoice::TURTWIG: + select_turtwig(env.console, context); + break; + case StarterChoice::CHIMCHAR: + select_chimchar(env.console, context); + break; + case StarterChoice::PIPLUP: + select_piplup(env.console, context); + break; + } + fight_starly(env.console, context); + post_starly_clenaup(env.console, context); + }, + true); +} +static void checkpoint_0003(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // Segment 00-002: Pokedex acquisition + tutorial_part_2_navigation(env, env.console, context); + }, + true); +} +static void checkpoint_0004(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // Segment 00-003: Poketech acquisition + tutorial_part_3_navigation(env, env.console, context); + }, + true); + + // Save and advance progress at the catch boundary. Retries of this + // section must start from the caught-Pokemon save, not the old route. + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + tutorial_part_3_after_catch_navigation(env, env.console, context); + }, + true); +} +// DIAGNOSTIC ONLY: Remove before pushing to production. +void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + tutorial_part_3_after_catch_navigation(env, env.console, context); + }, + true); +} - // ======================== - // Checkpoint 0003 Wrapper - // ======================== +static void checkpoint_0005(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // Segment 00-004: Poketech acquisition + tutorial_part_4_navigation(env, env.console, context); + }, + true); +} - std::string AutoStory_Checkpoint_0003::name() const { return "00.03 - Outside Verity after starter"; } - std::string AutoStory_Checkpoint_0003::start_text() const { return AutoStory_Checkpoint_0002().end_text(); } - std::string AutoStory_Checkpoint_0003::end_text() const { return "Standing in front of The professor after recieving the Pokedex"; } +// ======================== +// Checkpoint 0001 Wrapper +// ======================== +std::string AutoStory_Checkpoint_0001::name() const { + return "00.01 - After TV"; +} +std::string AutoStory_Checkpoint_0001::start_text() const { + return "Game launched, Standing in the player's room after tv stops playing"; +} +std::string AutoStory_Checkpoint_0001::end_text() const { + return "Standing outside Lake Verity before picking starter."; +} - void AutoStory_Checkpoint_0003::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0003(env, context, options, stats); - } - // ======================== - // Checkpoint 0004 Wrapper - // ======================== - - std::string AutoStory_Checkpoint_0004::name() const { return "00.04 - After Pokedex"; } - std::string AutoStory_Checkpoint_0004::start_text() const { return AutoStory_Checkpoint_0003().end_text(); } - std::string AutoStory_Checkpoint_0004::end_text() const { return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; } - - void AutoStory_Checkpoint_0004::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0004(env, context, options, stats); - } - // ======================== - // Checkpoint 0005 Wrapper - // ======================== - - std::string AutoStory_Checkpoint_0005::name() const { return "00.05 - Jubilife After Dawn"; } - std::string AutoStory_Checkpoint_0005::start_text() const { return AutoStory_Checkpoint_0004().end_text(); } - std::string AutoStory_Checkpoint_0005::end_text() const { return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; } - - void AutoStory_Checkpoint_0005::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0005(env, context, options, stats); - } - - } - } +void AutoStory_Checkpoint_0001::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0001(env, context, options, stats); +} + +// ======================== +// Checkpoint 0002 Wrapper +// ======================== + +std::string AutoStory_Checkpoint_0002::name() const { + return "00.02 - Outside Verity before starter"; +} +std::string AutoStory_Checkpoint_0002::start_text() const { + return AutoStory_Checkpoint_0001().end_text(); +} +std::string AutoStory_Checkpoint_0002::end_text() const { + return "Standing outside Lake Verity after picking starter."; +} + +void AutoStory_Checkpoint_0002::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0002(env, context, options, stats); +} + + +// ======================== +// Checkpoint 0003 Wrapper +// ======================== + +std::string AutoStory_Checkpoint_0003::name() const { + return "00.03 - Outside Verity after starter"; +} +std::string AutoStory_Checkpoint_0003::start_text() const { + return AutoStory_Checkpoint_0002().end_text(); +} +std::string AutoStory_Checkpoint_0003::end_text() const { + return "Standing in front of The professor after recieving the Pokedex"; +} + +void AutoStory_Checkpoint_0003::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0003(env, context, options, stats); +} +// ======================== +// Checkpoint 0004 Wrapper +// ======================== + +std::string AutoStory_Checkpoint_0004::name() const { + return "00.04 - After Pokedex"; +} +std::string AutoStory_Checkpoint_0004::start_text() const { + return AutoStory_Checkpoint_0003().end_text(); +} +std::string AutoStory_Checkpoint_0004::end_text() const { + return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; +} + +void AutoStory_Checkpoint_0004::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0004(env, context, options, stats); +} +// ======================== +// Checkpoint 0005 Wrapper +// ======================== + +std::string AutoStory_Checkpoint_0005::name() const { + return "00.05 - Jubilife After Dawn"; +} +std::string AutoStory_Checkpoint_0005::start_text() const { + return AutoStory_Checkpoint_0004().end_text(); +} +std::string AutoStory_Checkpoint_0005::end_text() const { + return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; +} + +void AutoStory_Checkpoint_0005::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0005(env, context, options, stats); +} + +} +} } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h index 3d6b73b7a5..d683b0ac7c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h @@ -8,96 +8,70 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment00_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment00_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // DIAGNOSTIC ONLY: Remove before pushing to production. // Starts at the post-catch save and runs only the rest of checkpoint 00.04. -void diagnostic_checkpoint_0004_after_catch( - SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats -); +void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Segment_00 : public AutoStory_Segment{ +class AutoStory_Segment_00 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0000 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0000 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0005 : public AutoStory_Checkpoint { @@ -106,63 +80,39 @@ class AutoStory_Checkpoint_0005 : public AutoStory_Checkpoint { virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: game launched, in front of the TV. // end: Standing outside lake varety before starter //Segment 00-00 begins -void checkpoint_000( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats -); +void checkpoint_000(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats); // start: Standing outside lake varety before starter // end: Standing outside lake varety after starter battle //Segment 00-001 begins -void checkpoint_001( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats -); +void checkpoint_001(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats); // start: Standing outside lake varety after starter battle // end: recieved Pokedex, standing in front of the professor //Segment 00-002 begins -void checkpoint_002( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats -); +void checkpoint_002(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats); // start: recieved Pokedex, standing in front of the professor // end: Standing in the entrance to Jubilife City after speaking to dawn //Segment 00-003 begins -void checkpoint_003( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats -); +void checkpoint_003(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats); // start: Standing in the entrance to Jubilife City after speaking to dawn // end: Standing in the Pokemon Center ready to deposit pokemon after recieving poketech. //Segment 00-004 begins -void checkpoint_004( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats -); +void checkpoint_004(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + EventNotificationOption& notif_status_update, AutoStoryStats& stats); } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 3ad6b48d94..1885900e93 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -3,2109 +3,1737 @@ * From: https://github.com/PokemonAutomation/ */ -#include "PokemonBDSP_AutoStory_Segment_01.h" -#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" - -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" -#include "CommonFramework/GlobalAutoPaths.h" #include #include #include + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTools/ImageDiff.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h" + +#include "PokemonBDSP_AutoStory_Segment_01.h" + using namespace std::chrono_literals; namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { +namespace NintendoSwitch { +namespace PokemonBDSP { - static const std::string& mine_navigation_resource_path() { - static const std::string path = RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; - return path; - } +static const std::string& mine_navigation_resource_path(){ + static const std::string path = RESOURCE_PATH() + "PokemonBDSP/Autostory/Oreburg_mine_nav/"; + return path; +} + +enum class MoveDirection { + UP, + DOWN, + LEFT, + RIGHT +}; + +// Left joystick vector for a direction (matches the game's joystick convention used in this file). +static std::pair joystick_for(MoveDirection direction){ + switch (direction){ + case MoveDirection::UP: + return {0.0, 1.0}; + case MoveDirection::DOWN: + return {0.0, -1.0}; + case MoveDirection::LEFT: + return {-1.0, 0.0}; + case MoveDirection::RIGHT: + return {1.0, 0.0}; + } + return {0.0, 0.0}; +} + +// Runs from a wild battle that is already in progress (menu visible or about to be). +// Throws OperationFailedException if it cannot escape within `timeout`. + +// The main game menu is independent evidence that battle has ended. - enum class MoveDirection { UP, DOWN, LEFT, RIGHT }; - // Left joystick vector for a direction (matches the game's joystick convention used in this file). - static std::pair joystick_for(MoveDirection direction) { - switch (direction) { - case MoveDirection::UP: return { 0.0, 1.0 }; - case MoveDirection::DOWN: return { 0.0, -1.0 }; - case MoveDirection::LEFT: return { -1.0, 0.0 }; - case MoveDirection::RIGHT: return { 1.0, 0.0 }; +// Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. +// Returns false (without throwing) only if every attempt times out, so callers can skip +// an optional interaction. Multiple attempts guard against a single missed detection. +static bool try_detect_dialogue(VideoStream& stream, ProControllerContext& context, const std::string& label, + size_t attempts = 3, std::chrono::milliseconds window = 5000ms){ + for (size_t attempt = 0; attempt < attempts; attempt++){ + ShortDialogWatcher watcher_cyan(COLOR_CYAN); + ShortDialogWatcher watcher_red(COLOR_RED); + if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0){ + stream.log(label + " dialogue detected."); + return true; + } + stream.log(label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + + std::to_string(attempts) + ").", + COLOR_ORANGE); + } + return false; +} + +struct DoorwayWalkOptions { + BattleType battle_type = BattleType::STANDARD; + // Active movement per step. Time spent in battles or waiting is never counted. + std::chrono::milliseconds step = 200ms; + // Extra walking allowed beyond `travel_time` before giving up. + std::chrono::milliseconds grace = 1600ms; + // How long to watch for a black screen after each step. + std::chrono::milliseconds black_screen_window = 1500ms; + // After a black screen, how long to wait for a battle menu before deciding it was a doorway. + std::chrono::milliseconds battle_menu_window = 8000ms; + size_t max_battles = 10; + // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). + bool detect_white_screen = false; +}; + +// Reusable: walk in `direction` for a cumulative `travel_time` of active movement until +// a black screen NOT followed by a battle menu (a doorway/area transition) is seen. +// Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are +// escaped and do not consume the time budget. +// Throws OperationFailedException if the doorway is not found within +// travel_time + options.grace, if more than options.max_battles occur, or if a battle +// cannot be escaped. +// Returns the total active walking time used. +static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, ProControllerContext& context, + MoveDirection direction, std::chrono::milliseconds travel_time, + const DoorwayWalkOptions& options = DoorwayWalkOptions()){ + enum class NavState { + WALKING, + BLACK_SCREEN, + IN_BATTLE, + DONE + }; + + const auto [x, y] = joystick_for(direction); + const std::chrono::milliseconds max_travel = travel_time + options.grace; + + std::chrono::milliseconds walked = 0ms; + size_t battles = 0; + NavState state = NavState::WALKING; + + while (state != NavState::DONE){ + switch (state){ + case NavState::WALKING: { + if (walked >= max_travel){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "walk_until_doorway: Doorway not found within time budget.", + stream); + } + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {x, y}, options.step, 0ms); + context.wait_for_all_requests(); + walked += options.step; + + BlackScreenWatcher black_screen; + WhiteScreenWatcher white_screen; + if (options.detect_white_screen){ + int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); + if (ret == 0){ + state = NavState::BLACK_SCREEN; + }else if (ret == 1){ + stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); + state = NavState::DONE; } - return { 0.0, 0.0 }; + }else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0){ + state = NavState::BLACK_SCREEN; + } + break; + } + case NavState::BLACK_SCREEN: { + stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); + BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); + if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0){ + state = NavState::IN_BATTLE; + }else{ + state = NavState::DONE; + } + break; + } + case NavState::IN_BATTLE: { + if (++battles > options.max_battles){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "walk_until_doorway: Too many battles while walking.", stream); } + stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + + std::to_string(travel_time.count()) + "ms.", + COLOR_ORANGE); + escape_wild_battle(stream, context, options.battle_type); + state = NavState::WALKING; + break; + } + case NavState::DONE: + break; + } + } + stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", + COLOR_GREEN); + return walked; +} - // Runs from a wild battle that is already in progress (menu visible or about to be). - // Throws OperationFailedException if it cannot escape within `timeout`. - // The main game menu is independent evidence that battle has ended. +// ---------------------------------------------------------------- +// Segment 1 Part 1: Jubilife City to Oreburgh Gate +// Navigates the player from Jubilife City to the Oreburgh Gate, including the +// first Barry fight. +// --------------------------------------------------------------------------- +void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + // Wait for game to fully accept input after save/overworld return + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1465ms, 0ms); + pbf_wait(context, 127ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 644ms, 0ms); + pbf_wait(context, 698ms); + leave_pokemon_center_before_barry_1(stream, context); + pbf_wait(context, 2340ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 742ms, 0ms); + pbf_wait(context, 348ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2694ms, 0ms); + pbf_wait(context, 482ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3810ms, 0ms); + + wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2761ms, 0ms); + pbf_wait(context, 1647ms); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + wait_for_dialogue(stream, context, "Dallas fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); //fighting Dallas + context.wait_for_all_requests(); + walk_through_grass(stream, context, 1, 0, 5, "right through grass"); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 600ms, 0ms); + walk_through_grass(stream, context, 1, 0, 10, "right through grass"); + //there is a small chance that the player dodges sSebastian, so this + //function addresses that edge case + if (try_detect_dialogue(stream, context, "Sebastian fight")){ + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); //fighting Sebastian + context.wait_for_all_requests(); + }else{ + stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); + } + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2000ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 8000ms, 0ms); + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + enter_cave_with_down_right_search(stream, context, "jubilife_to_oreburgh_gate", 10000ms); +} +void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + pbf_wait(context, 2000ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 879ms, 0ms); + wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); + pbf_mash_button(context, BUTTON_B, 15000ms); + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + //Similar to sebastian, an edge case exists where we run into too many + //wild battles and don't reach curtis or diana. This detect dialog function solves that. + while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)){ + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); //fighting Curtis + walk_through_grass(stream, context, 1, 0, 10, "right through gate"); + while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)){ + walk_through_grass(stream, context, 1, 0, 1, "right through gate"); + } + pbf_mash_button(context, BUTTON_B, 5000ms); + Barry_1_fight(env, stream, context); //fighting Diana + // Walk right until the gate exit transition (white screen, or black screen without a battle menu). + // Wild battles are escaped without using up the budget. Throws on failure so the + // checkpoint is retried. + DoorwayWalkOptions gate_exit_options; + gate_exit_options.detect_white_screen = true; + walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); + context.wait_for_all_requests(); +} +// --------------------------------------------------------------------------- +// Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine +// Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. +// Navigates to the mine. +// ---------------------------------------------------------------------------- +void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1056ms, 0ms); + while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)){ + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1056ms, 0ms); + } + pbf_mash_button(context, BUTTON_B, 12000ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 319ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 332ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3124ms, 0ms); + pbf_wait(context, 23ms); + bool black_screen_found = false; + for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++){ + BlackScreenWatcher black_screen; + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0.000000, -1.000000}, 4740ms, 0ms); + }, + {black_screen}); + if (ret == 0){ + stream.log("Black screen detected."); + black_screen_found = true; + } + } + context.wait_for_all_requests(); + if (!black_screen_found){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "oreburgh_gate_to_mine: No black screen after 3 attempts.", stream); + } + // Let the mine entrance transition fully finish so the next step's black screen + // watcher can't pick up the tail of this one. + BlackScreenOverWatcher black_over; + wait_until(stream, context, 6000ms, {{black_over}}); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); +} +// Diagnostic only: identifies the entrance start without moving the player. +struct MineCenterOffset { + bool valid; + double offset; + double score; +}; +// Finds the sideways shift of stationary scenery relative to the center picture. +// Positive offset: scenery shifted right, so the player is left of center. +static MineCenterOffset measure_mine_center_offset(const ImageViewRGB32& reference, const ImageViewRGB32& live){ + if (!reference || !live){ + return {false, 0.0, 1000.0}; + } - // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. - // Returns false (without throwing) only if every attempt times out, so callers can skip - // an optional interaction. Multiple attempts guard against a single missed detection. - static bool try_detect_dialogue( - VideoStream& stream, - ProControllerContext& context, - const std::string& label, - size_t attempts = 3, - std::chrono::milliseconds window = 5000ms - ) { - for (size_t attempt = 0; attempt < attempts; attempt++) { - ShortDialogWatcher watcher_cyan(COLOR_CYAN); - ShortDialogWatcher watcher_red(COLOR_RED); - if (wait_until(stream, context, window, {{watcher_cyan}, {watcher_red}}) >= 0) { - stream.log(label + " dialogue detected."); - return true; - } - stream.log( - label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + std::to_string(attempts) + ").", - COLOR_ORANGE - ); - } - return false; - } + // Use a fixed working size so the search works at different capture resolutions. + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const ImageRGB32 live_scaled = live.scale_to(960, 540); - struct DoorwayWalkOptions { - BattleType battle_type = BattleType::STANDARD; - // Active movement per step. Time spent in battles or waiting is never counted. - std::chrono::milliseconds step = 200ms; - // Extra walking allowed beyond `travel_time` before giving up. - std::chrono::milliseconds grace = 1600ms; - // How long to watch for a black screen after each step. - std::chrono::milliseconds black_screen_window = 1500ms; - // After a black screen, how long to wait for a battle menu before deciding it was a doorway. - std::chrono::milliseconds battle_menu_window = 8000ms; - size_t max_battles = 10; - // Also treat a white screen as the doorway transition (e.g. leaving the Oreburgh Gate). - bool detect_white_screen = false; - }; - - // Reusable: walk in `direction` for a cumulative `travel_time` of active movement until - // a black screen NOT followed by a battle menu (a doorway/area transition) is seen. - // Wild battles (black screen followed by the battle menu, e.g. in caves or grass) are - // escaped and do not consume the time budget. - // Throws OperationFailedException if the doorway is not found within - // travel_time + options.grace, if more than options.max_battles occur, or if a battle - // cannot be escaped. - // Returns the total active walking time used. - static std::chrono::milliseconds walk_until_doorway( - VideoStream& stream, - ProControllerContext& context, - MoveDirection direction, - std::chrono::milliseconds travel_time, - const DoorwayWalkOptions& options = DoorwayWalkOptions() - ) { - enum class NavState { WALKING, BLACK_SCREEN, IN_BATTLE, DONE }; - - const auto [x, y] = joystick_for(direction); - const std::chrono::milliseconds max_travel = travel_time + options.grace; - - std::chrono::milliseconds walked = 0ms; - size_t battles = 0; - NavState state = NavState::WALKING; - - while (state != NavState::DONE) { - switch (state) { - case NavState::WALKING: { - if (walked >= max_travel) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "walk_until_doorway: Doorway not found within time budget.", - stream - ); - } - context.wait_for_all_requests(); - pbf_move_left_joystick(context, {x, y}, options.step, 0ms); - context.wait_for_all_requests(); - walked += options.step; - - BlackScreenWatcher black_screen; - WhiteScreenWatcher white_screen; - if (options.detect_white_screen) { - int ret = wait_until(stream, context, options.black_screen_window, {{black_screen}, {white_screen}}); - if (ret == 0) { - state = NavState::BLACK_SCREEN; - } else if (ret == 1) { - stream.log("[AutoStory] White screen detected.", COLOR_ORANGE); - state = NavState::DONE; - } - } else if (wait_until(stream, context, options.black_screen_window, {{black_screen}}) == 0) { - state = NavState::BLACK_SCREEN; - } - break; - } - case NavState::BLACK_SCREEN: { - stream.log("[AutoStory] Black screen detected. Checking for battle menu.", COLOR_ORANGE); - BattleMenuWatcher battle_menu(options.battle_type, COLOR_YELLOW); - if (wait_until(stream, context, options.battle_menu_window, {{battle_menu}}) == 0) { - state = NavState::IN_BATTLE; - } else { - state = NavState::DONE; - } - break; - } - case NavState::IN_BATTLE: { - if (++battles > options.max_battles) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "walk_until_doorway: Too many battles while walking.", - stream - ); - } - stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + std::to_string(travel_time.count()) + "ms.", COLOR_ORANGE); - escape_wild_battle(stream, context, options.battle_type); - state = NavState::WALKING; - break; - } - case NavState::DONE: - break; - } - } - stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", COLOR_GREEN); - return walked; - } + // Entrance beam and both posts. Avoid the player and shadow. + const std::array boxes = {ImageFloatBox(0.35, 0.285, 0.30, 0.07), + ImageFloatBox(0.355, 0.37, 0.035, 0.23), + ImageFloatBox(0.615, 0.37, 0.035, 0.23)}; + const int search_radius = (int)std::lround(0.10 * live_scaled.width()); + std::vector scores(2 * search_radius + 1); + std::vector worst_regions(2 * search_radius + 1); + int best_shift = 0; + double best_score = 1000.0; + // Search up to 10% of the screen width in either direction. + for (int shift = -search_radius; shift <= search_radius; shift++){ + double total = 0.0; + double worst = 0.0; + for (const ImageFloatBox& box : boxes){ + const ImageViewRGB32 reference_crop = extract_box_reference(reference_scaled, box); + // Translate the same crop horizontally in the live image. + const ImageViewRGB32 live_crop = extract_box_reference(live_scaled, box, shift, 0); + const double score = ImageMatch::pixel_RMSD(reference_crop, live_crop); - // ---------------------------------------------------------------- - // Segment 1 Part 1: Jubilife City to Oreburgh Gate - // Navigates the player from Jubilife City to the Oreburgh Gate, including the - // first Barry fight. - // --------------------------------------------------------------------------- - void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - // Wait for game to fully accept input after save/overworld return - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1465ms, 0ms); - pbf_wait(context, 127ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 644ms, 0ms); - pbf_wait(context, 698ms); - leave_pokemon_center_before_barry_1(stream, context); - pbf_wait(context, 2340ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 742ms, 0ms); - pbf_wait(context, 348ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2694ms, 0ms); - pbf_wait(context, 482ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3810ms, 0ms); - - wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2761ms, 0ms); - pbf_wait(context, 1647ms); - walk_through_grass(stream, context, 1, 0, 5, "right through grass"); - wait_for_dialogue(stream, context, "Dallas fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Dallas - context.wait_for_all_requests(); - walk_through_grass(stream, context, 1, 0, 5, "right through grass"); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 600ms, 0ms); - walk_through_grass(stream, context, 1, 0, 10, "right through grass"); - //there is a small chance that the player dodges sSebastian, so this - //function addresses that edge case - if (try_detect_dialogue(stream, context, "Sebastian fight")) { - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Sebastian - context.wait_for_all_requests(); - } else { - stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); - } - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 2000ms, 0ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 8000ms, 0ms); - pbf_wait(context, 200ms); - context.wait_for_all_requests(); - enter_cave_with_down_right_search(stream,context, - "jubilife_to_oreburgh_gate",10000ms); - } - void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {1.000000, -0.000000 }, 879ms, 0ms); - wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); - pbf_mash_button(context, BUTTON_B, 15000ms); - walk_through_grass(stream, context, 1, 0, 10, "right through gate"); - //Similar to sebastian, an edge case exists where we run into too many - //wild battles and don't reach curtis or diana. This detect dialog function solves that. - while (!try_detect_dialogue(stream, context, "Curtis fight", 1, 3000ms)) { - walk_through_grass(stream, context, 1, 0, 1, "right through gate"); - } - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Curtis - walk_through_grass(stream, context, 1, 0, 10, "right through gate"); - while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)) { - walk_through_grass(stream, context, 1, 0, 1, "right through gate"); - } - pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context);//fighting Diana - // Walk right until the gate exit transition (white screen, or black screen without a battle menu). - // Wild battles are escaped without using up the budget. Throws on failure so the - // checkpoint is retried. - DoorwayWalkOptions gate_exit_options; - gate_exit_options.detect_white_screen = true; - walk_until_doorway(stream, context, MoveDirection::RIGHT, 4000ms, gate_exit_options); - context.wait_for_all_requests(); + total += score; + if (score > worst){ + worst = score; } - // --------------------------------------------------------------------------- - // Segment 1 Part 2: Oreburgh Gate to Oreburgh Mine - // Assumes that the player is standing outside the Oreburgh Gate on the Jubilife City side. - // Navigates to the mine. - // ---------------------------------------------------------------------------- - void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - while (!try_detect_dialogue(stream, context, "Phase 2 (oreburgh tour)", 1, 3000ms)) { - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 1056ms, 0ms); - } - pbf_mash_button(context, BUTTON_B, 12000ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 319ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -1.000000 }, 332ms, 0ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 3124ms, 0ms); - pbf_wait(context, 23ms); - bool black_screen_found = false; - for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++) { - BlackScreenWatcher black_screen; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 4740ms, 0ms); - }, - { black_screen } - ); - if (ret == 0) { - stream.log("Black screen detected."); - black_screen_found = true; - } - } - context.wait_for_all_requests(); - - if (!black_screen_found) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "oreburgh_gate_to_mine: No black screen after 3 attempts.", - stream - ); - } + } - // Let the mine entrance transition fully finish so the next step's black screen - // watcher can't pick up the tail of this one. - BlackScreenOverWatcher black_over; - wait_until(stream, context, 6000ms, {{black_over}}); - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - } + const double average = total / boxes.size(); + const size_t index = static_cast(shift + search_radius); + + scores[index] = average; + worst_regions[index] = worst; - // Diagnostic only: identifies the entrance start without moving the player. + if (average < best_score){ + best_score = average; + best_shift = shift; + } + } + // Reject another similarly good match well away from the chosen position. + double alternative_score = 1000.0; + for (int shift = -search_radius; shift <= search_radius; shift++){ + if (std::abs(shift - best_shift) <= std::lround(0.0125 * live_scaled.width())){ + continue; + } - struct MineCenterOffset { - bool valid; - double offset; - double score; - }; + const double score = scores[static_cast(shift + search_radius)]; + if (score < alternative_score){ + alternative_score = score; + } + } - // Finds the sideways shift of stationary scenery relative to the center picture. - // Positive offset: scenery shifted right, so the player is left of center. - static MineCenterOffset measure_mine_center_offset( - const ImageViewRGB32& reference, - const ImageViewRGB32& live - ) { - if (!reference || !live) { - return { false, 0.0, 1000.0 }; - } + const size_t best_index = static_cast(best_shift + search_radius); - // Use a fixed working size so the search works at different capture resolutions. - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - const ImageRGB32 live_scaled = live.scale_to(960, 540); + const bool valid = best_score <= 20.0 && worst_regions[best_index] <= 30.0 && + alternative_score - best_score >= 3.0 && std::abs(best_shift) < search_radius; - // Entrance beam and both posts. Avoid the player and shadow. - const std::array boxes = { - ImageFloatBox(0.35, 0.285, 0.30, 0.07), - ImageFloatBox(0.355, 0.37, 0.035, 0.23), - ImageFloatBox(0.615, 0.37, 0.035, 0.23) - }; + return {valid, static_cast(best_shift) / live_scaled.width(), best_score}; +} +// Test only: align with the entrance center, then stop. +static bool oreburgh_mine_alignment_test(VideoStream& stream, ProControllerContext& context){ + const ImageRGB32 reference(mine_navigation_resource_path() + "mine_start_center.png"); - const int search_radius = (int)std::lround(0.10*live_scaled.width()); - std::vector scores(2*search_radius+1); - std::vector worst_regions(2*search_radius+1); + constexpr size_t MAX_MOVEMENTS = 12; + constexpr double CENTER_TOLERANCE = 0.008; - int best_shift = 0; - double best_score = 1000.0; + size_t movements = 0; + size_t center_streak = 0; - // Search up to 10% of the screen width in either direction. - for (int shift = -search_radius; shift <= search_radius; shift++) { - double total = 0.0; - double worst = 0.0; + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); - for (const ImageFloatBox& box : boxes) { - const ImageViewRGB32 reference_crop = - extract_box_reference(reference_scaled, box); + while (true){ + const VideoSnapshot frame = stream.video().snapshot(); - // Translate the same crop horizontally in the live image. - const ImageViewRGB32 live_crop = - extract_box_reference(live_scaled, box, shift, 0); + const MineCenterOffset measurement = measure_mine_center_offset(reference, frame); - const double score = - ImageMatch::pixel_RMSD(reference_crop, live_crop); + stream.log("[Mine align] Offset=" + std::to_string(measurement.offset) + + ", score=" + std::to_string(measurement.score) + (measurement.valid ? ", trusted" : ", unsure")); - total += score; - if (score > worst) { - worst = score; - } - } + if (!measurement.valid){ + stream.log("[Mine align] Entrance position unsure. Stopping.", COLOR_ORANGE); + return false; + } - const double average = total / boxes.size(); - const size_t index = static_cast(shift + search_radius); + if (std::abs(measurement.offset) <= CENTER_TOLERANCE){ + center_streak++; - scores[index] = average; - worst_regions[index] = worst; + if (center_streak >= 3){ + stream.log("[Mine align] Center reached. Test complete.", COLOR_GREEN); + return true; + } - if (average < best_score) { - best_score = average; - best_shift = shift; - } - } + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; + } - // Reject another similarly good match well away from the chosen position. - double alternative_score = 1000.0; + center_streak = 0; - for (int shift = -search_radius; shift <= search_radius; shift++) { - if (std::abs(shift - best_shift) <= std::lround(0.0125*live_scaled.width())) { - continue; - } + // Check the latest position before enforcing the movement limit. + if (movements >= MAX_MOVEMENTS){ + stream.log("[Mine align] Movement limit reached. Stopping.", COLOR_ORANGE); + return false; + } - const double score = scores[static_cast(shift + search_radius)]; - if (score < alternative_score) { - alternative_score = score; - } - } + const double x = measurement.offset > 0.0 ? 1.0 : -1.0; - const size_t best_index = static_cast(best_shift + search_radius); + // Smaller movements when close to the center. + const auto duration = std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; - const bool valid = - best_score <= 20.0 && - worst_regions[best_index] <= 30.0 && - alternative_score - best_score >= 3.0 && - std::abs(best_shift) < search_radius; + stream.log(x > 0.0 ? "[Mine align] Moving a little right." : "[Mine align] Moving a little left."); - return { - valid, - static_cast(best_shift) / live_scaled.width(), - best_score - }; - } - // Test only: align with the entrance center, then stop. - static bool oreburgh_mine_alignment_test( - VideoStream& stream, - ProControllerContext& context - ) { - const ImageRGB32 reference( - mine_navigation_resource_path() + "mine_start_center.png" - ); - - constexpr size_t MAX_MOVEMENTS = 12; - constexpr double CENTER_TOLERANCE = 0.008; - - size_t movements = 0; - size_t center_streak = 0; - - context.wait_for_all_requests(); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - - while (true) { - const VideoSnapshot frame = stream.video().snapshot(); - - const MineCenterOffset measurement = - measure_mine_center_offset(reference, frame); - - stream.log( - "[Mine align] Offset=" + - std::to_string(measurement.offset) + - ", score=" + std::to_string(measurement.score) + - (measurement.valid ? ", trusted" : ", unsure") - ); - - if (!measurement.valid) { - stream.log( - "[Mine align] Entrance position unsure. Stopping.", - COLOR_ORANGE - ); - return false; - } + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); - if (std::abs(measurement.offset) <= CENTER_TOLERANCE) { - center_streak++; + const int result = run_until(stream, context, + [x, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, 0.0}, duration, 0ms); + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + }, + {black, menu, dialog}); - if (center_streak >= 3) { - stream.log( - "[Mine align] Center reached. Test complete.", - COLOR_GREEN - ); - return true; - } + if (result >= 0){ + stream.log("[Mine align] Battle or transition detected. " + "Stopping the alignment test.", + COLOR_ORANGE); + return false; + } - pbf_wait(context, 250ms); - context.wait_for_all_requests(); - continue; - } + movements++; + } +} +// Confirm a battle before using the existing escape helper. +static bool mine_escape_and_settle(VideoStream& stream, ProControllerContext& context){ + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - center_streak = 0; + if (wait_until(stream, context, 12s, {{menu}}) != 0){ + stream.log("[Mine encounter] Battle menu not confirmed. Stopping.", COLOR_ORANGE); + return false; + } - // Check the latest position before enforcing the movement limit. - if (movements >= MAX_MOVEMENTS) { - stream.log( - "[Mine align] Movement limit reached. Stopping.", - COLOR_ORANGE - ); - return false; - } + stream.log("[Mine encounter] Battle confirmed. Escaping."); + escape_wild_battle(stream, context); + context.wait_for_all_requests(); - const double x = measurement.offset > 0.0 ? 1.0 : -1.0; - - // Smaller movements when close to the center. - const auto duration = - std::abs(measurement.offset) < 0.025 ? 40ms : 80ms; - - stream.log( - x > 0.0 - ? "[Mine align] Moving a little right." - : "[Mine align] Moving a little left." - ); - - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - - const int result = run_until( - stream, - context, - [x, duration](ProControllerContext& ctx) { - pbf_move_left_joystick( - ctx, { x, 0.0 }, duration, 0ms - ); - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - }, - { black, menu, dialog } - ); - - if (result >= 0) { - stream.log( - "[Mine align] Battle or transition detected. " - "Stopping the alignment test.", - COLOR_ORANGE - ); - return false; - } + // Check the return waits instead of ignoring their results. + BlackScreenWatcher visible(COLOR_YELLOW, ImageFloatBox(0.1, 0.1, 0.8, 0.8), 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms); - movements++; - } - } - // Confirm a battle before using the existing escape helper. - static bool mine_escape_and_settle( - VideoStream& stream, - ProControllerContext& context - ) { - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - - if (wait_until(stream, context, 12s, { {menu} }) != 0) { - stream.log( - "[Mine encounter] Battle menu not confirmed. Stopping.", - COLOR_ORANGE - ); - return false; - } + if (wait_until(stream, context, 8s, {{visible}}) != 0){ + stream.log("[Mine encounter] Screen did not return. Stopping.", COLOR_ORANGE); + return false; + } - stream.log("[Mine encounter] Battle confirmed. Escaping."); - escape_wild_battle(stream, context); - context.wait_for_all_requests(); - - // Check the return waits instead of ignoring their results. - BlackScreenWatcher visible( - COLOR_YELLOW, - ImageFloatBox(0.1, 0.1, 0.8, 0.8), - 100, - 10, - BlackScreenWatcher::FinderType::GONE, - 500ms - ); - - if (wait_until(stream, context, 8s, { {visible} }) != 0) { - stream.log( - "[Mine encounter] Screen did not return. Stopping.", - COLOR_ORANGE - ); - return false; - } + pbf_wait(context, 800ms); + context.wait_for_all_requests(); - pbf_wait(context, 800ms); - context.wait_for_all_requests(); + stream.log("[Mine encounter] Escaped. Returning to the rock-walking loop.", COLOR_GREEN); + return true; +} - stream.log( - "[Mine encounter] Escaped. Returning to the rock-walking loop.", - COLOR_GREEN - ); - return true; - } +// Temporary test: wiggle along the corridor until a battle is detected. +static bool mine_force_encounter_test(VideoStream& stream, ProControllerContext& context){ + stream.log("[Mine encounter] Starting forced encounter wiggle."); - // Temporary test: wiggle along the corridor until a battle is detected. - static bool mine_force_encounter_test( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine encounter] Starting forced encounter wiggle."); - - const auto deadline = - std::chrono::steady_clock::now() + 60s; - - size_t pulses = 0; - - while (std::chrono::steady_clock::now() < deadline) { - // Alternate down and up. Do not move sideways. - const double y = pulses % 2 == 0 ? -1.0 : 1.0; - - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - - const int result = run_until( - stream, - context, - [y](ProControllerContext& ctx) { - pbf_move_left_joystick( - ctx, { 0.0, y }, 120ms, 0ms - ); - pbf_wait(ctx, 400ms); - ctx.wait_for_all_requests(); - }, - { black, menu, dialog } - ); - - if (result >= 0) { - stream.log( - "[Mine encounter] Wiggle interrupted. Checking for battle." - ); - return mine_escape_and_settle(stream, context); - } + const auto deadline = std::chrono::steady_clock::now() + 60s; - pulses++; - } + size_t pulses = 0; - stream.log( - "[Mine encounter] No encounter within 60 seconds. " - "Test incomplete; stopping.", - COLOR_ORANGE - ); - return false; - } + while (std::chrono::steady_clock::now() < deadline){ + // Alternate down and up. Do not move sideways. + const double y = pulses % 2 == 0 ? -1.0 : 1.0; - // Watch throughout an action, including its movement and picture checks. - // After an encounter, restart the action using fresh watcher state. - template - static bool mine_run_with_encounters( - VideoStream& stream, - ProControllerContext& context, - Action action - ) { - for (size_t encounters = 0;;) { - BlackScreenWatcher black; - BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher dialog(COLOR_YELLOW); - bool completed = false; - - const int result = run_until( - stream, context, - [&](ProControllerContext& ctx) { - completed = action(ctx); - }, - { black, menu, dialog } - ); - - if (result < 0) { - return completed; - } + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); - if (++encounters > 10) { - stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); - return false; - } + const int result = run_until(stream, context, + [y](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, y}, 120ms, 0ms); + pbf_wait(ctx, 400ms); + ctx.wait_for_all_requests(); + }, + {black, menu, dialog}); - if (!mine_escape_and_settle(stream, context)) { - return false; - } + if (result >= 0){ + stream.log("[Mine encounter] Wiggle interrupted. Checking for battle."); + return mine_escape_and_settle(stream, context); + } - stream.log("[Mine encounter] Escaped; resuming interrupted action."); - } - } + pulses++; + } - static bool mine_timed_walk( - VideoStream& stream, - ProControllerContext& context, - double x, - double y, - std::chrono::milliseconds duration - ) { - auto remaining = duration; - - return mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - while (remaining > 0ms) { - const auto pulse = remaining < 100ms ? remaining : 100ms; - pbf_move_left_joystick(ctx, { x, y }, pulse, 0ms); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - - // An interrupted pulse is retried after escaping. - // This is a timing budget, not exact distance tracking. - remaining -= pulse; - } - return true; - } - ); - } + stream.log("[Mine encounter] No encounter within 60 seconds. " + "Test incomplete; stopping.", + COLOR_ORANGE); + return false; +} +// Watch throughout an action, including its movement and picture checks. +// After an encounter, restart the action using fresh watcher state. +template +static bool mine_run_with_encounters(VideoStream& stream, ProControllerContext& context, Action action){ + for (size_t encounters = 0;;){ + BlackScreenWatcher black; + BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher dialog(COLOR_YELLOW); + bool completed = false; + + const int result = run_until(stream, context, + [&](ProControllerContext& ctx){ + completed = action(ctx); + }, + {black, menu, dialog}); + + if (result < 0){ + return completed; + } + if (++encounters > 10){ + stream.log("[Mine encounter] Encounter limit reached.", COLOR_ORANGE); + return false; + } - struct MinePictureScore { - double average = 1000.0; - double worst = 1000.0; - double horizontal = 0; - double vertical = 0; - }; - - // Supports four, five, or six regions. Existing four-region callers keep - // their original search ranges through the default arguments. - template - static MinePictureScore mine_score_picture( - const ImageRGB32& reference, - const ImageRGB32& live, - const std::array& boxes, - double horizontal_range = 1.0/60, - double vertical_range = 2.0/135 - ) { - MinePictureScore best; - const int radius_x = (int)std::lround(horizontal_range*live.width()); - const int radius_y = (int)std::lround(vertical_range*live.height()); - for (int y = -radius_y; y <= radius_y; y++) { - for (int x = -radius_x; x <= radius_x; x++) { - double total = 0.0; - double worst = 0.0; - for (const ImageFloatBox& box : boxes) { - const ImageViewRGB32 ref = extract_box_reference(reference, box); - const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); - const double score = ImageMatch::pixel_RMSD(ref, patch); - total += score; - if (score > worst) worst = score; - } - const double average = total / boxes.size(); - if (average < best.average) { - best.average = average; - best.worst = worst; - best.horizontal = (double)x/live.width(); - best.vertical = (double)y/live.height(); - } - } - } - return best; - } + if (!mine_escape_and_settle(stream, context)){ + return false; + } + stream.log("[Mine encounter] Escaped; resuming interrupted action."); + } +} - static bool mine_check_picture( - VideoStream& stream, - ProControllerContext& context, - const char* filename, - const std::array& boxes, - const std::string& label, - size_t max_attempts = 8, - double max_average = 20.0, - double max_patch = 30.0 - ) { - const ImageRGB32 reference( - mine_navigation_resource_path() + filename - ); - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - - const bool confirmed = mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - // The watcher stays active throughout settling and sampling. - // There is no separate 500ms watcher wait before each sample. - ctx.wait_for_all_requests(); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - - // Restart confirmation after a battle; do not reuse old matches. - size_t matching_frames = 0; - - for (size_t attempt = 0; attempt < max_attempts; attempt++) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log(label + ": no video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - const MinePictureScore score = - mine_score_picture(reference_scaled, live, boxes); - - stream.log( - label + ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) - ); - - if (score.average <= max_average && score.worst <= max_patch) { - if (++matching_frames >= 3) { - return true; - } - } - else { - matching_frames = 0; - } - - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - } - - return false; - } - ); +static bool mine_timed_walk(VideoStream& stream, ProControllerContext& context, double x, double y, + std::chrono::milliseconds duration){ + auto remaining = duration; - stream.log( - label + (confirmed ? ": confirmed." : ": not confirmed."), - confirmed ? COLOR_GREEN : COLOR_ORANGE - ); - return confirmed; - } + return mine_run_with_encounters(stream, context, [&](ProControllerContext& ctx){ + while (remaining > 0ms){ + const auto pulse = remaining < 100ms ? remaining : 100ms; + pbf_move_left_joystick(ctx, {x, y}, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); - struct MineWalkOptions { - double x = 0.0; - double y = 0.0; - std::chrono::milliseconds pulse = 40ms; - std::chrono::milliseconds settle = 500ms; - std::chrono::milliseconds initial_settle = 0ms; - size_t max_movements = 30; - double horizontal_range = 0.0125; - double vertical_range = 1.0/90; - double max_average = 20.0; - double max_patch = 30.0; - }; - - template - static bool mine_walk_to_endpoints( - VideoStream& stream, - ProControllerContext& context, - const std::array& files, - const std::array, Endpoints>& boxes, - const MineWalkOptions& options, - const std::string& label, - bool force_encounter = false - ) { - std::array, Endpoints> references; - for (size_t index = 0; index < Endpoints; index++) { - const ImageRGB32 image( - mine_navigation_resource_path() + files[index] - ); - references[index] = std::make_unique(image.scale_to(960, 540)); - } + // An interrupted pulse is retried after escaping. + // This is a timing budget, not exact distance tracking. + remaining -= pulse; + } + return true; + }); +} - size_t movements = 0; - bool forced_completed = false; - - while (true) { - bool request_forced_test = false; - const bool reached = mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - if (options.initial_settle > 0ms) { - pbf_wait(ctx, options.initial_settle); - ctx.wait_for_all_requests(); - } - // Reset the match history whenever an encounter interrupts us. - size_t matching_frames = 0; - int previous_endpoint = -1; - - while (true) { - ctx.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log(label + ": no video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - int accepted = -1; - double accepted_average = 1000.0; - - for (size_t endpoint = 0; endpoint < Endpoints; endpoint++) { - const MinePictureScore score = mine_score_picture( - *references[endpoint], live, boxes[endpoint], - options.horizontal_range, options.vertical_range - ); - stream.log( - label + " Endpoint " + std::to_string(endpoint + 1) + - ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) + - ", screen-relative offset=(" + std::to_string(score.horizontal) + - "," + std::to_string(score.vertical) + ")" - ); - - if (score.average <= options.max_average && - score.worst <= options.max_patch && - score.average < accepted_average) { - accepted = static_cast(endpoint); - accepted_average = score.average; - } - } - - if (accepted >= 0) { - matching_frames = accepted == previous_endpoint - ? matching_frames + 1 : 1; - previous_endpoint = accepted; - if (matching_frames >= 3) { - stream.log( - label + " Endpoint " + std::to_string(accepted + 1) + - " confirmed.", COLOR_GREEN - ); - return true; - } - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - previous_endpoint = -1; - - // Exit the guard before invoking the diagnostic wiggle: - // it has its own encounter watcher and must not be nested. - if (force_encounter && !forced_completed && movements >= 4) { - request_forced_test = true; - return false; - } - - if (movements >= options.max_movements) { - stream.log(label + ": movement limit reached.", COLOR_ORANGE); - return false; - } - - // Count issued commands, including interrupted commands. - movements++; - pbf_move_left_joystick( - ctx, { options.x, options.y }, options.pulse, 0ms - ); - pbf_wait(ctx, options.settle); - ctx.wait_for_all_requests(); - } - } - ); - - if (reached) return true; - if (!request_forced_test) return false; - if (!mine_force_encounter_test(stream, context)) return false; - forced_completed = true; - stream.log(label + ": forced encounter completed; resuming."); + +struct MinePictureScore { + double average = 1000.0; + double worst = 1000.0; + double horizontal = 0; + double vertical = 0; +}; + +// Supports four, five, or six regions. Existing four-region callers keep +// their original search ranges through the default arguments. +template +static MinePictureScore mine_score_picture(const ImageRGB32& reference, const ImageRGB32& live, + const std::array& boxes, + double horizontal_range = 1.0 / 60, double vertical_range = 2.0 / 135){ + MinePictureScore best; + const int radius_x = (int)std::lround(horizontal_range * live.width()); + const int radius_y = (int)std::lround(vertical_range * live.height()); + for (int y = -radius_y; y <= radius_y; y++){ + for (int x = -radius_x; x <= radius_x; x++){ + double total = 0.0; + double worst = 0.0; + for (const ImageFloatBox& box : boxes){ + const ImageViewRGB32 ref = extract_box_reference(reference, box); + const ImageViewRGB32 patch = extract_box_reference(live, box, x, y); + const double score = ImageMatch::pixel_RMSD(ref, patch); + total += score; + if (score > worst){ + worst = score; } } - - static bool oreburgh_mine_walk_to_first_rock( - VideoStream& stream, - ProControllerContext& context, - bool force_encounter = false - ) { - const std::array files = { "mine_path_01.png" }; - const std::array, 1> boxes = {{ - {{ - ImageFloatBox(0.482, 0.585, 0.055, 0.055), - ImageFloatBox(0.335, 0.670, 0.050, 0.055), - ImageFloatBox(0.412, 0.670, 0.050, 0.055), - ImageFloatBox(0.570, 0.752, 0.045, 0.055), - ImageFloatBox(0.645, 0.752, 0.045, 0.055), - ImageFloatBox(0.570, 0.290, 0.080, 0.120) - }} - }}; - - MineWalkOptions options; - options.x = 0.0; - options.y = -1.0; - options.pulse = 80ms; - options.max_movements = 40; - options.horizontal_range = 0.025; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine path 01]", force_encounter - ); + const double average = total / boxes.size(); + if (average < best.average){ + best.average = average; + best.worst = worst; + best.horizontal = (double)x / live.width(); + best.vertical = (double)y / live.height(); } + } + } + return best; +} - static bool oreburgh_mine_move_right( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array files = { "mine_path_01_1.png", "mine_path_01_2.png" }; - const std::array, 2> boxes = {{ - {{ - ImageFloatBox(0.400, 0.585, 0.055, 0.055), - ImageFloatBox(0.245, 0.670, 0.050, 0.055), - ImageFloatBox(0.322, 0.670, 0.050, 0.055), - ImageFloatBox(0.480, 0.752, 0.045, 0.055), - ImageFloatBox(0.555, 0.752, 0.045, 0.055), - ImageFloatBox(0.485, 0.290, 0.080, 0.090) - }}, - {{ - ImageFloatBox(0.325, 0.585, 0.055, 0.055), - ImageFloatBox(0.170, 0.670, 0.050, 0.055), - ImageFloatBox(0.244, 0.670, 0.050, 0.055), - ImageFloatBox(0.395, 0.752, 0.045, 0.055), - ImageFloatBox(0.470, 0.752, 0.045, 0.055), - ImageFloatBox(0.415, 0.290, 0.080, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = 1.0; - options.y = 0.0; - options.pulse = 40ms; - options.max_movements = 20; - options.horizontal_range = 0.0125; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine right]" - ); - } - static bool oreburgh_mine_down_past_central_rock( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array files = { "mine_path_02_1.png", "mine_path_02_2.png" }; - const std::array, 2> boxes = {{ - {{ - ImageFloatBox(0.345, 0.420, 0.040, 0.045), - ImageFloatBox(0.195, 0.500, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.410, 0.590, 0.040, 0.045), - ImageFloatBox(0.480, 0.590, 0.040, 0.045), - ImageFloatBox(0.445, 0.180, 0.060, 0.090) - }}, - {{ - ImageFloatBox(0.420, 0.420, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.345, 0.500, 0.040, 0.045), - ImageFloatBox(0.485, 0.590, 0.040, 0.045), - ImageFloatBox(0.555, 0.590, 0.040, 0.045), - ImageFloatBox(0.520, 0.180, 0.060, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = 0.0; - options.y = -1.0; - options.pulse = 40ms; - options.max_movements = 30; - // options.horizontal_range = 0.0125; - // Path 02 can be shifted horizontally after the first right endpoint. - // The screenshot regression matches at about -2.08% of screen width. - options.horizontal_range = 0.025; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine down 02]" - ); - } +static bool mine_check_picture(VideoStream& stream, ProControllerContext& context, const char* filename, + const std::array& boxes, const std::string& label, + size_t max_attempts = 8, double max_average = 20.0, double max_patch = 30.0){ + const ImageRGB32 reference(mine_navigation_resource_path() + filename); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + + const bool confirmed = mine_run_with_encounters(stream, context, [&](ProControllerContext& ctx){ + // The watcher stays active throughout settling and sampling. + // There is no separate 500ms watcher wait before each sample. + ctx.wait_for_all_requests(); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); - static bool oreburgh_mine_move_left_to_gap( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array files = { "mine_path_03.png" }; - const std::array, 1> boxes = {{ - {{ - ImageFloatBox(0.340, 0.495, 0.040, 0.045), - ImageFloatBox(0.410, 0.495, 0.040, 0.045), - ImageFloatBox(0.555, 0.580, 0.040, 0.045), - ImageFloatBox(0.625, 0.580, 0.040, 0.045), - ImageFloatBox(0.580, 0.180, 0.060, 0.090) - }} - }}; - - MineWalkOptions options; - options.x = -1.0; - options.y = 0.0; - options.pulse = 40ms; - options.max_movements = 30; - options.horizontal_range = 0.0125; - return mine_walk_to_endpoints( - stream, context, files, boxes, options, "[Mine left 03]" - ); + // Restart confirmation after a battle; do not reuse old matches. + size_t matching_frames = 0; + + for (size_t attempt = 0; attempt < max_attempts; attempt++){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + stream.log(label + ": no video frame.", COLOR_ORANGE); + return false; } + const ImageRGB32 live = frame->scale_to(960, 540); + const MinePictureScore score = mine_score_picture(reference_scaled, live, boxes); + stream.log(label + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst)); - // Confirm we are far enough down before beginning the leftward section. - - - // Each picture stage owns its movement and matching settings. - struct MinePictureStep { - const char* file; - const char* label; - std::array boxes; - double x; - double y; - std::chrono::milliseconds pulse = 80ms; - size_t max_movements = 30; - double max_average = 20.0; - double max_patch = 30.0; - }; - - static bool mine_run_picture_step( - VideoStream& stream, ProControllerContext& context, - const MinePictureStep& step - ) { - MineWalkOptions options; - options.x = step.x; - options.y = step.y; - options.pulse = step.pulse; - options.max_movements = step.max_movements; - options.max_average = step.max_average; - options.max_patch = step.max_patch; - options.horizontal_range = 1.0/60; - options.vertical_range = 2.0/135; - options.initial_settle = 700ms; - options.settle = 700ms; - const std::array files = {step.file}; - const std::array, 1> boxes = {{step.boxes}}; - return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); + if (score.average <= max_average && score.worst <= max_patch){ + if (++matching_frames >= 3){ + return true; + } + }else{ + matching_frames = 0; } - static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_05.png", "[Mine endpoint 05]", - { - ImageFloatBox(0.240, 0.240, 0.055, 0.080), - ImageFloatBox(0.390, 0.220, 0.050, 0.090), - ImageFloatBox(0.360, 0.090, 0.030, 0.030), - ImageFloatBox(0.550, 0.140, 0.040, 0.040) - }, - 0.0, -1.0, 60ms, 10, 20.0, 30.0 - }; - return mine_run_picture_step(stream, context, step); - } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + } - // Timed movement through paths 6 and 7, with picture checks. - static bool oreburgh_mine_timed_to_07( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine 04-07] Walking down for 1500ms."); + return false; + }); - if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)) { - return false; - } + stream.log(label + (confirmed ? ": confirmed." : ": not confirmed."), confirmed ? COLOR_GREEN : COLOR_ORANGE); + return confirmed; +} - if (!oreburgh_mine_confirm_05(stream, context)) { +struct MineWalkOptions { + double x = 0.0; + double y = 0.0; + std::chrono::milliseconds pulse = 40ms; + std::chrono::milliseconds settle = 500ms; + std::chrono::milliseconds initial_settle = 0ms; + size_t max_movements = 30; + double horizontal_range = 0.0125; + double vertical_range = 1.0 / 90; + double max_average = 20.0; + double max_patch = 30.0; +}; + +template +static bool mine_walk_to_endpoints(VideoStream& stream, ProControllerContext& context, + const std::array& files, + const std::array, Endpoints>& boxes, + const MineWalkOptions& options, const std::string& label, + bool force_encounter = false){ + std::array, Endpoints> references; + for (size_t index = 0; index < Endpoints; index++){ + const ImageRGB32 image(mine_navigation_resource_path() + files[index]); + references[index] = std::make_unique(image.scale_to(960, 540)); + } + + size_t movements = 0; + bool forced_completed = false; + + while (true){ + bool request_forced_test = false; + const bool reached = mine_run_with_encounters(stream, context, [&](ProControllerContext& ctx){ + if (options.initial_settle > 0ms){ + pbf_wait(ctx, options.initial_settle); + ctx.wait_for_all_requests(); + } + // Reset the match history whenever an encounter interrupts us. + size_t matching_frames = 0; + int previous_endpoint = -1; + + while (true){ + ctx.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + stream.log(label + ": no video frame.", COLOR_ORANGE); return false; } - stream.log("[Mine 04-07] Walking left for 3000ms."); + const ImageRGB32 live = frame->scale_to(960, 540); + int accepted = -1; + double accepted_average = 1000.0; + + for (size_t endpoint = 0; endpoint < Endpoints; endpoint++){ + const MinePictureScore score = mine_score_picture(*references[endpoint], live, boxes[endpoint], + options.horizontal_range, options.vertical_range); + stream.log(label + " Endpoint " + std::to_string(endpoint + 1) + + ": average=" + std::to_string(score.average) + + ", worst patch=" + std::to_string(score.worst) + ", screen-relative offset=(" + + std::to_string(score.horizontal) + "," + std::to_string(score.vertical) + ")"); + + if (score.average <= options.max_average && score.worst <= options.max_patch && + score.average < accepted_average){ + accepted = static_cast(endpoint); + accepted_average = score.average; + } + } - if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)) { - return false; + if (accepted >= 0){ + matching_frames = accepted == previous_endpoint ? matching_frames + 1 : 1; + previous_endpoint = accepted; + if (matching_frames >= 3){ + stream.log(label + " Endpoint " + std::to_string(accepted + 1) + " confirmed.", COLOR_GREEN); + return true; + } + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; } - // Path 6: crate, nearby small rock, large rock, upper-right small rock. - const std::array path6_boxes = { - ImageFloatBox(0.350, 0.250, 0.085, 0.075), - ImageFloatBox(0.405, 0.510, 0.035, 0.040), - ImageFloatBox(0.520, 0.300, 0.050, 0.080), - ImageFloatBox(0.620, 0.350, 0.030, 0.040) - }; - - if (!mine_check_picture( - stream, context, - "mine_path_06.png", path6_boxes, - "[Mine midpoint 06]" - )) { + matching_frames = 0; + previous_endpoint = -1; + + // Exit the guard before invoking the diagnostic wiggle: + // it has its own encounter watcher and must not be nested. + if (force_encounter && !forced_completed && movements >= 4){ + request_forced_test = true; return false; } - stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); - - if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)) { + if (movements >= options.max_movements){ + stream.log(label + ": movement limit reached.", COLOR_ORANGE); return false; } - // Path 7: crate and three visible rock surfaces. - const std::array path7_boxes = { - ImageFloatBox(0.365, 0.025, 0.070, 0.055), - ImageFloatBox(0.420, 0.270, 0.035, 0.040), - ImageFloatBox(0.560, 0.415, 0.025, 0.040), - ImageFloatBox(0.530, 0.650, 0.060, 0.090) - }; - - return mine_check_picture( - stream, context, - "mine_path_07.png", path7_boxes, - "[Mine endpoint 07]" - ); + // Count issued commands, including interrupted commands. + movements++; + pbf_move_left_joystick(ctx, {options.x, options.y}, options.pulse, 0ms); + pbf_wait(ctx, options.settle); + ctx.wait_for_all_requests(); } + }); - // Move left from path 7 to the position shown in path 8. - static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_08.png", "[Mine endpoint 08]", - { - ImageFloatBox(0.495, 0.035, 0.070, 0.055), - ImageFloatBox(0.550, 0.270, 0.035, 0.040), - ImageFloatBox(0.695, 0.420, 0.030, 0.040), - ImageFloatBox(0.555, 0.585, 0.035, 0.040) - }, - -1.0, 0.0, 40ms, 20, 20.0, 30.0 - }; - return mine_run_picture_step(stream, context, step); - } + if (reached){ + return true; + } + if (!request_forced_test){ + return false; + } + if (!mine_force_encounter_test(stream, context)){ + return false; + } + forced_completed = true; + stream.log(label + ": forced encounter completed; resuming."); + } +} - // Walk down along the left wall to path 9. - - static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context) { - const MinePictureStep step = { - "mine_path_09.png", "[Mine endpoint 09]", - { - ImageFloatBox(0.550, 0.115, 0.030, 0.040), - ImageFloatBox(0.550, 0.390, 0.035, 0.040), - ImageFloatBox(0.620, 0.390, 0.025, 0.035), - ImageFloatBox(0.500, 0.720, 0.085, 0.080) - }, - 0.0, -1.0, 40ms, 25, 25.0, 35.0 - }; - return mine_run_picture_step(stream, context, step); - } +static bool oreburgh_mine_walk_to_first_rock(VideoStream& stream, ProControllerContext& context, + bool force_encounter = false){ + const std::array files = {"mine_path_01.png"}; + const std::array, 1> boxes = { + {{{ImageFloatBox(0.482, 0.585, 0.055, 0.055), ImageFloatBox(0.335, 0.670, 0.050, 0.055), + ImageFloatBox(0.412, 0.670, 0.050, 0.055), ImageFloatBox(0.570, 0.752, 0.045, 0.055), + ImageFloatBox(0.645, 0.752, 0.045, 0.055), ImageFloatBox(0.570, 0.290, 0.080, 0.120)}}}}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 80ms; + options.max_movements = 40; + options.horizontal_range = 0.025; + return mine_walk_to_endpoints(stream, context, files, boxes, options, "[Mine path 01]", force_encounter); +} +static bool oreburgh_mine_move_right(VideoStream& stream, ProControllerContext& context){ + const std::array files = {"mine_path_01_1.png", "mine_path_01_2.png"}; + const std::array, 2> boxes = { + {{{ImageFloatBox(0.400, 0.585, 0.055, 0.055), ImageFloatBox(0.245, 0.670, 0.050, 0.055), + ImageFloatBox(0.322, 0.670, 0.050, 0.055), ImageFloatBox(0.480, 0.752, 0.045, 0.055), + ImageFloatBox(0.555, 0.752, 0.045, 0.055), ImageFloatBox(0.485, 0.290, 0.080, 0.090)}}, + {{ImageFloatBox(0.325, 0.585, 0.055, 0.055), ImageFloatBox(0.170, 0.670, 0.050, 0.055), + ImageFloatBox(0.244, 0.670, 0.050, 0.055), ImageFloatBox(0.395, 0.752, 0.045, 0.055), + ImageFloatBox(0.470, 0.752, 0.045, 0.055), ImageFloatBox(0.415, 0.290, 0.080, 0.090)}}}}; + + MineWalkOptions options; + options.x = 1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 20; + options.horizontal_range = 0.0125; + return mine_walk_to_endpoints(stream, context, files, boxes, options, "[Mine right]"); +} +static bool oreburgh_mine_down_past_central_rock(VideoStream& stream, ProControllerContext& context){ + const std::array files = {"mine_path_02_1.png", "mine_path_02_2.png"}; + const std::array, 2> boxes = { + {{{ImageFloatBox(0.345, 0.420, 0.040, 0.045), ImageFloatBox(0.195, 0.500, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), ImageFloatBox(0.410, 0.590, 0.040, 0.045), + ImageFloatBox(0.480, 0.590, 0.040, 0.045), ImageFloatBox(0.445, 0.180, 0.060, 0.090)}}, + {{ImageFloatBox(0.420, 0.420, 0.040, 0.045), ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.345, 0.500, 0.040, 0.045), ImageFloatBox(0.485, 0.590, 0.040, 0.045), + ImageFloatBox(0.555, 0.590, 0.040, 0.045), ImageFloatBox(0.520, 0.180, 0.060, 0.090)}}}}; + + MineWalkOptions options; + options.x = 0.0; + options.y = -1.0; + options.pulse = 40ms; + options.max_movements = 30; + // options.horizontal_range = 0.0125; + // Path 02 can be shifted horizontally after the first right endpoint. + // The screenshot regression matches at about -2.08% of screen width. + options.horizontal_range = 0.025; + return mine_walk_to_endpoints(stream, context, files, boxes, options, "[Mine down 02]"); +} - // Require dialogue to appear first. - // Then finish only after it has been absent for one continuous second. - static bool mine_finish_item_dialogue( - VideoStream& stream, - ProControllerContext& context - ) { - ShortDialogDetector dialogue(COLOR_YELLOW); +static bool oreburgh_mine_move_left_to_gap(VideoStream& stream, ProControllerContext& context){ + const std::array files = {"mine_path_03.png"}; + const std::array, 1> boxes = { + {{{ImageFloatBox(0.340, 0.495, 0.040, 0.045), ImageFloatBox(0.410, 0.495, 0.040, 0.045), + ImageFloatBox(0.555, 0.580, 0.040, 0.045), ImageFloatBox(0.625, 0.580, 0.040, 0.045), + ImageFloatBox(0.580, 0.180, 0.060, 0.090)}}}}; + + MineWalkOptions options; + options.x = -1.0; + options.y = 0.0; + options.pulse = 40ms; + options.max_movements = 30; + options.horizontal_range = 0.0125; + return mine_walk_to_endpoints(stream, context, files, boxes, options, "[Mine left 03]"); +} - bool saw_dialogue = false; - auto absent_since = std::chrono::steady_clock::now(); - const auto start = std::chrono::steady_clock::now(); - const auto deadline = start + 30s; +// Confirm we are far enough down before beginning the leftward section. + + +// Each picture stage owns its movement and matching settings. +struct MinePictureStep { + const char* file; + const char* label; + std::array boxes; + double x; + double y; + std::chrono::milliseconds pulse = 80ms; + size_t max_movements = 30; + double max_average = 20.0; + double max_patch = 30.0; +}; + +static bool mine_run_picture_step(VideoStream& stream, ProControllerContext& context, const MinePictureStep& step){ + MineWalkOptions options; + options.x = step.x; + options.y = step.y; + options.pulse = step.pulse; + options.max_movements = step.max_movements; + options.max_average = step.max_average; + options.max_patch = step.max_patch; + options.horizontal_range = 1.0 / 60; + options.vertical_range = 2.0 / 135; + options.initial_settle = 700ms; + options.settle = 700ms; + const std::array files = {step.file}; + const std::array, 1> boxes = {{step.boxes}}; + return mine_walk_to_endpoints(stream, context, files, boxes, options, step.label); +} - stream.log("[Mine item] Starting interaction."); +static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context){ + const MinePictureStep step = {"mine_path_05.png", + "[Mine endpoint 05]", + {ImageFloatBox(0.240, 0.240, 0.055, 0.080), ImageFloatBox(0.390, 0.220, 0.050, 0.090), + ImageFloatBox(0.360, 0.090, 0.030, 0.030), + ImageFloatBox(0.550, 0.140, 0.040, 0.040)}, + 0.0, + -1.0, + 60ms, + 10, + 20.0, + 30.0}; + return mine_run_picture_step(stream, context, step); +} - while (std::chrono::steady_clock::now() < deadline) { - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); +// Timed movement through paths 6 and 7, with picture checks. +static bool oreburgh_mine_timed_to_07(VideoStream& stream, ProControllerContext& context){ + stream.log("[Mine 04-07] Walking down for 1500ms."); - if (!frame) { - stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); - return false; - } + if (!mine_timed_walk(stream, context, 0.0, -1.0, 1500ms)){ + return false; + } - const auto now = std::chrono::steady_clock::now(); - const bool visible = dialogue.detect(frame); + if (!oreburgh_mine_confirm_05(stream, context)){ + return false; + } - if (visible) { - if (!saw_dialogue) { - stream.log("[Mine item] Dialogue detected."); - } + stream.log("[Mine 04-07] Walking left for 3000ms."); - saw_dialogue = true; - absent_since = now; + if (!mine_timed_walk(stream, context, -1.0, 0.0, 3000ms)){ + return false; + } - // Short mash bursts allow frequent checks. - pbf_mash_button(context, BUTTON_A, 100ms); - } - else if (!saw_dialogue) { - if (now - start >= 10s) { - stream.log( - "[Mine item] No dialogue appeared. Stopping.", - COLOR_ORANGE - ); - return false; - } - - // Try to start the interaction. - pbf_press_button(context, BUTTON_A, 60ms, 100ms); - } - else if (now - absent_since >= 1s) { - stream.log( - "[Mine item] Dialogue absent for one second. Finished.", - COLOR_GREEN - ); - return true; - } + // Path 6: crate, nearby small rock, large rock, upper-right small rock. + const std::array path6_boxes = { + ImageFloatBox(0.350, 0.250, 0.085, 0.075), ImageFloatBox(0.405, 0.510, 0.035, 0.040), + ImageFloatBox(0.520, 0.300, 0.050, 0.080), ImageFloatBox(0.620, 0.350, 0.030, 0.040)}; - pbf_wait(context, 100ms); - context.wait_for_all_requests(); - } + if (!mine_check_picture(stream, context, "mine_path_06.png", path6_boxes, "[Mine midpoint 06]")){ + return false; + } - stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); - return false; - } + stream.log("[Mine 04-07] Path 6 confirmed; walking down for 600ms."); + if (!mine_timed_walk(stream, context, 0.0, -1.0, 600ms)){ + return false; + } - static bool oreburgh_mine_09_to_14( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array path10_boxes = { - ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate - ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock - ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock - ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock - }; - - const MinePictureStep path10_boxes_step = { - "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path10_boxes_step)) { - return false; - } + // Path 7: crate and three visible rock surfaces. + const std::array path7_boxes = { + ImageFloatBox(0.365, 0.025, 0.070, 0.055), ImageFloatBox(0.420, 0.270, 0.035, 0.040), + ImageFloatBox(0.560, 0.415, 0.025, 0.040), ImageFloatBox(0.530, 0.650, 0.060, 0.090)}; - const std::array path11_boxes = { - ImageFloatBox(0.405, 0.560, 0.070, 0.080), - ImageFloatBox(0.475, 0.270, 0.030, 0.040), - ImageFloatBox(0.575, 0.290, 0.045, 0.080), - ImageFloatBox(0.550, 0.575, 0.030, 0.040) - }; - - const MinePictureStep path11_boxes_step = { - "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path11_boxes_step)) { - return false; - } + return mine_check_picture(stream, context, "mine_path_07.png", path7_boxes, "[Mine endpoint 07]"); +} - const std::array path12_boxes = { - ImageFloatBox(0.260, 0.560, 0.080, 0.080), - ImageFloatBox(0.355, 0.270, 0.025, 0.035), - ImageFloatBox(0.435, 0.290, 0.040, 0.070), - ImageFloatBox(0.415, 0.585, 0.025, 0.035) - }; - - const MinePictureStep path12_boxes_step = { - "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path12_boxes_step)) { - return false; - } +// Move left from path 7 to the position shown in path 8. +static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context){ + const MinePictureStep step = {"mine_path_08.png", + "[Mine endpoint 08]", + {ImageFloatBox(0.495, 0.035, 0.070, 0.055), ImageFloatBox(0.550, 0.270, 0.035, 0.040), + ImageFloatBox(0.695, 0.420, 0.030, 0.040), + ImageFloatBox(0.555, 0.585, 0.035, 0.040)}, + -1.0, + 0.0, + 40ms, + 20, + 20.0, + 30.0}; + return mine_run_picture_step(stream, context, step); +} - const std::array path13_boxes = { - ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate - ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate - ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock - ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock - }; - - const MinePictureStep path13_boxes_step = { - "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path13_boxes_step)) { - return false; - } +// Walk down along the left wall to path 9. + +static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context){ + const MinePictureStep step = {"mine_path_09.png", + "[Mine endpoint 09]", + {ImageFloatBox(0.550, 0.115, 0.030, 0.040), ImageFloatBox(0.550, 0.390, 0.035, 0.040), + ImageFloatBox(0.620, 0.390, 0.025, 0.035), + ImageFloatBox(0.500, 0.720, 0.085, 0.080)}, + 0.0, + -1.0, + 40ms, + 25, + 25.0, + 35.0}; + return mine_run_picture_step(stream, context, step); +} - // Turn left, then verify position before starting the interaction. - pbf_move_left_joystick(context, { -1.0, 0.0 }, 40ms, 0ms); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - if (!mine_check_picture( - stream, context, - "mine_path_14.png", path13_boxes, "[Mine position 14]", 3 - )) { - return false; - } - stream.log("[Mine item] Position confirmed. Pressing A."); +// Require dialogue to appear first. +// Then finish only after it has been absent for one continuous second. +static bool mine_finish_item_dialogue(VideoStream& stream, ProControllerContext& context){ + ShortDialogDetector dialogue(COLOR_YELLOW); - pbf_press_button(context, BUTTON_A, 80ms, 200ms); - context.wait_for_all_requests(); + bool saw_dialogue = false; + auto absent_since = std::chrono::steady_clock::now(); - return mine_finish_item_dialogue(stream, context); - } + const auto start = std::chrono::steady_clock::now(); + const auto deadline = start + 30s; - // Compare a reference patch with a shifted live patch using the same - // brightness adjustment as the native ImageMatchDetector. - static double mine_offset_patch_score( - const ImageRGB32& reference, - const ImageRGB32& live, - const ImageFloatBox& box, - double horizontal, - double vertical - ) { - const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); - ImageFloatBox shifted = box; - shifted.x += horizontal; - shifted.y += vertical; - const ImageViewRGB32 live_crop = extract_box_reference(live, shifted); - ImageRGB32 adjusted = live_crop.scale_to( - reference_crop.width(), reference_crop.height() - ); - - const auto reference_average = - ImageMatch::pixel_average(reference_crop, reference_crop); - const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); - auto scale = reference_average / live_average; - if (std::isnan(scale.r)) scale.r = 1.0; - if (std::isnan(scale.g)) scale.g = 1.0; - if (std::isnan(scale.b)) scale.b = 1.0; - scale.bound(0.8, 1.2); - ImageMatch::scale_brightness(adjusted, scale); - - return ImageMatch::pixel_RMSD(reference_crop, adjusted); - } + stream.log("[Mine item] Starting interaction."); - struct MineRoarkOffsetScore { - double left = 1000.0; - double right = 1000.0; - double helmet = 1000.0; - double horizontal = 0; - double vertical = 0; - double helmet_horizontal = 0; - double helmet_vertical = 0; - }; - - static MineRoarkOffsetScore mine_score_roark_offsets( - const ImageRGB32& reference, - const ImageRGB32& live - ) { - const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); - const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); - const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); - - MineRoarkOffsetScore best; - double best_worst_rock = 1000.0; - double best_total = 2000.0; - - // Both rocks must use the SAME offset. They cannot match independently. - // Search ranges are screen fractions; enumerate pixels only for scoring. - const int radius_x = (int)std::lround(live.width()/60.0); - const int radius_y = (int)std::lround(live.height()*2.0/135); - for (int y = -radius_y; y <= radius_y; y++) { - for (int x = -radius_x; x <= radius_x; x++) { - const double dx = (double)x/live.width(), dy = (double)y/live.height(); - const double left = mine_offset_patch_score(reference, live, left_box, dx, dy); - const double right = mine_offset_patch_score(reference, live, right_box, dx, dy); - const double worst = left > right ? left : right; - const double total = left + right; - - if (worst < best_worst_rock || - (worst == best_worst_rock && total < best_total)) { - best_worst_rock = worst; - best_total = total; - best.left = left; - best.right = right; - best.horizontal = (double)x/live.width(); - best.vertical = (double)y/live.height(); - } - } - } + while (std::chrono::steady_clock::now() < deadline){ + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); - // Roark's helmet may move slightly relative to the stationary rocks. - // Keep this extra search small so another red object cannot match far away. - const int helmet_radius_x = (int)std::lround(live.width()*0.00625); - const int helmet_radius_y = (int)std::lround(live.height()/90.0); - for (int extra_y = -helmet_radius_y; extra_y <= helmet_radius_y; extra_y++) { - for (int extra_x = -helmet_radius_x; extra_x <= helmet_radius_x; extra_x++) { - const double score = mine_offset_patch_score( - reference, live, helmet_box, - best.horizontal + (double)extra_x/live.width(), - best.vertical + (double)extra_y/live.height() - ); - if (score < best.helmet) { - best.helmet = score; - best.helmet_horizontal = (double)extra_x/live.width(); - best.helmet_vertical = (double)extra_y/live.height(); - } - } - } + if (!frame){ + stream.log("[Mine item] No video frame. Stopping.", COLOR_ORANGE); + return false; + } + + const auto now = std::chrono::steady_clock::now(); + const bool visible = dialogue.detect(frame); - return best; + if (visible){ + if (!saw_dialogue){ + stream.log("[Mine item] Dialogue detected."); } - static bool oreburgh_mine_align_to_18( - VideoStream& stream, - ProControllerContext& context - ) { - const ImageRGB32 reference( - mine_navigation_resource_path() + "Mine_path_18.png" - ); - const ImageRGB32 reference_scaled = reference.scale_to(960, 540); - const double max_rmsd = 65.0; - size_t movements = 0; - - return mine_run_with_encounters( - stream, context, - [&](ProControllerContext& ctx) { - // Fresh confirmation after any encounter. - size_t matching_frames = 0; - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - - while (true) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); - return false; - } - - const ImageRGB32 live = frame->scale_to(960, 540); - const MineRoarkOffsetScore score = - mine_score_roark_offsets(reference_scaled, live); - - stream.log( - "[Mine endpoint 18] Left rock=" + std::to_string(score.left) + - ", Right rock=" + std::to_string(score.right) + - ", Helmet=" + std::to_string(score.helmet) + - ", shared screen-relative offset=(" + std::to_string(score.horizontal) + - "," + std::to_string(score.vertical) + - "), helmet screen-relative extra=(" + std::to_string(score.helmet_horizontal) + - "," + std::to_string(score.helmet_vertical) + ")" - ); - - if (score.left <= max_rmsd && score.right <= max_rmsd && - score.helmet <= max_rmsd) { - if (++matching_frames >= 3) { - stream.log( - "[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN - ); - return true; - } - pbf_wait(ctx, 250ms); - ctx.wait_for_all_requests(); - continue; - } - - matching_frames = 0; - if (movements >= 80) { - stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); - return false; - } - - const bool approaching = score.left <= 120.0 && - score.right <= 120.0 && score.helmet <= 150.0; - const bool near = score.left <= 100.0 && - score.right <= 100.0 && score.helmet <= 100.0; - const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; - - // Use the outer encounter watcher; do not nest mine_timed_walk. - // Count issued pulses, including any interrupted by a battle. - movements++; - pbf_move_left_joystick(ctx, { 1.0, 0.0 }, pulse, 0ms); - pbf_wait(ctx, 700ms); - ctx.wait_for_all_requests(); - } - } - ); + saw_dialogue = true; + absent_since = now; + + // Short mash bursts allow frequent checks. + pbf_mash_button(context, BUTTON_A, 100ms); + }else if (!saw_dialogue){ + if (now - start >= 10s){ + stream.log("[Mine item] No dialogue appeared. Stopping.", COLOR_ORANGE); + return false; } - static bool oreburgh_mine_14_to_19( - VideoStream& stream, - ProControllerContext& context - ) { - const std::array path15_boxes = { - ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate - ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate - ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock - ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock - }; - - const MinePictureStep path15_boxes_step = { - "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path15_boxes_step)) { - return false; - } + // Try to start the interaction. + pbf_press_button(context, BUTTON_A, 60ms, 100ms); + }else if (now - absent_since >= 1s){ + stream.log("[Mine item] Dialogue absent for one second. Finished.", COLOR_GREEN); + return true; + } - // Move below the worker before continuing right. - const std::array path16_boxes = { - ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate - ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate - ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock - ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock - }; - - const MinePictureStep path16_boxes_step = { - "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, - 80ms, 30, 20.0, 30.0 - }; - if (!mine_run_picture_step(stream, context, path16_boxes_step)) { - return false; - } + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } - // Skip 17 and find the actual stopping point at 18. - if (!oreburgh_mine_align_to_18(stream, context)) { - return false; - } + stream.log("[Mine item] Dialogue timeout. Stopping.", COLOR_ORANGE); + return false; +} - // Position 19 is the upward turn toward Roark. - pbf_move_left_joystick(context, { 0.0, 1.0 }, 40ms, 0ms); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - stream.log( - "[Mine endpoint 19] Position 18 confirmed; north turn sent.", - COLOR_GREEN - ); - return true; - } +static bool oreburgh_mine_09_to_14(VideoStream& stream, ProControllerContext& context){ + const std::array path10_boxes = { + ImageFloatBox(0.405, 0.720, 0.085, 0.080), // Crate + ImageFloatBox(0.480, 0.120, 0.030, 0.040), // Upper rock + ImageFloatBox(0.575, 0.420, 0.045, 0.080), // Large right rock + ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock + }; - bool oreburgh_mine_route( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options - ) { - stream.log("[Mine resources] " + mine_navigation_resource_path()); - struct Step { - const char* name; - bool (*run)(VideoStream&, ProControllerContext&); - }; - - const std::array steps = { { - { - "Align entrance", - oreburgh_mine_alignment_test - }, - { - "Walk to first rock", - [](VideoStream& video, ProControllerContext& controller) { - return oreburgh_mine_walk_to_first_rock( - video, controller, false - ); - } - }, - { - "Move right of first rock", - oreburgh_mine_move_right - }, - { - "Move below first rock", - oreburgh_mine_down_past_central_rock - }, - { - "Move left into center gap", - oreburgh_mine_move_left_to_gap - }, - { - "Reach position 07", - oreburgh_mine_timed_to_07 - }, - { - "Reach position 08", - oreburgh_mine_left_to_08 - }, - { - "Reach position 09", - oreburgh_mine_down_to_09 - }, - { - "Reach 14 and collect item", - oreburgh_mine_09_to_14 - }, - { - "Reach 18 and turn north for 19", - oreburgh_mine_14_to_19 - } - } }; + const MinePictureStep path10_boxes_step = { + "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; + if (!mine_run_picture_step(stream, context, path10_boxes_step)){ + return false; + } - const size_t first = static_cast(options.first); - const size_t last = static_cast(options.last); + const std::array path11_boxes = { + ImageFloatBox(0.405, 0.560, 0.070, 0.080), ImageFloatBox(0.475, 0.270, 0.030, 0.040), + ImageFloatBox(0.575, 0.290, 0.045, 0.080), ImageFloatBox(0.550, 0.575, 0.030, 0.040)}; - if (first > last || last >= steps.size()) { - stream.log("[Mine route] Invalid stage range.", COLOR_ORANGE); - return false; - } + const MinePictureStep path11_boxes_step = { + "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; + if (!mine_run_picture_step(stream, context, path11_boxes_step)){ + return false; + } - for (size_t index = first; index <= last; index++) { - const Step& step = steps[index]; + const std::array path12_boxes = { + ImageFloatBox(0.260, 0.560, 0.080, 0.080), ImageFloatBox(0.355, 0.270, 0.025, 0.035), + ImageFloatBox(0.435, 0.290, 0.040, 0.070), ImageFloatBox(0.415, 0.585, 0.025, 0.035)}; - stream.log( - std::string("[Mine route] Starting: ") + step.name - ); + const MinePictureStep path12_boxes_step = { + "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; + if (!mine_run_picture_step(stream, context, path12_boxes_step)){ + return false; + } - bool succeeded = false; + const std::array path13_boxes = { + ImageFloatBox(0.290, 0.230, 0.070, 0.060), // Upper crate + ImageFloatBox(0.285, 0.390, 0.070, 0.060), // Lower crate + ImageFloatBox(0.425, 0.265, 0.025, 0.035), // Upper small rock + ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock + }; + + const MinePictureStep path13_boxes_step = { + "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; + if (!mine_run_picture_step(stream, context, path13_boxes_step)){ + return false; + } - try { - succeeded = step.run(stream, context); - } - catch (OperationFailedException& error) { - stream.log( - std::string("[Mine route] Failed during ") + - step.name + ": " + error.message(), - COLOR_ORANGE - ); - - if (options.reset_on_failure) { - throw; - } - - stream.log( - "[Mine route] Test stopped without requesting a reset.", - COLOR_ORANGE - ); - return false; - } + // Turn left, then verify position before starting the interaction. + pbf_move_left_joystick(context, {-1.0, 0.0}, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); - if (!succeeded) { - stream.log( - std::string("[Mine route] Stage failed: ") + step.name, - COLOR_ORANGE - ); - - if (options.reset_on_failure) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - std::string("Mine route failed during: ") + step.name, - stream - ); - } - - return false; - } + if (!mine_check_picture(stream, context, "mine_path_14.png", path13_boxes, "[Mine position 14]", 3)){ + return false; + } + stream.log("[Mine item] Position confirmed. Pressing A."); - stream.log( - std::string("[Mine route] Completed: ") + step.name, - COLOR_GREEN - ); - } + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + context.wait_for_all_requests(); - stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); - return true; + return mine_finish_item_dialogue(stream, context); +} + +// Compare a reference patch with a shifted live patch using the same +// brightness adjustment as the native ImageMatchDetector. +static double mine_offset_patch_score(const ImageRGB32& reference, const ImageRGB32& live, const ImageFloatBox& box, + double horizontal, double vertical){ + const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); + ImageFloatBox shifted = box; + shifted.x += horizontal; + shifted.y += vertical; + const ImageViewRGB32 live_crop = extract_box_reference(live, shifted); + ImageRGB32 adjusted = live_crop.scale_to(reference_crop.width(), reference_crop.height()); + + const auto reference_average = ImageMatch::pixel_average(reference_crop, reference_crop); + const auto live_average = ImageMatch::pixel_average(adjusted, reference_crop); + auto scale = reference_average / live_average; + if (std::isnan(scale.r)){ + scale.r = 1.0; + } + if (std::isnan(scale.g)){ + scale.g = 1.0; + } + if (std::isnan(scale.b)){ + scale.b = 1.0; + } + scale.bound(0.8, 1.2); + ImageMatch::scale_brightness(adjusted, scale); + + return ImageMatch::pixel_RMSD(reference_crop, adjusted); +} + +struct MineRoarkOffsetScore { + double left = 1000.0; + double right = 1000.0; + double helmet = 1000.0; + double horizontal = 0; + double vertical = 0; + double helmet_horizontal = 0; + double helmet_vertical = 0; +}; + +static MineRoarkOffsetScore mine_score_roark_offsets(const ImageRGB32& reference, const ImageRGB32& live){ + const ImageFloatBox left_box(0.415, 0.400, 0.050, 0.095); + const ImageFloatBox right_box(0.555, 0.400, 0.055, 0.095); + const ImageFloatBox helmet_box(0.485, 0.335, 0.035, 0.045); + + MineRoarkOffsetScore best; + double best_worst_rock = 1000.0; + double best_total = 2000.0; + + // Both rocks must use the SAME offset. They cannot match independently. + // Search ranges are screen fractions; enumerate pixels only for scoring. + const int radius_x = (int)std::lround(live.width() / 60.0); + const int radius_y = (int)std::lround(live.height() * 2.0 / 135); + for (int y = -radius_y; y <= radius_y; y++){ + for (int x = -radius_x; x <= radius_x; x++){ + const double dx = (double)x / live.width(), dy = (double)y / live.height(); + const double left = mine_offset_patch_score(reference, live, left_box, dx, dy); + const double right = mine_offset_patch_score(reference, live, right_box, dx, dy); + const double worst = left > right ? left : right; + const double total = left + right; + + if (worst < best_worst_rock || (worst == best_worst_rock && total < best_total)){ + best_worst_rock = worst; + best_total = total; + best.left = left; + best.right = right; + best.horizontal = (double)x / live.width(); + best.vertical = (double)y / live.height(); } + } + } - // Start Roark's conversation once, then advance it with B. - // Require dialogue to be seen before accepting its disappearance. - - static bool mine_talk_to_roark( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[Mine Roark] Pressing A to start conversation."); - pbf_press_button(context, BUTTON_A, 80ms, 100ms); - context.wait_for_all_requests(); - - // The opening cutscene precedes the first dialogue box. - // Do not mash B or measure dialogue absence during that cutscene. - wait_for_dialogue(stream, context, "[Mine Roark] First box", 30s); - - ShortDialogDetector dialogue(COLOR_YELLOW); - const auto deadline = std::chrono::steady_clock::now() + 60s; - bool timing_absence = false; - auto absent_since = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() < deadline) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); - return false; - } - const auto now = std::chrono::steady_clock::now(); - if (dialogue.detect(frame)) { - timing_absence = false; - } - else { - if (!timing_absence) { - timing_absence = true; - absent_since = now; - } - if (now - absent_since >= 4s) { - stream.log( - "[Mine Roark] Dialogue absent for 4 seconds. Finished.", - COLOR_GREEN - ); - return true; - } - } - pbf_mash_button(context, BUTTON_B, 100ms); - context.wait_for_all_requests(); - } - stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); - return false; + // Roark's helmet may move slightly relative to the stationary rocks. + // Keep this extra search small so another red object cannot match far away. + const int helmet_radius_x = (int)std::lround(live.width() * 0.00625); + const int helmet_radius_y = (int)std::lround(live.height() / 90.0); + for (int extra_y = -helmet_radius_y; extra_y <= helmet_radius_y; extra_y++){ + for (int extra_x = -helmet_radius_x; extra_x <= helmet_radius_x; extra_x++){ + const double score = + mine_offset_patch_score(reference, live, helmet_box, best.horizontal + (double)extra_x / live.width(), + best.vertical + (double)extra_y / live.height()); + if (score < best.helmet){ + best.helmet = score; + best.helmet_horizontal = (double)extra_x / live.width(); + best.helmet_vertical = (double)extra_y / live.height(); } + } + } - // Retain the developer test entry point; both callers share the same route. - bool oreburgh_mine_first_leg_test( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options - ) { - return oreburgh_mine_route(stream, context, options); + return best; +} + +static bool oreburgh_mine_align_to_18(VideoStream& stream, ProControllerContext& context){ + const ImageRGB32 reference(mine_navigation_resource_path() + "Mine_path_18.png"); + const ImageRGB32 reference_scaled = reference.scale_to(960, 540); + const double max_rmsd = 65.0; + size_t movements = 0; + + return mine_run_with_encounters(stream, context, [&](ProControllerContext& ctx){ + // Fresh confirmation after any encounter. + size_t matching_frames = 0; + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + + while (true){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + stream.log("[Mine endpoint 18] No video frame.", COLOR_ORANGE); + return false; } - // Shared final step for the live route and the entrance-start test. - static bool mine_talk_and_escape(VideoStream& stream, ProControllerContext& context) { - if (!mine_talk_to_roark(stream, context)) { - return false; - } - stream.log("[Mine exit] Finding Escape Rope in the Bag."); - if (!use_escape_rope(stream, context, 8)) { - return false; + const ImageRGB32 live = frame->scale_to(960, 540); + const MineRoarkOffsetScore score = mine_score_roark_offsets(reference_scaled, live); + + stream.log("[Mine endpoint 18] Left rock=" + std::to_string(score.left) + + ", Right rock=" + std::to_string(score.right) + ", Helmet=" + std::to_string(score.helmet) + + ", shared screen-relative offset=(" + std::to_string(score.horizontal) + "," + + std::to_string(score.vertical) + "), helmet screen-relative extra=(" + + std::to_string(score.helmet_horizontal) + "," + std::to_string(score.helmet_vertical) + ")"); + + if (score.left <= max_rmsd && score.right <= max_rmsd && score.helmet <= max_rmsd){ + if (++matching_frames >= 3){ + stream.log("[Mine endpoint 18] Rocks and helmet confirmed.", COLOR_GREEN); + return true; } - stream.log("[Mine exit] Roark conversation and Escape Rope exit succeeded.", COLOR_GREEN); - return true; + pbf_wait(ctx, 250ms); + ctx.wait_for_all_requests(); + continue; } - bool oreburgh_mine_route_and_escape_test( - VideoStream& stream, - ProControllerContext& context - ) { - MineRouteTestOptions route; - route.reset_on_failure = false; - if (!oreburgh_mine_first_leg_test(stream, context, route)) { - return false; - } - try { - if (!mine_talk_and_escape(stream, context)) { - stream.log("[Mine exit test] Failed. Stopping without resetting.", COLOR_ORANGE); - return false; - } - } - catch (OperationFailedException& error) { - stream.log("[Mine exit test] " + error.message(), COLOR_ORANGE); - stream.log("[Mine exit test] Stopped without requesting a reset.", COLOR_ORANGE); - return false; - } - return true; + matching_frames = 0; + if (movements >= 80){ + stream.log("[Mine endpoint 18] Movement limit reached.", COLOR_ORANGE); + return false; } - void oreburgh_mine_nav( - VideoStream& stream, ProControllerContext& context) { - walk_through_grass(stream, context, 0, -1, 1, "down into mine"); - context.wait_for_all_requests(); - walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); - context.wait_for_all_requests(); - pbf_wait(context, 1824ms); - - MineRouteTestOptions route; - route.reset_on_failure = true; - - if (!oreburgh_mine_route(stream, context, route)) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Mine route did not complete.", stream - ); - } + const bool approaching = score.left <= 120.0 && score.right <= 120.0 && score.helmet <= 150.0; + const bool near = score.left <= 100.0 && score.right <= 100.0 && score.helmet <= 100.0; + const auto pulse = near ? 20ms : approaching ? 40ms : 60ms; + // Use the outer encounter watcher; do not nest mine_timed_walk. + // Count issued pulses, including any interrupted by a battle. + movements++; + pbf_move_left_joystick(ctx, {1.0, 0.0}, pulse, 0ms); + pbf_wait(ctx, 700ms); + ctx.wait_for_all_requests(); + } + }); +} - if (!mine_talk_and_escape(stream, context)) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Mine route: Roark conversation or Escape Rope exit failed.", - stream - ); - } +static bool oreburgh_mine_14_to_19(VideoStream& stream, ProControllerContext& context){ + const std::array path15_boxes = { + ImageFloatBox(0.140, 0.220, 0.080, 0.070), // Upper crate + ImageFloatBox(0.125, 0.370, 0.080, 0.070), // Lower crate + ImageFloatBox(0.285, 0.250, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path15_boxes_step = { + "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; + if (!mine_run_picture_step(stream, context, path15_boxes_step)){ + return false; + } - } - // --------------------------------------------------------------------------- - // Segment 1 Part 3: Oreburgh Mine to Gym - // Assumes that escape rope was picked up and used to exit the mine. - // Follows the validated blind route into the gym. - // ---------------------------------------------------------------------------- - void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - - stream.log("[Mine to gym] Following recorded route from the mine exit."); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 3995ms, 0ms); - pbf_wait(context, 672ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 681ms, 0ms); - pbf_wait(context, 468ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1081ms, 0ms); - pbf_wait(context, 420ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2445ms, 0ms); - pbf_wait(context, 1056ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 95ms, 0ms); - pbf_wait(context, 4787ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1309ms, 0ms); - - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4450ms, 0ms); - //pbf_wait(context, 3635ms); - //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); - //pbf_wait(context, 3795ms); - context.wait_for_all_requests(); - - //stream.log("[Mine to gym] Staying west of the hiker; moving north along the left fence."); - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); - //pbf_wait(context, 602ms); - //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); - //pbf_wait(context, 5069ms); - //pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); - //pbf_wait(context, 605ms); - //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); - //pbf_wait(context, 1081ms); - //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); - //pbf_wait(context, 2642ms); - //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); - //pbf_wait(context, 5340ms); - //context.wait_for_all_requests(); - - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); - //pbf_wait(context, 140ms); - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); - //context.wait_for_all_requests(); - //stream.log("[Mine to gym] Recorded movement finished; waiting for gym entry to settle."); - // walk_until_doorway(stream, context, MoveDirection::UP, 5s); - // Finish entering the doorway with a bounded blind input. - //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); - //context.wait_for_all_requests(); - //pbf_wait(context, 1500ms); - //context.wait_for_all_requests(); - stream.log("[Mine to gym] Blind mine-to-gym sequence completed.", COLOR_GREEN); - } + // Move below the worker before continuing right. + const std::array path16_boxes = { + ImageFloatBox(0.145, 0.160, 0.080, 0.070), // Upper crate + ImageFloatBox(0.135, 0.305, 0.080, 0.070), // Lower crate + ImageFloatBox(0.290, 0.195, 0.035, 0.045), // Small upper rock + ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock + }; + + const MinePictureStep path16_boxes_step = { + "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; + if (!mine_run_picture_step(stream, context, path16_boxes_step)){ + return false; + } - // --------------------------------------------------------------------------- - // Segment 1 Part 4: Gym navigation - // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. - // --------------------------------------------------------------------------- - - void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - - //Upon entering the Gym. - // The entry camera sequence lasts about 7.5 seconds in the reference - // video. Allow another 500ms before sending puzzle movement inputs. - context.wait_for_all_requests(); - env.log("[Gym puzzle] Waiting for the entry cutscene to finish."); - pbf_wait(context, 8000ms); - context.wait_for_all_requests(); - - struct Move { - DpadPosition dir; - int count; - }; - const Move sequence[] = { - {DPAD_UP, 3}, - {DPAD_LEFT, 4}, - {DPAD_UP, 5}, - {DPAD_RIGHT, 8}, - {DPAD_UP, 11}, - {DPAD_LEFT, 5}, - {DPAD_UP, 4}, - }; - - for (const auto& move : sequence) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } + // Skip 17 and find the actual stopping point at 18. + if (!oreburgh_mine_align_to_18(stream, context)){ + return false; + } - //Button presses to initiate the Gym battle. - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); - pbf_mash_button(context, BUTTON_B, 5000ms); - //Barry 1 battle logic can be used here since the moves don't matter this early. - Barry_1_fight(env, stream, context); - - context.wait_for_all_requests(); - wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); - ShortDialogDetector post_battle_dialogue(COLOR_YELLOW); - const auto dialogue_deadline = std::chrono::steady_clock::now() + 180s; - auto dialogue_absent_since = std::chrono::steady_clock::now(); - bool dialogue_finished = false; - stream.log("[Gym post battle] Clearing dialogue until absent for 4 seconds."); - while (std::chrono::steady_clock::now() < dialogue_deadline) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Gym post battle: No video frame while clearing dialogue.", stream - ); - } - const auto now = std::chrono::steady_clock::now(); - if (post_battle_dialogue.detect(frame)) { - dialogue_absent_since = now; - } - else if (now - dialogue_absent_since >= 4s) { - dialogue_finished = true; - break; - } - pbf_mash_button(context, BUTTON_B, 100ms); - context.wait_for_all_requests(); - } - if (!dialogue_finished) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Gym post battle: Dialogue did not finish within 180 seconds.", stream - ); - } - stream.log("[Gym post battle] Dialogue absent for 4 seconds. Walking down."); + // Position 19 is the upward turn toward Roark. + pbf_move_left_joystick(context, {0.0, 1.0}, 40ms, 0ms); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); - for (int i = 0; i < 21; i++) { - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } + stream.log("[Mine endpoint 19] Position 18 confirmed; north turn sent.", COLOR_GREEN); + return true; +} - // Exit the building. - pbf_wait(context, 1000ms); - } +bool oreburgh_mine_route(VideoStream& stream, ProControllerContext& context, const MineRouteTestOptions& options){ + stream.log("[Mine resources] " + mine_navigation_resource_path()); + struct Step { + const char* name; + bool (*run)(VideoStream&, ProControllerContext&); + }; + + const std::array steps = {{{"Align entrance", oreburgh_mine_alignment_test}, + {"Walk to first rock", + [](VideoStream& video, ProControllerContext& controller){ + return oreburgh_mine_walk_to_first_rock(video, controller, false); + }}, + {"Move right of first rock", oreburgh_mine_move_right}, + {"Move below first rock", oreburgh_mine_down_past_central_rock}, + {"Move left into center gap", oreburgh_mine_move_left_to_gap}, + {"Reach position 07", oreburgh_mine_timed_to_07}, + {"Reach position 08", oreburgh_mine_left_to_08}, + {"Reach position 09", oreburgh_mine_down_to_09}, + {"Reach 14 and collect item", oreburgh_mine_09_to_14}, + {"Reach 18 and turn north for 19", oreburgh_mine_14_to_19}}}; + + const size_t first = static_cast(options.first); + const size_t last = static_cast(options.last); + + if (first > last || last >= steps.size()){ + stream.log("[Mine route] Invalid stage range.", COLOR_ORANGE); + return false; + } + for (size_t index = first; index <= last; index++){ + const Step& step = steps[index]; - std::string AutoStory_Segment_01::name() const { return "01: Jubilife City to First Badge"; } - std::string AutoStory_Segment_01::start_text() const { return "Start: Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } - - void AutoStory_Segment_01::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_checkpoint_0101().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0102().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0103().run_checkpoint(env, context, options, stats); - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); - } + stream.log(std::string("[Mine route] Starting: ") + step.name); + bool succeeded = false; - std::string AutoStory_checkpoint_0101::name() const { return "01.01 - Maxed Starter from Home"; } - std::string AutoStory_checkpoint_0101::start_text() const { return "Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; } - std::string AutoStory_checkpoint_0101::end_text() const { return "Standing outside the Oreburgh Gate on the Jubilife City side."; } - // ======================== - // Checkpoint 0101 Wrapper - // ======================== - void AutoStory_checkpoint_0101::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0101(env, context, options, stats); + try { + succeeded = step.run(stream, context); + } catch (OperationFailedException& error){ + stream.log(std::string("[Mine route] Failed during ") + step.name + ": " + error.message(), COLOR_ORANGE); + + if (options.reset_on_failure){ + throw; } + stream.log("[Mine route] Test stopped without requesting a reset.", COLOR_ORANGE); + return false; + } - void checkpoint_0101( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - jubilife_to_oreburgh_gate(env, env.console, context); - oreburgh_gate_nav(env, env.console, context); - }, - true - ); - } + if (!succeeded){ + stream.log(std::string("[Mine route] Stage failed: ") + step.name, COLOR_ORANGE); - // ======================== - // Checkpoint 0102 Wrapper - // ======================== - - std::string AutoStory_checkpoint_0102::name() const { return "01.02 - Oreburgh Mine"; } - std::string AutoStory_checkpoint_0102::start_text() const { return AutoStory_checkpoint_0101().end_text(); } - std::string AutoStory_checkpoint_0102::end_text() const { return "Inside Oreburgh Gym, ready to navigate to Roark."; } - - void AutoStory_checkpoint_0102::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0102(env, context, options, stats); + if (options.reset_on_failure){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, std::string("Mine route failed during: ") + step.name, stream); } + return false; + } - void checkpoint_0102( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - oreburgh_gate_to_mine(env, env.console, context); - oreburgh_mine_nav(env.console, context); - // Standalone test entry points are not part of the checkpoint route. - // oreburgh_mine_first_leg_test(env.console, context); - // oreburgh_mine_route_and_escape_test(env.console, context); - oreburgh_mine_to_gym(env, env.console, context); - }, - - true - ); - } + stream.log(std::string("[Mine route] Completed: ") + step.name, COLOR_GREEN); + } - // ======================== - // Checkpoint 0103 Wrapper - // ======================== - - std::string AutoStory_checkpoint_0103::name() const { return "01.03 - Oreburgh Gym"; } - std::string AutoStory_checkpoint_0103::start_text() const { return AutoStory_checkpoint_0102().end_text(); } - std::string AutoStory_checkpoint_0103::end_text() const { return "Standing at Roark after defeating him and acquiring the first badge."; } - - void AutoStory_checkpoint_0103::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0103(env, context, options, stats); + stream.log("[Mine route] Selected stages completed.", COLOR_GREEN); + return true; +} + +// Start Roark's conversation once, then advance it with B. +// Require dialogue to be seen before accepting its disappearance. + +static bool mine_talk_to_roark(VideoStream& stream, ProControllerContext& context){ + stream.log("[Mine Roark] Pressing A to start conversation."); + pbf_press_button(context, BUTTON_A, 80ms, 100ms); + context.wait_for_all_requests(); + + // The opening cutscene precedes the first dialogue box. + // Do not mash B or measure dialogue absence during that cutscene. + wait_for_dialogue(stream, context, "[Mine Roark] First box", 30s); + + ShortDialogDetector dialogue(COLOR_YELLOW); + const auto deadline = std::chrono::steady_clock::now() + 60s; + bool timing_absence = false; + auto absent_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + stream.log("[Mine Roark] No video frame.", COLOR_ORANGE); + return false; + } + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(frame)){ + timing_absence = false; + }else{ + if (!timing_absence){ + timing_absence = true; + absent_since = now; + } + if (now - absent_since >= 4s){ + stream.log("[Mine Roark] Dialogue absent for 4 seconds. Finished.", COLOR_GREEN); + return true; } + } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + stream.log("[Mine Roark] Conversation timed out.", COLOR_ORANGE); + return false; +} +// Retain the developer test entry point; both callers share the same route. +bool oreburgh_mine_first_leg_test(VideoStream& stream, ProControllerContext& context, + const MineRouteTestOptions& options){ + return oreburgh_mine_route(stream, context, options); +} - void checkpoint_0103( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - //#. Oreburgh Gym navigation and battle - gym_Puzzle_01(env, env.console, context); +// Shared final step for the live route and the entrance-start test. +static bool mine_talk_and_escape(VideoStream& stream, ProControllerContext& context){ + if (!mine_talk_to_roark(stream, context)){ + return false; + } + stream.log("[Mine exit] Finding Escape Rope in the Bag."); + if (!use_escape_rope(stream, context, 8)){ + return false; + } + stream.log("[Mine exit] Roark conversation and Escape Rope exit succeeded.", COLOR_GREEN); + return true; +} - }, +bool oreburgh_mine_route_and_escape_test(VideoStream& stream, ProControllerContext& context){ + MineRouteTestOptions route; + route.reset_on_failure = false; + if (!oreburgh_mine_first_leg_test(stream, context, route)){ + return false; + } + try { + if (!mine_talk_and_escape(stream, context)){ + stream.log("[Mine exit test] Failed. Stopping without resetting.", COLOR_ORANGE); + return false; + } + } catch (OperationFailedException& error){ + stream.log("[Mine exit test] " + error.message(), COLOR_ORANGE); + stream.log("[Mine exit test] Stopped without requesting a reset.", COLOR_ORANGE); + return false; + } + return true; +} - true - ); - } +void oreburgh_mine_nav(VideoStream& stream, ProControllerContext& context){ + walk_through_grass(stream, context, 0, -1, 1, "down into mine"); + context.wait_for_all_requests(); + walk_until_doorway(stream, context, MoveDirection::DOWN, 4350ms); + context.wait_for_all_requests(); + pbf_wait(context, 1824ms); + MineRouteTestOptions route; + route.reset_on_failure = true; + if (!oreburgh_mine_route(stream, context, route)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Mine route did not complete.", + stream); + } + + + if (!mine_talk_and_escape(stream, context)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Mine route: Roark conversation or Escape Rope exit failed.", stream); + } +} +// --------------------------------------------------------------------------- +// Segment 1 Part 3: Oreburgh Mine to Gym +// Assumes that escape rope was picked up and used to exit the mine. +// Follows the validated blind route into the gym. +// ---------------------------------------------------------------------------- +void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + + stream.log("[Mine to gym] Following recorded route from the mine exit."); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 3995ms, 0ms); + pbf_wait(context, 672ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 681ms, 0ms); + pbf_wait(context, 468ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1081ms, 0ms); + pbf_wait(context, 420ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2445ms, 0ms); + pbf_wait(context, 1056ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 95ms, 0ms); + pbf_wait(context, 4787ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1309ms, 0ms); + + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4450ms, 0ms); + //pbf_wait(context, 3635ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 640ms, 0ms); + //pbf_wait(context, 3795ms); + context.wait_for_all_requests(); + + //stream.log("[Mine to gym] Staying west of the hiker; moving north along the left fence."); + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); + //pbf_wait(context, 602ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2364ms, 0ms); + //pbf_wait(context, 5069ms); + //pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1079ms, 0ms); + //pbf_wait(context, 605ms); + //pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 728ms, 0ms); + //pbf_wait(context, 1081ms); + //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 155ms, 0ms); + //pbf_wait(context, 2642ms); + //pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 64ms, 0ms); + //pbf_wait(context, 5340ms); + //context.wait_for_all_requests(); + + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 541ms, 0ms); + //pbf_wait(context, 140ms); + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 47ms, 0ms); + //context.wait_for_all_requests(); + //stream.log("[Mine to gym] Recorded movement finished; waiting for gym entry to settle."); + // walk_until_doorway(stream, context, MoveDirection::UP, 5s); + // Finish entering the doorway with a bounded blind input. + //pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1000ms, 0ms); + //context.wait_for_all_requests(); + //pbf_wait(context, 1500ms); + //context.wait_for_all_requests(); + stream.log("[Mine to gym] Blind mine-to-gym sequence completed.", COLOR_GREEN); +} + +// --------------------------------------------------------------------------- +// Segment 1 Part 4: Gym navigation +// Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. +// --------------------------------------------------------------------------- + +void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + // The entry camera sequence lasts about 7.5 seconds in the reference + // video. Allow another 500ms before sending puzzle movement inputs. + context.wait_for_all_requests(); + env.log("[Gym puzzle] Waiting for the entry cutscene to finish."); + pbf_wait(context, 8000ms); + context.wait_for_all_requests(); + + struct Move { + DpadPosition dir; + int count; + }; + const Move sequence[] = { + {DPAD_UP, 3}, {DPAD_LEFT, 4}, {DPAD_UP, 5}, {DPAD_RIGHT, 8}, {DPAD_UP, 11}, {DPAD_LEFT, 5}, {DPAD_UP, 4}, + }; + + for (const auto& move : sequence){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + + //Button presses to initiate the Gym battle. + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); + pbf_mash_button(context, BUTTON_B, 5000ms); + //Barry 1 battle logic can be used here since the moves don't matter this early. + Barry_1_fight(env, stream, context); + + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); + ShortDialogDetector post_battle_dialogue(COLOR_YELLOW); + const auto dialogue_deadline = std::chrono::steady_clock::now() + 180s; + auto dialogue_absent_since = std::chrono::steady_clock::now(); + bool dialogue_finished = false; + stream.log("[Gym post battle] Clearing dialogue until absent for 4 seconds."); + while (std::chrono::steady_clock::now() < dialogue_deadline){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Gym post battle: No video frame while clearing dialogue.", stream); + } + const auto now = std::chrono::steady_clock::now(); + if (post_battle_dialogue.detect(frame)){ + dialogue_absent_since = now; + }else if (now - dialogue_absent_since >= 4s){ + dialogue_finished = true; + break; } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); } + if (!dialogue_finished){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Gym post battle: Dialogue did not finish within 180 seconds.", stream); + } + stream.log("[Gym post battle] Dialogue absent for 4 seconds. Walking down."); + + for (int i = 0; i < 21; i++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } + + // Exit the building. + pbf_wait(context, 1000ms); +} + + +std::string AutoStory_Segment_01::name() const { + return "01: Jubilife City to First Badge"; +} +std::string AutoStory_Segment_01::start_text() const { + return "Start: Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; +} +std::string AutoStory_Segment_01::end_text() const { + return "End: First Gym Badge acquired (Oreburgh City)."; +} + +void AutoStory_Segment_01::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_0101().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0102().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0103().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + + +std::string AutoStory_checkpoint_0101::name() const { + return "01.01 - Maxed Starter from Home"; +} +std::string AutoStory_checkpoint_0101::start_text() const { + return "Received Starter back from Pokemon Home. Max leveled pokemon should have the moveset in the wiki."; +} +std::string AutoStory_checkpoint_0101::end_text() const { + return "Standing outside the Oreburgh Gate on the Jubilife City side."; +} +// ======================== +// Checkpoint 0101 Wrapper +// ======================== +void AutoStory_checkpoint_0101::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0101(env, context, options, stats); +} + + +void checkpoint_0101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + jubilife_to_oreburgh_gate(env, env.console, context); + oreburgh_gate_nav(env, env.console, context); + }, + true); +} + +// ======================== +// Checkpoint 0102 Wrapper +// ======================== + +std::string AutoStory_checkpoint_0102::name() const { + return "01.02 - Oreburgh Mine"; +} +std::string AutoStory_checkpoint_0102::start_text() const { + return AutoStory_checkpoint_0101().end_text(); +} +std::string AutoStory_checkpoint_0102::end_text() const { + return "Inside Oreburgh Gym, ready to navigate to Roark."; +} + +void AutoStory_checkpoint_0102::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0102(env, context, options, stats); +} + + +void checkpoint_0102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + oreburgh_gate_to_mine(env, env.console, context); + oreburgh_mine_nav(env.console, context); + // Standalone test entry points are not part of the checkpoint route. + // oreburgh_mine_first_leg_test(env.console, context); + // oreburgh_mine_route_and_escape_test(env.console, context); + oreburgh_mine_to_gym(env, env.console, context); + }, + + true); +} + +// ======================== +// Checkpoint 0103 Wrapper +// ======================== + +std::string AutoStory_checkpoint_0103::name() const { + return "01.03 - Oreburgh Gym"; +} +std::string AutoStory_checkpoint_0103::start_text() const { + return AutoStory_checkpoint_0102().end_text(); +} +std::string AutoStory_checkpoint_0103::end_text() const { + return "Standing at Roark after defeating him and acquiring the first badge."; +} + +void AutoStory_checkpoint_0103::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0103(env, context, options, stats); +} + + +void checkpoint_0103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + //#. Oreburgh Gym navigation and battle + gym_Puzzle_01(env, env.console, context); + }, + + true); +} + + +} +} } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h index d72ceed662..0646ee9306 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -8,151 +8,107 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment01_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // Route ownership: Oreburgh navigation and isolated route tests belong to Segment 01. - enum class MineRouteStage { - ALIGN_ENTRANCE, - FIRST_ROCK, - RIGHT_OF_FIRST_ROCK, - BELOW_FIRST_ROCK, - CENTER_GAP, - POSITION_07, - POSITION_08, - POSITION_09, - ITEM_AT_14, - ROARK_AT_19 - }; - - struct MineRouteTestOptions { - MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; - MineRouteStage last = MineRouteStage::ROARK_AT_19; - - // False: stop the test on failure. - // True: let the checkpoint retry handler reset the game. - bool reset_on_failure = false; - }; - -void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void oreburgh_mine_nav( - VideoStream& stream, ProControllerContext& context); - - void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); - - // Starts inside the mine at an entrance position; stops outside after - // talking to Roark and using Escape Rope. Does not reset on failure. - bool oreburgh_mine_route_and_escape_test( - VideoStream& stream, - ProControllerContext& context - ); - - // Shared stage runner for production navigation and isolated route tests. - bool oreburgh_mine_route( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options - ); - - bool oreburgh_mine_first_leg_test( - VideoStream& stream, - ProControllerContext& context, - const MineRouteTestOptions& options = MineRouteTestOptions() - ); - -class AutoStory_Segment_01 : public AutoStory_Segment{ +enum class MineRouteStage { + ALIGN_ENTRANCE, + FIRST_ROCK, + RIGHT_OF_FIRST_ROCK, + BELOW_FIRST_ROCK, + CENTER_GAP, + POSITION_07, + POSITION_08, + POSITION_09, + ITEM_AT_14, + ROARK_AT_19 +}; + +struct MineRouteTestOptions { + MineRouteStage first = MineRouteStage::ALIGN_ENTRANCE; + MineRouteStage last = MineRouteStage::ROARK_AT_19; + + // False: stop the test on failure. + // True: let the checkpoint retry handler reset the game. + bool reset_on_failure = false; +}; + +void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); + +void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); + +void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); + +void oreburgh_mine_nav(VideoStream& stream, ProControllerContext& context); + +void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); + +void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); + +// Starts inside the mine at an entrance position; stops outside after +// talking to Roark and using Escape Rope. Does not reset on failure. +bool oreburgh_mine_route_and_escape_test(VideoStream& stream, ProControllerContext& context); + +// Shared stage runner for production navigation and isolated route tests. +bool oreburgh_mine_route(VideoStream& stream, ProControllerContext& context, const MineRouteTestOptions& options); + +bool oreburgh_mine_first_leg_test(VideoStream& stream, ProControllerContext& context, + const MineRouteTestOptions& options = MineRouteTestOptions()); + +class AutoStory_Segment_01 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint{ +class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0101( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0102 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0102( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0103 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0103( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index 746fedd610..c1edd06c50 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -5,2376 +5,2633 @@ * */ -#include "CommonFramework/GlobalAutoPaths.h" #include -#include #include +#include + #include + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "../Utils/PokemonBDSP_OverworldMatchers.h" -#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" -#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h" + #include "PokemonBDSP_AutoStory_Segment_02.h" using namespace std::chrono_literals; -namespace PokemonAutomation{ - namespace NintendoSwitch{ - namespace PokemonBDSP{ - static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - size_t confirmations = 0, missing = 0; - for (size_t attempt = 0; attempt < 40; attempt++) { - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: No video frame.", stream); - const OverworldMatchResult result = match_oreburgh_mart(*frame.frame); - VideoOverlaySet overlays(stream.overlay()); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Mart alignment] score=" + std::to_string(landmark.score) + - ", screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); - } - if (!result.located) { - confirmations = 0; - if (++missing >= 8) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: Emblem match missing or ambiguous.", stream); - // A passing shadow must not cause an immediate checkpoint reset. - // Stay still until a trusted match returns; never steer by a rejected candidate. - pbf_wait(context, 250ms); - context.wait_for_all_requests(); - continue; - } - missing = 0; - if (result.at_target) { - if (++confirmations >= 3) { stream.log("[Mart alignment] Door position confirmed."); return; } - pbf_wait(context, 500ms); - } else { - confirmations = 0; - const double dx = result.dx, dy = result.dy; - const bool horizontal = std::abs(dx) >= std::abs(dy)*9.0/16; - const double error = horizontal ? dx : dy*9.0/16; - const auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; - pbf_move_left_joystick(context, - {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, - horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, duration, 0ms); - pbf_wait(context, 700ms); - } - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: Could not reach the doorway reference position.", stream); +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { +static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + size_t confirmations = 0, missing = 0; + for (size_t attempt = 0; attempt < 40; attempt++){ + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mart alignment: No video frame.", stream); + } + const OverworldMatchResult result = match_oreburgh_mart(*frame.frame); + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); } - - static void enter_oreburgh_gate_from_city(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - stream.log("[Oreburgh Gate entry] Moving up, then searching left/down for the entrance."); - // Observe before the first move so entry during the upward - // alignment or either search direction cannot be missed. - BlackScreenWatcher black; - const int result = run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0.0, 1.0}, 800ms, 100ms); - for (size_t attempt = 0; attempt < 30; attempt++) { - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 80ms); - pbf_move_left_joystick(ctx, {0.0, -1.0}, 120ms, 80ms); - ctx.wait_for_all_requests(); - } - // Keep watching if the last pulse starts a delayed fade. - pbf_wait(ctx, 1500ms); - }, {{black}}); - if (result != 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate entry: No black transition during left/down search.", stream); - stream.log("[Oreburgh Gate entry] Black transition detected; movement stopped."); - BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, - 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); - if (wait_until(stream, context, 15s, {{visible}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate entry: Transition did not finish.", stream); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - stream.log("[Oreburgh Gate entry] Cave visible; continuing the route.", COLOR_GREEN); + stream.log("[Mart alignment] score=" + std::to_string(landmark.score) + ", screen-relative offset=(" + + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + } + if (!result.located){ + confirmations = 0; + if (++missing >= 8){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: Emblem match missing or ambiguous.", stream); } - - // Call at a safe walking point, away from trainer/NPC prompts. - void force_repel_expiry(VideoStream& stream, ProControllerContext& context, - RepelRenewalChoice choice, bool north_south, bool require_expiry) { - context.wait_for_all_requests(); - stream.log(std::string("[Repel expiry] Wiggling ") + - (north_south ? "north/south" : "west/east") + " until expiry; renewal response=" + - (choice == RepelRenewalChoice::YES ? "Yes." : "No.")); - const Button response = choice == RepelRenewalChoice::YES ? BUTTON_A : BUTTON_B; - ShortDialogWatcher expired(COLOR_CYAN); - // 108 fresh Max Repels, two calibration runs: mean 41.244682s, - // sample SD 3.668059s. Round mean + SD up to the next millisecond. - // Match the calibration's 500ms movement / 100ms rest pulses. - const auto movement_deadline = std::chrono::steady_clock::now() + 44913ms; - const int result = run_until(stream, context, - [north_south, movement_deadline](ProControllerContext& ctx) { - while (std::chrono::steady_clock::now() + 1200ms <= movement_deadline) { - // Complete steps before reversing so the Repel counter advances. - if (north_south) { - pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 100ms); - pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 100ms); - } else { - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 500ms, 100ms); - pbf_move_left_joystick(ctx, {1.0, 0.0}, 500ms, 100ms); - } - ctx.wait_for_all_requests(); - } - // Finish the observation window without an unpaired move. - const auto remaining = std::chrono::duration_cast( - movement_deadline - std::chrono::steady_clock::now()); - if (remaining > 0ms) pbf_wait(ctx, remaining); - }, {{expired}}); - if (result != 0) { - if (require_expiry) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Repel renewal: Required expiry prompt not observed within calibrated limit.",stream); - stream.log("[Repel expiry] Calibrated 44.913s limit reached without an expiry prompt; checking clear overworld before continuing.", COLOR_ORANGE); - } - // Renewal has a gap before the final used-Repel message. A single - // overworld frame is insufficient: require three continuous clear seconds. - context.wait_for_all_requests(); - ShortDialogDetector dialogue(COLOR_YELLOW); - BlackScreenDetector black; - OverworldDetector overworld; - VideoOverlaySet overlays(stream.overlay()); - dialogue.make_overlays(overlays); - black.make_overlays(overlays); - overworld.make_overlays(overlays); - const auto deadline = std::chrono::steady_clock::now() + 30s; - auto quiet_since = std::chrono::steady_clock::now(); - bool quiet = false; - while (std::chrono::steady_clock::now() < deadline) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Repel renewal: No video frame during cleanup.", stream); - const auto now = std::chrono::steady_clock::now(); - if (dialogue.detect(*frame.frame)) { - quiet = false; - pbf_mash_button(context, response, 100ms); - } else if (black.detect(*frame.frame) || !overworld.detect(*frame.frame)) { - quiet = false; - pbf_wait(context, 100ms); - } else { - if (!quiet) { quiet = true; quiet_since = now; } - if (now - quiet_since >= 3s) { - context.wait_for_all_requests(); - if (result == 0) { - stream.log(std::string("[Repel expiry] Renewal ") + - (choice == RepelRenewalChoice::YES ? "accepted" : "declined") + - "; three continuous seconds of overworld confirmed."); - } else { - stream.log("[Repel expiry] No expiry observed within the calibrated limit; three continuous seconds of clear overworld confirmed. Continuing the route."); - } - return; - } - // Do not send confirmation/cancel buttons while the overworld is clear. - pbf_wait(context, 100ms); - } - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Repel renewal: Three clear seconds not confirmed after renewal.", stream); + // A passing shadow must not cause an immediate checkpoint reset. + // Stay still until a trusted match returns; never steer by a rejected candidate. + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; + } + missing = 0; + if (result.at_target){ + if (++confirmations >= 3){ + stream.log("[Mart alignment] Door position confirmed."); + return; } + pbf_wait(context, 500ms); + }else{ + confirmations = 0; + const double dx = result.dx, dy = result.dy; + const bool horizontal = std::abs(dx) >= std::abs(dy) * 9.0 / 16; + const double error = horizontal ? dx : dy * 9.0 / 16; + const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; + pbf_move_left_joystick(context, + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, + duration, 0ms); + pbf_wait(context, 700ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: Could not reach the doorway reference position.", stream); +} - static void wait_for_trainer_battle_menu(VideoStream& stream, ProControllerContext& context) { +static void enter_oreburgh_gate_from_city(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + stream.log("[Oreburgh Gate entry] Moving up, then searching left/down for the entrance."); + // Observe before the first move so entry during the upward + // alignment or either search direction cannot be missed. + BlackScreenWatcher black; + const int result = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 800ms, 100ms); + for (size_t attempt = 0; attempt < 30; attempt++){ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 80ms); + pbf_move_left_joystick(ctx, {0.0, -1.0}, 120ms, 80ms); + ctx.wait_for_all_requests(); + } + // Keep watching if the last pulse starts a delayed fade. + pbf_wait(ctx, 1500ms); + }, + {{black}}); + if (result != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Oreburgh Gate entry: No black transition during left/down search.", + stream); + } + stream.log("[Oreburgh Gate entry] Black transition detected; movement stopped."); + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate entry: Transition did not finish.", stream); + } + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + stream.log("[Oreburgh Gate entry] Cave visible; continuing the route.", COLOR_GREEN); +} + +// Call at a safe walking point, away from trainer/NPC prompts. +void force_repel_expiry(VideoStream& stream, ProControllerContext& context, RepelRenewalChoice choice, bool north_south, + bool require_expiry){ + context.wait_for_all_requests(); + stream.log(std::string("[Repel expiry] Wiggling ") + (north_south ? "north/south" : "west/east") + + " until expiry; renewal response=" + (choice == RepelRenewalChoice::YES ? "Yes." : "No.")); + const Button response = choice == RepelRenewalChoice::YES ? BUTTON_A : BUTTON_B; + ShortDialogWatcher expired(COLOR_CYAN); + // 108 fresh Max Repels, two calibration runs: mean 41.244682s, + // sample SD 3.668059s. Round mean + SD up to the next millisecond. + // Match the calibration's 500ms movement / 100ms rest pulses. + const auto movement_deadline = std::chrono::steady_clock::now() + 44913ms; + const int result = + run_until(stream, context, + [north_south, movement_deadline](ProControllerContext& ctx){ + while (std::chrono::steady_clock::now() + 1200ms <= movement_deadline){ + // Complete steps before reversing so the Repel counter advances. + if (north_south){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 100ms); + pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 100ms); + }else{ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 500ms, 100ms); + pbf_move_left_joystick(ctx, {1.0, 0.0}, 500ms, 100ms); + } + ctx.wait_for_all_requests(); + } + // Finish the observation window without an unpaired move. + const auto remaining = std::chrono::duration_cast( + movement_deadline - std::chrono::steady_clock::now()); + if (remaining > 0ms){ + pbf_wait(ctx, remaining); + } + }, + {{expired}}); + if (result != 0){ + if (require_expiry){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: Required expiry prompt not observed within calibrated limit.", stream); + } + stream.log( + "[Repel expiry] Calibrated 44.913s limit reached without an expiry prompt; checking clear overworld before continuing.", + COLOR_ORANGE); + } + // Renewal has a gap before the final used-Repel message. A single + // overworld frame is insufficient: require three continuous clear seconds. + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BlackScreenDetector black; + OverworldDetector overworld; + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + black.make_overlays(overlays); + overworld.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 30s; + auto quiet_since = std::chrono::steady_clock::now(); + bool quiet = false; + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: No video frame during cleanup.", stream); + } + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(*frame.frame)){ + quiet = false; + pbf_mash_button(context, response, 100ms); + }else if (black.detect(*frame.frame) || !overworld.detect(*frame.frame)){ + quiet = false; + pbf_wait(context, 100ms); + }else{ + if (!quiet){ + quiet = true; + quiet_since = now; + } + if (now - quiet_since >= 3s){ context.wait_for_all_requests(); - ShortDialogDetector dialogue(COLOR_YELLOW); - BattleMenuDetector menu(BattleType::TRAINER, COLOR_GREEN); - VideoOverlaySet overlays(stream.overlay()); - dialogue.make_overlays(overlays); - menu.make_overlays(overlays); - const auto deadline = std::chrono::steady_clock::now() + 120s; - size_t confirmed = 0; - bool previous_dialogue = false; - while (std::chrono::steady_clock::now() < deadline) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Trainer approach: No video frame.", stream); - const bool dialog = dialogue.detect(*frame.frame); - if (dialog && !previous_dialogue) stream.log("[Trainer approach] Clearing dialogue with B."); - previous_dialogue = dialog; - if (menu.detect(*frame.frame)) { - if (++confirmed >= 3) { - stream.log("[Trainer approach] Battle menu confirmed; starting combat."); - return; - } - pbf_wait(context, 100ms); - } else { - confirmed = 0; - pbf_mash_button(context, BUTTON_B, 100ms); - } - context.wait_for_all_requests(); + if (result == 0){ + stream.log(std::string("[Repel expiry] Renewal ") + + (choice == RepelRenewalChoice::YES ? "accepted" : "declined") + + "; three continuous seconds of overworld confirmed."); + }else{ + stream.log( + "[Repel expiry] No expiry observed within the calibrated limit; three continuous seconds of clear overworld confirmed. Continuing the route."); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Trainer approach: Battle menu did not appear after dialogue cleanup.", stream); + return; } + // Do not send confirmation/cancel buttons while the overworld is clear. + pbf_wait(context, 100ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Repel renewal: Three clear seconds not confirmed after renewal.", stream); +} - static void exit_oreburgh_gate_to_route203(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - stream.log("[Oreburgh Gate exit] Moving west until the white transition starts."); - WhiteScreenWatcher white; - const int result = run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 6000ms, 0ms); - pbf_wait(ctx, 1000ms); - }, {{white}}); - if (result != 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate exit: White transition did not start.", stream); - WhiteScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, - 500, 10, WhiteScreenWatcher::FinderType::GONE, 500ms); - if (wait_until(stream, context, 15s, {{visible}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate exit: White transition did not finish.", stream); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); +static void wait_for_trainer_battle_menu(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector menu(BattleType::TRAINER, COLOR_GREEN); + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + menu.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 120s; + size_t confirmed = 0; + bool previous_dialogue = false; + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Trainer approach: No video frame.", stream); + } + const bool dialog = dialogue.detect(*frame.frame); + if (dialog && !previous_dialogue){ + stream.log("[Trainer approach] Clearing dialogue with B."); + } + previous_dialogue = dialog; + if (menu.detect(*frame.frame)){ + if (++confirmed >= 3){ + stream.log("[Trainer approach] Battle menu confirmed; starting combat."); + return; } + pbf_wait(context, 100ms); + }else{ + confirmed = 0; + pbf_mash_button(context, BUTTON_B, 100ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Trainer approach: Battle menu did not appear after dialogue cleanup.", + stream); +} - static void approach_galactic_dialogue(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - ShortDialogDetector dialogue(COLOR_YELLOW); - BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic approach: No video frame.", stream); - // Alignment may already have handed off after the story started. - if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)) return; - stream.log("[Galactic approach] Plaza position confirmed; sidestepping left around the sign, then moving up until story dialogue."); - ShortDialogWatcher started(COLOR_YELLOW); - BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); - const int result = run_until(stream, context, - [](ProControllerContext& ctx) { - // The reference position faces the sign, which blocks a - // straight upward approach and consumes A as a sign read. - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 150ms); - ctx.wait_for_all_requests(); - for (size_t pulse = 0; pulse < 20; pulse++) { - pbf_move_left_joystick(ctx, {0.0, 1.0}, 100ms, 100ms); - ctx.wait_for_all_requests(); - } - // If an NPC blocks the final approach, initiate interaction. - pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); - pbf_wait(ctx, 1500ms); - }, {{started}, {menu}}); - if (result < 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic approach: Story dialogue/battle did not start during the bounded upward approach.", stream); - stream.log("[Galactic approach] Story dialogue/battle detected; movement stopped."); - } +static void exit_oreburgh_gate_to_route203(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + stream.log("[Oreburgh Gate exit] Moving west until the white transition starts."); + WhiteScreenWatcher white; + const int result = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 6000ms, 0ms); + pbf_wait(ctx, 1000ms); + }, + {{white}}); + if (result != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate exit: White transition did not start.", stream); + } + WhiteScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate exit: White transition did not finish.", stream); + } + pbf_wait(context, 700ms); + context.wait_for_all_requests(); +} - // Dialogue indicates the trainer interaction started; combat still waits - // for a confirmed battle menu before invoking the battle handler. - static bool watch_route204_trainer_event(VideoStream& stream, ProControllerContext& context, - const std::function& action, const std::string& label) { - context.wait_for_all_requests(); - ShortDialogWatcher dialogue(COLOR_YELLOW); - BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); - const int result = run_until(stream, context, - [&action](ProControllerContext& ctx) { action(ctx); }, {{dialogue}, {menu}}); - const bool detected = result >= 0; - stream.log("[" + label + "] Trainer dialogue/battle detected=" + std::to_string(detected)); - return detected; +static void approach_galactic_dialogue(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic approach: No video frame.", stream); + } + // Alignment may already have handed off after the story started. + if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)){ + return; + } + stream.log( + "[Galactic approach] Plaza position confirmed; sidestepping left around the sign, then moving up until story dialogue."); + ShortDialogWatcher started(COLOR_YELLOW); + BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); + const int result = run_until(stream, context, + [](ProControllerContext& ctx){ + // The reference position faces the sign, which blocks a + // straight upward approach and consumes A as a sign read. + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 150ms); + ctx.wait_for_all_requests(); + for (size_t pulse = 0; pulse < 20; pulse++){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 100ms, 100ms); + ctx.wait_for_all_requests(); + } + // If an NPC blocks the final approach, initiate interaction. + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + pbf_wait(ctx, 1500ms); + }, + {{started}, {menu}}); + if (result < 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Galactic approach: Story dialogue/battle did not start during the bounded upward approach.", stream); + } + stream.log("[Galactic approach] Story dialogue/battle detected; movement stopped."); +} + +// Dialogue indicates the trainer interaction started; combat still waits +// for a confirmed battle menu before invoking the battle handler. +static bool watch_route204_trainer_event(VideoStream& stream, ProControllerContext& context, + const std::function& action, + const std::string& label){ + context.wait_for_all_requests(); + ShortDialogWatcher dialogue(COLOR_YELLOW); + BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); + const int result = run_until(stream, context, + [&action](ProControllerContext& ctx){ + action(ctx); + }, + {{dialogue}, {menu}}); + const bool detected = result >= 0; + stream.log("[" + label + "] Trainer dialogue/battle detected=" + std::to_string(detected)); + return detected; +} + +static bool approach_route204_female_trainer(VideoStream& stream, ProControllerContext& context){ + return watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 8000ms, 0ms); + // Replaces the 2374ms settle plus the unbounded-looking + // generic dialogue wait with one explicit detection window. + pbf_wait(ctx, 2500ms); + }, + "Route 204 female trainer approach"); +} + +static void recover_route204_female_trainer(VideoStream& stream, ProControllerContext& context){ + stream.log("[Route 204 female trainer] Missed initial trigger; crossing her sight line down/up."); + const bool recovered = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + for (size_t pair = 0; pair < 8; pair++){ + // We have passed her: go back down first, then up. + pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 150ms); + pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 150ms); + ctx.wait_for_all_requests(); } + pbf_wait(ctx, 2500ms); + }, + "Route 204 female trainer recovery"); + if (!recovered){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 female trainer: No dialogue/battle after bounded down/up recovery.", stream); + } +} - static bool approach_route204_female_trainer(VideoStream& stream, ProControllerContext& context) { - return watch_route204_trainer_event(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0.0, 1.0}, 8000ms, 0ms); - // Replaces the 2374ms settle plus the unbounded-looking - // generic dialogue wait with one explicit detection window. - pbf_wait(ctx, 2500ms); - }, "Route 204 female trainer approach"); +static void recover_route204_youngster(VideoStream& stream, ProControllerContext& context){ + // The recording ends above the youngster after passing left. + // Cross his row down/up instead of resetting after one miss. + const bool recovered = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + for (size_t pair = 0; pair < 6; pair++){ + pbf_move_left_joystick(ctx, {0, -1}, 500ms, 200ms); + // Body collision can leave us adjacent without a sightline trigger. + pbf_press_button(ctx, BUTTON_A, 100ms, 250ms); + pbf_move_left_joystick(ctx, {0, 1}, 500ms, 200ms); + pbf_press_button(ctx, BUTTON_A, 100ms, 250ms); + ctx.wait_for_all_requests(); } + pbf_wait(ctx, 2500ms); + }, + "Route 204 youngster sightline recovery"); + if (!recovered){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 youngster: No trigger after bounded down/up recovery.", + stream); + } +} + +static void floaroma_house_transition(VideoStream& stream, ProControllerContext& context, double y){ + context.wait_for_all_requests(); + BlackScreenWatcher black; + const int result = run_until(stream, context, + [y](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, y}, 2500ms, 0ms); + pbf_wait(ctx, 1000ms); + }, + {{black}}); + if (result != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma house: Door transition not detected.", stream); + } + context.wait_for_all_requests(); + BlackScreenDetector black_frame; + const auto deadline = std::chrono::steady_clock::now() + 15s; + auto clear_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma house: No video frame after transition.", stream); + } + const auto now = std::chrono::steady_clock::now(); + if (black_frame.detect(*frame.frame)){ + clear_since = now; + }else if (now - clear_since >= 500ms){ + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + return; + } + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma house: Door transition did not finish.", stream); +} + +static void establish_floaroma_house_exit(VideoStream& stream, ProControllerContext& context){ + stream.log("[Floaroma checkpoint] Blind northward house entry, then immediate exit."); + floaroma_house_transition(stream, context, 1); + floaroma_house_transition(stream, context, -1); + stream.log("[Floaroma checkpoint] House entry/exit completed; ready for checkpoint save.", COLOR_GREEN); +} - static void recover_route204_female_trainer(VideoStream& stream, ProControllerContext& context) { - stream.log("[Route 204 female trainer] Missed initial trigger; crossing her sight line down/up."); - const bool recovered = watch_route204_trainer_event(stream, context, - [](ProControllerContext& ctx) { - for (size_t pair = 0; pair < 8; pair++) { - // We have passed her: go back down first, then up. - pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 150ms); - pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 150ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx, 2500ms); - }, "Route 204 female trainer recovery"); - if (!recovered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 female trainer: No dialogue/battle after bounded down/up recovery.", stream); +static void recover_teighler_trigger(VideoStream& stream, ProControllerContext& context){ + // The first pass ended east of the corner. Return left, then + // cross her sightline along the same bottom ledge for five seconds. + const bool recovered = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + // The old 500ms sweeps stopped beneath/west of Teighler. + // Cross the full bottom ledge in both directions, still + // limiting the total recovery motion to five seconds. + for (size_t pair = 0; pair < 3; pair++){ + const Milliseconds sweep = pair < 2 ? 1000ms : 500ms; + pbf_move_left_joystick(ctx, {-1, 0}, sweep, 0ms); + pbf_move_left_joystick(ctx, {1, 0}, sweep, 0ms); + ctx.wait_for_all_requests(); } + pbf_wait(ctx, 2500ms); + }, + "Teighler five-second ledge recovery"); + if (!recovered){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Teighler: No battle after five-second left/right ledge recovery.", + stream); + } +} - static void recover_route204_youngster(VideoStream& stream, ProControllerContext& context) { - // The recording ends above the youngster after passing left. - // Cross his row down/up instead of resetting after one miss. - const bool recovered = watch_route204_trainer_event(stream, context, - [](ProControllerContext& ctx) { - for (size_t pair = 0; pair < 6; pair++) { - pbf_move_left_joystick(ctx, {0, -1}, 500ms, 200ms); - // Body collision can leave us adjacent without a sightline trigger. - pbf_press_button(ctx, BUTTON_A, 100ms, 250ms); - pbf_move_left_joystick(ctx, {0, 1}, 500ms, 200ms); - pbf_press_button(ctx, BUTTON_A, 100ms, 250ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx, 2500ms); - }, "Route 204 youngster sightline recovery"); - if (!recovered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 youngster: No trigger after bounded down/up recovery.", stream); +// True means the next trainer triggered before the position was reached. +// False means the sign position was confirmed. Both outcomes are explicit. +// Route 204 arrival accepts a small standing area, rather than one +// exact pose. Preserve strong observations through passing shadows +// only while stationary; every movement invalidates this history. +static bool align_to_route204_floaroma_turn(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector battle_menu(BattleType::TRAINER, COLOR_GREEN); + size_t confirmed = 0, missing = 0, blocked_left = 0; + double previous_dx = 1; + bool previously_left = false; + stream.log("[Route 204 north turn] Locate left to the sign; correct height after horizontal alignment."); + for (size_t attempt = 0; attempt < 60; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: No video frame.", stream); + } + if (dialogue.detect(*frame.frame) || battle_menu.detect(*frame.frame)){ + return true; + } + const auto result = match_route204_floaroma_turn(*frame.frame); + if (result.at_target){ + if (++confirmed >= 3){ + stream.log("[Route 204 north turn] Sign position confirmed; ready to continue west.", COLOR_GREEN); + return false; + } + pbf_wait(context, 150ms); + context.wait_for_all_requests(); + continue; + } + confirmed = 0; + double x = -1, y = 0; + Milliseconds duration = 100ms; + if (result.located){ + missing = 0; + stream.log("[Route 204 north turn] Sign offset=(" + std::to_string(result.dx) + "," + + std::to_string(result.dy) + ")"); + const bool horizontal = std::abs(result.dx) > 1.0 / 50; + if (previously_left && std::abs(result.dx - previous_dx) < 1.0 / 384){ + blocked_left++; + }else{ + blocked_left = 0; + } + // Primarily locate left. If a fence/NPC blocks progress, + // correct height briefly before resuming measured travel. + const bool correct_height = !horizontal || (blocked_left >= 3 && std::abs(result.dy) > 1.0 / 54); + x = correct_height ? 0.0 : (result.dx < 0 ? -1.0 : 1.0); + y = correct_height ? (result.dy > 0 ? -1.0 : 1.0) : 0.0; + const double error = correct_height ? std::abs(result.dy) : std::abs(result.dx); + duration = error < .08 ? 40ms : 100ms; + previous_dx = result.dx; + previously_left = x < 0; + if (correct_height){ + blocked_left = 0; } + }else{ + previously_left = false; + blocked_left = 0; + if (++missing > 12){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Sign unavailable during bounded leftward acquisition.", stream); + } + } + if (watch_route204_trainer_event( + stream, context, + [x, y, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 250ms); + }, + "Route 204 north sign alignment")){ + return true; + } + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Sign position did not converge.", stream); +} - static void floaroma_house_transition(VideoStream& stream, ProControllerContext& context, double y) { - context.wait_for_all_requests(); - BlackScreenWatcher black; - const int result=run_until(stream,context, - [y](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,y},2500ms,0ms); - pbf_wait(ctx,1000ms); - },{{black}}); - if (result!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma house: Door transition not detected.",stream); +static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + size_t confirmed = 0, missing = 0; + bool previously_located = false; + stream.log("[Windworks approach] Locate beside the sign before the blind wall/NPC approach."); + for (size_t attempt = 0; attempt < 80; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: No video frame.", stream); + } + const auto result = match_windworks_sign_approach(*frame.frame); + if (result.at_target){ + if (++confirmed >= 3){ + return; + } + pbf_wait(context, 150ms); + }else{ + confirmed = 0; + double x = 0, y = 1; + Milliseconds duration = 100ms; + if (result.located){ + missing = 0; + previously_located = true; + // Correct large height errors first, including coming + // south from the wall, then clear the grass horizontally. + const bool vertical = result.dy > .08 || std::abs(result.dx) <= 1.0 / 50; + const double error = vertical ? std::abs(result.dy) : std::abs(result.dx); + x = vertical ? 0 : (result.dx < 0 ? -1 : 1); + y = vertical ? (result.dy > 0 ? -1 : 1) : 0; + duration = error < .08 ? 40ms : 100ms; + stream.log("[Windworks approach] Sign offset=(" + std::to_string(result.dx) + "," + + std::to_string(result.dy) + ")"); + }else if (previously_located && ++missing <= 12){ + pbf_wait(context, 250ms); context.wait_for_all_requests(); - BlackScreenDetector black_frame; - const auto deadline=std::chrono::steady_clock::now()+15s; - auto clear_since=std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now()=500ms) { - pbf_wait(context,700ms); - context.wait_for_all_requests(); - return; - } - pbf_wait(context,100ms); - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma house: Door transition did not finish.",stream); + continue; + }else if (previously_located || ++missing > 12){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: Sign unavailable during bounded northward acquisition.", stream); } + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, 250ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: Sign position did not converge.", stream); +} - static void establish_floaroma_house_exit(VideoStream& stream, ProControllerContext& context) { - stream.log("[Floaroma checkpoint] Blind northward house entry, then immediate exit."); - floaroma_house_transition(stream,context,1); - floaroma_house_transition(stream,context,-1); - stream.log("[Floaroma checkpoint] House entry/exit completed; ready for checkpoint save.",COLOR_GREEN); +struct Route204ArrivalTracker { + using Clock = std::chrono::steady_clock; + static constexpr double TOLERANCE_X = 1.0 / 32; + static constexpr double TOLERANCE_Y = 1.0 / 36; + struct Anchor { + bool seen = false; + double dx = 0, dy = 0; + Clock::time_point timestamp{}; + }; + Anchor anchors[2]; + bool holding = false; + Clock::time_point hold_started{}; + void reset(){ + anchors[0].seen = anchors[1].seen = false; + holding = false; + } + bool observe(const OverworldMatchResult& result, Clock::time_point now){ + if (result.landmarks.size() != 2){ + reset(); + return false; + } + for (const auto& landmark : result.landmarks){ + if (landmark.accepted && (std::abs(landmark.dx) > TOLERANCE_X || std::abs(landmark.dy) > TOLERANCE_Y)){ + reset(); + return false; } - - static void recover_teighler_trigger(VideoStream& stream, ProControllerContext& context) { - // The first pass ended east of the corner. Return left, then - // cross her sightline along the same bottom ledge for five seconds. - const bool recovered=watch_route204_trainer_event(stream,context, - [](ProControllerContext& ctx) { - // The old 500ms sweeps stopped beneath/west of Teighler. - // Cross the full bottom ledge in both directions, still - // limiting the total recovery motion to five seconds. - for (size_t pair=0; pair<3; pair++) { - const Milliseconds sweep = pair<2 ? 1000ms : 500ms; - pbf_move_left_joystick(ctx,{-1,0},sweep,0ms); - pbf_move_left_joystick(ctx,{1,0},sweep,0ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx,2500ms); - },"Teighler five-second ledge recovery"); - if (!recovered) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Teighler: No battle after five-second left/right ledge recovery.",stream); + } + for (size_t i = 0; i < 2; i++){ + const auto& landmark = result.landmarks[i]; + if (!landmark.accepted){ + continue; } - - // True means the next trainer triggered before the position was reached. - // False means the sign position was confirmed. Both outcomes are explicit. - // Route 204 arrival accepts a small standing area, rather than one - // exact pose. Preserve strong observations through passing shadows - // only while stationary; every movement invalidates this history. - static bool align_to_route204_floaroma_turn(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - ShortDialogDetector dialogue(COLOR_YELLOW); - BattleMenuDetector battle_menu(BattleType::TRAINER, COLOR_GREEN); - size_t confirmed=0, missing=0, blocked_left=0; - double previous_dx=1; - bool previously_left=false; - stream.log("[Route 204 north turn] Locate left to the sign; correct height after horizontal alignment."); - for (size_t attempt=0; attempt<60; attempt++) { - const auto frame=stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: No video frame.", stream); - if (dialogue.detect(*frame.frame) || battle_menu.detect(*frame.frame)) return true; - const auto result=match_route204_floaroma_turn(*frame.frame); - if (result.at_target) { - if (++confirmed>=3) { - stream.log("[Route 204 north turn] Sign position confirmed; ready to continue west.", COLOR_GREEN); - return false; - } - pbf_wait(context,150ms); - context.wait_for_all_requests(); - continue; - } - confirmed=0; - double x=-1, y=0; - Milliseconds duration=100ms; - if (result.located) { - missing=0; - stream.log("[Route 204 north turn] Sign offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); - const bool horizontal=std::abs(result.dx)>1.0/50; - if (previously_left && std::abs(result.dx-previous_dx)<1.0/384) blocked_left++; - else blocked_left=0; - // Primarily locate left. If a fence/NPC blocks progress, - // correct height briefly before resuming measured travel. - const bool correct_height=!horizontal || - (blocked_left>=3 && std::abs(result.dy)>1.0/54); - x=correct_height ? 0.0 : (result.dx<0 ? -1.0 : 1.0); - y=correct_height ? (result.dy>0 ? -1.0 : 1.0) : 0.0; - const double error=correct_height ? std::abs(result.dy) : std::abs(result.dx); - duration=error<.08 ? 40ms : 100ms; - previous_dx=result.dx; - previously_left=x<0; - if (correct_height) blocked_left=0; - } else { - previously_left=false; - blocked_left=0; - if (++missing>12) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: Sign unavailable during bounded leftward acquisition.", stream); - } - if (watch_route204_trainer_event(stream,context, - [x,y,duration](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{x,y},duration,0ms); - pbf_wait(ctx,250ms); - }, "Route 204 north sign alignment")) return true; + anchors[i] = {true, landmark.dx, landmark.dy, now}; + if (!holding){ + holding = true; + hold_started = now; + } + } + for (auto& anchor : anchors){ + if (anchor.seen && now - anchor.timestamp > 10s){ + anchor.seen = false; + } + } + return anchors[0].seen && anchors[1].seen && std::abs(anchors[0].dx - anchors[1].dx) <= 1.0 / 64 && + std::abs(anchors[0].dy - anchors[1].dy) <= 1.0 / 54; + } + bool wait_for_shadow(Clock::time_point now) const { + // Bound the stationary wait even if one anchor keeps matching. + return holding && now - hold_started < 10s; + } +}; + +static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& context, bool northbound = false){ + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector menu(BattleType::TRAINER, COLOR_GREEN); + BlackScreenDetector black; + WhiteScreenDetector white; + size_t confirmations = 0, missing = 0, blocked_east = 0, detours = 0; + double previous_dx = 1; + bool previous_east = false; + Route204ArrivalTracker arrival; + for (size_t attempt = 0; attempt < 80; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: No video frame.", stream); + } + if (dialogue.detect(*frame.frame) || menu.detect(*frame.frame)){ + stream.log("[Route 204 sign] Next trainer triggered; stopping alignment."); + return true; + } + double x = 0, y = 0; + bool detour = false, measured = false; + double measured_dx = 0; + Milliseconds duration = 250ms; + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + menu.make_overlays(overlays); + if (black.detect(*frame.frame) || white.detect(*frame.frame)){ + confirmations = 0; + arrival.reset(); + }else{ + const auto result = + northbound ? match_route204_north_approach(*frame.frame) : match_route204_sign(*frame.frame); + measured = result.located; + measured_dx = result.dx; + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: Sign position did not converge.", stream); + stream.log("[Route 204 sign] " + landmark.name + " score=" + std::to_string(landmark.score) + + ", margin=" + std::to_string(landmark.margin) + + ", accepted=" + std::to_string(landmark.accepted) + ", offset=(" + + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } - - static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - size_t confirmed=0, missing=0; - bool previously_located=false; - stream.log("[Windworks approach] Locate beside the sign before the blind wall/NPC approach."); - for (size_t attempt=0; attempt<80; attempt++) { - const auto frame=stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks approach: No video frame.",stream); - const auto result=match_windworks_sign_approach(*frame.frame); - if (result.at_target) { - if (++confirmed>=3) return; - pbf_wait(context,150ms); - } else { - confirmed=0; - double x=0,y=1; - Milliseconds duration=100ms; - if (result.located) { - missing=0; - previously_located=true; - // Correct large height errors first, including coming - // south from the wall, then clear the grass horizontally. - const bool vertical=result.dy>.08 || std::abs(result.dx)<=1.0/50; - const double error=vertical?std::abs(result.dy):std::abs(result.dx); - x=vertical?0:(result.dx<0?-1:1); - y=vertical?(result.dy>0?-1:1):0; - duration=error<.08?40ms:100ms; - stream.log("[Windworks approach] Sign offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); - } else if (previously_located && ++missing<=12) { - pbf_wait(context,250ms); - context.wait_for_all_requests(); - continue; - } else if (previously_located || ++missing>12) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks approach: Sign unavailable during bounded northward acquisition.",stream); - } - pbf_move_left_joystick(context,{x,y},duration,0ms); - pbf_wait(context,250ms); + const auto now = Route204ArrivalTracker::Clock::now(); + // The broad pre-battle cache tolerance can accept the sign + // while still inside the fence's collision column. + bool clear_column = true; + if (northbound){ + for (const auto& landmark : result.landmarks){ + if (landmark.accepted && std::abs(landmark.dx) > 1.0 / 128){ + clear_column = false; } - context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks approach: Sign position did not converge.",stream); } - - struct Route204ArrivalTracker { - using Clock = std::chrono::steady_clock; - static constexpr double TOLERANCE_X = 1.0/32; - static constexpr double TOLERANCE_Y = 1.0/36; - struct Anchor { - bool seen = false; - double dx = 0, dy = 0; - Clock::time_point timestamp{}; - }; - Anchor anchors[2]; - bool holding = false; - Clock::time_point hold_started{}; - void reset() { - anchors[0].seen = anchors[1].seen = false; - holding = false; + if (!clear_column){ + arrival.reset(); + } + const bool arrived = clear_column && arrival.observe(result, now); + if (arrived){ + if (++confirmations >= 3){ + stream.log(northbound + ? "[Route 204 sign] Left-of-sign northbound position confirmed." + : "[Route 204 sign] Position confirmed; ready for the next trainer approach."); + return false; } - bool observe(const OverworldMatchResult& result, Clock::time_point now) { - if (result.landmarks.size() != 2) { reset(); return false; } - for (const auto& landmark : result.landmarks) { - if (landmark.accepted && - (std::abs(landmark.dx)>TOLERANCE_X || std::abs(landmark.dy)>TOLERANCE_Y)) { - reset(); - return false; - } - } - for (size_t i = 0; i < 2; i++) { - const auto& landmark = result.landmarks[i]; - if (!landmark.accepted) continue; - anchors[i] = {true, landmark.dx, landmark.dy, now}; - if (!holding) { holding = true; hold_started = now; } - } - for (auto& anchor : anchors) { - if (anchor.seen && now-anchor.timestamp>10s) anchor.seen = false; - } - return anchors[0].seen && anchors[1].seen && - std::abs(anchors[0].dx-anchors[1].dx)<=1.0/64 && - std::abs(anchors[0].dy-anchors[1].dy)<=1.0/54; + }else if (arrival.wait_for_shadow(now)){ + // One strong nearby anchor is enough to hold still, + // but both are still required to confirm arrival. + confirmations = 0; + missing = 0; + duration = 250ms; + stream.log("[Route 204 sign] Holding near target while waiting for shadowed landmark."); + }else if (result.located){ + confirmations = 0; + missing = 0; + stream.log(std::string("[Route 204 sign] ") + + (result.coarse ? "Sign-guided acquisition." : "Both landmarks located.")); + // At the wrong height, leftward movement can hit the + // grass boundary. Acquire the sign's height first. + const bool horizontal = (!result.coarse || std::abs(result.dy) <= 1.0 / 54) && + std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16; + const double error = horizontal ? result.dx : result.dy * 9.0 / 16; + x = horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0; + y = horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0); + duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 1.0 / 64 ? 40ms : 16ms; + if (result.coarse && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 72){ + // Resume fine acquisition after the bounded shadow wait. + // The bounded shadow wait has ended. Make a small + // correction instead of holding indefinitely. + duration = 40ms; } - bool wait_for_shadow(Clock::time_point now) const { - // Bound the stationary wait even if one anchor keeps matching. - return holding && now-hold_started<10s; + }else{ + confirmations = 0; + if (++missing > 24){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: Landmarks missing during bounded acquisition.", stream); } - }; - - static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& context, bool northbound = false) { - context.wait_for_all_requests(); - ShortDialogDetector dialogue(COLOR_YELLOW); - BattleMenuDetector menu(BattleType::TRAINER, COLOR_GREEN); - BlackScreenDetector black; - WhiteScreenDetector white; - size_t confirmations = 0, missing = 0, blocked_east = 0, detours = 0; - double previous_dx = 1; - bool previous_east = false; - Route204ArrivalTracker arrival; - for (size_t attempt = 0; attempt < 80; attempt++) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 sign: No video frame.", stream); - if (dialogue.detect(*frame.frame) || menu.detect(*frame.frame)) { - stream.log("[Route 204 sign] Next trainer triggered; stopping alignment."); - return true; - } - double x = 0, y = 0; - bool detour = false, measured = false; - double measured_dx = 0; - Milliseconds duration = 250ms; - VideoOverlaySet overlays(stream.overlay()); - dialogue.make_overlays(overlays); - menu.make_overlays(overlays); - if (black.detect(*frame.frame) || white.detect(*frame.frame)) { - confirmations = 0; - arrival.reset(); - } else { - const auto result = northbound ? match_route204_north_approach(*frame.frame) : match_route204_sign(*frame.frame); - measured=result.located;measured_dx=result.dx; - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Route 204 sign] " + landmark.name + " score=" + std::to_string(landmark.score) + - ", margin=" + std::to_string(landmark.margin) + ", accepted=" + std::to_string(landmark.accepted) + - ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); - } - const auto now = Route204ArrivalTracker::Clock::now(); - // The broad pre-battle cache tolerance can accept the sign - // while still inside the fence's collision column. - bool clear_column=true; - if (northbound) for (const auto& landmark:result.landmarks) - if (landmark.accepted && std::abs(landmark.dx)>1.0/128) clear_column=false; - if (!clear_column) arrival.reset(); - const bool arrived = clear_column && arrival.observe(result, now); - if (arrived) { - if (++confirmations >= 3) { - stream.log(northbound ? "[Route 204 sign] Left-of-sign northbound position confirmed." : "[Route 204 sign] Position confirmed; ready for the next trainer approach."); - return false; - } - } else if (arrival.wait_for_shadow(now)) { - // One strong nearby anchor is enough to hold still, - // but both are still required to confirm arrival. - confirmations = 0; - missing = 0; - duration = 250ms; - stream.log("[Route 204 sign] Holding near target while waiting for shadowed landmark."); - } else if (result.located) { - confirmations = 0; - missing = 0; - stream.log(std::string("[Route 204 sign] ") + (result.coarse ? "Sign-guided acquisition." : "Both landmarks located.")); - // At the wrong height, leftward movement can hit the - // grass boundary. Acquire the sign's height first. - const bool horizontal = (!result.coarse || std::abs(result.dy)<=1.0/54) && - std::abs(result.dx)>=std::abs(result.dy)*9.0/16; - const double error = horizontal ? result.dx : result.dy*9.0/16; - x = horizontal ? (result.dx>0 ? 1.0 : -1.0) : 0.0; - y = horizontal ? 0.0 : (result.dy<0 ? 1.0 : -1.0); - duration = std::abs(error)>1.0/24 ? 80ms : std::abs(error)>1.0/64 ? 40ms : 16ms; - if (result.coarse && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/72) { - // Resume fine acquisition after the bounded shadow wait. - // The bounded shadow wait has ended. Make a small - // correction instead of holding indefinitely. - duration = 40ms; - } - } else { - confirmations = 0; - if (++missing > 24) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 sign: Landmarks missing during bounded acquisition.", stream); - // The sign is north of the defeated female trainer. - // Search that height first, then return below it. - if (northbound) { - // Tyler leaves us west of the sign. Search east - // along the road instead of climbing into grass. - x=1.0;y=0.0; - } else y = missing<=8 ? 1.0 : -1.0; - duration = 100ms; - } - } - if (northbound && measured && x>0 && previous_east && std::abs(previous_dx-measured_dx)<1.0/384) blocked_east++; - else blocked_east=0; - previous_east=northbound && measured && x>0; - // Reuse this snapshot; no movement has been sent since it. - if (previous_east) previous_dx=measured_dx; - if (blocked_east>=3) { - if (++detours>2) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north approach: Tyler obstruction did not clear.",stream); - stream.log("[Route 204 sign] Eastward movement blocked; passing below defeated Tyler."); - detour=true;blocked_east=0;previous_east=false; - } - if (x != 0 || y != 0 || detour) arrival.reset(); - // Observe throughout each correction and its settle period, - // so an early next-trainer trigger cancels remaining movement. - if (watch_route204_trainer_event(stream, context, - [x,y,duration,detour](ProControllerContext& ctx) { - if (detour) { - pbf_move_left_joystick(ctx,{0,-1},180ms,0ms); - pbf_move_left_joystick(ctx,{1,0},400ms,0ms); - pbf_move_left_joystick(ctx,{0,1},180ms,0ms); - pbf_wait(ctx,500ms); - } else if (x!=0 || y!=0) { - pbf_move_left_joystick(ctx, {x,y}, duration, 0ms); - pbf_wait(ctx, 500ms); - } else pbf_wait(ctx, duration); - }, "Route 204 sign alignment")) return true; + // The sign is north of the defeated female trainer. + // Search that height first, then return below it. + if (northbound){ + // Tyler leaves us west of the sign. Search east + // along the road instead of climbing into grass. + x = 1.0; + y = 0.0; + }else{ + y = missing <= 8 ? 1.0 : -1.0; } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 sign: Position could not be confirmed.", stream); + duration = 100ms; } - - static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - // The cave's brown corridor and black map void distinguish an - // indoor return from a battle fade or an outdoor scene. All ROIs - // are normalized and visible without labels in the overlays. - const ImageFloatBox floor_left{0.42, 0.35, 0.08, 0.10}; - const ImageFloatBox floor_right{0.52, 0.35, 0.08, 0.10}; - const ImageFloatBox map_void{0.15, 0.85, 0.10, 0.08}; - auto cave_visible = [&](const ImageRGB32& frame) { - for (const auto& box : {floor_left, floor_right}) { - const auto stats = image_stats(extract_box_reference(frame, box)); - if (stats.average.r < stats.average.g + 12 || - stats.average.g < stats.average.b + 12) return false; - } - const auto empty = image_stats(extract_box_reference(frame, map_void)); - return empty.average.r < 20 && empty.average.g < 20 && - empty.average.b < 20 && empty.stddev.sum() < 30; - }; - auto handle_trainer = [&] { - stream.log("[Ravaged Path approach] Lass Samantha triggered; completing battle before continuing."); - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - clear_post_battle_dialogue(stream, context); - }; - Route204ArrivalTracker arrival; - size_t position_confirmations = 0; - auto missing_since = std::chrono::steady_clock::now(); - bool initial_approach = true; - size_t acquisition_pulses = 0; - size_t blocked_north=0; - double previous_north_dy=1; - bool previous_coarse_north=false; - for (size_t attempt = 0; attempt < 120; attempt++) { - ShortDialogWatcher dialogue(COLOR_YELLOW); - BattleMenuWatcher battle(BattleType::TRAINER, COLOR_GREEN); - BlackScreenWatcher entrance; - VideoOverlaySet overlays(stream.overlay()); - overlays.add(COLOR_CYAN, floor_left); - overlays.add(COLOR_CYAN, floor_right); - overlays.add(COLOR_CYAN, map_void); - const auto before = stream.video().snapshot(); - if (!before) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path entrance: No video frame.", stream); - ShortDialogDetector current_dialogue; - BattleMenuDetector current_battle(BattleType::TRAINER); - if (current_dialogue.detect(*before.frame) || current_battle.detect(*before.frame)) { - handle_trainer(); - arrival.reset(); - position_confirmations = 0; - missing_since = std::chrono::steady_clock::now(); - continue; - } - double x = 0, y = 0; - Milliseconds duration = 250ms; - if (initial_approach) { - // Retain the northbound approach from Tyler. Subsequent - // movement is landmark-guided, without the old right sweep. - initial_approach = false; - y = 1; - duration = 2400ms; - } else { - auto result = match_ravaged_path_south_entrance(*before.frame); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Ravaged Path locator] " + landmark.name + - " score=" + std::to_string(landmark.score) + - ", margin=" + std::to_string(landmark.margin) + - ", accepted=" + std::to_string(landmark.accepted) + - ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); - } - const auto now = std::chrono::steady_clock::now(); - auto fixed = result; - fixed.landmarks.resize(2); - // Cave entry needs tighter horizontal alignment than the - // standing area beside the Route 204 sign. - bool horizontal_ready = true; - for (const auto& anchor : fixed.landmarks) - if (anchor.accepted && std::abs(anchor.dx)>1.0/64) horizontal_ready = false; - if (!horizontal_ready) arrival.reset(); - const bool arrived = horizontal_ready && arrival.observe(fixed, now); - if (arrived) { - if (++position_confirmations >= 3) { - stream.log("[Ravaged Path locator] Entrance position confirmed; moving north into cave.", COLOR_GREEN); - y = 1; - duration = 1200ms; - } - } else if (arrival.wait_for_shadow(now)) { - position_confirmations = 0; - stream.log("[Ravaged Path locator] Holding still for shadowed landmark."); - } else if (result.located) { - missing_since = now; - position_confirmations = 0; - // Escape the upper-right alcove vertically before - // moving left. Near the doorway, correct horizontal first. - if (std::abs(result.dy)>1.0/36) { - y = result.dy>0 ? -1.0 : 1.0; - } else if (std::abs(result.dx)>1.0/64) { - x = result.dx>0 ? 1.0 : -1.0; - } - duration = 80ms; - if (result.coarse && y>0 && previous_coarse_north && - std::abs(result.dy-previous_north_dy)<1.0/384) blocked_north++; - else blocked_north=0; - previous_north_dy=result.dy; - previous_coarse_north=result.coarse && y>0; - if (blocked_north>=4) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path locator: Northward coarse acquisition blocked; refusing repeated movement.",stream); - if (result.coarse && x==0 && y==0) { - // After the stationary shadow wait, correct a - // small height error that may still change the - // sign's perspective enough to prevent matching. - if (std::abs(result.dy)<=1.0/72) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path locator: Near entrance, but sign remained shadowed; refusing blind correction.", stream); - y = result.dy<0 ? 1.0 : -1.0; - duration = 40ms; - } - if (y<0) { - // Fixed entrance geometry predicts the lass's - // feet when she is shadowed/off-screen. A visible - // lass may tighten this limit, never relax it. - double boundary = 930.0/1080 + result.dy; - if (result.landmarks.size()>2 && result.landmarks[2].accepted) - boundary = std::min(boundary, - result.landmarks[2].found.y + result.landmarks[2].found.height); - // Leave >12% screen height above her before any - // short down pulse. No blind southward search. - if (boundary - 0.55 <= 0.12) { - y = 0; - stream.log("[Ravaged Path locator] Downward correction blocked by lass boundary.", COLOR_RED); - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path locator: Southern safety boundary reached.", stream); - } - } - } else { - position_confirmations = 0; - if (now-missing_since>10s) { - // Failed recording: still beside the Route 204 - // sign, blocked below the northbound approach. - // Only this recognizable route authorizes search. - const auto route = match_route204_sign(*before.frame); - const bool known_route = !route.landmarks.empty() && - route.landmarks[0].accepted && route.landmarks[0].score>=.94 && - route.landmarks[0].margin>=.30; - if (known_route && acquisition_pulses<8) { - const bool sidestep = acquisition_pulses++ % 2 == 0; - x = sidestep ? -1.0 : 0.0; - y = sidestep ? 0.0 : 1.0; - duration = sidestep ? 150ms : 500ms; - stream.log("[Ravaged Path locator] Recognized Route 204 sign; bounded left/north acquisition."); - } else OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path locator: Landmarks unavailable after bounded acquisition.", stream); - } - } - } - if (x!=0 || y!=0) { - arrival.reset(); - position_confirmations = 0; - missing_since = std::chrono::steady_clock::now(); - } - const int ret = run_until(stream, context, - [x,y,duration](ProControllerContext& ctx) { - if (x!=0 || y!=0) { - pbf_move_left_joystick(ctx, {x,y}, duration, 0ms); - pbf_wait(ctx, 500ms); - } else pbf_wait(ctx, duration); - }, {{entrance}, {dialogue}, {battle}}); - context.wait_for_all_requests(); - if (ret == 1 || ret == 2) { - handle_trainer(); - arrival.reset(); - position_confirmations = 0; - missing_since = std::chrono::steady_clock::now(); - continue; - } - if (ret != 0) continue; - // A trainer transition can also go black. Require a stable - // cave scene, or route the detected battle through combat. - const auto deadline = std::chrono::steady_clock::now() + 15s; - size_t confirmed = 0; - bool trainer_seen = false; - while (std::chrono::steady_clock::now() < deadline) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path entrance: No frame after transition.", stream); - if (current_dialogue.detect(*frame.frame) || current_battle.detect(*frame.frame)) { - trainer_seen = true; - break; - } - if (cave_visible(*frame.frame)) { - if (++confirmed >= 5) { - stream.log("[Ravaged Path entrance] Transition and indoor corridor confirmed.", COLOR_GREEN); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - return; - } - } else confirmed = 0; - pbf_wait(context, 100ms); - context.wait_for_all_requests(); + } + if (northbound && measured && x > 0 && previous_east && std::abs(previous_dx - measured_dx) < 1.0 / 384){ + blocked_east++; + }else{ + blocked_east = 0; + } + previous_east = northbound && measured && x > 0; + // Reuse this snapshot; no movement has been sent since it. + if (previous_east){ + previous_dx = measured_dx; + } + if (blocked_east >= 3){ + if (++detours > 2){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 204 north approach: Tyler obstruction did not clear.", + stream); + } + stream.log("[Route 204 sign] Eastward movement blocked; passing below defeated Tyler."); + detour = true; + blocked_east = 0; + previous_east = false; + } + if (x != 0 || y != 0 || detour){ + arrival.reset(); + } + // Observe throughout each correction and its settle period, + // so an early next-trainer trigger cancels remaining movement. + if (watch_route204_trainer_event( + stream, context, + [x, y, duration, detour](ProControllerContext& ctx){ + if (detour){ + pbf_move_left_joystick(ctx, {0, -1}, 180ms, 0ms); + pbf_move_left_joystick(ctx, {1, 0}, 400ms, 0ms); + pbf_move_left_joystick(ctx, {0, 1}, 180ms, 0ms); + pbf_wait(ctx, 500ms); + }else if (x != 0 || y != 0){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 500ms); + }else{ + pbf_wait(ctx, duration); } - if (trainer_seen) handle_trainer(); - arrival.reset(); - position_confirmations = 0; - missing_since = std::chrono::steady_clock::now(); + }, + "Route 204 sign alignment")){ + return true; + } + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: Position could not be confirmed.", stream); +} + +static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + context.wait_for_all_requests(); + // The cave's brown corridor and black map void distinguish an + // indoor return from a battle fade or an outdoor scene. All ROIs + // are normalized and visible without labels in the overlays. + const ImageFloatBox floor_left{0.42, 0.35, 0.08, 0.10}; + const ImageFloatBox floor_right{0.52, 0.35, 0.08, 0.10}; + const ImageFloatBox map_void{0.15, 0.85, 0.10, 0.08}; + auto cave_visible = [&](const ImageRGB32& frame){ + for (const auto& box : {floor_left, floor_right}){ + const auto stats = image_stats(extract_box_reference(frame, box)); + if (stats.average.r < stats.average.g + 12 || stats.average.g < stats.average.b + 12){ + return false; + } + } + const auto empty = image_stats(extract_box_reference(frame, map_void)); + return empty.average.r < 20 && empty.average.g < 20 && empty.average.b < 20 && empty.stddev.sum() < 30; + }; + auto handle_trainer = [&] { + stream.log("[Ravaged Path approach] Lass Samantha triggered; completing battle before continuing."); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + Route204ArrivalTracker arrival; + size_t position_confirmations = 0; + auto missing_since = std::chrono::steady_clock::now(); + bool initial_approach = true; + size_t acquisition_pulses = 0; + size_t blocked_north = 0; + double previous_north_dy = 1; + bool previous_coarse_north = false; + for (size_t attempt = 0; attempt < 120; attempt++){ + ShortDialogWatcher dialogue(COLOR_YELLOW); + BattleMenuWatcher battle(BattleType::TRAINER, COLOR_GREEN); + BlackScreenWatcher entrance; + VideoOverlaySet overlays(stream.overlay()); + overlays.add(COLOR_CYAN, floor_left); + overlays.add(COLOR_CYAN, floor_right); + overlays.add(COLOR_CYAN, map_void); + const auto before = stream.video().snapshot(); + if (!before){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path entrance: No video frame.", stream); + } + ShortDialogDetector current_dialogue; + BattleMenuDetector current_battle(BattleType::TRAINER); + if (current_dialogue.detect(*before.frame) || current_battle.detect(*before.frame)){ + handle_trainer(); + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + continue; + } + double x = 0, y = 0; + Milliseconds duration = 250ms; + if (initial_approach){ + // Retain the northbound approach from Tyler. Subsequent + // movement is landmark-guided, without the old right sweep. + initial_approach = false; + y = 1; + duration = 2400ms; + }else{ + auto result = match_ravaged_path_south_entrance(*before.frame); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path entrance: Could not confirm entry after bounded approach.", stream); + stream.log("[Ravaged Path locator] " + landmark.name + " score=" + std::to_string(landmark.score) + + ", margin=" + std::to_string(landmark.margin) + + ", accepted=" + std::to_string(landmark.accepted) + ", offset=(" + + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } - - static void exit_ravaged_path_to_route204(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - stream.log("[Ravaged Path north exit] Right wall, lower wall, then left/down search."); - // Observe the entire approach: reaching the exit during either - // wall movement must cancel the remaining queued commands. - WhiteScreenWatcher white; - const int result=run_until(stream,context, - [](ProControllerContext& ctx) { - // Deliberate overtravel establishes the walls despite - // variation in the position after Rock Smash. - pbf_move_left_joystick(ctx,{1,0},3000ms,100ms); - pbf_move_left_joystick(ctx,{0,-1},1800ms,100ms); - for (size_t attempt=0; attempt<30; attempt++) { - pbf_move_left_joystick(ctx,{-1,0},160ms,80ms); - pbf_move_left_joystick(ctx,{0,-1},120ms,80ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx,1500ms); - },{{white}}); - if (result!=0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path north exit: No white transition during left/down search.",stream); - context.wait_for_all_requests(); - stream.log("[Ravaged Path north exit] White transition detected; movement stopped."); - WhiteScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8},500,10, - WhiteScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,15s,{{visible}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path north exit: White transition did not finish.",stream); - pbf_wait(context,700ms); - context.wait_for_all_requests(); + const auto now = std::chrono::steady_clock::now(); + auto fixed = result; + fixed.landmarks.resize(2); + // Cave entry needs tighter horizontal alignment than the + // standing area beside the Route 204 sign. + bool horizontal_ready = true; + for (const auto& anchor : fixed.landmarks){ + if (anchor.accepted && std::abs(anchor.dx) > 1.0 / 64){ + horizontal_ready = false; + } } - - void roark_to_floaroma(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 550ms, 0ms); - pbf_wait(context, 1046ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 2000ms, 0ms); - pbf_wait(context, 288ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 250ms, 0ms); - pbf_wait(context, 1009ms); - align_to_oreburgh_mart(stream, context); - pbf_wait(context, 1009ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 1128ms, 0ms); - pbf_wait(context, 2924ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 885ms, 0ms); - buy_items(stream, context, { {ShopItem::Repel, BUY_MAX} }); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1193ms, 0ms); - pbf_wait(context, 2226ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 2544ms, 0ms); - pbf_wait(context, 753ms); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 464ms, 0ms); - pbf_wait(context, 519ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1467ms, 0ms); - pbf_wait(context, 2816ms); - // Barry conversation - wait_for_dialogue(stream, context, "Barry conversation"); - context.wait_for_all_requests(); - - ShortDialogDetector dialogue(COLOR_YELLOW); - const auto started = std::chrono::steady_clock::now(); - auto absent_since = started; - - while (true) { - const auto frame = stream.video().snapshot(); - if (!frame) { + if (!horizontal_ready){ + arrival.reset(); + } + const bool arrived = horizontal_ready && arrival.observe(fixed, now); + if (arrived){ + if (++position_confirmations >= 3){ + stream.log("[Ravaged Path locator] Entrance position confirmed; moving north into cave.", + COLOR_GREEN); + y = 1; + duration = 1200ms; + } + }else if (arrival.wait_for_shadow(now)){ + position_confirmations = 0; + stream.log("[Ravaged Path locator] Holding still for shadowed landmark."); + }else if (result.located){ + missing_since = now; + position_confirmations = 0; + // Escape the upper-right alcove vertically before + // moving left. Near the doorway, correct horizontal first. + if (std::abs(result.dy) > 1.0 / 36){ + y = result.dy > 0 ? -1.0 : 1.0; + }else if (std::abs(result.dx) > 1.0 / 64){ + x = result.dx > 0 ? 1.0 : -1.0; + } + duration = 80ms; + if (result.coarse && y > 0 && previous_coarse_north && + std::abs(result.dy - previous_north_dy) < 1.0 / 384){ + blocked_north++; + }else{ + blocked_north = 0; + } + previous_north_dy = result.dy; + previous_coarse_north = result.coarse && y > 0; + if (blocked_north >= 4){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Northward coarse acquisition blocked; refusing repeated movement.", + stream); + } + if (result.coarse && x == 0 && y == 0){ + // After the stationary shadow wait, correct a + // small height error that may still change the + // sign's perspective enough to prevent matching. + if (std::abs(result.dy) <= 1.0 / 72){ OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - "Barry conversation: No video frame.", stream - ); + "Ravaged Path locator: Near entrance, but sign remained shadowed; refusing blind correction.", + stream); } - - const auto now = std::chrono::steady_clock::now(); - if (now - started >= 180s) { + y = result.dy < 0 ? 1.0 : -1.0; + duration = 40ms; + } + if (y < 0){ + // Fixed entrance geometry predicts the lass's + // feet when she is shadowed/off-screen. A visible + // lass may tighten this limit, never relax it. + double boundary = 930.0 / 1080 + result.dy; + if (result.landmarks.size() > 2 && result.landmarks[2].accepted){ + boundary = std::min(boundary, result.landmarks[2].found.y + result.landmarks[2].found.height); + } + // Leave >12% screen height above her before any + // short down pulse. No blind southward search. + if (boundary - 0.55 <= 0.12){ + y = 0; + stream.log("[Ravaged Path locator] Downward correction blocked by lass boundary.", COLOR_RED); OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - "Barry conversation: Dialogue did not finish.", stream - ); - } - - if (dialogue.detect(*frame.frame)) { - absent_since = now; - } - else if (now - absent_since >= 2s) { - break; + "Ravaged Path locator: Southern safety boundary reached.", stream); } - - // Keep mashing through pauses between dialogue boxes. - pbf_mash_button(context, BUTTON_B, 100ms); - context.wait_for_all_requests(); - } - - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 282ms, 0ms); - pbf_wait(context, 483ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 218ms, 0ms); - pbf_wait(context, 482ms); - enter_oreburgh_gate_from_city(stream, context); - activate_repel(stream, context, 5); - exit_oreburgh_gate_to_route203(stream, context); - // Align north, then go only far enough west to jump the ledge. - // Avoid descending the upper stairs and climbing straight back up. - pbf_move_left_joystick(context, {0.0, 1.0}, 600ms, 0ms); - pbf_wait(context, 400ms); - pbf_move_left_joystick(context, {-1.0, 0.0}, 2100ms, 0ms); - pbf_wait(context, 300ms); - pbf_move_left_joystick(context, {0.0, -1.0}, 1700ms, 0ms); - pbf_wait(context, 500ms); - // Cross the lower stairs at the offset needed for the next north leg. - // Combine the old left/right correction and the two upward moves. - pbf_move_left_joystick(context, {-1.0, 0.0}, 850ms, 0ms); - pbf_wait(context, 150ms); - pbf_move_left_joystick(context, {0.0, 1.0}, 1800ms, 0ms); - pbf_wait(context, 150ms); - pbf_move_left_joystick(context, {-1.0, 0.0}, 1400ms, 0ms); - pbf_wait(context, 100ms); - pbf_move_left_joystick(context, {0.0, -1.0}, 1000ms, 0ms); - pbf_wait(context, 100ms); - pbf_move_left_joystick(context, {-1.0, 0.0}, 1600ms, 0ms); - pbf_wait(context, 600ms); - // Exhaust the active Repel and decline renewal before the safe - // Jubilife approach, story battle, and Pokemon Center sequence. - force_repel_expiry(stream, context, RepelRenewalChoice::NO); - // One clock setup covers this whole new locator strip: street, - // Galactic plaza approach, and the post-battle return. Existing - // locators elsewhere keep their current time-independent behavior. - stream.log("[Jubilife locator setup] Setting 13:00 once for the street/plaza locator strip; preserving the date."); - DateTime locator_time; - locator_time.hour = 13; - locator_time.minute = 0; - locator_time.second = 0; - set_time_fast(env, context, locator_time, true); - pbf_wait(context, 5000ms); - context.wait_for_all_requests(); - // Stop east of the TV-station overshoot seen in the recording. - // Acquire/correct the street lane before moving north; the locator - // now supplies vertical travel instead of the blind 2579ms up move. - pbf_move_left_joystick(context, {-1.0, 0.0}, 3700ms, 0ms); - pbf_wait(context, 850ms); - align_to_overworld_landmarks(stream, context, match_jubilife_street_approach, - "Jubilife street alignment", false, true); - // Stop the upward approach if the story dialogue starts early. - ShortDialogWatcher plaza_story(COLOR_YELLOW); - run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0.0, 1.0}, 2600ms, 0ms); - pbf_wait(ctx, 250ms); - }, {{plaza_story}}); - align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, "Jubilife plaza alignment", true); - approach_galactic_dialogue(stream, context); - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - clear_post_battle_dialogue(stream, context); - pbf_wait(context, 2000ms); - clear_post_battle_dialogue(stream, context); - // Rowan and the Poketch president leave the player north of the - // reference. Bring the plaza landmarks back into view first. - stream.log("[Galactic return] Dialogue cleared; short downward return before reacquiring the plaza."); - pbf_move_left_joystick(context, {0.0, -1.0}, 600ms, 0ms); - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, - "Jubilife post-battle plaza alignment"); - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 5141ms, 0ms); - pbf_wait(context, 749ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 868ms, 0ms); - pbf_wait(context, 1073ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 431ms, 0ms); - pbf_wait(context, 3099ms); - // Stop outside: the old additional 1028ms up move sometimes - // entered the building before exterior alignment even started. - // Repel is inactive here; no expiry search is needed before the Center. - align_and_enter_jubilife_center(stream, context); - if (!heal_pokemon(stream, context, "Jubilife")) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Jubilife Center: Healing/exit did not complete.", stream); - - pbf_move_left_joystick(context, { 0.000000, -1.000000 }, 1440ms, 0ms); - pbf_wait(context, 2259ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 765ms, 0ms); - pbf_wait(context, 439ms); - const bool missed_trainer = !approach_route204_female_trainer(stream, context); - stream.log("[Route 204 female trainer] missed_trainer=" + std::to_string(missed_trainer)); - if (missed_trainer) recover_route204_female_trainer(stream, context); - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - clear_post_battle_dialogue(stream, context); - // Resume encounter protection after the Center and the following battle. - if (!activate_repel(stream, context, 6)) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Post-Center battle: Could not activate a new Repel from the Bag.", stream); - } - context.wait_for_all_requests(); - const bool next_trainer_started = align_to_route204_sign(stream, context); - if (!next_trainer_started) { - const bool triggered = watch_route204_trainer_event(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 1200ms, 0ms); - pbf_wait(ctx, 2500ms); - }, "Route 204 next trainer approach"); - if (!triggered) recover_route204_youngster(stream, context); - } - // An early locator interruption still has to complete combat; - // it must never fall through into overworld movement mid-battle. - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - clear_post_battle_dialogue(stream, context); - // Keep the recently activated Repel through Ravaged Path. - // Reacquire west of the sign after Tyler before moving north; - // do not spend its remaining steps on an early expiry search. - bool northbound_aligned=false; - for (size_t recovery=0; recovery<3; recovery++) { - if (!align_to_route204_sign(stream,context,true)) {northbound_aligned=true;break;} - wait_for_trainer_battle_menu(stream,context); - Barry_1_fight(env,stream,context); - clear_post_battle_dialogue(stream,context); - } - if (!northbound_aligned) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north approach: Trainer interruptions exhausted.",stream); - enter_ravaged_path_from_route204(env, stream, context); - //cave navigation - - // Ravaged Path -> Floaroma -> Valley Windworks -> Mars. - // First-pass route from ravagedpath.txt and the 2026-10-13 - // 19-48-08 recording. Timings need live route validation. - auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms) { - pbf_move_left_joystick(context, {x, y}, duration, 0ms); - pbf_wait(context, settle); - context.wait_for_all_requests(); - }; - auto battle = [&](const std::string& label) { - stream.log("[Segment 02 battle] " + label); - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - clear_post_battle_dialogue(stream, context); - }; - // Break the blocking rock using the existing HM confirmation helper. - move(0, 1, 1100ms, 900ms); - move(1, 0, 600ms); - use_HM(stream, context, "Rock Smash in Ravaged Path"); - context.wait_for_all_requests(); - clear_post_battle_dialogue(stream, context); - // Failed frames stop against the remaining center rocks. Pass - // above their collision row before the eastward exit approach. - move(0,1,200ms); - exit_ravaged_path_to_route204(stream,context); - move(0, -1, 350ms, 500ms); - - // Resolve the current Repel before searching for Teighler so - // its expiry prompt cannot interrupt trainer acquisition. - // Renew here to protect the remaining route to Floaroma. - force_repel_expiry(stream, context, RepelRenewalChoice::YES); - - // Route 204 north: go around the cliff to Aroma Lady Teighler. - move(-1, 0, 1300ms); - move(0, 1, 1300ms, 650ms); - // Timed approach: get onto the alcove's left edge, descend - // to its bottom ledge, then cross right through her sightline. - // Watch the entire approach so an early trigger stops movement. - if (!watch_route204_trainer_event(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {1, 0}, 600ms, 100ms); - pbf_move_left_joystick(ctx, {0, -1}, 600ms, 100ms); - pbf_move_left_joystick(ctx, {1, 0}, 1000ms, 0ms); - pbf_wait(ctx, 3000ms); - }, "Aroma Lady Teighler timed ledge approach")) { - recover_teighler_trigger(stream, context); } - stream.log("[Segment 02 battle] Aroma Lady Teighler: move slot 2."); - wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_move_slot(stream,context,2,"Aroma Lady Teighler"); - clear_post_battle_dialogue(stream,context); - - // Omit the Awakening. The grass north of Teighler can trigger - // the Bug Catcher: finish that battle before any Center locator. - // Small watched steps preserve the remaining route if he interrupts. - size_t northbound_battles = 0; - ShortDialogDetector northbound_dialogue(COLOR_YELLOW); - BattleMenuDetector northbound_menu(BattleType::TRAINER, COLOR_GREEN); - auto northbound_move = [&](double x, double y, Milliseconds remaining, bool stop_after_battle = false) { - while (remaining > 0ms) { - const Milliseconds step = remaining > 200ms ? 200ms : remaining; - // A dialogue can appear at the end of the previous - // short step. Check it directly before restarting the - // watcher's 250ms confirmation window or sending input. - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire( + }else{ + position_confirmations = 0; + if (now - missing_since > 10s){ + // Failed recording: still beside the Route 204 + // sign, blocked below the northbound approach. + // Only this recognizable route authorizes search. + const auto route = match_route204_sign(*before.frame); + const bool known_route = !route.landmarks.empty() && route.landmarks[0].accepted && + route.landmarks[0].score >= .94 && route.landmarks[0].margin >= .30; + if (known_route && acquisition_pulses < 8){ + const bool sidestep = acquisition_pulses++ % 2 == 0; + x = sidestep ? -1.0 : 0.0; + y = sidestep ? 0.0 : 1.0; + duration = sidestep ? 150ms : 500ms; + stream.log("[Ravaged Path locator] Recognized Route 204 sign; bounded left/north acquisition."); + }else{ + OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 northbound: No video frame.", stream); - const bool interrupted = northbound_dialogue.detect(*frame.frame) || - northbound_menu.detect(*frame.frame) || - watch_route204_trainer_event(stream, context, - [x,y,step](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {x,y}, step, 0ms); - }, "Route 204 northbound movement"); - if (interrupted) { - if (++northbound_battles > 2) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 northbound: Too many trainer interruptions.", stream); - battle("Route 204 north Bug Catcher"); - // The sign locator establishes the next turn after this - // battle; do not finish an obsolete northward timer. - if (stop_after_battle) return; - // Replay only this possibly interrupted short step, - // never the complete pre-battle travel duration. - } else remaining -= step; + "Ravaged Path locator: Landmarks unavailable after bounded acquisition.", stream); } - }; - northbound_move(1, 0, 2000ms); // Reach the right wall. - northbound_move(0, 1, 450ms); // Clear the corner above it. - northbound_move(1, 0, 1200ms); // Move into the northbound lane. - // Restore the north/west turns from the successful manual - // recording. The former 3200ms up stopped at the Bug Catcher, - // well short of town, and omitted the flower-bed detour. - northbound_move(0, 1, 2200ms, true); - // Locate left to the sign after the Bug Catcher, or proceed - // directly if skipped. Do not seek out a missed trainer. - for (size_t recovery=0; align_to_route204_floaroma_turn(stream,context); recovery++) { - if (recovery>=2) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: Too many trainer interruptions.", stream); - battle("Route 204 north Bug Catcher"); } - northbound_move(-1, 0, 1100ms); - northbound_move(0, 1, 150ms); - northbound_move(-1, 0, 1200ms); - northbound_move(-1, 0, 800ms); - // Continue through Floaroma's southern fence opening before - // turning east. The shorter approach stopped against the fence. - stream.log("[Floaroma approach] Moving farther north before Center approach."); - northbound_move(0, 1, 2700ms); - // Expire the Repel in the town approach lane before moving - // toward the Center. Alignment corrects any partial final step. - force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); - establish_floaroma_house_exit(stream,context); - context.wait_for_all_requests(); - stream.log("[Segment 02] Floaroma house exit established; ready for town checkpoint.", COLOR_GREEN); } - - static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context) { - auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms) { - pbf_move_left_joystick(context, {x, y}, duration, 0ms); - pbf_wait(context, settle); - context.wait_for_all_requests(); - }; - auto transition = [&](double x, double y, Milliseconds duration, const std::string& label) { - context.wait_for_all_requests(); - stream.log("[Route transition] " + label); - BlackScreenWatcher black; - WhiteScreenWatcher white; - const int ret = run_until(stream, context, - [x, y, duration](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); - pbf_wait(ctx, 1500ms); - // Bounded doorway correction if the recorded move stops short. - for (size_t pulse = 0; pulse < 10; pulse++) { - pbf_move_left_joystick(ctx, {x, y}, 100ms, 100ms); - } - }, {{black}, {white}}); - if (ret < 0) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Entrance/exit transition not detected.", stream); - context.wait_for_all_requests(); - BlackScreenDetector black_frame; - WhiteScreenDetector white_frame; - // Accept either fade type, then require a stable return before moving. - const auto deadline = std::chrono::steady_clock::now() + 15s; - auto clear_since = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() < deadline) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame during transition.", stream); - const auto now = std::chrono::steady_clock::now(); - if (black_frame.detect(*frame.frame) || white_frame.detect(*frame.frame)) clear_since = now; - else if (now - clear_since >= 500ms) { - pbf_wait(context, 700ms); - context.wait_for_all_requests(); - return; - } - pbf_wait(context, 100ms); - context.wait_for_all_requests(); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not finish.", stream); - }; - auto battle = [&](const std::string& label) { - stream.log("[Segment 02 battle] " + label); - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - clear_post_battle_dialogue(stream, context); - }; - auto talk = [&](const std::string& label, Button response = BUTTON_B) { - stream.log("[Segment 02 dialogue] " + label); - pbf_press_button(context, BUTTON_A, 150ms, 500ms); - context.wait_for_all_requests(); - wait_for_dialogue(stream, context, label, 5000ms); - clear_post_battle_dialogue(stream, context, response); - }; - - // The house exit fixes the start pose. Use the median of the - // twenty successful recorded approaches: 24 south taps, then - // nine east taps. Retain the recorded tap cadence; replacing - // it with one continuous hold changes acceleration/step timing. - stream.log("[Floaroma Center] Blind south/east approach from the house exit."); - for (size_t step=0;step<24;step++) move(0,-1,80ms,250ms); - for (size_t step=0;step<9;step++) move(1,0,80ms,500ms); - transition(0, 1, 950ms, "Floaroma Pokemon Center entrance"); - // heal_pokemon includes a watched exit; do not exit a second time. - if (!heal_pokemon(stream, context, "Floaroma")) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma Center: Healing/exit did not complete.", stream); - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - // Clear the Center doorway to the west before turning north, - // so we cannot walk back through the door. Repel is already off. - move(-1, 0, 1500ms); - - // Meet the girl on Route 205, then return for the Works Key. - move(0, 1, 1000ms); - // Include the westward clearance and the former 650 ms east leg. - move(1, 0, 6550ms, 900ms); - talk("Route 205 girl requesting help for her father"); - move(-1, 0, 4500ms, 500ms); - // Repel is already inactive; omit the recorded renewal A presses. - move(-1, 0, 3000ms); - move(0, 1, 2300ms); - // The westward overtravel establishes the tree wall. The - // recording then stops west of the Meadow gap: move right - // into that opening instead of pushing farther left. - move(1, 0, 300ms); - transition(0, 1, 1000ms, "Floaroma Meadow entrance"); - move(0, 1, 1100ms); - pbf_press_button(context, BUTTON_A, 150ms, 500ms); - stream.log("[Segment 02 battle] Floaroma Meadow first Galactic grunt."); - wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); - // The second grunt starts another battle automatically. Do not - // wait for quiet overworld or send movement between the fights. - battle("Floaroma Meadow second Galactic grunt"); - talk("Floaroma Meadow honey seller and Works Key"); - if (!clear_new_item_flags(stream,context,6)) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Floaroma Meadow: Bag NEW flags did not clear.",stream); - transition(0, -1, 1000ms, "Floaroma Meadow exit"); - move(0, -1, 2600ms); - - // No Repel is needed on the town/road route to Valley Windworks. - move(1, 0, 4200ms); - move(0, -1, 200ms); - move(1, 0, 5500ms); - move(0, -1, 500ms); - move(1, 0, 1200ms); - align_to_windworks_sign(stream, context); - // From the sign-side pose, the building wall fixes north/south - // position and the grunt blocks the subsequent eastward travel. - move(0, 1, 2000ms); - move(1, 0, 1500ms); - pbf_press_button(context, BUTTON_A, 150ms, 500ms); - battle("Valley Windworks door Galactic grunt"); - - // Probe the door after the grunt retreats. Cancel all remaining - // movement as soon as dialogue appears, preserving doorway alignment. - bool door_dialogue=false; - for (size_t attempt=0; attempt<20 && !door_dialogue; attempt++) { - ShortDialogWatcher dialogue(COLOR_YELLOW); - door_dialogue=run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},100ms,0ms); - pbf_press_button(ctx,BUTTON_A,150ms,400ms); - pbf_move_left_joystick(ctx,{1,0},40ms,0ms); - pbf_wait(ctx,250ms); - },{{dialogue}})>=0; + } + if (x != 0 || y != 0){ + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + } + const int ret = run_until(stream, context, + [x, y, duration](ProControllerContext& ctx){ + if (x != 0 || y != 0){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 500ms); + }else{ + pbf_wait(ctx, duration); + } + }, + {{entrance}, {dialogue}, {battle}}); + context.wait_for_all_requests(); + if (ret == 1 || ret == 2){ + handle_trainer(); + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + continue; + } + if (ret != 0){ + continue; + } + // A trainer transition can also go black. Require a stable + // cave scene, or route the detected battle through combat. + const auto deadline = std::chrono::steady_clock::now() + 15s; + size_t confirmed = 0; + bool trainer_seen = false; + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path entrance: No frame after transition.", stream); + } + if (current_dialogue.detect(*frame.frame) || current_battle.detect(*frame.frame)){ + trainer_seen = true; + break; + } + if (cave_visible(*frame.frame)){ + if (++confirmed >= 5){ + stream.log("[Ravaged Path entrance] Transition and indoor corridor confirmed.", COLOR_GREEN); + pbf_wait(context, 700ms); context.wait_for_all_requests(); + return; } - if (!door_dialogue) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Valley Windworks: Door dialogue not detected after bounded up/A/right probes.",stream); - stream.log("[Windworks door] Dialogue detected; confirm Works Key for two seconds."); - pbf_mash_button(context,BUTTON_A,2000ms); - context.wait_for_all_requests(); - // Interaction reaches the door from west of its entrance lane. - // One additional recorded nudge centers the subsequent northward entry. - move(1, 0, 40ms); - transition(0, 1, 1000ms, "Valley Windworks entrance"); - clear_post_battle_dialogue(stream, context); // Entrance warning from the grunt. - move(-1, 0, 2000ms); // Combined 1210ms + 817ms, separated by only 5ms. - bool fought_grunt=watch_route204_trainer_event(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0, 1}, 1250ms, 0ms); - pbf_wait(ctx, 2500ms); - }, "Valley Windworks interior grunt approach"); - if (fought_grunt) battle("Valley Windworks interior Galactic grunt"); - auto grunt_position=[&]() { - OverworldMatchResult position; - for (size_t attempt=0;attempt<12;attempt++) { - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if (frame) position=match_windworks_interior_grunt(*frame.frame); - if (position.located) return position; - pbf_wait(context,250ms); - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks interior: Could not determine player position relative to the grunt.",stream); - return position; - }; - auto position=grunt_position(); - if (!fought_grunt && position.dy<=.025) { - stream.log("[Windworks interior] Below/level with grunt; recover north and interact."); - for (size_t attempt=0;attempt<20 && !fought_grunt;attempt++) { - fought_grunt=watch_route204_trainer_event(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},100ms,0ms); - pbf_press_button(ctx,BUTTON_A,150ms,500ms); - },"Windworks interior below-grunt recovery"); - } - if (!fought_grunt) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks interior: North/A recovery did not trigger the grunt battle.",stream); - battle("Valley Windworks interior Galactic grunt (below recovery)"); - } - auto align_above_grunt=[&]() { - // Re-establish the west wall after a below-grunt interaction, - // then climb past his row before crossing the partition gap. - move(-1,0,1000ms); - bool above=false; - for (size_t attempt=0;attempt<20;attempt++) { - position=grunt_position(); - if (position.dy>.025) {above=true;break;} - move(0,1,80ms); - } - if (!above) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks interior: Could not get above the defeated grunt.",stream); - }; - if (fought_grunt) align_above_grunt(); - else stream.log("[Windworks interior] Above grunt; try the rightward crossing for his battle."); - const bool crossing_event=watch_route204_trainer_event(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},750ms,0ms); - pbf_wait(ctx,2500ms); - },"Windworks interior upper crossing"); - if (crossing_event) { - if (!fought_grunt) battle("Valley Windworks interior Galactic grunt (upper crossing)"); - else clear_post_battle_dialogue(stream,context); - // The event cancelled travel at an unknown point. Reacquire - // the west wall and upper row, then cross from that known pose. - align_above_grunt(); - move(1,0,750ms,450ms); - } else if (!fought_grunt) { - stream.log("[Windworks interior] Rightward crossing completed without a battle; grunt missed, continuing to Mars."); - } + }else{ + confirmed = 0; + } + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + if (trainer_seen){ + handle_trainer(); + } + arrival.reset(); + position_confirmations = 0; + missing_since = std::chrono::steady_clock::now(); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path entrance: Could not confirm entry after bounded approach.", + stream); +} - // The upper crossing above replaces the old blind north/right - // pair. Retain the remaining turns between the machines. - move(0, -1, 300ms, 0ms); - move(1, 0, 900ms); - move(0, 1, 250ms); - move(1, 0, 550ms, 400ms); - move(0, 1, 200ms); - move(1, 0, 750ms, 500ms); - pbf_press_button(context, BUTTON_A, 150ms, 500ms); - battle("Commander Mars"); - clear_post_battle_dialogue(stream, context); // Father/daughter reunion after Mars leaves. - stream.log("[Segment 02] Mars defeated; post-battle dialogue cleared.", COLOR_GREEN); +static void exit_ravaged_path_to_route204(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + stream.log("[Ravaged Path north exit] Right wall, lower wall, then left/down search."); + // Observe the entire approach: reaching the exit during either + // wall movement must cancel the remaining queued commands. + WhiteScreenWatcher white; + const int result = run_until(stream, context, + [](ProControllerContext& ctx){ + // Deliberate overtravel establishes the walls despite + // variation in the position after Rock Smash. + pbf_move_left_joystick(ctx, {1, 0}, 3000ms, 100ms); + pbf_move_left_joystick(ctx, {0, -1}, 1800ms, 100ms); + for (size_t attempt = 0; attempt < 30; attempt++){ + pbf_move_left_joystick(ctx, {-1, 0}, 160ms, 80ms); + pbf_move_left_joystick(ctx, {0, -1}, 120ms, 80ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx, 1500ms); + }, + {{white}}); + if (result != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path north exit: No white transition during left/down search.", + stream); + } + context.wait_for_all_requests(); + stream.log("[Ravaged Path north exit] White transition detected; movement stopped."); + WhiteScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path north exit: White transition did not finish.", stream); + } + pbf_wait(context, 700ms); + context.wait_for_all_requests(); +} + +void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + pbf_move_left_joystick(context, {1.000000, -0.000000}, 550ms, 0ms); + pbf_wait(context, 1046ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2000ms, 0ms); + pbf_wait(context, 288ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 250ms, 0ms); + pbf_wait(context, 1009ms); + align_to_oreburgh_mart(stream, context); + pbf_wait(context, 1009ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1128ms, 0ms); + pbf_wait(context, 2924ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 885ms, 0ms); + buy_items(stream, context, {{ShopItem::Repel, BUY_MAX}}); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1193ms, 0ms); + pbf_wait(context, 2226ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2544ms, 0ms); + pbf_wait(context, 753ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 464ms, 0ms); + pbf_wait(context, 519ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1467ms, 0ms); + pbf_wait(context, 2816ms); + // Barry conversation + wait_for_dialogue(stream, context, "Barry conversation"); + context.wait_for_all_requests(); + + ShortDialogDetector dialogue(COLOR_YELLOW); + const auto started = std::chrono::steady_clock::now(); + auto absent_since = started; + + while (true){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Barry conversation: No video frame.", stream); + } + const auto now = std::chrono::steady_clock::now(); + if (now - started >= 180s){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Barry conversation: Dialogue did not finish.", stream); + } + + if (dialogue.detect(*frame.frame)){ + absent_since = now; + }else if (now - absent_since >= 2s){ + break; + } + + // Keep mashing through pauses between dialogue boxes. + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 282ms, 0ms); + pbf_wait(context, 483ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 218ms, 0ms); + pbf_wait(context, 482ms); + enter_oreburgh_gate_from_city(stream, context); + activate_repel(stream, context, 5); + exit_oreburgh_gate_to_route203(stream, context); + // Align north, then go only far enough west to jump the ledge. + // Avoid descending the upper stairs and climbing straight back up. + pbf_move_left_joystick(context, {0.0, 1.0}, 600ms, 0ms); + pbf_wait(context, 400ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 2100ms, 0ms); + pbf_wait(context, 300ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 1700ms, 0ms); + pbf_wait(context, 500ms); + // Cross the lower stairs at the offset needed for the next north leg. + // Combine the old left/right correction and the two upward moves. + pbf_move_left_joystick(context, {-1.0, 0.0}, 850ms, 0ms); + pbf_wait(context, 150ms); + pbf_move_left_joystick(context, {0.0, 1.0}, 1800ms, 0ms); + pbf_wait(context, 150ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 1400ms, 0ms); + pbf_wait(context, 100ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 1000ms, 0ms); + pbf_wait(context, 100ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 1600ms, 0ms); + pbf_wait(context, 600ms); + // Exhaust the active Repel and decline renewal before the safe + // Jubilife approach, story battle, and Pokemon Center sequence. + force_repel_expiry(stream, context, RepelRenewalChoice::NO); + // One clock setup covers this whole new locator strip: street, + // Galactic plaza approach, and the post-battle return. Existing + // locators elsewhere keep their current time-independent behavior. + stream.log("[Jubilife locator setup] Setting 13:00 once for the street/plaza locator strip; preserving the date."); + DateTime locator_time; + locator_time.hour = 13; + locator_time.minute = 0; + locator_time.second = 0; + set_time_fast(env, context, locator_time, true); + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + // Stop east of the TV-station overshoot seen in the recording. + // Acquire/correct the street lane before moving north; the locator + // now supplies vertical travel instead of the blind 2579ms up move. + pbf_move_left_joystick(context, {-1.0, 0.0}, 3700ms, 0ms); + pbf_wait(context, 850ms); + align_to_overworld_landmarks(stream, context, match_jubilife_street_approach, "Jubilife street alignment", false, + true); + // Stop the upward approach if the story dialogue starts early. + ShortDialogWatcher plaza_story(COLOR_YELLOW); + run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 2600ms, 0ms); + pbf_wait(ctx, 250ms); + }, + {{plaza_story}}); + align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, "Jubilife plaza alignment", true); + approach_galactic_dialogue(stream, context); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + pbf_wait(context, 2000ms); + clear_post_battle_dialogue(stream, context); + // Rowan and the Poketch president leave the player north of the + // reference. Bring the plaza landmarks back into view first. + stream.log("[Galactic return] Dialogue cleared; short downward return before reacquiring the plaza."); + pbf_move_left_joystick(context, {0.0, -1.0}, 600ms, 0ms); + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, + "Jubilife post-battle plaza alignment"); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 5141ms, 0ms); + pbf_wait(context, 749ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 868ms, 0ms); + pbf_wait(context, 1073ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 431ms, 0ms); + pbf_wait(context, 3099ms); + // Stop outside: the old additional 1028ms up move sometimes + // entered the building before exterior alignment even started. + // Repel is inactive here; no expiry search is needed before the Center. + align_and_enter_jubilife_center(stream, context); + if (!heal_pokemon(stream, context, "Jubilife")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Jubilife Center: Healing/exit did not complete.", stream); + } + + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1440ms, 0ms); + pbf_wait(context, 2259ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 765ms, 0ms); + pbf_wait(context, 439ms); + const bool missed_trainer = !approach_route204_female_trainer(stream, context); + stream.log("[Route 204 female trainer] missed_trainer=" + std::to_string(missed_trainer)); + if (missed_trainer){ + recover_route204_female_trainer(stream, context); + } + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + // Resume encounter protection after the Center and the following battle. + if (!activate_repel(stream, context, 6)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Post-Center battle: Could not activate a new Repel from the Bag.", + stream); + } + context.wait_for_all_requests(); + const bool next_trainer_started = align_to_route204_sign(stream, context); + if (!next_trainer_started){ + const bool triggered = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 1200ms, 0ms); + pbf_wait(ctx, 2500ms); + }, + "Route 204 next trainer approach"); + if (!triggered){ + recover_route204_youngster(stream, context); + } + } + // An early locator interruption still has to complete combat; + // it must never fall through into overworld movement mid-battle. + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + // Keep the recently activated Repel through Ravaged Path. + // Reacquire west of the sign after Tyler before moving north; + // do not spend its remaining steps on an early expiry search. + bool northbound_aligned = false; + for (size_t recovery = 0; recovery < 3; recovery++){ + if (!align_to_route204_sign(stream, context, true)){ + northbound_aligned = true; + break; + } + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + } + if (!northbound_aligned){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 204 north approach: Trainer interruptions exhausted.", stream); + } + enter_ravaged_path_from_route204(env, stream, context); + //cave navigation + + // Ravaged Path -> Floaroma -> Valley Windworks -> Mars. + // First-pass route from ravagedpath.txt and the 2026-10-13 + // 19-48-08 recording. Timings need live route validation. + auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms){ + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, settle); + context.wait_for_all_requests(); + }; + auto battle = [&](const std::string& label){ + stream.log("[Segment 02 battle] " + label); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + // Break the blocking rock using the existing HM confirmation helper. + move(0, 1, 1100ms, 900ms); + move(1, 0, 600ms); + use_HM(stream, context, "Rock Smash in Ravaged Path"); + context.wait_for_all_requests(); + clear_post_battle_dialogue(stream, context); + // Failed frames stop against the remaining center rocks. Pass + // above their collision row before the eastward exit approach. + move(0, 1, 200ms); + exit_ravaged_path_to_route204(stream, context); + move(0, -1, 350ms, 500ms); + + // Resolve the current Repel before searching for Teighler so + // its expiry prompt cannot interrupt trainer acquisition. + // Renew here to protect the remaining route to Floaroma. + force_repel_expiry(stream, context, RepelRenewalChoice::YES); + + // Route 204 north: go around the cliff to Aroma Lady Teighler. + move(-1, 0, 1300ms); + move(0, 1, 1300ms, 650ms); + // Timed approach: get onto the alcove's left edge, descend + // to its bottom ledge, then cross right through her sightline. + // Watch the entire approach so an early trigger stops movement. + if (!watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 600ms, 100ms); + pbf_move_left_joystick(ctx, {0, -1}, 600ms, 100ms); + pbf_move_left_joystick(ctx, {1, 0}, 1000ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + "Aroma Lady Teighler timed ledge approach")){ + recover_teighler_trigger(stream, context); + } + stream.log("[Segment 02 battle] Aroma Lady Teighler: move slot 2."); + wait_for_trainer_battle_menu(stream, context); + fight_trainer_using_move_slot(stream, context, 2, "Aroma Lady Teighler"); + clear_post_battle_dialogue(stream, context); + + // Omit the Awakening. The grass north of Teighler can trigger + // the Bug Catcher: finish that battle before any Center locator. + // Small watched steps preserve the remaining route if he interrupts. + size_t northbound_battles = 0; + ShortDialogDetector northbound_dialogue(COLOR_YELLOW); + BattleMenuDetector northbound_menu(BattleType::TRAINER, COLOR_GREEN); + auto northbound_move = [&](double x, double y, Milliseconds remaining, bool stop_after_battle = false){ + while (remaining > 0ms){ + const Milliseconds step = remaining > 200ms ? 200ms : remaining; + // A dialogue can appear at the end of the previous + // short step. Check it directly before restarting the + // watcher's 250ms confirmation window or sending input. + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 northbound: No video frame.", stream); } - static void align_to_route205_marker(VideoStream& stream, ProControllerContext& context, bool forest) { - context.wait_for_all_requests(); - const std::string label=forest?"Route 205 forest entrance":"Route 205 bridge"; - size_t confirmed=0,missing=0; - bool acquired=false; - for (size_t attempt=0;attempt<80;attempt++) { - const auto frame=stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": No video frame.",stream); - const auto result=forest?match_route205_forest_entrance(*frame.frame):match_route205_bridge(*frame.frame); - if (result.at_target) { - acquired=true;missing=0; - if (++confirmed>=3) {stream.log("["+label+"] Sign position confirmed.",COLOR_GREEN);return;} - pbf_wait(context,150ms); - } else { - confirmed=0; - double x=0,y=1; - Milliseconds duration=80ms; - if (result.located) { - acquired=true;missing=0; - stream.log("["+label+"] Sign offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); - const bool horizontal=std::abs(result.dx)>1.0/50; - const double error=horizontal?std::abs(result.dx):std::abs(result.dy); - x=horizontal?(result.dx<0?-1:1):0; - y=horizontal?0:(result.dy>0?-1:1); - duration=error<.08?40ms:80ms; - } else if (++missing>12) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Sign unavailable during bounded acquisition.",stream); - } else if (acquired) { - // Never walk past a sign after losing a trusted match. - pbf_wait(context,250ms); - context.wait_for_all_requests(); - continue; - } - pbf_move_left_joystick(context,{x,y},duration,0ms); - pbf_wait(context,250ms); - } - context.wait_for_all_requests(); + const bool interrupted = northbound_dialogue.detect(*frame.frame) || northbound_menu.detect(*frame.frame) || + watch_route204_trainer_event( + stream, context, + [x, y, step](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, step, 0ms); + }, + "Route 204 northbound movement"); + if (interrupted){ + if (++northbound_battles > 2){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 204 northbound: Too many trainer interruptions.", + stream); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Sign position did not converge.",stream); + battle("Route 204 north Bug Catcher"); + // The sign locator establishes the next turn after this + // battle; do not finish an obsolete northward timer. + if (stop_after_battle){ + return; + } + // Replay only this possibly interrupted short step, + // never the complete pre-battle travel duration. + }else{ + remaining -= step; } + } + }; + northbound_move(1, 0, 2000ms); // Reach the right wall. + northbound_move(0, 1, 450ms); // Clear the corner above it. + northbound_move(1, 0, 1200ms); // Move into the northbound lane. + // Restore the north/west turns from the successful manual + // recording. The former 3200ms up stopped at the Bug Catcher, + // well short of town, and omitted the flower-bed detour. + northbound_move(0, 1, 2200ms, true); + // Locate left to the sign after the Bug Catcher, or proceed + // directly if skipped. Do not seek out a missed trainer. + for (size_t recovery = 0; align_to_route204_floaroma_turn(stream, context); recovery++){ + if (recovery >= 2){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 204 north turn: Too many trainer interruptions.", stream); + } + battle("Route 204 north Bug Catcher"); + } + northbound_move(-1, 0, 1100ms); + northbound_move(0, 1, 150ms); + northbound_move(-1, 0, 1200ms); + northbound_move(-1, 0, 800ms); + // Continue through Floaroma's southern fence opening before + // turning east. The shorter approach stopped against the fence. + stream.log("[Floaroma approach] Moving farther north before Center approach."); + northbound_move(0, 1, 2700ms); + // Expire the Repel in the town approach lane before moving + // toward the Center. Alignment corrects any partial final step. + force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); + establish_floaroma_house_exit(stream, context); + context.wait_for_all_requests(); + stream.log("[Segment 02] Floaroma house exit established; ready for town checkpoint.", COLOR_GREEN); +} - static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - size_t confirmed=0,missing=0; - for (size_t attempt=0;attempt<80;attempt++) { - const auto frame=stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 205 bank tree: No video frame.",stream); - const auto result=match_route205_bank_tree(*frame.frame); - if (!result.located) { - confirmed=0; - if (++missing>=12) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 205 bank tree: Tree and blue-hat child unavailable; holding position.",stream); - pbf_wait(context,250ms); - } else if (result.at_target) { - missing=0; - if (++confirmed>=3) {stream.log("[Route 205 bank tree] Position confirmed.",COLOR_GREEN);return;} - pbf_wait(context,150ms); - } else { - confirmed=0;missing=0; - stream.log("[Route 205 bank tree] Offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); - // Clear the north bridge lip before attempting to step west. - // Once at bank height, keep horizontal priority beside the tree. - const bool clear_bank=result.dy < -1.0/54; - const bool horizontal=!clear_bank && std::abs(result.dx)>1.0/96; - const double error=horizontal?std::abs(result.dx):std::abs(result.dy); - const double x=horizontal?(result.dx<0?-1:1):0; - const double y=horizontal?0:(result.dy>0?-1:1); - pbf_move_left_joystick(context,{x,y},error<.08?40ms:80ms,0ms); - pbf_wait(context,250ms); - } - context.wait_for_all_requests(); - } +static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms){ + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, settle); + context.wait_for_all_requests(); + }; + auto transition = [&](double x, double y, Milliseconds duration, const std::string& label){ + context.wait_for_all_requests(); + stream.log("[Route transition] " + label); + BlackScreenWatcher black; + WhiteScreenWatcher white; + const int ret = run_until(stream, context, + [x, y, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 1500ms); + // Bounded doorway correction if the recorded move stops short. + for (size_t pulse = 0; pulse < 10; pulse++){ + pbf_move_left_joystick(ctx, {x, y}, 100ms, 100ms); + } + }, + {{black}, {white}}); + if (ret < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Entrance/exit transition not detected.", stream); + } + context.wait_for_all_requests(); + BlackScreenDetector black_frame; + WhiteScreenDetector white_frame; + // Accept either fade type, then require a stable return before moving. + const auto deadline = std::chrono::steady_clock::now() + 15s; + auto clear_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 205 bank tree: Position did not converge.",stream); + label + ": No video frame during transition.", stream); + } + const auto now = std::chrono::steady_clock::now(); + if (black_frame.detect(*frame.frame) || white_frame.detect(*frame.frame)){ + clear_since = now; + }else if (now - clear_since >= 500ms){ + pbf_wait(context, 700ms); + context.wait_for_all_requests(); + return; + } + pbf_wait(context, 100ms); + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Transition did not finish.", stream); + }; + auto battle = [&](const std::string& label){ + stream.log("[Segment 02 battle] " + label); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + auto talk = [&](const std::string& label, Button response = BUTTON_B){ + stream.log("[Segment 02 dialogue] " + label); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, label, 5000ms); + clear_post_battle_dialogue(stream, context, response); + }; + + // The house exit fixes the start pose. Use the median of the + // twenty successful recorded approaches: 24 south taps, then + // nine east taps. Retain the recorded tap cadence; replacing + // it with one continuous hold changes acceleration/step timing. + stream.log("[Floaroma Center] Blind south/east approach from the house exit."); + for (size_t step = 0; step < 24; step++){ + move(0, -1, 80ms, 250ms); + } + for (size_t step = 0; step < 9; step++){ + move(1, 0, 80ms, 500ms); + } + transition(0, 1, 950ms, "Floaroma Pokemon Center entrance"); + // heal_pokemon includes a watched exit; do not exit a second time. + if (!heal_pokemon(stream, context, "Floaroma")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma Center: Healing/exit did not complete.", stream); + } + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + // Clear the Center doorway to the west before turning north, + // so we cannot walk back through the door. Repel is already off. + move(-1, 0, 1500ms); + + // Meet the girl on Route 205, then return for the Works Key. + move(0, 1, 1000ms); + // Include the westward clearance and the former 650 ms east leg. + move(1, 0, 6550ms, 900ms); + talk("Route 205 girl requesting help for her father"); + move(-1, 0, 4500ms, 500ms); + // Repel is already inactive; omit the recorded renewal A presses. + move(-1, 0, 3000ms); + move(0, 1, 2300ms); + // The westward overtravel establishes the tree wall. The + // recording then stops west of the Meadow gap: move right + // into that opening instead of pushing farther left. + move(1, 0, 300ms); + transition(0, 1, 1000ms, "Floaroma Meadow entrance"); + move(0, 1, 1100ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + stream.log("[Segment 02 battle] Floaroma Meadow first Galactic grunt."); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + // The second grunt starts another battle automatically. Do not + // wait for quiet overworld or send movement between the fights. + battle("Floaroma Meadow second Galactic grunt"); + talk("Floaroma Meadow honey seller and Works Key"); + if (!clear_new_item_flags(stream, context, 6)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Floaroma Meadow: Bag NEW flags did not clear.", stream); + } + transition(0, -1, 1000ms, "Floaroma Meadow exit"); + move(0, -1, 2600ms); + + // No Repel is needed on the town/road route to Valley Windworks. + move(1, 0, 4200ms); + move(0, -1, 200ms); + move(1, 0, 5500ms); + move(0, -1, 500ms); + move(1, 0, 1200ms); + align_to_windworks_sign(stream, context); + // From the sign-side pose, the building wall fixes north/south + // position and the grunt blocks the subsequent eastward travel. + move(0, 1, 2000ms); + move(1, 0, 1500ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + battle("Valley Windworks door Galactic grunt"); + + // Probe the door after the grunt retreats. Cancel all remaining + // movement as soon as dialogue appears, preserving doorway alignment. + bool door_dialogue = false; + for (size_t attempt = 0; attempt < 20 && !door_dialogue; attempt++){ + ShortDialogWatcher dialogue(COLOR_YELLOW); + door_dialogue = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 100ms, 0ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 400ms); + pbf_move_left_joystick(ctx, {1, 0}, 40ms, 0ms); + pbf_wait(ctx, 250ms); + }, + {{dialogue}}) >= 0; + context.wait_for_all_requests(); + } + if (!door_dialogue){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Valley Windworks: Door dialogue not detected after bounded up/A/right probes.", stream); + } + stream.log("[Windworks door] Dialogue detected; confirm Works Key for two seconds."); + pbf_mash_button(context, BUTTON_A, 2000ms); + context.wait_for_all_requests(); + // Interaction reaches the door from west of its entrance lane. + // One additional recorded nudge centers the subsequent northward entry. + move(1, 0, 40ms); + transition(0, 1, 1000ms, "Valley Windworks entrance"); + clear_post_battle_dialogue(stream, context); // Entrance warning from the grunt. + move(-1, 0, 2000ms); // Combined 1210ms + 817ms, separated by only 5ms. + bool fought_grunt = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 1250ms, 0ms); + pbf_wait(ctx, 2500ms); + }, + "Valley Windworks interior grunt approach"); + if (fought_grunt){ + battle("Valley Windworks interior Galactic grunt"); + } + auto grunt_position = [&](){ + OverworldMatchResult position; + for (size_t attempt = 0; attempt < 12; attempt++){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (frame){ + position = match_windworks_interior_grunt(*frame.frame); } + if (position.located){ + return position; + } + pbf_wait(context, 250ms); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: Could not determine player position relative to the grunt.", stream); + return position; + }; + auto position = grunt_position(); + if (!fought_grunt && position.dy <= .025){ + stream.log("[Windworks interior] Below/level with grunt; recover north and interact."); + for (size_t attempt = 0; attempt < 20 && !fought_grunt; attempt++){ + fought_grunt = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 100ms, 0ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + }, + "Windworks interior below-grunt recovery"); + } + if (!fought_grunt){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: North/A recovery did not trigger the grunt battle.", stream); + } + battle("Valley Windworks interior Galactic grunt (below recovery)"); + } + auto align_above_grunt = [&](){ + // Re-establish the west wall after a below-grunt interaction, + // then climb past his row before crossing the partition gap. + move(-1, 0, 1000ms); + bool above = false; + for (size_t attempt = 0; attempt < 20; attempt++){ + position = grunt_position(); + if (position.dy > .025){ + above = true; + break; + } + move(0, 1, 80ms); + } + if (!above){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: Could not get above the defeated grunt.", + stream); + } + }; + if (fought_grunt){ + align_above_grunt(); + }else{ + stream.log("[Windworks interior] Above grunt; try the rightward crossing for his battle."); + } + const bool crossing_event = watch_route204_trainer_event( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 750ms, 0ms); + pbf_wait(ctx, 2500ms); + }, + "Windworks interior upper crossing"); + if (crossing_event){ + if (!fought_grunt){ + battle("Valley Windworks interior Galactic grunt (upper crossing)"); + }else{ + clear_post_battle_dialogue(stream, context); + } + // The event cancelled travel at an unknown point. Reacquire + // the west wall and upper row, then cross from that known pose. + align_above_grunt(); + move(1, 0, 750ms, 450ms); + }else if (!fought_grunt){ + stream.log( + "[Windworks interior] Rightward crossing completed without a battle; grunt missed, continuing to Mars."); + } - static void align_route205_patrol_position(VideoStream& stream, ProControllerContext& context, - const std::function& match, const std::string& label) { - size_t confirmed=0,missing=0; - for (size_t attempt=0;attempt<100;attempt++) { - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": No video frame during patrol positioning.",stream); - const auto result=match(*frame.frame); - if (!result.located) { - confirmed=0; - if (++missing>=16) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Patrol landmarks unavailable; holding position.",stream); - pbf_wait(context,250ms); - } else if (result.at_target) { - missing=0; - if (++confirmed>=3) {stream.log("["+label+"] Patrol position confirmed.",COLOR_GREEN);return;} - pbf_wait(context,150ms); - } else { - confirmed=0;missing=0; - stream.log("["+label+"] Patrol offset=("+std::to_string(result.dx)+","+std::to_string(result.dy)+")"); - // Correct height first so the subsequent horizontal step - // stays on the stair landing rather than hitting its cliff. - const bool vertical=std::abs(result.dy)>1.0/54; - const double error=vertical?std::abs(result.dy):std::abs(result.dx); - pbf_move_left_joystick(context, - {vertical?0:(result.dx<0?-1.0:1.0),vertical?(result.dy>0?-1.0:1.0):0}, - error<.08?40ms:80ms,0ms); - pbf_wait(context,250ms); - } - } + // The upper crossing above replaces the old blind north/right + // pair. Retain the remaining turns between the machines. + move(0, -1, 300ms, 0ms); + move(1, 0, 900ms); + move(0, 1, 250ms); + move(1, 0, 550ms, 400ms); + move(0, 1, 200ms); + move(1, 0, 750ms, 500ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + battle("Commander Mars"); + clear_post_battle_dialogue(stream, context); // Father/daughter reunion after Mars leaves. + stream.log("[Segment 02] Mars defeated; post-battle dialogue cleared.", COLOR_GREEN); +} +static void align_to_route205_marker(VideoStream& stream, ProControllerContext& context, bool forest){ + context.wait_for_all_requests(); + const std::string label = forest ? "Route 205 forest entrance" : "Route 205 bridge"; + size_t confirmed = 0, missing = 0; + bool acquired = false; + for (size_t attempt = 0; attempt < 80; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + } + const auto result = forest ? match_route205_forest_entrance(*frame.frame) : match_route205_bridge(*frame.frame); + if (result.at_target){ + acquired = true; + missing = 0; + if (++confirmed >= 3){ + stream.log("[" + label + "] Sign position confirmed.", COLOR_GREEN); + return; + } + pbf_wait(context, 150ms); + }else{ + confirmed = 0; + double x = 0, y = 1; + Milliseconds duration = 80ms; + if (result.located){ + acquired = true; + missing = 0; + stream.log("[" + label + "] Sign offset=(" + std::to_string(result.dx) + "," + + std::to_string(result.dy) + ")"); + const bool horizontal = std::abs(result.dx) > 1.0 / 50; + const double error = horizontal ? std::abs(result.dx) : std::abs(result.dy); + x = horizontal ? (result.dx < 0 ? -1 : 1) : 0; + y = horizontal ? 0 : (result.dy > 0 ? -1 : 1); + duration = error < .08 ? 40ms : 80ms; + }else if (++missing > 12){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Patrol position did not converge.",stream); + label + ": Sign unavailable during bounded acquisition.", + stream); + }else if (acquired){ + // Never walk past a sign after losing a trusted match. + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + continue; } + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, 250ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Sign position did not converge.", stream); +} - void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - auto move=[&](double x,double y,Milliseconds duration,Milliseconds settle=250ms) { - pbf_move_left_joystick(context,{x,y},duration,0ms); - pbf_wait(context,settle); - context.wait_for_all_requests(); - }; - auto battle=[&](const std::string& label) { - stream.log("[Route 205 battle] "+label); - wait_for_trainer_battle_menu(stream,context); - Barry_1_fight(env,stream,context); - clear_post_battle_dialogue(stream,context); - }; - auto patrol_battle=[&](const std::string& label, - const std::function& match, - const std::function& approach) { - // One watcher covers every blind step, locator correction and - // stationary wait. An early encounter cancels queued movement. - if (!watch_route204_trainer_event(stream,context, - [&](ProControllerContext& ctx) { - approach(ctx); - ctx.wait_for_all_requests(); - align_route205_patrol_position(stream,ctx,match,label); - pbf_wait(ctx,45000ms); - },label)) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,label+": Trainer did not approach the confirmed patrol position.",stream); - battle(label); - // Either trainer can interrupt the approach. After the fight, - // continue the route without returning to a patrol waypoint - // or replaying the canceled approach movement. - }; - - // Recorded path around the Windworks machines from the exact - // reunion position at the end of 02.02. Keep directional turns. - move(-1,0,1335ms,847ms); - move(0,1,880ms,209ms); - move(-1,0,906ms,471ms); - move(0,-1,427ms,728ms); - move(-1,0,1213ms,936ms); - move(-1,0,991ms,175ms); - move(0,-1,1714ms,456ms); - move(1,-1,6809ms,1862ms); - move(0,-1,922ms,554ms); - move(-1,0,813ms,1370ms); - move(0,-1,1862ms,145ms); - move(-1,0,3478ms,500ms); - // Replace the manual left/right/up sign adjustments with the - // supplied repeatable position south of the bridge. - align_to_route205_marker(stream,context,false); - // Stop on the north bank, before Jacob's sight line. The longer - // hold reached his patrol and interrupted the westward crossing. - move(0,1,800ms,500ms); - if (!activate_repel(stream,context,6)) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Route 205: Could not activate Repel before the grass.",stream); - - // Cross beside the golden tree first, then travel through the - // grass west of Jacob. Clamp north against the plateau cliff - // before completing the westward approach to its south stairs. - // Keep the recorded total westward travel, split around the turn. - move(-1,0,600ms,268ms); - align_to_route205_bank_tree(stream,context); - move(0,1,2500ms,250ms); - move(-1,0,2212ms,268ms); - // Route 204's missed-fight pattern: one continuous approach, - // then stand still and decide before sending the next movement. - ShortDialogDetector hiker_dialogue(COLOR_YELLOW); - BattleDialogDetector hiker_intro(COLOR_YELLOW); - BattleMenuDetector hiker_menu(BattleType::TRAINER,COLOR_GREEN); - BlackScreenDetector hiker_transition; - const auto hiker_started=[&]() { - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Daniel north pass: No frame while checking for a dodged battle.",stream); - return hiker_dialogue.detect(*frame.frame) || hiker_intro.detect(*frame.frame) || - hiker_menu.detect(*frame.frame) || hiker_transition.detect(*frame.frame); - }; - ShortDialogWatcher hiker_dialogue_watch(COLOR_YELLOW); - DetectorToFinder hiker_intro_watch("Daniel battle intro",250ms,COLOR_YELLOW); - BattleMenuWatcher hiker_menu_watch(BattleType::TRAINER,COLOR_GREEN); - BlackScreenWatcher hiker_transition_watch; - const bool hiker_encounter=hiker_started() || run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},1200ms,0ms); - ctx.wait_for_all_requests(); - stream.log("[Daniel dodge check] North movement stopped; observing without input."); - // Battle intro uses a long dialogue box before the menu. - // Keep one uninterrupted watcher through the approach and - // this stationary window; no wiggle and no A press. - pbf_wait(ctx,10000ms); - },{{hiker_dialogue_watch},{hiker_intro_watch},{hiker_menu_watch},{hiker_transition_watch}})>=0 || hiker_started(); - stream.log("[Daniel dodge check] missed_trainer="+std::to_string(!hiker_encounter)); - if (hiker_encounter) { - battle("Hiker Daniel"); - stream.log("[Route 205] Daniel defeated; moving north from his battle landing to Zackary."); - } else { - stream.log("[Route 205] Daniel dodged; continuing north up the stairs before turning right to Zackary.",COLOR_GREEN); - } - // Daniel stops us at his row when he engages. A skipped pass - // has already carried us farther north. Resume from each pose - // with its own remaining climb, then confirm the visual waypoint. - // Stop at the bottom of the upper landing: the previous extra - // 300 ms north put us in Zackary's walking lane before the turn. - const Milliseconds north_to_zackary=hiker_encounter?1000ms:400ms; - patrol_battle("Camper Zackary",match_route205_zackary, - [north_to_zackary](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},north_to_zackary,250ms); - // Clamp against the right cliff at the landing's bottom - // corner before visual confirmation and stationary wait. - pbf_move_left_joystick(ctx,{1,0},900ms,250ms); - }); - // Restore the west side for the recorded bridge/stair turns. - move(-1,0,1800ms,250ms); - patrol_battle("Battle Girl Kelsey",match_route205_kelsey, - [](ProControllerContext& ctx) { - const auto step=[&](double x,double y,Milliseconds duration,Milliseconds settle) { - pbf_move_left_joystick(ctx,{x,y},duration,0ms); - pbf_wait(ctx,settle); - }; - step(0,1,638ms,673ms); - step(1,0,476ms,508ms); - step(0,1,717ms,254ms); - step(-1,0,415ms,430ms); - step(0,1,2517ms,57ms); - step(1,0,410ms,299ms); - step(0,1,1343ms,500ms); - step(-1,0,294ms,250ms); - }); - stream.log("[Route 205] Kelsey defeated; taking the left/north/right/north forest approach."); - // Original recorded turns around the ledge and northern outcrop. - // This exit applies even if Kelsey interrupted an earlier step. - move(-1,0,415ms,430ms); - // Renew in the cleared lane so expiry cannot interrupt the climb. - // Keep both recorded northward movement durations unchanged. - force_repel_expiry(stream,context,RepelRenewalChoice::YES,true); - move(0,1,2517ms,57ms); - move(1,0,410ms,299ms); - move(0,1,1343ms,500ms); - align_to_route205_marker(stream,context,true); - move(0,1,20ms); - stream.log("[Route 205] Forest entrance sign confirmed; checkpoint complete.",COLOR_GREEN); +static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + size_t confirmed = 0, missing = 0; + for (size_t attempt = 0; attempt < 80; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: No video frame.", stream); + } + const auto result = match_route205_bank_tree(*frame.frame); + if (!result.located){ + confirmed = 0; + if (++missing >= 12){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: Tree and blue-hat child unavailable; holding position.", stream); } - // Cheryl controls her own Pokemon. Select move slot 2 visually, - // then A advances target selection and battle text, as in the recording. - void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - (void)env; - // Second recorded run, beginning at the northern Route 205 sign. - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1466ms, 0ms); - pbf_wait(context, 1588ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 531ms, 0ms); - pbf_wait(context, 3398ms); - wait_for_dialogue(stream,context,"Cheryl introduction"); - // Reuse the Galactic plaza cutscene cleanup, including a second - // pass after the animation gap before route movement resumes. - clear_post_battle_dialogue(stream,context); - pbf_wait(context,2000ms); - clear_post_battle_dialogue(stream,context); - auto encounter=[&](const std::string& label,const std::function& approach) { - if (!watch_route204_trainer_event(stream,context,approach,label)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": No trainer encounter on the recorded approach.",stream); - stream.log("[Eterna Forest battle] "+label); - wait_for_trainer_battle_menu(stream,context); - // Ordinary trainers return after the confirmed battle-exit transition. - fight_trainer_using_moves(stream,context,2,label,false); - }; - encounter("Lass Briana and Bug Catcher Jack",[&](ProControllerContext& context) { - pbf_move_left_joystick(context, {1.000000, -0.000000}, 579ms, 0ms); - pbf_wait(context, 404ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 494ms, 0ms); - pbf_wait(context, 358ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1011ms, 0ms); - pbf_wait(context, 205ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1980ms, 0ms); - pbf_wait(context, 125ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1522ms, 0ms); - pbf_wait(context, 38ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1295ms, 0ms); - pbf_wait(context, 1811ms); - pbf_wait(context,10000ms); - }); - encounter("Psychics Lindsey and Elijah",[&](ProControllerContext& context) { - pbf_move_left_joystick(context, {0.000000, 1.000000}, 707ms, 0ms); - pbf_wait(context, 437ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1211ms, 0ms); - pbf_wait(context, 39ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1440ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1426ms, 0ms); - pbf_wait(context, 54ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1788ms, 0ms); - pbf_wait(context, 3269ms); - pbf_wait(context,10000ms); - }); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 2844ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 12ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1358ms, 0ms); - pbf_wait(context, 223ms); - // The recording renews here before walking into the last pair. - force_repel_expiry(stream,context,RepelRenewalChoice::YES,true); - encounter("Bug Catchers Donald and Phillip",[&](ProControllerContext& context) { - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2335ms, 0ms); - pbf_wait(context, 1960ms); - pbf_wait(context,10000ms); - }); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3878ms, 0ms); - pbf_wait(context, 33ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 6451ms, 0ms); - pbf_wait(context, 117ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1727ms, 0ms); - pbf_wait(context, 207ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1693ms, 0ms); - pbf_wait(context, 109ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1613ms, 0ms); - pbf_wait(context, 538ms); - // Clear the recorded expiry prompt before Cheryl's farewell. - clear_post_battle_dialogue(stream,context,BUTTON_A); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 928ms, 0ms); - pbf_wait(context, 1487ms); - wait_for_dialogue(stream,context,"Cheryl farewell"); - // Reuse the Galactic plaza cutscene cleanup, including a second - // pass after the animation gap before route movement resumes. - clear_post_battle_dialogue(stream,context); - pbf_wait(context,2000ms); - clear_post_battle_dialogue(stream,context); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1238ms, 0ms); - pbf_wait(context, 2157ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 510ms, 0ms); - pbf_wait(context, 544ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 864ms, 0ms); - pbf_wait(context, 289ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1694ms, 0ms); - pbf_wait(context, 383ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 643ms, 0ms); - pbf_wait(context, 346ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 844ms, 0ms); - pbf_wait(context, 122ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 678ms, 0ms); - pbf_wait(context, 151ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 4143ms, 0ms); - context.wait_for_all_requests(); - stream.log("[Eterna Forest] East exit and Route 205 bridges complete; at Eterna City approach.",COLOR_GREEN); + pbf_wait(context, 250ms); + }else if (result.at_target){ + missing = 0; + if (++confirmed >= 3){ + stream.log("[Route 205 bank tree] Position confirmed.", COLOR_GREEN); + return; } - void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - (void)env; - // Clear Repel on the open street before locator corrections. - // Reuse the helper's horizontal wiggles and decline renewal. - force_repel_expiry(stream,context,RepelRenewalChoice::NO,false); - // Reuse landmark alignment to clear the blue post east of the - // door before entering. Healing starts only after map transition. - align_to_overworld_landmarks(stream,context,match_eterna_center_entrance, - "Eterna Center doorway",false,true); - BlackScreenOverWatcher center_entry(COLOR_RED,{0.1,0.1,0.8,0.8}); - int entry=run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); - pbf_wait(ctx,3000ms); - },{{center_entry}}); - if (entry!=0) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center doorway: Entry transition not confirmed.",stream); - context.wait_for_all_requests(); - if (!heal_pokemon(stream,context,"Eterna")) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit was not confirmed.",stream); - pbf_wait(context, 500ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 500ms, 0ms); - pbf_wait(context, 433ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 580ms, 0ms); - pbf_wait(context, 537ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 144ms, 0ms); - pbf_wait(context, 508ms); - bool entered_house=false; - align_to_overworld_landmarks(stream,context,match_eterna_underground_house, - "Underground Man house doorway",false,true,false,match_eterna_underground_man,&entered_house); - BlackScreenOverWatcher house_entry(COLOR_RED,{0.1,0.1,0.8,0.8}); - if (!entered_house && run_until(stream,context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); - pbf_wait(ctx,3000ms); - },{{house_entry}})!=0) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Underground Man house: Entry not confirmed.",stream); - pbf_wait(context,500ms); - // Retain the recorded northward approach from the fixed entrance. - pbf_move_left_joystick(context,{0,1},290ms,0ms); - align_to_overworld_landmarks(stream,context,match_eterna_underground_man, - "Underground Man interaction",false,true); - pbf_move_left_joystick(context,{0,1},80ms,200ms); - pbf_press_button(context,BUTTON_A,80ms,200ms); - wait_for_dialogue(stream,context,"Underground Man introduction"); - // Recorded forced walk: 4.7s between house and street dialogue. - // Keep watching for that gap plus a 500ms hardware buffer. - clear_post_battle_dialogue(stream,context,BUTTON_A,5200ms, - "PokemonBDSP/Autostory/EternaUndergroundMan/ExplorerKitCard.png"); - // The first-use tutorial opens the Bag, uses the Kit, and takes - // us underground automatically. The dialogue helper also clears - // Roark's introduction. Do not reopen the Bag to enter again. - const auto underground_frame=stream.video().snapshot(); - const char* underground_reference=underground_frame && underground_frame.frame->width()>=1600 - ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrival.png" - : underground_frame && underground_frame.frame->width()>=1100 - ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalMedium.png" - : "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalSmall.png"; - ImageMatchDetector underground_map( - std::make_shared(RESOURCE_PATH()+underground_reference), - ImageFloatBox{.005,.110,.085,.130},30.0); - if (!underground_frame || !underground_map.detect(*underground_frame.frame)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Grand Underground tutorial: Underground arrival not confirmed.",stream); - stream.log("[Grand Underground] Tutorial dialogue complete; returning with Y."); - // User-requested full settling interval immediately before Y. - pbf_wait(context,2000ms); - pbf_press_button(context, BUTTON_Y, 129ms, 0ms); - pbf_wait(context, 2076ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 88ms, 0ms); - pbf_wait(context, 819ms); - pbf_press_button(context, BUTTON_A, 127ms, 0ms); - pbf_wait(context, 1972ms); - pbf_press_button(context, BUTTON_A, 127ms, 0ms); - pbf_wait(context, 5330ms); - // Keep the Y / Go up / Yes inputs above, then watch the - // Underground Man's reward dialogue through the surface return. - context.wait_for_all_requests(); - wait_for_dialogue(stream,context,"Underground Man return"); - clear_post_battle_dialogue(stream,context,BUTTON_A,3500ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 584ms, 0ms); - pbf_wait(context, 85ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1448ms, 0ms); - pbf_wait(context, 122ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1455ms, 0ms); - pbf_wait(context, 145ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1257ms, 0ms); - pbf_wait(context, 956ms); - // Reuse the plaza/Cheryl cutscene cleanup rather than a - // recorded number of A presses during Cynthia's camera changes. - context.wait_for_all_requests(); - wait_for_dialogue(stream,context,"Cynthia introduction"); - clear_post_battle_dialogue(stream,context,BUTTON_A,3500ms); - // Let Cynthia's departure/camera finish before any route input, - // then watch once more for delayed dialogue on slower hardware. - pbf_wait(context,2000ms); - context.wait_for_all_requests(); - clear_post_battle_dialogue(stream,context,BUTTON_A,1500ms); - pbf_wait(context,500ms); - stream.log("[Cynthia exit] Departure settled; moving west and south to reacquire the Center."); - pbf_move_left_joystick(context,{-1,0},1200ms,100ms); - pbf_move_left_joystick(context,{0,-1},1800ms,300ms); - pbf_move_left_joystick(context, { 1,0 }, 1200ms, 100ms); - pbf_move_left_joystick(context, { 0,-1 }, 250ms, 300ms); - // Clear rewards before the approach can cross the gym doorway. - context.wait_for_all_requests(); - if (!clear_new_item_flags(stream,context)) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"New reward flags could not be cleared before continuing.",stream); - // Keep a loose horizontal street lane, but normalize the row: - // the following fixed south leg requires a repeatable start. - align_to_overworld_landmarks(stream,context,match_eterna_center_entrance, - "Eterna Center gym departure",false,true,true,nullptr,nullptr,.03,.02); - // User's eternapctogym.txt with a shorter south leg; omit its final left127ms correction. - // Start acquiring north of the old fence collision, then let - // gym landmarks correct the final row and rightward approach. - pbf_move_left_joystick(context,{0,-1},4300ms,0ms); - pbf_wait(context,642ms); - pbf_move_left_joystick(context,{1,0},1125ms,0ms); - pbf_wait(context,939ms); - pbf_move_left_joystick(context,{0,1},56ms,0ms); - pbf_wait(context,467ms); - pbf_move_left_joystick(context,{0,1},39ms,0ms); - pbf_wait(context,1255ms); - // Configurable only for a watched gym entry attempt. The normal - // three-landmark exterior arrival gates remain unchanged. - constexpr double GYM_MODERATE_ENTRY_CONFIDENCE=.88; - constexpr size_t GYM_ENTRY_ATTEMPTS=3; - bool gym_confirmed=false,transition_seen=false; - for(size_t attempt=1;attempt<=GYM_ENTRY_ATTEMPTS && !gym_confirmed;attempt++){ - stream.log("[Eterna Gym entry] Attempt "+std::to_string(attempt)+"/"+std::to_string(GYM_ENTRY_ATTEMPTS)); - if(!transition_seen){ - bool already_inside=false; - // Watch throughout alignment, not just the final north - // hold: a correction can cross the doorway first. - BlackScreenWatcher entering(COLOR_RED,{0.1,0.1,0.8,0.8}); - const int outcome=run_until(stream,context, - [&](ProControllerContext& ctx){ - align_to_overworld_landmarks(stream,ctx,match_eterna_gym_entrance, - "Eterna Gym doorway",false,true,false,match_eterna_gym_statues,&already_inside, - 0,0,GYM_MODERATE_ENTRY_CONFIDENCE); - if(!already_inside){ - pbf_move_left_joystick(ctx,{0,1},3000ms,0ms); - pbf_wait(ctx,5000ms); - } - },{{entering}}); - transition_seen=outcome==0 || already_inside; - stream.log("[Eterna Gym entry] Black transition detected="+std::to_string(outcome==0)+ - ", interior seen during alignment="+std::to_string(already_inside)); - } - if(transition_seen){ - BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8},100,10, - BlackScreenWatcher::FinderType::GONE,500ms); - if(wait_until(stream,context,30s,{{visible}})!=0){ - stream.log("[Eterna Gym entry] Transition has not finished; retrying within limit."); - continue; - } - stream.log("[Eterna Gym entry] Transition finished; verifying both statues."); - } - // Black alone never proves entry. Stop navigation and retry - // verification if a transition occurred into an unknown room. - size_t stable=0; - for(size_t check=0;check<8;check++){ - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if(!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Gym: No interior verification frame.",stream); - const auto interior=match_eterna_gym_statues(*frame.frame); - for(const auto& statue:interior.landmarks) stream.log("[Eterna Gym statues] "+statue.name+ - " score="+std::to_string(statue.score)+", margin="+std::to_string(statue.margin)+ - ", offset=("+std::to_string(statue.dx)+","+std::to_string(statue.dy)+")"); - stable=interior.located ? stable+1 : 0; - stream.log("[Eterna Gym statues] Both in expected positions="+std::to_string(interior.located)+ - ", stable frames="+std::to_string(stable)+"/3"); - if(stable>=3){gym_confirmed=true;break;} - pbf_wait(context,250ms); - } - if(!gym_confirmed) stream.log("[Eterna Gym entry] Statues not verified; bounded retry."); - } - if(!gym_confirmed) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Gym: Both entrance statues were not verified after three attempts.",stream); - stream.log("[Eterna City] Watched Explorer Kit/Cynthia events and verified gym entry complete.",COLOR_GREEN); + pbf_wait(context, 150ms); + }else{ + confirmed = 0; + missing = 0; + stream.log("[Route 205 bank tree] Offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + + ")"); + // Clear the north bridge lip before attempting to step west. + // Once at bank height, keep horizontal priority beside the tree. + const bool clear_bank = result.dy < -1.0 / 54; + const bool horizontal = !clear_bank && std::abs(result.dx) > 1.0 / 96; + const double error = horizontal ? std::abs(result.dx) : std::abs(result.dy); + const double x = horizontal ? (result.dx < 0 ? -1 : 1) : 0; + const double y = horizontal ? 0 : (result.dy > 0 ? -1 : 1); + pbf_move_left_joystick(context, {x, y}, error < .08 ? 40ms : 80ms, 0ms); + pbf_wait(context, 250ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: Position did not converge.", stream); +} + +static void align_route205_patrol_position(VideoStream& stream, ProControllerContext& context, + const std::function& match, + const std::string& label){ + size_t confirmed = 0, missing = 0; + for (size_t attempt = 0; attempt < 100; attempt++){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame during patrol positioning.", stream); + } + const auto result = match(*frame.frame); + if (!result.located){ + confirmed = 0; + if (++missing >= 16){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Patrol landmarks unavailable; holding position.", + stream); } - static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ - - //Upon entering the Gym. - - struct Move { - DpadPosition dir; - int count; - }; - - const Move sequence1[] = { - {DPAD_UP, 5}, - {DPAD_LEFT, 2}, - {DPAD_UP, 9}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 7}, - {DPAD_LEFT, 3}, - {DPAD_UP, 2}, - {DPAD_LEFT, 5}, - {DPAD_DOWN, 4}, - }; - - const Move sequence2[] = { - {DPAD_UP, 6}, - {DPAD_LEFT, 6}, - {DPAD_DOWN, 9}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 5}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 6}, - {DPAD_RIGHT, 5}, - }; - - const Move sequence3[] = { - {DPAD_LEFT, 5}, - {DPAD_UP, 6}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 5}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 5}, - {DPAD_LEFT, 2}, - {DPAD_UP, 8}, - {DPAD_RIGHT, 7}, - {DPAD_DOWN, 2}, - {DPAD_RIGHT, 5}, - {DPAD_DOWN, 6}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 5}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, - {DPAD_RIGHT, 6}, - {DPAD_UP, 3}, - {DPAD_RIGHT, 2}, - }; - - const Move sequence4[] = { - {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, - {DPAD_LEFT, 6}, - {DPAD_DOWN, 6}, - {DPAD_RIGHT, 11}, - {DPAD_UP, 3}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 16}, - {DPAD_LEFT, 6}, - }; - - const Move sequence5[] = { - {DPAD_RIGHT, 6}, - {DPAD_DOWN, 17}, - {DPAD_LEFT, 5}, - {DPAD_DOWN, 2}, - {DPAD_LEFT, 8}, - {DPAD_UP, 11}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 7}, - {DPAD_LEFT, 2}, - {DPAD_UP, 7}, - }; - - const Move sequence6[] = { - {DPAD_DOWN, 10}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 4}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 13}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 2}, - }; - - for(int i=0; i < 7; i++){ - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - } + pbf_wait(context, 250ms); + }else if (result.at_target){ + missing = 0; + if (++confirmed >= 3){ + stream.log("[" + label + "] Patrol position confirmed.", COLOR_GREEN); + return; + } + pbf_wait(context, 150ms); + }else{ + confirmed = 0; + missing = 0; + stream.log("[" + label + "] Patrol offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + + ")"); + // Correct height first so the subsequent horizontal step + // stays on the stair landing rather than hitting its cliff. + const bool vertical = std::abs(result.dy) > 1.0 / 54; + const double error = vertical ? std::abs(result.dy) : std::abs(result.dx); + pbf_move_left_joystick( + context, {vertical ? 0 : (result.dx < 0 ? -1.0 : 1.0), vertical ? (result.dy > 0 ? -1.0 : 1.0) : 0}, + error < .08 ? 40ms : 80ms, 0ms); + pbf_wait(context, 250ms); + } + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Patrol position did not converge.", stream); +} - // Reuse the Roark/Cynthia dialogue watcher so Gardenia's - // introduction finishes before the doorway movement starts. - pbf_press_button(context,BUTTON_A,150ms,300ms); - context.wait_for_all_requests(); - wait_for_dialogue(stream,context,"Gardenia introduction"); - clear_post_battle_dialogue(stream,context,BUTTON_A,3500ms); - stream.log("[Eterna Gym puzzle] Gardenia introduction dialogue cleared."); - // Door transition and gym camera introduction take about 7.5s - // in 2026-10-18 20-01-01; include 500ms for hardware variation. - stream.log("[Eterna Gym puzzle] Entering Gardenia's doorway; waiting 8000ms for transition/cutscene (includes 500ms buffer)."); - pbf_press_dpad(context, DPAD_UP, 80ms, 8000ms); - - for (const auto& move : sequence1){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); +void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms){ + pbf_move_left_joystick(context, {x, y}, duration, 0ms); + pbf_wait(context, settle); + context.wait_for_all_requests(); + }; + auto battle = [&](const std::string& label){ + stream.log("[Route 205 battle] " + label); + wait_for_trainer_battle_menu(stream, context); + Barry_1_fight(env, stream, context); + clear_post_battle_dialogue(stream, context); + }; + auto patrol_battle = [&](const std::string& label, + const std::function& match, + const std::function& approach){ + // One watcher covers every blind step, locator correction and + // stationary wait. An early encounter cancels queued movement. + if (!watch_route204_trainer_event( + stream, context, + [&](ProControllerContext& ctx){ + approach(ctx); + ctx.wait_for_all_requests(); + align_route205_patrol_position(stream, ctx, match, label); + pbf_wait(ctx, 45000ms); + }, + label)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Trainer did not approach the confirmed patrol position.", + stream); + } + battle(label); + // Either trainer can interrupt the approach. After the fight, + // continue the route without returning to a patrol waypoint + // or replaying the canceled approach movement. + }; + + // Recorded path around the Windworks machines from the exact + // reunion position at the end of 02.02. Keep directional turns. + move(-1, 0, 1335ms, 847ms); + move(0, 1, 880ms, 209ms); + move(-1, 0, 906ms, 471ms); + move(0, -1, 427ms, 728ms); + move(-1, 0, 1213ms, 936ms); + move(-1, 0, 991ms, 175ms); + move(0, -1, 1714ms, 456ms); + move(1, -1, 6809ms, 1862ms); + move(0, -1, 922ms, 554ms); + move(-1, 0, 813ms, 1370ms); + move(0, -1, 1862ms, 145ms); + move(-1, 0, 3478ms, 500ms); + // Replace the manual left/right/up sign adjustments with the + // supplied repeatable position south of the bridge. + align_to_route205_marker(stream, context, false); + // Stop on the north bank, before Jacob's sight line. The longer + // hold reached his patrol and interrupted the westward crossing. + move(0, 1, 800ms, 500ms); + if (!activate_repel(stream, context, 6)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205: Could not activate Repel before the grass.", stream); + } + + // Cross beside the golden tree first, then travel through the + // grass west of Jacob. Clamp north against the plateau cliff + // before completing the westward approach to its south stairs. + // Keep the recorded total westward travel, split around the turn. + move(-1, 0, 600ms, 268ms); + align_to_route205_bank_tree(stream, context); + move(0, 1, 2500ms, 250ms); + move(-1, 0, 2212ms, 268ms); + // Route 204's missed-fight pattern: one continuous approach, + // then stand still and decide before sending the next movement. + ShortDialogDetector hiker_dialogue(COLOR_YELLOW); + BattleDialogDetector hiker_intro(COLOR_YELLOW); + BattleMenuDetector hiker_menu(BattleType::TRAINER, COLOR_GREEN); + BlackScreenDetector hiker_transition; + const auto hiker_started = [&](){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Daniel north pass: No frame while checking for a dodged battle.", + stream); + } + return hiker_dialogue.detect(*frame.frame) || hiker_intro.detect(*frame.frame) || + hiker_menu.detect(*frame.frame) || hiker_transition.detect(*frame.frame); + }; + ShortDialogWatcher hiker_dialogue_watch(COLOR_YELLOW); + DetectorToFinder hiker_intro_watch("Daniel battle intro", 250ms, COLOR_YELLOW); + BattleMenuWatcher hiker_menu_watch(BattleType::TRAINER, COLOR_GREEN); + BlackScreenWatcher hiker_transition_watch; + const bool hiker_encounter = + hiker_started() || + run_until( + stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 1200ms, 0ms); + ctx.wait_for_all_requests(); + stream.log("[Daniel dodge check] North movement stopped; observing without input."); + // Battle intro uses a long dialogue box before the menu. + // Keep one uninterrupted watcher through the approach and + // this stationary window; no wiggle and no A press. + pbf_wait(ctx, 10000ms); + }, + {{hiker_dialogue_watch}, {hiker_intro_watch}, {hiker_menu_watch}, {hiker_transition_watch}}) >= 0 || + hiker_started(); + stream.log("[Daniel dodge check] missed_trainer=" + std::to_string(!hiker_encounter)); + if (hiker_encounter){ + battle("Hiker Daniel"); + stream.log("[Route 205] Daniel defeated; moving north from his battle landing to Zackary."); + }else{ + stream.log("[Route 205] Daniel dodged; continuing north up the stairs before turning right to Zackary.", + COLOR_GREEN); + } + // Daniel stops us at his row when he engages. A skipped pass + // has already carried us farther north. Resume from each pose + // with its own remaining climb, then confirm the visual waypoint. + // Stop at the bottom of the upper landing: the previous extra + // 300 ms north put us in Zackary's walking lane before the turn. + const Milliseconds north_to_zackary = hiker_encounter ? 1000ms : 400ms; + patrol_battle("Camper Zackary", match_route205_zackary, [north_to_zackary](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, north_to_zackary, 250ms); + // Clamp against the right cliff at the landing's bottom + // corner before visual confirmation and stationary wait. + pbf_move_left_joystick(ctx, {1, 0}, 900ms, 250ms); + }); + // Restore the west side for the recorded bridge/stair turns. + move(-1, 0, 1800ms, 250ms); + patrol_battle("Battle Girl Kelsey", match_route205_kelsey, [](ProControllerContext& ctx){ + const auto step = [&](double x, double y, Milliseconds duration, Milliseconds settle){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, settle); + }; + step(0, 1, 638ms, 673ms); + step(1, 0, 476ms, 508ms); + step(0, 1, 717ms, 254ms); + step(-1, 0, 415ms, 430ms); + step(0, 1, 2517ms, 57ms); + step(1, 0, 410ms, 299ms); + step(0, 1, 1343ms, 500ms); + step(-1, 0, 294ms, 250ms); + }); + stream.log("[Route 205] Kelsey defeated; taking the left/north/right/north forest approach."); + // Original recorded turns around the ledge and northern outcrop. + // This exit applies even if Kelsey interrupted an earlier step. + move(-1, 0, 415ms, 430ms); + // Renew in the cleared lane so expiry cannot interrupt the climb. + // Keep both recorded northward movement durations unchanged. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, true); + move(0, 1, 2517ms, 57ms); + move(1, 0, 410ms, 299ms); + move(0, 1, 1343ms, 500ms); + align_to_route205_marker(stream, context, true); + move(0, 1, 20ms); + stream.log("[Route 205] Forest entrance sign confirmed; checkpoint complete.", COLOR_GREEN); +} +// Cheryl controls her own Pokemon. Select move slot 2 visually, +// then A advances target selection and battle text, as in the recording. +void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + (void)env; + // Second recorded run, beginning at the northern Route 205 sign. + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1466ms, 0ms); + pbf_wait(context, 1588ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 531ms, 0ms); + pbf_wait(context, 3398ms); + wait_for_dialogue(stream, context, "Cheryl introduction"); + // Reuse the Galactic plaza cutscene cleanup, including a second + // pass after the animation gap before route movement resumes. + clear_post_battle_dialogue(stream, context); + pbf_wait(context, 2000ms); + clear_post_battle_dialogue(stream, context); + auto encounter = [&](const std::string& label, const std::function& approach){ + if (!watch_route204_trainer_event(stream, context, approach, label)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": No trainer encounter on the recorded approach.", stream); + } + stream.log("[Eterna Forest battle] " + label); + wait_for_trainer_battle_menu(stream, context); + // Ordinary trainers return after the confirmed battle-exit transition. + fight_trainer_using_moves(stream, context, 2, label, false); + }; + encounter("Lass Briana and Bug Catcher Jack", [&](ProControllerContext& context){ + pbf_move_left_joystick(context, {1.000000, -0.000000}, 579ms, 0ms); + pbf_wait(context, 404ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 494ms, 0ms); + pbf_wait(context, 358ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1011ms, 0ms); + pbf_wait(context, 205ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1980ms, 0ms); + pbf_wait(context, 125ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1522ms, 0ms); + pbf_wait(context, 38ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1295ms, 0ms); + pbf_wait(context, 1811ms); + pbf_wait(context, 10000ms); + }); + encounter("Psychics Lindsey and Elijah", [&](ProControllerContext& context){ + pbf_move_left_joystick(context, {0.000000, 1.000000}, 707ms, 0ms); + pbf_wait(context, 437ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1211ms, 0ms); + pbf_wait(context, 39ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1440ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1426ms, 0ms); + pbf_wait(context, 54ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1788ms, 0ms); + pbf_wait(context, 3269ms); + pbf_wait(context, 10000ms); + }); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2844ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 12ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1358ms, 0ms); + pbf_wait(context, 223ms); + // The recording renews here before walking into the last pair. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, true); + encounter("Bug Catchers Donald and Phillip", [&](ProControllerContext& context){ + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2335ms, 0ms); + pbf_wait(context, 1960ms); + pbf_wait(context, 10000ms); + }); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3878ms, 0ms); + pbf_wait(context, 33ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 6451ms, 0ms); + pbf_wait(context, 117ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1727ms, 0ms); + pbf_wait(context, 207ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1693ms, 0ms); + pbf_wait(context, 109ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1613ms, 0ms); + pbf_wait(context, 538ms); + // Clear the recorded expiry prompt before Cheryl's farewell. + clear_post_battle_dialogue(stream, context, BUTTON_A); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 928ms, 0ms); + pbf_wait(context, 1487ms); + wait_for_dialogue(stream, context, "Cheryl farewell"); + // Reuse the Galactic plaza cutscene cleanup, including a second + // pass after the animation gap before route movement resumes. + clear_post_battle_dialogue(stream, context); + pbf_wait(context, 2000ms); + clear_post_battle_dialogue(stream, context); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1238ms, 0ms); + pbf_wait(context, 2157ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 510ms, 0ms); + pbf_wait(context, 544ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 864ms, 0ms); + pbf_wait(context, 289ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1694ms, 0ms); + pbf_wait(context, 383ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 643ms, 0ms); + pbf_wait(context, 346ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 844ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 678ms, 0ms); + pbf_wait(context, 151ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 4143ms, 0ms); + context.wait_for_all_requests(); + stream.log("[Eterna Forest] East exit and Route 205 bridges complete; at Eterna City approach.", COLOR_GREEN); +} +void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, + ProControllerContext& context){ + (void)env; + // Clear Repel on the open street before locator corrections. + // Reuse the helper's horizontal wiggles and decline renewal. + force_repel_expiry(stream, context, RepelRenewalChoice::NO, false); + // Reuse landmark alignment to clear the blue post east of the + // door before entering. Healing starts only after map transition. + align_to_overworld_landmarks(stream, context, match_eterna_center_entrance, "Eterna Center doorway", false, true); + BlackScreenOverWatcher center_entry(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int entry = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{center_entry}}); + if (entry != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Center doorway: Entry transition not confirmed.", stream); + } + context.wait_for_all_requests(); + if (!heal_pokemon(stream, context, "Eterna")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Center exit was not confirmed.", stream); + } + pbf_wait(context, 500ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 500ms, 0ms); + pbf_wait(context, 433ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 580ms, 0ms); + pbf_wait(context, 537ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 144ms, 0ms); + pbf_wait(context, 508ms); + bool entered_house = false; + align_to_overworld_landmarks(stream, context, match_eterna_underground_house, "Underground Man house doorway", + false, true, false, match_eterna_underground_man, &entered_house); + BlackScreenOverWatcher house_entry(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + if (!entered_house && run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{house_entry}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Underground Man house: Entry not confirmed.", stream); + } + pbf_wait(context, 500ms); + // Retain the recorded northward approach from the fixed entrance. + pbf_move_left_joystick(context, {0, 1}, 290ms, 0ms); + align_to_overworld_landmarks(stream, context, match_eterna_underground_man, "Underground Man interaction", false, + true); + pbf_move_left_joystick(context, {0, 1}, 80ms, 200ms); + pbf_press_button(context, BUTTON_A, 80ms, 200ms); + wait_for_dialogue(stream, context, "Underground Man introduction"); + // Recorded forced walk: 4.7s between house and street dialogue. + // Keep watching for that gap plus a 500ms hardware buffer. + clear_post_battle_dialogue(stream, context, BUTTON_A, 5200ms, + "PokemonBDSP/Autostory/EternaUndergroundMan/ExplorerKitCard.png"); + // The first-use tutorial opens the Bag, uses the Kit, and takes + // us underground automatically. The dialogue helper also clears + // Roark's introduction. Do not reopen the Bag to enter again. + const auto underground_frame = stream.video().snapshot(); + const char* underground_reference = underground_frame && underground_frame.frame->width() >= 1600 + ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrival.png" + : underground_frame && underground_frame.frame->width() >= 1100 + ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalMedium.png" + : "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalSmall.png"; + ImageMatchDetector underground_map(std::make_shared(RESOURCE_PATH() + underground_reference), + ImageFloatBox{.005, .110, .085, .130}, 30.0); + if (!underground_frame || !underground_map.detect(*underground_frame.frame)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Grand Underground tutorial: Underground arrival not confirmed.", + stream); + } + stream.log("[Grand Underground] Tutorial dialogue complete; returning with Y."); + // User-requested full settling interval immediately before Y. + pbf_wait(context, 2000ms); + pbf_press_button(context, BUTTON_Y, 129ms, 0ms); + pbf_wait(context, 2076ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 88ms, 0ms); + pbf_wait(context, 819ms); + pbf_press_button(context, BUTTON_A, 127ms, 0ms); + pbf_wait(context, 1972ms); + pbf_press_button(context, BUTTON_A, 127ms, 0ms); + pbf_wait(context, 5330ms); + // Keep the Y / Go up / Yes inputs above, then watch the + // Underground Man's reward dialogue through the surface return. + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Underground Man return"); + clear_post_battle_dialogue(stream, context, BUTTON_A, 3500ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 584ms, 0ms); + pbf_wait(context, 85ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1448ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1455ms, 0ms); + pbf_wait(context, 145ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1257ms, 0ms); + pbf_wait(context, 956ms); + // Reuse the plaza/Cheryl cutscene cleanup rather than a + // recorded number of A presses during Cynthia's camera changes. + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Cynthia introduction"); + clear_post_battle_dialogue(stream, context, BUTTON_A, 3500ms); + // Let Cynthia's departure/camera finish before any route input, + // then watch once more for delayed dialogue on slower hardware. + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + clear_post_battle_dialogue(stream, context, BUTTON_A, 1500ms); + pbf_wait(context, 500ms); + stream.log("[Cynthia exit] Departure settled; moving west and south to reacquire the Center."); + pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); + pbf_move_left_joystick(context, {0, -1}, 1800ms, 300ms); + pbf_move_left_joystick(context, {1, 0}, 1200ms, 100ms); + pbf_move_left_joystick(context, {0, -1}, 250ms, 300ms); + // Clear rewards before the approach can cross the gym doorway. + context.wait_for_all_requests(); + if (!clear_new_item_flags(stream, context)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "New reward flags could not be cleared before continuing.", stream); + } + // Keep a loose horizontal street lane, but normalize the row: + // the following fixed south leg requires a repeatable start. + align_to_overworld_landmarks(stream, context, match_eterna_center_entrance, "Eterna Center gym departure", false, + true, true, nullptr, nullptr, .03, .02); + // User's eternapctogym.txt with a shorter south leg; omit its final left127ms correction. + // Start acquiring north of the old fence collision, then let + // gym landmarks correct the final row and rightward approach. + pbf_move_left_joystick(context, {0, -1}, 4300ms, 0ms); + pbf_wait(context, 642ms); + pbf_move_left_joystick(context, {1, 0}, 1125ms, 0ms); + pbf_wait(context, 939ms); + pbf_move_left_joystick(context, {0, 1}, 56ms, 0ms); + pbf_wait(context, 467ms); + pbf_move_left_joystick(context, {0, 1}, 39ms, 0ms); + pbf_wait(context, 1255ms); + // Configurable only for a watched gym entry attempt. The normal + // three-landmark exterior arrival gates remain unchanged. + constexpr double GYM_MODERATE_ENTRY_CONFIDENCE = .88; + constexpr size_t GYM_ENTRY_ATTEMPTS = 3; + bool gym_confirmed = false, transition_seen = false; + for (size_t attempt = 1; attempt <= GYM_ENTRY_ATTEMPTS && !gym_confirmed; attempt++){ + stream.log("[Eterna Gym entry] Attempt " + std::to_string(attempt) + "/" + std::to_string(GYM_ENTRY_ATTEMPTS)); + if (!transition_seen){ + bool already_inside = false; + // Watch throughout alignment, not just the final north + // hold: a correction can cross the doorway first. + BlackScreenWatcher entering(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + const int outcome = run_until( + stream, context, + [&](ProControllerContext& ctx){ + align_to_overworld_landmarks(stream, ctx, match_eterna_gym_entrance, "Eterna Gym doorway", false, + true, false, match_eterna_gym_statues, &already_inside, 0, 0, + GYM_MODERATE_ENTRY_CONFIDENCE); + if (!already_inside){ + pbf_move_left_joystick(ctx, {0, 1}, 3000ms, 0ms); + pbf_wait(ctx, 5000ms); } - } + }, + {{entering}}); + transition_seen = outcome == 0 || already_inside; + stream.log("[Eterna Gym entry] Black transition detected=" + std::to_string(outcome == 0) + + ", interior seen during alignment=" + std::to_string(already_inside)); + } + if (transition_seen){ + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{visible}}) != 0){ + stream.log("[Eterna Gym entry] Transition has not finished; retrying within limit."); + continue; + } + stream.log("[Eterna Gym entry] Transition finished; verifying both statues."); + } + // Black alone never proves entry. Stop navigation and retry + // verification if a transition occurred into an unknown room. + size_t stable = 0; + for (size_t check = 0; check < 8; check++){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Gym: No interior verification frame.", stream); + } + const auto interior = match_eterna_gym_statues(*frame.frame); + for (const auto& statue : interior.landmarks){ + stream.log("[Eterna Gym statues] " + statue.name + " score=" + std::to_string(statue.score) + + ", margin=" + std::to_string(statue.margin) + ", offset=(" + std::to_string(statue.dx) + + "," + std::to_string(statue.dy) + ")"); + } + stable = interior.located ? stable + 1 : 0; + stream.log("[Eterna Gym statues] Both in expected positions=" + std::to_string(interior.located) + + ", stable frames=" + std::to_string(stable) + "/3"); + if (stable >= 3){ + gym_confirmed = true; + break; + } + pbf_wait(context, 250ms); + } + if (!gym_confirmed){ + stream.log("[Eterna Gym entry] Statues not verified; bounded retry."); + } + } + if (!gym_confirmed){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Gym: Both entrance statues were not verified after three attempts.", stream); + } + stream.log("[Eterna City] Watched Explorer Kit/Cynthia events and verified gym entry complete.", COLOR_GREEN); +} +static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + + //Upon entering the Gym. + + struct Move { + DpadPosition dir; + int count; + }; + + const Move sequence1[] = { + {DPAD_UP, 5}, {DPAD_LEFT, 2}, {DPAD_UP, 9}, {DPAD_RIGHT, 2}, {DPAD_UP, 7}, + {DPAD_LEFT, 3}, {DPAD_UP, 2}, {DPAD_LEFT, 5}, {DPAD_DOWN, 4}, + }; + + const Move sequence2[] = { + {DPAD_UP, 6}, {DPAD_LEFT, 6}, {DPAD_DOWN, 9}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, {DPAD_DOWN, 4}, {DPAD_LEFT, 2}, {DPAD_DOWN, 6}, {DPAD_RIGHT, 5}, + }; + + const Move sequence3[] = { + {DPAD_LEFT, 5}, {DPAD_UP, 6}, {DPAD_RIGHT, 2}, {DPAD_UP, 5}, {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, {DPAD_LEFT, 2}, {DPAD_UP, 8}, {DPAD_RIGHT, 7}, {DPAD_DOWN, 2}, + {DPAD_RIGHT, 5}, {DPAD_DOWN, 6}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 5}, {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, {DPAD_RIGHT, 6}, {DPAD_UP, 3}, {DPAD_RIGHT, 2}, + }; + + const Move sequence4[] = { + {DPAD_LEFT, 2}, {DPAD_DOWN, 4}, {DPAD_LEFT, 6}, {DPAD_DOWN, 6}, {DPAD_RIGHT, 11}, + {DPAD_UP, 3}, {DPAD_RIGHT, 2}, {DPAD_UP, 16}, {DPAD_LEFT, 6}, + }; + + const Move sequence5[] = { + {DPAD_RIGHT, 6}, {DPAD_DOWN, 17}, {DPAD_LEFT, 5}, {DPAD_DOWN, 2}, {DPAD_LEFT, 8}, + {DPAD_UP, 11}, {DPAD_RIGHT, 2}, {DPAD_UP, 7}, {DPAD_LEFT, 2}, {DPAD_UP, 7}, + }; + + const Move sequence6[] = { + {DPAD_DOWN, 10}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 4}, {DPAD_LEFT, 2}, + {DPAD_DOWN, 13}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 2}, + }; + + for (int i = 0; i < 7; i++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } - //Button presses to initiate Trainer battle 01 (Lass Caroline). + // Reuse the Roark/Cynthia dialogue watcher so Gardenia's + // introduction finishes before the doorway movement starts. + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Gardenia introduction"); + clear_post_battle_dialogue(stream, context, BUTTON_A, 3500ms); + stream.log("[Eterna Gym puzzle] Gardenia introduction dialogue cleared."); + // Door transition and gym camera introduction take about 7.5s + // in 2026-10-18 20-01-01; include 500ms for hardware variation. + stream.log( + "[Eterna Gym puzzle] Entering Gardenia's doorway; waiting 8000ms for transition/cutscene (includes 500ms buffer)."); + pbf_press_dpad(context, DPAD_UP, 80ms, 8000ms); + + for (const auto& move : sequence1){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } - pbf_press_button(context,BUTTON_A,150ms,300ms); - wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Lass Caroline",false); + //Button presses to initiate Trainer battle 01 (Lass Caroline). - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + wait_for_trainer_battle_menu(stream, context); + fight_trainer_using_moves(stream, context, 2, "Lass Caroline", false); - for (const auto& move : sequence2){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. - //Button presses to initiate Trainer battle 02 (Aroma Lady Jenna). + for (const auto& move : sequence2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } - pbf_press_button(context,BUTTON_A,150ms,300ms); - wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Aroma Lady Jenna",false); + //Button presses to initiate Trainer battle 02 (Aroma Lady Jenna). - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + wait_for_trainer_battle_menu(stream, context); + fight_trainer_using_moves(stream, context, 2, "Aroma Lady Jenna", false); - for (const auto& move : sequence3){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. - //Button presses to initiate Trainer battle 03 (Aroma Lady Angela). + for (const auto& move : sequence3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } - pbf_press_button(context,BUTTON_A,150ms,300ms); - wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Aroma Lady Angela",false); + //Button presses to initiate Trainer battle 03 (Aroma Lady Angela). - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + wait_for_trainer_battle_menu(stream, context); + fight_trainer_using_moves(stream, context, 2, "Aroma Lady Angela", false); - for (const auto& move : sequence4){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - //Button presses to initiate Trainer battle 04 (Beauty Lindsay). + for (const auto& move : sequence4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } - pbf_press_button(context,BUTTON_A,150ms,300ms); - wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Beauty Lindsay",false); + //Button presses to initiate Trainer battle 04 (Beauty Lindsay). - pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + wait_for_trainer_battle_menu(stream, context); + fight_trainer_using_moves(stream, context, 2, "Beauty Lindsay", false); - for (const auto& move : sequence5){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } + pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue - //Button presses to initiate Gym battle. + for (const auto& move : sequence5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } - pbf_press_button(context,BUTTON_A,150ms,300ms); - wait_for_trainer_battle_menu(stream,context); - fight_trainer_using_moves(stream,context,2,"Gardenia",true); + //Button presses to initiate Gym battle. - // The shared battle handler already advances reward dialogue - // while confirming the overworld. Do not require the badge - // animation again after it may have finished or be underway. - // Continue the existing B watcher through remaining rewards; - // its clear confirmation spans the observed ~5s badge gap. - stream.log("[Eterna Gym puzzle] Shared battle cleanup returned; clearing remaining Gardenia rewards with B."); - clear_post_battle_dialogue(stream,context,BUTTON_B,6000ms); - stream.log("[Eterna Gym puzzle] Gardenia post-battle dialogue cleared; starting exit path."); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + wait_for_trainer_battle_menu(stream, context); + fight_trainer_using_moves(stream, context, 2, "Gardenia", true); - for (const auto& move : sequence6){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } + // The shared battle handler already advances reward dialogue + // while confirming the overworld. Do not require the badge + // animation again after it may have finished or be underway. + // Continue the existing B watcher through remaining rewards; + // its clear confirmation spans the observed ~5s badge gap. + stream.log("[Eterna Gym puzzle] Shared battle cleanup returned; clearing remaining Gardenia rewards with B."); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); + stream.log("[Eterna Gym puzzle] Gardenia post-battle dialogue cleared; starting exit path."); - pbf_wait(context, 2000ms); + for (const auto& move : sequence6){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } - for(int i=0; i < 9; i++){ - pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); - } + pbf_wait(context, 2000ms); - //Exits Buiilding - pbf_wait(context, 1000ms); - } + for (int i = 0; i < 9; i++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 300ms); + } - std::string AutoStory_Segment_02::name() const{ return "02: First Badge to Second Badge"; } - std::string AutoStory_Segment_02::start_text() const{ return "Start: First Gym Badge acquired (Oreburgh City)."; } - std::string AutoStory_Segment_02::end_text() const{ return "End: Second Gym Badge acquired (Eterna City)."; } - - void AutoStory_Segment_02::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const{ - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); + //Exits Buiilding + pbf_wait(context, 1000ms); +} - AutoStory_checkpoint_0201().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0202().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0203().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0204().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0205().run_checkpoint(env, context, options, stats); - AutoStory_checkpoint_0206().run_checkpoint(env, context, options, stats); +std::string AutoStory_Segment_02::name() const { + return "02: First Badge to Second Badge"; +} +std::string AutoStory_Segment_02::start_text() const { + return "Start: First Gym Badge acquired (Oreburgh City)."; +} +std::string AutoStory_Segment_02::end_text() const { + return "End: Second Gym Badge acquired (Eterna City)."; +} - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); - } - std::string AutoStory_checkpoint_0201::name() const { return "02.01 - Outside Gym 1"; } - std::string AutoStory_checkpoint_0201::start_text() const { return "First Gym Badge acquired (Oreburgh City)."; } - std::string AutoStory_checkpoint_0201::end_text() const { return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; } - - void AutoStory_checkpoint_0201::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0201(env, context, options, stats); - } +void AutoStory_Segment_02::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_checkpoint_0201().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0202().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0203().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0204().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0205().run_checkpoint(env, context, options, stats); + AutoStory_checkpoint_0206().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} +std::string AutoStory_checkpoint_0201::name() const { + return "02.01 - Outside Gym 1"; +} +std::string AutoStory_checkpoint_0201::start_text() const { + return "First Gym Badge acquired (Oreburgh City)."; +} +std::string AutoStory_checkpoint_0201::end_text() const { + return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; +} +void AutoStory_checkpoint_0201::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0201(env, context, options, stats); +} - void checkpoint_0201( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - roark_to_floaroma(env, env.console, context); - }, +void checkpoint_0201(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + roark_to_floaroma(env, env.console, context); + }, - true - ); - } - std::string AutoStory_checkpoint_0202::name() const { return "02.02 - Floaroma Town"; } - std::string AutoStory_checkpoint_0202::start_text() const { - return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; - } - std::string AutoStory_checkpoint_0202::end_text() const { - return "Standing in Valley Windworks after defeating Commander Mars."; - } - void AutoStory_checkpoint_0202::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - floaroma_to_mars(env, env.console, context); - }, true); - } + true); +} +std::string AutoStory_checkpoint_0202::name() const { + return "02.02 - Floaroma Town"; +} +std::string AutoStory_checkpoint_0202::start_text() const { + return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; +} +std::string AutoStory_checkpoint_0202::end_text() const { + return "Standing in Valley Windworks after defeating Commander Mars."; +} +void AutoStory_checkpoint_0202::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + floaroma_to_mars(env, env.console, context); + }, + true); +} - std::string AutoStory_checkpoint_0203::name() const { return "02.03 - Route 205 to Eterna Forest"; } - std::string AutoStory_checkpoint_0203::start_text() const { return "Standing in Valley Windworks after defeating Commander Mars."; } - std::string AutoStory_checkpoint_0203::end_text() const { return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; } - - void AutoStory_checkpoint_0203::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0203(env, context, options, stats); - } +std::string AutoStory_checkpoint_0203::name() const { + return "02.03 - Route 205 to Eterna Forest"; +} +std::string AutoStory_checkpoint_0203::start_text() const { + return "Standing in Valley Windworks after defeating Commander Mars."; +} +std::string AutoStory_checkpoint_0203::end_text() const { + return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; +} +void AutoStory_checkpoint_0203::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0203(env, context, options, stats); +} - void checkpoint_0203( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - mars_to_eterna_forest(env, env.console, context); - }, +void checkpoint_0203(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + mars_to_eterna_forest(env, env.console, context); + }, - true - ); - } + true); +} - std::string AutoStory_checkpoint_0204::name() const{ return "02.04 - Eterna Forest"; } - std::string AutoStory_checkpoint_0204::start_text() const{ return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; } - std::string AutoStory_checkpoint_0204::end_text() const{ return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; } - - void AutoStory_checkpoint_0204::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const{ - checkpoint_0204(env, context, options, stats); - } +std::string AutoStory_checkpoint_0204::name() const { + return "02.04 - Eterna Forest"; +} +std::string AutoStory_checkpoint_0204::start_text() const { + return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; +} +std::string AutoStory_checkpoint_0204::end_text() const { + return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; +} +void AutoStory_checkpoint_0204::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0204(env, context, options, stats); +} - void checkpoint_0204( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ){ - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/){ - // TODO: implement Segment 02 gameplay logic - eterna_forest(env, env.console, context); - }, +void checkpoint_0204(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 02 gameplay logic + eterna_forest(env, env.console, context); + }, - true - ); - } + true); +} - std::string AutoStory_checkpoint_0205::name() const { return "02.05 - Eterna City to Gym"; } - std::string AutoStory_checkpoint_0205::start_text() const { return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; } - std::string AutoStory_checkpoint_0205::end_text() const { return "Inside Eterna Gym at the entrance, facing north."; } - - void AutoStory_checkpoint_0205::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0205(env, context, options, stats); - } +std::string AutoStory_checkpoint_0205::name() const { + return "02.05 - Eterna City to Gym"; +} +std::string AutoStory_checkpoint_0205::start_text() const { + return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; +} +std::string AutoStory_checkpoint_0205::end_text() const { + return "Inside Eterna Gym at the entrance, facing north."; +} +void AutoStory_checkpoint_0205::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0205(env, context, options, stats); +} - void checkpoint_0205( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - // TODO: implement Segment 02 gameplay logic - eterna_forest_exit_to_gym(env, env.console, context); - - }, - true - ); - } - std::string AutoStory_checkpoint_0206::name() const { return "02.06 - Inside Gym"; } - std::string AutoStory_checkpoint_0206::start_text() const { return "Inside Eterna Gym at the entrance, facing north."; } - std::string AutoStory_checkpoint_0206::end_text() const { return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; } - - void AutoStory_checkpoint_0206::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const { - checkpoint_0206(env, context, options, stats); - } +void checkpoint_0205(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + // TODO: implement Segment 02 gameplay logic + eterna_forest_exit_to_gym(env, env.console, context); + }, + true); +} +std::string AutoStory_checkpoint_0206::name() const { + return "02.06 - Inside Gym"; +} +std::string AutoStory_checkpoint_0206::start_text() const { + return "Inside Eterna Gym at the entrance, facing north."; +} +std::string AutoStory_checkpoint_0206::end_text() const { + return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; +} - void checkpoint_0206( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { - - //#. Eterna Gym navigation and battle - gym_Puzzle_02(env, env.console, context); - }, - true - ); - } +void AutoStory_checkpoint_0206::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0206(env, context, options, stats); +} - } - } + +void checkpoint_0206(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ + //#. Eterna Gym navigation and battle + gym_Puzzle_02(env, env.console, context); + }, + true); +} + +} +} } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h index eed30fc4bd..198590774f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h @@ -8,52 +8,43 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment02_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // Reused at safe walking points to synchronize Repel expiration and renewal. -enum class RepelRenewalChoice { YES, NO }; -void force_repel_expiry(VideoStream& stream, ProControllerContext& context, - RepelRenewalChoice choice, bool north_south=false, bool require_expiry=false); +enum class RepelRenewalChoice { + YES, + NO +}; +void force_repel_expiry(VideoStream& stream, ProControllerContext& context, RepelRenewalChoice choice, + bool north_south = false, bool require_expiry = false); -class AutoStory_Segment_02 : public AutoStory_Segment{ +class AutoStory_Segment_02 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint{ +class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: first badge acquired // end: Floaroma central house exit, facing south, Repel inactive -void checkpoint_0201( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0201(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); // Save/retry boundary at the central Floaroma house exit, matching 02.01 end. class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { @@ -61,100 +52,64 @@ class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0203( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0203(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0204( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0204(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0205( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0205(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0206 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0206( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0206(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 89a3329420..9e965562a5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -1,4 +1,4 @@ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" + /* BDSP AutoStory - Segment 03 * From after acquiring the second badge until after acquiring the third badge. * @@ -6,2063 +6,2272 @@ * */ -#include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include #include #include #include + +#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" -#include "CommonTools/VisualDetectors/ImageMatchDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" + +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "CommonTools/VisualDetectors/ImageMatchDetector.h" + +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" -#include "../Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "../Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" -#include "../Utils/PokemonBDSP_OverworldMatchers.h" -#include "../Utils/PokemonBDSP_Autostory_Segment_01_tools.h" -#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h" + #include "PokemonBDSP_AutoStory_Segment_02.h" + #include "PokemonBDSP_AutoStory_Segment_03.h" using namespace std::chrono_literals; -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ - static void segment03_finish_transition(VideoStream& stream, ProControllerContext& context, - const std::string& label) { - BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{visible}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Transition did not finish.",stream); - pbf_wait(context,500ms); - context.wait_for_all_requests(); +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { +static void segment03_finish_transition(VideoStream& stream, ProControllerContext& context, const std::string& label){ + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Transition did not finish.", stream); } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); +} - // Stationary destination confirmation: do not steer during an NPC reward - // scene or declare arrival from a fade that has only just started. - static void segment03_confirm_location(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, - size_t observations = 20, bool advance_dialogue = false, bool require_target = false, - OverworldMatchResult (*instruction_card)(const ImageRGB32&) = nullptr) { - context.wait_for_all_requests(); - size_t confirmations=0; - for (size_t observation=0;observation= 3){ + stream.log("[" + label + "] Scene confirmed.", COLOR_GREEN); + return; } - const auto match=frame ? matcher(*frame.frame) : OverworldMatchResult{}; - if (require_target ? match.at_target : match.located) { - if (++confirmations>=3) { stream.log("["+label+"] Scene confirmed.",COLOR_GREEN); return; } - pbf_wait(context,250ms); - } else { - confirmations=0; - if (advance_dialogue) pbf_press_button(context,BUTTON_A,150ms,500ms); - else pbf_wait(context,500ms); + pbf_wait(context, 250ms); + }else{ + confirmations = 0; + if (advance_dialogue){ + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + }else{ + pbf_wait(context, 500ms); } - context.wait_for_all_requests(); } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Expected scene not confirmed.", stream); +} + +// Watch the approach itself, cancelling queued movement as soon as the +// trainer speaks or a battle starts. The existing battle engine owns combat. +static void segment03_expected_encounter(VideoStream& stream, ProControllerContext& context, const std::string& label, + const std::function& approach, + Milliseconds encounter_wait = 45000ms, bool require_encounter = true){ + context.wait_for_all_requests(); + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + const int encountered = run_until(stream, context, + [&](ProControllerContext& ctx){ + approach(ctx); + pbf_wait(ctx, encounter_wait); + }, + {{dialogue}, {starting}, {menu}}); + if (encountered < 0){ + if (!require_encounter){ + stream.log("[" + label + "] No encounter within " + std::to_string(encounter_wait.count()) + + "ms after approach; trainer dodged, continuing.", + COLOR_GREEN); + return; + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Expected trainer encounter did not start.", stream); + } + stream.log("[" + label + "] Encounter detected; route movement cancelled."); + fight_trainer_using_moves(stream, context, 2, label, false); +} + +static void segment03_wait_image(VideoStream& stream, ProControllerContext& context, const std::string& resource, + const ImageFloatBox& box, const std::string& label){ + auto reference = std::make_shared(RESOURCE_PATH() + resource); + ImageMatchWatcher ready(reference, box, 25.0, false, 250ms); + if (wait_until(stream, context, 5s, {{ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Expected scene not confirmed.",stream); + label + ": Visual confirmation missing.", stream); } +} - // Watch the approach itself, cancelling queued movement as soon as the - // trainer speaks or a battle starts. The existing battle engine owns combat. - static void segment03_expected_encounter(VideoStream& stream, ProControllerContext& context, - const std::string& label, const std::function& approach, - Milliseconds encounter_wait=45000ms, bool require_encounter=true) { +static void segment03_register_bike(VideoStream& stream, ProControllerContext& context){ + // Receiving Bike adds new flags after the earlier pre-shop Bag pass. + if (!clear_new_item_flags(stream, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Bike: Could not clear reward flags.", stream); + } + BagItemTemplate bike; + bike.reference_path = "PokemonBDSP/Autostory/EternaBikeShop/BikeBag.png"; + bike.label = "Bike"; + bike.reference_row = 4; + if (!find_bag_item(stream, context, bike, 8)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Bike: Bike selection not confirmed.", stream); + } + // Locate the Key Items tab and Bike first, then use the fixed Register + // sequence without depending on popup, wheel, or confirmation images. + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + pbf_press_dpad(context, DPAD_DOWN, 80ms, 250ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 250ms); + pbf_press_button(context, BUTTON_A, 150ms, 500ms); + pbf_mash_button(context, BUTTON_B, 2000ms); + context.wait_for_all_requests(); + stream.log("[Eterna Bike] Registration inputs sent; preserving the reward's mounted state.", COLOR_GREEN); +} + +// Stop at the completed doorway transition, rather than spending the +// remaining movement budget inside the next map. +// Recorded Cycling Road movement may cross a gate before its budget ends. +// Cancel at either fade color and resume only after that fade has cleared. +// Recover the successful descent endpoint before spending eastward travel. +// Correct height first, while still west of the honey-tree collision column. +static void segment03_align_lower_stair_lane(VideoStream& stream, ProControllerContext& context){ + size_t confirmations = 0, missing = 0; + for (size_t observation = 0; observation < 40; observation++){ context.wait_for_all_requests(); - ShortDialogWatcher dialogue; - StartBattleDetector starting(stream.overlay()); - BattleMenuWatcher menu(BattleType::TRAINER); - const int encountered=run_until(stream,context, - [&](ProControllerContext& ctx) { approach(ctx); pbf_wait(ctx,encounter_wait); }, - {{dialogue},{starting},{menu}}); - if (encountered<0) { - if (!require_encounter) { - stream.log("["+label+"] No encounter within "+std::to_string(encounter_wait.count())+ - "ms after approach; trainer dodged, continuing.",COLOR_GREEN); + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: No video frame.", stream); + } + const auto match = match_route208_lower_stair_lane(*frame.frame); + stream.log("[Route 208 lower stair lane] located=" + std::to_string(match.located) + + ", target=" + std::to_string(match.at_target) + ", offset=(" + std::to_string(match.dx) + "," + + std::to_string(match.dy) + ")"); + if (!match.located){ + confirmations = 0; + if (++missing > 6){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: Cliff/tree anchors missing; no blind search.", stream); + } + pbf_wait(context, 500ms); + continue; + } + missing = 0; + if (match.at_target && !match.coarse){ + if (++confirmations >= 3){ return; } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Expected trainer encounter did not start.",stream); + pbf_wait(context, 250ms); + continue; } - stream.log("["+label+"] Encounter detected; route movement cancelled."); - fight_trainer_using_moves(stream, context, 2, label, false); + confirmations = 0; + const bool vertical = match.coarse || std::abs(match.dy) > 1.0 / 54; + const double error = vertical ? match.dy * 9.0 / 16 : match.dx; + const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 1.0 / 64 ? 40ms : 16ms; + pbf_move_left_joystick(context, + {vertical ? 0.0 : (error > 0 ? 1.0 : -1.0), vertical ? (error < 0 ? 1.0 : -1.0) : 0.0}, + duration, 0ms); + pbf_wait(context, 500ms); } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 208 lower stair lane: Could not confirm the safe descent endpoint.", + stream); +} - static void segment03_wait_image(VideoStream& stream, ProControllerContext& context, - const std::string& resource, const ImageFloatBox& box, const std::string& label) { - auto reference=std::make_shared(RESOURCE_PATH()+resource); - ImageMatchWatcher ready(reference,box,25.0,false,250ms); - if (wait_until(stream,context,5s,{{ready}}) != 0) +static bool segment03_cycling_move(VideoStream& stream, ProControllerContext& context, double x, double y, + Milliseconds duration, Milliseconds rest, const std::string& label, + bool require_transition = false){ + context.wait_for_all_requests(); + BlackScreenWatcher black; + WhiteScreenWatcher white; + BattleMenuWatcher battle(BattleType::TRAINER); + const int result = run_until(stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, rest); + }, + {{black}, {white}, {battle}}); + if (result == 2){ + fight_trainer_using_moves(stream, context, 2, label + " encounter", false); + return false; + } + if (result < 0){ + if (require_transition){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Visual confirmation missing.",stream); + label + ": Transition did not start.", stream); + } + return false; + } + BlackScreenWatcher black_gone(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + WhiteScreenWatcher white_gone(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, + 500ms); + const int finished = result == 1 ? wait_until(stream, context, 30s, {{white_gone}}) + : wait_until(stream, context, 30s, {{black_gone}}); + if (finished != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Transition did not finish.", stream); } + // A cleared fade can precede restored movement control. + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + return true; +} - static void segment03_register_bike(VideoStream& stream, ProControllerContext& context) { - // Receiving Bike adds new flags after the earlier pre-shop Bag pass. - if (!clear_new_item_flags(stream,context)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Bike: Could not clear reward flags.",stream); - BagItemTemplate bike; - bike.reference_path="PokemonBDSP/Autostory/EternaBikeShop/BikeBag.png"; - bike.label="Bike"; - bike.reference_row=4; - if (!find_bag_item(stream,context,bike,8)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Bike: Bike selection not confirmed.",stream); - // Locate the Key Items tab and Bike first, then use the fixed Register - // sequence without depending on popup, wheel, or confirmation images. - pbf_press_button(context,BUTTON_A,150ms,500ms); - pbf_press_dpad(context,DPAD_DOWN,80ms,250ms); - pbf_press_button(context,BUTTON_A,150ms,500ms); - pbf_press_dpad(context,DPAD_RIGHT,80ms,250ms); - pbf_press_button(context,BUTTON_A,150ms,500ms); - pbf_mash_button(context,BUTTON_B,2000ms); - context.wait_for_all_requests(); - stream.log("[Eterna Bike] Registration inputs sent; preserving the reward's mounted state.",COLOR_GREEN); - } - - // Stop at the completed doorway transition, rather than spending the - // remaining movement budget inside the next map. - // Recorded Cycling Road movement may cross a gate before its budget ends. - // Cancel at either fade color and resume only after that fade has cleared. - // Recover the successful descent endpoint before spending eastward travel. - // Correct height first, while still west of the honey-tree collision column. - static void segment03_align_lower_stair_lane(VideoStream& stream, ProControllerContext& context) { - size_t confirmations=0,missing=0; - for (size_t observation=0;observation<40;observation++) { - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if (!frame) +// Returns true if an encounter began during alignment. The battle strip +// then owns it and counts completion without replaying entry movement. +static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + size_t confirmed = 0, missing = 0; + bool have_pose = false; + double previous_dx = 0, previous_dy = 0; + size_t observations_without_strong_alignment = 0; + for (size_t observation = 0; observations_without_strong_alignment < 240; observation++){ + const auto frame = stream.video().snapshot(); + const auto match = frame ? match_cycling_road_bridge_approach(*frame.frame) : OverworldMatchResult{}; + const bool stable = match.located && have_pose && std::abs(match.dx - previous_dx) <= 1.0 / 192 && + std::abs(match.dy - previous_dy) <= 1.0 / 108; + // Reliable rail/deck geometry and a settled pose renew the budget. + // Keep correcting while the visual alignment remains strong. + if (stable){ + observations_without_strong_alignment = 0; + }else{ + ++observations_without_strong_alignment; + } + std::string diagnostic = + "[Cycling Road bridge] observation=" + std::to_string(observation + 1) + + " located=" + std::to_string(match.located) + " target=" + std::to_string(match.at_target) + + " stable=" + std::to_string(stable) + + " observations_without_strong_alignment=" + std::to_string(observations_without_strong_alignment) + + " offset=" + std::to_string(match.dx) + "," + std::to_string(match.dy); + for (const auto& anchor : match.landmarks){ + diagnostic += " [" + anchor.name + " coverage=" + std::to_string(anchor.score) + + " accepted=" + std::to_string(anchor.accepted) + "]"; + } + stream.log(diagnostic); + double x = 0, y = 0; + Milliseconds hold = 0ms, rest = 250ms; + if (!match.located){ + confirmed = 0; + have_pose = false; + if (++missing >= 6){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 lower stair lane: No video frame.",stream); - const auto match=match_route208_lower_stair_lane(*frame.frame); - stream.log("[Route 208 lower stair lane] located="+std::to_string(match.located)+ - ", target="+std::to_string(match.at_target)+ - ", offset=("+std::to_string(match.dx)+","+std::to_string(match.dy)+")"); - if (!match.located) { - confirmations=0; - if (++missing>6) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 lower stair lane: Cliff/tree anchors missing; no blind search.",stream); - pbf_wait(context,500ms); - continue; + "Cycling Road: Bridge deck and right rail not acquired.", + stream); } - missing=0; - if (match.at_target && !match.coarse) { - if (++confirmations>=3) return; - pbf_wait(context,250ms); - continue; + rest = 500ms; + }else{ + missing = 0; + if (std::abs(match.dx) > .32 || std::abs(match.dy) > .60){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Bridge alignment offset exceeds bounds.", + stream); } - confirmations=0; - const bool vertical=match.coarse || std::abs(match.dy)>1.0/54; - const double error=vertical ? match.dy*9.0/16 : match.dx; - const auto duration=std::abs(error)>1.0/24 ? 80ms : - std::abs(error)>1.0/64 ? 40ms : 16ms; - pbf_move_left_joystick(context, - {vertical ? 0.0 : (error>0 ? 1.0 : -1.0), - vertical ? (error<0 ? 1.0 : -1.0) : 0.0},duration,0ms); - pbf_wait(context,500ms); + if (!stable){ + // The camera keeps easing after the bicycle stops. Require + // two agreeing observations before every new correction. + confirmed = 0; + }else if (match.at_target){ + if (++confirmed >= 3){ + stream.log( + "[Cycling Road] Upper white platform/right rail pose confirmed; starting watched downhill entry.", + COLOR_GREEN); + return false; + } + }else{ + confirmed = 0; + // Align against the rail first. The demonstrated target is + // on the stationary upper white platform, before gravity. + if (std::abs(match.dx) > .012){ + x = match.dx > 0 ? 1 : -1; + hold = std::abs(match.dx) > .04 ? 320ms : 240ms; + }else if (std::abs(match.dy) > 1.0 / 54){ + y = match.dy > 0 ? -1 : 1; + hold = std::abs(match.dy) > .06 ? 320ms : 200ms; + } + } + previous_dx = match.dx; + previous_dy = match.dy; + // A movement invalidates the old pose. Reacquire two fresh + // settled measurements before another movement is allowed. + have_pose = hold == 0ms; } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 lower stair lane: Could not confirm the safe descent endpoint.",stream); - } - - static bool segment03_cycling_move(VideoStream& stream, ProControllerContext& context, - double x, double y, Milliseconds duration, Milliseconds rest, - const std::string& label, bool require_transition = false) { - context.wait_for_all_requests(); BlackScreenWatcher black; WhiteScreenWatcher white; - BattleMenuWatcher battle(BattleType::TRAINER); - const int result=run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{x,y},duration,0ms); - pbf_wait(ctx,rest); - },{{black},{white},{battle}}); - if (result==2) { - fight_trainer_using_moves(stream, context, 2, label+" encounter", false); - return false; - } - if (result<0) { - if (require_transition) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Transition did not start.",stream); - return false; + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + const int interrupted = run_until(stream, context, + [&](ProControllerContext& ctx){ + if (hold > 0ms){ + pbf_move_left_joystick(ctx, {x, y}, hold, rest); + }else{ + pbf_wait(ctx, rest); + } + ctx.wait_for_all_requests(); + }, + {{black}, {white}, {dialogue}, {starting}, {menu}}); + if (interrupted >= 2){ + stream.log("[Cycling Road] Encounter during bridge approach; alignment movement cancelled."); + return true; } - BlackScreenWatcher black_gone(COLOR_RED,{0.1,0.1,0.8,0.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - WhiteScreenWatcher white_gone(COLOR_RED,{0.1,0.1,0.8,0.8}, - 500,10,WhiteScreenWatcher::FinderType::GONE,500ms); - const int finished=result==1 - ? wait_until(stream,context,30s,{{white_gone}}) - : wait_until(stream,context,30s,{{black_gone}}); - if (finished!=0) + if (interrupted >= 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Transition did not finish.",stream); - // A cleared fade can precede restored movement control. - pbf_wait(context,2000ms); - context.wait_for_all_requests(); - return true; + "Cycling Road: Unexpected transition during bridge alignment.", + stream); + } } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Bridge lane alignment did not converge.", stream); +} - // Returns true if an encounter began during alignment. The battle strip - // then owns it and counts completion without replaying entry movement. - static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContext& context) { + +static void segment03_cycle_road_battles(VideoStream& stream, ProControllerContext& context, bool encounter_pending){ + size_t completed = 0; + auto watch = [&](bool initial){ context.wait_for_all_requests(); - size_t confirmed=0,missing=0; - bool have_pose=false; - double previous_dx=0,previous_dy=0; - size_t observations_without_strong_alignment=0; - for (size_t observation=0;observations_without_strong_alignment<240;observation++) { - const auto frame=stream.video().snapshot(); - const auto match=frame ? match_cycling_road_bridge_approach(*frame.frame) : OverworldMatchResult{}; - const bool stable=match.located && have_pose && - std::abs(match.dx-previous_dx)<=1.0/192 && - std::abs(match.dy-previous_dy)<=1.0/108; - // Reliable rail/deck geometry and a settled pose renew the budget. - // Keep correcting while the visual alignment remains strong. - if (stable) observations_without_strong_alignment=0; - else ++observations_without_strong_alignment; - std::string diagnostic="[Cycling Road bridge] observation="+std::to_string(observation+1)+ - " located="+std::to_string(match.located)+" target="+std::to_string(match.at_target)+ - " stable="+std::to_string(stable)+ - " observations_without_strong_alignment="+std::to_string(observations_without_strong_alignment)+ - " offset="+std::to_string(match.dx)+","+std::to_string(match.dy); - for (const auto& anchor:match.landmarks) - diagnostic+=" ["+anchor.name+" coverage="+std::to_string(anchor.score)+ - " accepted="+std::to_string(anchor.accepted)+"]"; - stream.log(diagnostic); - double x=0,y=0; - Milliseconds hold=0ms,rest=250ms; - if (!match.located) { - confirmed=0;have_pose=false; - if (++missing>=6) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Bridge deck and right rail not acquired.",stream); - rest=500ms; - } else { - missing=0; - if (std::abs(match.dx)>.32 || std::abs(match.dy)>.60) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Bridge alignment offset exceeds bounds.",stream); - if (!stable) { - // The camera keeps easing after the bicycle stops. Require - // two agreeing observations before every new correction. - confirmed=0; - } else if (match.at_target) { - if (++confirmed>=3) { - stream.log("[Cycling Road] Upper white platform/right rail pose confirmed; starting watched downhill entry.",COLOR_GREEN); - return false; - } - } else { - confirmed=0; - // Align against the rail first. The demonstrated target is - // on the stationary upper white platform, before gravity. - if (std::abs(match.dx)>.012) { - x=match.dx>0 ? 1 : -1; - hold=std::abs(match.dx)>.04 ? 320ms : 240ms; - } else if (std::abs(match.dy)>1.0/54) { - y=match.dy>0 ? -1 : 1; - hold=std::abs(match.dy)>.06 ? 320ms : 200ms; - } - } - previous_dx=match.dx;previous_dy=match.dy; - // A movement invalidates the old pose. Reacquire two fresh - // settled measurements before another movement is allowed. - have_pose=hold==0ms; - } - BlackScreenWatcher black; - WhiteScreenWatcher white; - ShortDialogWatcher dialogue; - StartBattleDetector starting(stream.overlay()); - BattleMenuWatcher menu(BattleType::TRAINER); - const int interrupted=run_until(stream,context, - [&](ProControllerContext& ctx) { - if (hold>0ms) pbf_move_left_joystick(ctx,{x,y},hold,rest); - else pbf_wait(ctx,rest); - ctx.wait_for_all_requests(); - },{{black},{white},{dialogue},{starting},{menu}}); - if (interrupted>=2) { - stream.log("[Cycling Road] Encounter during bridge approach; alignment movement cancelled."); - return true; + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + return run_until(stream, context, + [&](ProControllerContext& ctx){ + if (initial){ + pbf_move_left_joystick(ctx, {0, -1}, 3300ms, 0ms); + } + // From the first encounter onward the controller stays + // neutral. The bicycle rolls naturally into the next trainer. + pbf_wait(ctx, completed >= 3 ? 7000ms : 45000ms); + ctx.wait_for_all_requests(); + }, + {{dialogue}, {starting}, {menu}}); + }; + while (completed < 4){ + const int result = encounter_pending ? 0 : watch(completed == 0); + encounter_pending = false; + if (result < 0){ + if (completed >= 3){ + stream.log("[Cycling Road] Three battles complete; 7s without another encounter. Continuing south."); + return; } - if (interrupted>=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Unexpected transition during bridge alignment.",stream); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Expected at least three battles before the south gate.", stream); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Bridge lane alignment did not converge.",stream); + fight_trainer_using_moves(stream, context, 2, "Cycling Road encounter " + std::to_string(completed + 1), false); + ++completed; + stream.log("[Cycling Road] Completed battles: " + std::to_string(completed) + "/4."); } + stream.log("[Cycling Road] Four battles complete; skipping the quiet wait."); +} - - static void segment03_cycle_road_battles(VideoStream& stream, ProControllerContext& context, - bool encounter_pending) { - size_t completed=0; - auto watch=[&](bool initial) { - context.wait_for_all_requests(); - ShortDialogWatcher dialogue; - StartBattleDetector starting(stream.overlay()); - BattleMenuWatcher menu(BattleType::TRAINER); - return run_until(stream,context, - [&](ProControllerContext& ctx) { - if (initial) pbf_move_left_joystick(ctx,{0,-1},3300ms,0ms); - // From the first encounter onward the controller stays - // neutral. The bicycle rolls naturally into the next trainer. - pbf_wait(ctx,completed>=3 ? 7000ms : 45000ms); - ctx.wait_for_all_requests(); - },{{dialogue},{starting},{menu}}); - }; - while (completed<4) { - const int result=encounter_pending ? 0 : watch(completed==0); - encounter_pending=false; - if (result<0) { - if (completed>=3) { - stream.log("[Cycling Road] Three battles complete; 7s without another encounter. Continuing south."); - return; - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Expected at least three battles before the south gate.",stream); - } - fight_trainer_using_moves(stream, context, 2, "Cycling Road encounter "+std::to_string(completed+1), false); - ++completed; - stream.log("[Cycling Road] Completed battles: "+std::to_string(completed)+"/4."); +static void segment03_doorway(VideoStream& stream, ProControllerContext& context, bool north, + std::chrono::milliseconds duration, const std::string& label, double horizontal = 0){ + context.wait_for_all_requests(); + // Horizontal HQ stairs must cancel at fade-out, before loading can + // swallow or carry movement onto the next floor. Vertical door callers + // retain their existing transition behavior. + if (horizontal != 0){ + BlackScreenWatcher entering; + if (run_until(stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {horizontal, 0}, duration, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{entering}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Stair transition did not start.", stream); } - stream.log("[Cycling Road] Four battles complete; skipping the quiet wait."); + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Stair transition did not finish.", stream); + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + return; + } + BlackScreenOverWatcher transition(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + int result = + run_until(stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, north ? 1.0 : -1.0}, duration, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{transition}}); + if (result != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Doorway transition not confirmed.", stream); } + segment03_finish_transition(stream, context, label); +} - static void segment03_doorway(VideoStream& stream, ProControllerContext& context, - bool north, std::chrono::milliseconds duration, const std::string& label, double horizontal = 0) { - context.wait_for_all_requests(); - // Horizontal HQ stairs must cancel at fade-out, before loading can - // swallow or carry movement onto the next floor. Vertical door callers - // retain their existing transition behavior. - if (horizontal != 0) { - BlackScreenWatcher entering; - if (run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{horizontal,0},duration,0ms); - pbf_wait(ctx,3000ms); - },{{entering}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Stair transition did not start.",stream); - BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{visible}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Stair transition did not finish.",stream); - pbf_wait(context,500ms); - context.wait_for_all_requests(); - return; +void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ + auto fight = [&](const std::string& label, bool post_battle_dialogue_cleanup = false){ + fight_trainer_using_moves(stream, context, 2, label, post_battle_dialogue_cleanup); + }; + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1060ms, 0ms); + pbf_wait(context, 225ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 4232ms, 0ms); + pbf_wait(context, 269ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 287ms, 0ms); + pbf_wait(context, 1777ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 367ms, 0ms); + pbf_wait(context, 237ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1140ms, 0ms); + pbf_wait(context, 2621ms); + if (!heal_pokemon(stream, context, "Eterna before Galactic HQ")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Center exit was not confirmed.", stream); + } + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1895ms, 0ms); + pbf_wait(context, 130ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 710ms, 0ms); + pbf_wait(context, 921ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 129ms, 0ms); + pbf_wait(context, 354ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 813ms, 0ms); + pbf_wait(context, 565ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 618ms, 0ms); + pbf_wait(context, 483ms); + context.wait_for_all_requests(); + // Align with the sign and Center emblem before trying Cut. Never search blindly or + // keep pushing north against the hedge when the tree is east of us. + size_t hq_cut_confirmations = 0, hq_cut_missing = 0; + for (size_t correction = 0; correction < 20; correction++){ + const auto frame = stream.video().snapshot(); + const auto position = frame ? match_galactic_hq_cut(*frame.frame) : OverworldMatchResult{}; + for (const auto& anchor : position.landmarks){ + stream.log("[Galactic HQ Cut] " + anchor.name + " score=" + std::to_string(anchor.score) + + ", margin=" + std::to_string(anchor.margin) + ", offset=(" + std::to_string(anchor.dx) + "," + + std::to_string(anchor.dy) + ")"); } - BlackScreenOverWatcher transition(COLOR_RED,{0.1,0.1,0.8,0.8}); - int result=run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,north ? 1.0 : -1.0},duration,0ms); - pbf_wait(ctx,3000ms); - },{{transition}}); - if (result!=0) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,label+": Doorway transition not confirmed.",stream); - segment03_finish_transition(stream,context,label); - } - - void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context) { - auto fight=[&](const std::string& label, bool post_battle_dialogue_cleanup = false) { - fight_trainer_using_moves(stream, context, 2, label, post_battle_dialogue_cleanup); - }; - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1060ms, 0ms); - pbf_wait(context, 225ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 4232ms, 0ms); - pbf_wait(context, 269ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 287ms, 0ms); - pbf_wait(context, 1777ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 367ms, 0ms); - pbf_wait(context, 237ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1140ms, 0ms); - pbf_wait(context, 2621ms); - if (!heal_pokemon(stream,context,"Eterna before Galactic HQ")) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit was not confirmed.",stream); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1895ms, 0ms); - pbf_wait(context, 130ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 710ms, 0ms); - pbf_wait(context, 921ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 129ms, 0ms); - pbf_wait(context, 354ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 813ms, 0ms); - pbf_wait(context, 565ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 618ms, 0ms); - pbf_wait(context, 483ms); - context.wait_for_all_requests(); - // Align with the sign and Center emblem before trying Cut. Never search blindly or - // keep pushing north against the hedge when the tree is east of us. - size_t hq_cut_confirmations=0,hq_cut_missing=0; - for (size_t correction=0;correction<20;correction++) { - const auto frame=stream.video().snapshot(); - const auto position=frame ? match_galactic_hq_cut(*frame.frame) : OverworldMatchResult{}; - for (const auto& anchor:position.landmarks) - stream.log("[Galactic HQ Cut] "+anchor.name+" score="+std::to_string(anchor.score)+ - ", margin="+std::to_string(anchor.margin)+", offset=("+ - std::to_string(anchor.dx)+","+std::to_string(anchor.dy)+")"); - if (!position.located) { - hq_cut_confirmations=0; - if (++hq_cut_missing>6) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Front bush sign/Center alignment not confirmed.",stream); - // Let a passing shadow clear; never search by moving blindly. - pbf_wait(context,500ms); - context.wait_for_all_requests(); - continue; + if (!position.located){ + hq_cut_confirmations = 0; + if (++hq_cut_missing > 6){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Galactic HQ: Front bush sign/Center alignment not confirmed.", + stream); } - hq_cut_missing=0; - if (std::abs(position.dx)>.20) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Front bush alignment offset exceeds bounds.",stream); - if (position.at_target) { - if (++hq_cut_confirmations>=3) break; - } else { - hq_cut_confirmations=0; - pbf_move_left_joystick(context,{position.dx>0 ? 1.0 : -1.0,0}, - std::abs(position.dx)>1.0/24 ? 80ms : 40ms,0ms); - } - pbf_wait(context,250ms); + // Let a passing shadow clear; never search by moving blindly. + pbf_wait(context, 500ms); context.wait_for_all_requests(); + continue; } - if (hq_cut_confirmations<3) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Front bush column did not converge.",stream); - // Some attempts reach the bush before the interaction accepts input. - // Confirm its dialogue while stationary, then finish the Cut animation. - ShortDialogWatcher hq_cut_dialogue; - if (run_until(stream,context, - [](ProControllerContext& ctx) { - for (size_t press=0; press<30; press++) { - pbf_move_left_joystick(ctx,{0,1},80ms,100ms); - pbf_press_button(ctx,BUTTON_A,150ms,500ms); - } - },{{hq_cut_dialogue}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Front bush interaction did not produce dialogue.",stream); - clear_post_battle_dialogue(stream,context,BUTTON_A,6000ms); + hq_cut_missing = 0; + if (std::abs(position.dx) > .20){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Galactic HQ: Front bush alignment offset exceeds bounds.", stream); + } + if (position.at_target){ + if (++hq_cut_confirmations >= 3){ + break; + } + }else{ + hq_cut_confirmations = 0; + pbf_move_left_joystick(context, {position.dx > 0 ? 1.0 : -1.0, 0}, + std::abs(position.dx) > 1.0 / 24 ? 80ms : 40ms, 0ms); + } + pbf_wait(context, 250ms); context.wait_for_all_requests(); - // Cut succeeds, but the Center exit can leave us one tile west of - // the HQ doorway. Do not replay the interior route against its wall. - BlackScreenWatcher hq_entering; - const int hq_entry = run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},1507ms,193ms); + } + if (hq_cut_confirmations < 3){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush column did not converge.", stream); + } + // Some attempts reach the bush before the interaction accepts input. + // Confirm its dialogue while stationary, then finish the Cut animation. + ShortDialogWatcher hq_cut_dialogue; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 30; press++){ + pbf_move_left_joystick(ctx, {0, 1}, 80ms, 100ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + } + }, + {{hq_cut_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush interaction did not produce dialogue.", + stream); + } + clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); + context.wait_for_all_requests(); + // Cut succeeds, but the Center exit can leave us one tile west of + // the HQ doorway. Do not replay the interior route against its wall. + BlackScreenWatcher hq_entering; + const int hq_entry = run_until( + stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 1507ms, 193ms); + ctx.wait_for_all_requests(); + for (size_t correction = 0; correction < 4; correction++){ + stream.log("[Galactic HQ entry] No transition; correcting east toward doorway, attempt " + + std::to_string(correction + 1) + "/4."); + pbf_move_left_joystick(ctx, {1, 0}, 80ms, 100ms); + pbf_move_left_joystick(ctx, {0, 1}, 350ms, 250ms); ctx.wait_for_all_requests(); - for (size_t correction=0; correction<4; correction++) { - stream.log("[Galactic HQ entry] No transition; correcting east toward doorway, attempt "+ - std::to_string(correction+1)+"/4."); - pbf_move_left_joystick(ctx,{1,0},80ms,100ms); - pbf_move_left_joystick(ctx,{0,1},350ms,250ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx,1500ms); - },{{hq_entering}}); - if (hq_entry != 0) OperationFailedExceptionWithScreenshot::fire( + } + pbf_wait(ctx, 1500ms); + }, + {{hq_entering}}); + if (hq_entry != 0){ + OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Doorway transition not detected after bounded east correction.",stream); - stream.log("[Galactic HQ entry] Doorway transition detected; waiting for interior."); - BlackScreenWatcher hq_visible(COLOR_RED,{0.1,0.1,0.8,0.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{hq_visible}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Entry transition did not finish.",stream); - pbf_wait(context,500ms); - context.wait_for_all_requests(); - // galacticHQ.txt starts at the ground-floor entrance mat. - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1247ms, 0ms); - pbf_wait(context, 237ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1246ms, 0ms); - pbf_wait(context, 2989ms); - pbf_press_button(context,BUTTON_A,150ms,250ms); - fight("Galactic HQ entrance grunts",false); - // With one usable Pokemon these grunts battle consecutively. The next - // intro can outlast the ordinary dialogue quiet period. Keep movement - // stopped and reuse the battle handler only if its command menu appears. - // A party that fought both together needs no second battle handler. - BattleMenuWatcher hq_next_grunt(BattleType::TRAINER); - const int hq_next_battle = run_until(stream,context, - [](ProControllerContext& ctx) { pbf_mash_button(ctx,BUTTON_B,30000ms); }, - {{hq_next_grunt}}); - if (hq_next_battle == 0) { - stream.log("[Galactic HQ] Second entrance grunt detected before stair movement."); - fight("Galactic HQ second entrance grunt",false); - } - pbf_move_left_joystick(context, {0.000000, 1.000000}, 580ms, 0ms); - pbf_wait(context, 220ms); - segment03_doorway(stream,context,true,1077ms,"Galactic HQ stairs 1F to 2F",-1.0); - pbf_wait(context, 1145ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 758ms, 0ms); - pbf_wait(context, 487ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1633ms, 0ms); - pbf_wait(context, 254ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 925ms, 0ms); - pbf_wait(context, 188ms); - segment03_doorway(stream,context,true,1145ms,"Galactic HQ stairs 2F to 3F",-1.0); - pbf_wait(context, 1455ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 410ms, 0ms); - pbf_wait(context, 679ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 744ms, 0ms); - pbf_wait(context, 2084ms); - pbf_press_button(context,BUTTON_A,150ms,250ms); - fight("Galactic HQ upper-floor grunt",false); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2119ms, 0ms); - pbf_wait(context, 903ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 263ms, 0ms); - pbf_wait(context, 984ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 342ms, 0ms); - pbf_wait(context, 1806ms); - pbf_press_button(context,BUTTON_A,150ms,250ms); - fight("Galactic HQ Scientist",false); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 425ms, 0ms); - pbf_wait(context, 161ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 723ms, 0ms); - pbf_wait(context, 1639ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 494ms, 0ms); - pbf_wait(context, 678ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 409ms, 0ms); - pbf_wait(context, 858ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 389ms, 0ms); - pbf_wait(context, 1189ms); - // Align directly above Jupiter, then turn south to interact without - // trying to enter the narrow gap between her and the rescued NPC. - align_to_overworld_landmarks(stream,context,match_galactic_hq_jupiter, - "Galactic HQ Jupiter",false,true); - // Use an explicit Down press: the 16ms stick pulse can leave the - // previous right-facing direction active and interact with Clefairy. - pbf_press_dpad(context,DPAD_DOWN,80ms,250ms); - context.wait_for_all_requests(); - // Advance the introduction before entering the battle handler's - // B-based waiting loop. A single press may only open the first text. - BattleMenuWatcher jupiter_menu(BattleType::TRAINER); - if (run_until(stream,context, - [](ProControllerContext& ctx) { - for (size_t press=0;press<30;press++) - pbf_press_button(ctx,BUTTON_A,150ms,500ms); - pbf_wait(ctx,15000ms); - },{{jupiter_menu}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Jupiter interaction did not reach the battle menu.",stream); - fight("Commander Jupiter",true); - // Jupiter's departure and the rescued family have chained dialogue. - clear_post_battle_dialogue(stream,context,BUTTON_A,6000ms); - stream.log("[Galactic HQ] Rescue dialogue cleared; following recorded exit.",COLOR_GREEN); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 691ms, 0ms); - pbf_wait(context, 575ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 330ms, 0ms); - pbf_wait(context, 2575ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 523ms, 0ms); - pbf_wait(context, 239ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 717ms, 0ms); - pbf_wait(context, 1721ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 306ms, 0ms); - pbf_wait(context, 294ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1041ms, 0ms); - pbf_wait(context, 307ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 431ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 999ms, 0ms); - pbf_wait(context, 573ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 343ms, 0ms); - pbf_wait(context, 175ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 999ms, 0ms); - pbf_wait(context, 456ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 277ms, 0ms); - pbf_wait(context, 122ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 654ms, 0ms); - pbf_wait(context, 1092ms); - context.wait_for_all_requests(); - BlackScreenOverWatcher hq_leaving(COLOR_RED,{0.1,0.1,0.8,0.8}); - if (run_until(stream,context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx,{0,-1},2350ms,0ms); - pbf_move_left_joystick(ctx,{-1,-1},2744ms,0ms); - pbf_wait(ctx,3000ms); - },{{hq_leaving}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Exit transition not confirmed.",stream); - segment03_finish_transition(stream,context,"Galactic HQ exit"); - segment03_confirm_location(stream,context,match_galactic_hq_exterior,"Galactic HQ exterior"); - // Clamp against the uncut front bush; checkpoint 03.02 starts here. - pbf_move_left_joystick(context,{0,-1},350ms,500ms); - context.wait_for_all_requests(); - stream.log("[Galactic HQ] Outside, facing the front Cut bush.",COLOR_GREEN); - } - static void eterna_mart_escape_ropes(VideoStream& stream, ProControllerContext& context) { - stream.log("[Eterna Mart] Buying exactly 3 Escape Ropes after the Jupiter heal."); - // Recorded Center-exit to Mart-door path (eternamart1.txt). - pbf_move_left_joystick(context,{0,-1},2713ms,0ms); - pbf_wait(context,444ms); - pbf_move_left_joystick(context,{1,0},587ms,0ms); - pbf_wait(context,705ms); - segment03_doorway(stream,context,true,896ms,"Eterna Mart entrance"); - // Standard Mart layout: walk north from the entrance to the clerk. - pbf_move_left_joystick(context,{0,1},885ms,300ms); - if (!buy_items(stream,context,{{ShopItem::EscapeRope,3}})) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Mart: Could not buy 3 Escape Ropes.",stream); - segment03_doorway(stream,context,false,5000ms,"Eterna Mart exit"); + "Galactic HQ: Doorway transition not detected after bounded east correction.", stream); + } + stream.log("[Galactic HQ entry] Doorway transition detected; waiting for interior."); + BlackScreenWatcher hq_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{hq_visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Entry transition did not finish.", stream); + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + // galacticHQ.txt starts at the ground-floor entrance mat. + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1247ms, 0ms); + pbf_wait(context, 237ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1246ms, 0ms); + pbf_wait(context, 2989ms); + pbf_press_button(context, BUTTON_A, 150ms, 250ms); + fight("Galactic HQ entrance grunts", false); + // With one usable Pokemon these grunts battle consecutively. The next + // intro can outlast the ordinary dialogue quiet period. Keep movement + // stopped and reuse the battle handler only if its command menu appears. + // A party that fought both together needs no second battle handler. + BattleMenuWatcher hq_next_grunt(BattleType::TRAINER); + const int hq_next_battle = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 30000ms); + }, + {{hq_next_grunt}}); + if (hq_next_battle == 0){ + stream.log("[Galactic HQ] Second entrance grunt detected before stair movement."); + fight("Galactic HQ second entrance grunt", false); + } + pbf_move_left_joystick(context, {0.000000, 1.000000}, 580ms, 0ms); + pbf_wait(context, 220ms); + segment03_doorway(stream, context, true, 1077ms, "Galactic HQ stairs 1F to 2F", -1.0); + pbf_wait(context, 1145ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 758ms, 0ms); + pbf_wait(context, 487ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1633ms, 0ms); + pbf_wait(context, 254ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 925ms, 0ms); + pbf_wait(context, 188ms); + segment03_doorway(stream, context, true, 1145ms, "Galactic HQ stairs 2F to 3F", -1.0); + pbf_wait(context, 1455ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 410ms, 0ms); + pbf_wait(context, 679ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 744ms, 0ms); + pbf_wait(context, 2084ms); + pbf_press_button(context, BUTTON_A, 150ms, 250ms); + fight("Galactic HQ upper-floor grunt", false); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2119ms, 0ms); + pbf_wait(context, 903ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 263ms, 0ms); + pbf_wait(context, 984ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 342ms, 0ms); + pbf_wait(context, 1806ms); + pbf_press_button(context, BUTTON_A, 150ms, 250ms); + fight("Galactic HQ Scientist", false); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 425ms, 0ms); + pbf_wait(context, 161ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 723ms, 0ms); + pbf_wait(context, 1639ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 494ms, 0ms); + pbf_wait(context, 678ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 409ms, 0ms); + pbf_wait(context, 858ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 389ms, 0ms); + pbf_wait(context, 1189ms); + // Align directly above Jupiter, then turn south to interact without + // trying to enter the narrow gap between her and the rescued NPC. + align_to_overworld_landmarks(stream, context, match_galactic_hq_jupiter, "Galactic HQ Jupiter", false, true); + // Use an explicit Down press: the 16ms stick pulse can leave the + // previous right-facing direction active and interact with Clefairy. + pbf_press_dpad(context, DPAD_DOWN, 80ms, 250ms); + context.wait_for_all_requests(); + // Advance the introduction before entering the battle handler's + // B-based waiting loop. A single press may only open the first text. + BattleMenuWatcher jupiter_menu(BattleType::TRAINER); + if (run_until(stream, context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 30; press++){ + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + } + pbf_wait(ctx, 15000ms); + }, + {{jupiter_menu}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Jupiter interaction did not reach the battle menu.", + stream); + } + fight("Commander Jupiter", true); + // Jupiter's departure and the rescued family have chained dialogue. + clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); + stream.log("[Galactic HQ] Rescue dialogue cleared; following recorded exit.", COLOR_GREEN); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 691ms, 0ms); + pbf_wait(context, 575ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 330ms, 0ms); + pbf_wait(context, 2575ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 523ms, 0ms); + pbf_wait(context, 239ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 717ms, 0ms); + pbf_wait(context, 1721ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 306ms, 0ms); + pbf_wait(context, 294ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1041ms, 0ms); + pbf_wait(context, 307ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 431ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 11ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 999ms, 0ms); + pbf_wait(context, 573ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 343ms, 0ms); + pbf_wait(context, 175ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 999ms, 0ms); + pbf_wait(context, 456ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 277ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 654ms, 0ms); + pbf_wait(context, 1092ms); + context.wait_for_all_requests(); + BlackScreenOverWatcher hq_leaving(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 2350ms, 0ms); + pbf_move_left_joystick(ctx, {-1, -1}, 2744ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{hq_leaving}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Exit transition not confirmed.", stream); + } + segment03_finish_transition(stream, context, "Galactic HQ exit"); + segment03_confirm_location(stream, context, match_galactic_hq_exterior, "Galactic HQ exterior"); + // Clamp against the uncut front bush; checkpoint 03.02 starts here. + pbf_move_left_joystick(context, {0, -1}, 350ms, 500ms); + context.wait_for_all_requests(); + stream.log("[Galactic HQ] Outside, facing the front Cut bush.", COLOR_GREEN); +} +static void eterna_mart_escape_ropes(VideoStream& stream, ProControllerContext& context){ + stream.log("[Eterna Mart] Buying exactly 3 Escape Ropes after the Jupiter heal."); + // Recorded Center-exit to Mart-door path (eternamart1.txt). + pbf_move_left_joystick(context, {0, -1}, 2713ms, 0ms); + pbf_wait(context, 444ms); + pbf_move_left_joystick(context, {1, 0}, 587ms, 0ms); + pbf_wait(context, 705ms); + segment03_doorway(stream, context, true, 896ms, "Eterna Mart entrance"); + // Standard Mart layout: walk north from the entrance to the clerk. + pbf_move_left_joystick(context, {0, 1}, 885ms, 300ms); + if (!buy_items(stream, context, {{ShopItem::EscapeRope, 3}})){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Mart: Could not buy 3 Escape Ropes.", stream); + } + segment03_doorway(stream, context, false, 5000ms, "Eterna Mart exit"); + + // Retrace to the Center column, then use its doorway to normalize the + // position. Do not reverse the time spent loading the Mart interior. + pbf_move_left_joystick(context, {0, -1}, 896ms, 100ms); + pbf_move_left_joystick(context, {-1, 0}, 587ms, 100ms); + segment03_doorway(stream, context, true, 5000ms, "Eterna Center return after Mart"); + segment03_doorway(stream, context, false, 5000ms, "Eterna Center exit after Mart"); + align_to_overworld_landmarks(stream, context, match_eterna_center_departure, "Eterna Center departure after Mart", + false, true); +} - // Retrace to the Center column, then use its doorway to normalize the - // position. Do not reverse the time spent loading the Mart interior. - pbf_move_left_joystick(context,{0,-1},896ms,100ms); - pbf_move_left_joystick(context,{-1,0},587ms,100ms); - segment03_doorway(stream,context,true,5000ms,"Eterna Center return after Mart"); - segment03_doorway(stream,context,false,5000ms,"Eterna Center exit after Mart"); - align_to_overworld_landmarks(stream,context,match_eterna_center_departure, - "Eterna Center departure after Mart",false,true); +static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + if (frame && match_eterna_cycle_shop_interior(*frame.frame).located){ + segment03_confirm_location(stream, context, match_eterna_cycle_shop_interior, "Eterna bicycle shop interior"); + return; + } + bool entered = false; + // Watch the entire approach AND the locator. A fade cancels all queued + // input, even if the doorway is crossed before alignment has finished. + BlackScreenWatcher entering; + const int result = run_until( + stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 1514ms, 0ms); + pbf_wait(ctx, 177ms); + pbf_move_left_joystick(ctx, {1, 0}, 587ms, 0ms); + pbf_wait(ctx, 522ms); + pbf_move_left_joystick(ctx, {1, 0}, 144ms, 0ms); + pbf_wait(ctx, 439ms); + align_to_overworld_landmarks(stream, ctx, match_eterna_cycle_shop_entrance, "Eterna bicycle shop entrance", + false, true, false, match_eterna_cycle_shop_interior, &entered); + if (!entered){ + pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); + pbf_wait(ctx, 3000ms); + } + }, + {{entering}}); + if (result != 0 && !entered){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna bicycle shop: Entry transition not detected.", stream); } + segment03_finish_transition(stream, context, "Eterna bicycle shop entrance"); + segment03_confirm_location(stream, context, match_eterna_cycle_shop_interior, "Eterna bicycle shop interior"); + stream.log("[Eterna bicycle shop] Interior confirmed; outdoor alignment stopped.", COLOR_GREEN); +} - static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - auto frame=stream.video().snapshot(); - if (frame && match_eterna_cycle_shop_interior(*frame.frame).located) { - segment03_confirm_location(stream,context,match_eterna_cycle_shop_interior, - "Eterna bicycle shop interior"); - return; +void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ + // Continue from 03.01 outside HQ, directly in front of the Cut bush. + use_HM(stream, context, "Galactic HQ exit Cut"); + clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1413ms, 0ms); + pbf_wait(context, 182ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 712ms, 0ms); + pbf_wait(context, 91ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1126ms, 0ms); + pbf_wait(context, 27ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); + pbf_wait(context, 868ms); + align_to_overworld_landmarks(stream, context, match_eterna_center_entrance, "Eterna Center after Jupiter", false, + true); + segment03_doorway(stream, context, true, 4000ms, "Eterna Center entrance after Jupiter"); + if (!heal_pokemon(stream, context, "Eterna after Jupiter")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Center exit not confirmed.", stream); + } + segment03_finish_transition(stream, context, "Eterna Center heal exit"); + align_to_overworld_landmarks(stream, context, match_eterna_center_departure, "Eterna Center departure to Mart", + false, true); + eterna_mart_escape_ropes(stream, context); + segment03_enter_cycle_shop(stream, context); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 505ms, 0ms); + pbf_wait(context, 405ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 568ms, 0ms); + pbf_wait(context, 403ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 642ms, 0ms); + pbf_wait(context, 14ms); + // The recorded approach reaches the keeper even when the exact + // counter reference pose differs. Start talking before any reward wait. + // Cancel Up immediately on dialogue; all subsequent inputs are A only. + context.wait_for_all_requests(); + ShortDialogWatcher keeper_dialogue; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 20; press++){ + pbf_move_left_joystick(ctx, {0, 1}, 80ms, 100ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + } + }, + {{keeper_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Eterna bicycle shop: Keeper interaction did not produce dialogue.", + stream); + } + // The reward scene returns us outside already mounted. Wait for that + // positive destination instead of replaying a fixed dialogue burst. + segment03_confirm_location(stream, context, match_eterna_cycle_shop_reward, "Eterna bicycle reward complete", 180, + true, true, match_eterna_bike_riding_card); + segment03_register_bike(stream, context); + segment03_cycling_move(stream, context, -1, 0, 733ms, 316ms, "Cycling Road approach"); + segment03_cycling_move(stream, context, 0, -1, 3280ms, 3000ms, "Cycling Road north gate entrance", true); + segment03_cycling_move(stream, context, 0, -1, 1789ms, 3000ms, "Cycling Road north gate exit", true); + // Fade completion can precede restored player control. Settle and + // clear the steps before acquiring the bridge, not the cliff wall. + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + OverworldWatcher north_gate_overworld; + if (wait_until(stream, context, 10s, {{north_gate_overworld}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Overworld not visible after north gate exit settle.", + stream); + } + BlackScreenWatcher north_gate_black; + WhiteScreenWatcher north_gate_white; + ShortDialogWatcher north_gate_dialogue; + StartBattleDetector north_gate_starting(stream.overlay()); + BattleMenuWatcher north_gate_menu(BattleType::TRAINER); + const int clearance = run_until( + stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 750ms, 250ms); + ctx.wait_for_all_requests(); + }, + {{north_gate_black}, {north_gate_white}, {north_gate_dialogue}, {north_gate_starting}, {north_gate_menu}}); + if (clearance == 0 || clearance == 1){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Unexpected transition while clearing north gate steps.", + stream); + } + const bool cycling_encounter = clearance >= 2 || segment03_align_cycle_start(stream, context); + segment03_cycle_road_battles(stream, context, cycling_encounter); + // Any approach movement may already enter the south gate. Preserve + // that transition instead of requesting two more doorway crossings. + bool south_gate_entered = + segment03_cycling_move(stream, context, -1, 0, 129ms, 1499ms, "Cycling Road south gate approach"); + if (!south_gate_entered){ + south_gate_entered = + segment03_cycling_move(stream, context, -1, 0, 361ms, 3345ms, "Cycling Road south gate approach"); + } + if (!south_gate_entered){ + south_gate_entered = + segment03_cycling_move(stream, context, 0, -1, 472ms, 2574ms, "Cycling Road south gate approach"); + } + if (!south_gate_entered){ + segment03_cycling_move(stream, context, 0, -1, 1977ms, 1544ms, "Cycling Road south gate entrance", true); + } + segment03_confirm_location(stream, context, match_cycling_road_south_gate_interior, + "Cycling Road south gate interior"); + segment03_cycling_move(stream, context, 0, -1, 1977ms, 1544ms, "Cycling Road south gate exit", true); + OverworldWatcher south_gate_overworld; + if (wait_until(stream, context, 10s, {{south_gate_overworld}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Overworld not visible after south gate exit.", stream); + } + // Stay southbound until the scripted Dawn/Lucas dialogue actually + // appears. Cancel movement at the dialogue instead of spending inputs + // against the cutscene, and never begin the eastward trainer route early. + bool dawn_dialogue_seen = false; + for (size_t attempt = 0; attempt < 4 && !dawn_dialogue_seen; attempt++){ + ShortDialogWatcher dawn_dialogue; + // A dialogue already open after the transition needs no movement. + if (wait_until(stream, context, 500ms, {{dawn_dialogue}}) == 0){ + dawn_dialogue_seen = true; + break; } - bool entered=false; - // Watch the entire approach AND the locator. A fade cancels all queued - // input, even if the doorway is crossed before alignment has finished. - BlackScreenWatcher entering; - const int result=run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,-1},1514ms,0ms); - pbf_wait(ctx,177ms); - pbf_move_left_joystick(ctx,{1,0},587ms,0ms); - pbf_wait(ctx,522ms); - pbf_move_left_joystick(ctx,{1,0},144ms,0ms); - pbf_wait(ctx,439ms); - align_to_overworld_landmarks(stream,ctx,match_eterna_cycle_shop_entrance, - "Eterna bicycle shop entrance",false,true,false, - match_eterna_cycle_shop_interior,&entered); - if (!entered) { - pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); - pbf_wait(ctx,3000ms); - } - },{{entering}}); - if (result!=0 && !entered) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna bicycle shop: Entry transition not detected.",stream); - segment03_finish_transition(stream,context,"Eterna bicycle shop entrance"); - segment03_confirm_location(stream,context,match_eterna_cycle_shop_interior, - "Eterna bicycle shop interior"); - stream.log("[Eterna bicycle shop] Interior confirmed; outdoor alignment stopped.",COLOR_GREEN); - } - - void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context) { - // Continue from 03.01 outside HQ, directly in front of the Cut bush. - use_HM(stream,context,"Galactic HQ exit Cut"); - clear_post_battle_dialogue(stream,context,BUTTON_A,6000ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1413ms, 0ms); - pbf_wait(context, 182ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 712ms, 0ms); - pbf_wait(context, 91ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1126ms, 0ms); - pbf_wait(context, 27ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); - pbf_wait(context, 868ms); - align_to_overworld_landmarks(stream,context,match_eterna_center_entrance, - "Eterna Center after Jupiter",false,true); - segment03_doorway(stream,context,true,4000ms,"Eterna Center entrance after Jupiter"); - if (!heal_pokemon(stream,context,"Eterna after Jupiter")) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Eterna Center exit not confirmed.",stream); - segment03_finish_transition(stream,context,"Eterna Center heal exit"); - align_to_overworld_landmarks(stream,context,match_eterna_center_departure, - "Eterna Center departure to Mart",false,true); - eterna_mart_escape_ropes(stream,context); - segment03_enter_cycle_shop(stream,context); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 505ms, 0ms); - pbf_wait(context, 405ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 568ms, 0ms); - pbf_wait(context, 403ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 642ms, 0ms); - pbf_wait(context, 14ms); - // The recorded approach reaches the keeper even when the exact - // counter reference pose differs. Start talking before any reward wait. - // Cancel Up immediately on dialogue; all subsequent inputs are A only. - context.wait_for_all_requests(); - ShortDialogWatcher keeper_dialogue; - if (run_until(stream,context, - [](ProControllerContext& ctx) { - for (size_t press=0;press<20;press++) { - pbf_move_left_joystick(ctx,{0,1},80ms,100ms); - pbf_press_button(ctx,BUTTON_A,150ms,500ms); - } - },{{keeper_dialogue}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna bicycle shop: Keeper interaction did not produce dialogue.",stream); - // The reward scene returns us outside already mounted. Wait for that - // positive destination instead of replaying a fixed dialogue burst. - segment03_confirm_location(stream,context,match_eterna_cycle_shop_reward, - "Eterna bicycle reward complete",180,true,true, - match_eterna_bike_riding_card); - segment03_register_bike(stream,context); - segment03_cycling_move(stream,context,-1,0,733ms,316ms,"Cycling Road approach"); - segment03_cycling_move(stream,context,0,-1,3280ms,3000ms,"Cycling Road north gate entrance",true); - segment03_cycling_move(stream,context,0,-1,1789ms,3000ms,"Cycling Road north gate exit",true); - // Fade completion can precede restored player control. Settle and - // clear the steps before acquiring the bridge, not the cliff wall. - pbf_wait(context,2000ms); - context.wait_for_all_requests(); - OverworldWatcher north_gate_overworld; - if (wait_until(stream,context,10s,{{north_gate_overworld}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Overworld not visible after north gate exit settle.",stream); - BlackScreenWatcher north_gate_black; - WhiteScreenWatcher north_gate_white; - ShortDialogWatcher north_gate_dialogue; - StartBattleDetector north_gate_starting(stream.overlay()); - BattleMenuWatcher north_gate_menu(BattleType::TRAINER); - const int clearance=run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,-1},750ms,250ms); + BlackScreenWatcher dawn_black; + WhiteScreenWatcher dawn_white; + StartBattleDetector dawn_starting(stream.overlay()); + BattleMenuWatcher dawn_menu(BattleType::TRAINER); + const Milliseconds south = attempt == 0 ? 1546ms : 3000ms; + stream.log("[Route 207 Dawn] South approach attempt=" + std::to_string(attempt + 1)); + const int result = run_until( + stream, context, + [south](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, south, 500ms); ctx.wait_for_all_requests(); - },{{north_gate_black},{north_gate_white},{north_gate_dialogue},{north_gate_starting},{north_gate_menu}}); - if (clearance==0 || clearance==1) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Unexpected transition while clearing north gate steps.",stream); - const bool cycling_encounter=clearance>=2 || segment03_align_cycle_start(stream,context); - segment03_cycle_road_battles(stream,context,cycling_encounter); - // Any approach movement may already enter the south gate. Preserve - // that transition instead of requesting two more doorway crossings. - bool south_gate_entered=segment03_cycling_move(stream,context,-1,0,129ms,1499ms,"Cycling Road south gate approach"); - if (!south_gate_entered) - south_gate_entered=segment03_cycling_move(stream,context,-1,0,361ms,3345ms,"Cycling Road south gate approach"); - if (!south_gate_entered) - south_gate_entered=segment03_cycling_move(stream,context,0,-1,472ms,2574ms,"Cycling Road south gate approach"); - if (!south_gate_entered) - segment03_cycling_move(stream,context,0,-1,1977ms,1544ms,"Cycling Road south gate entrance",true); - segment03_confirm_location(stream,context,match_cycling_road_south_gate_interior, - "Cycling Road south gate interior"); - segment03_cycling_move(stream,context,0,-1,1977ms,1544ms,"Cycling Road south gate exit",true); - OverworldWatcher south_gate_overworld; - if (wait_until(stream,context,10s,{{south_gate_overworld}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Overworld not visible after south gate exit.",stream); - // Stay southbound until the scripted Dawn/Lucas dialogue actually - // appears. Cancel movement at the dialogue instead of spending inputs - // against the cutscene, and never begin the eastward trainer route early. - bool dawn_dialogue_seen=false; - for (size_t attempt=0;attempt<4 && !dawn_dialogue_seen;attempt++) { - ShortDialogWatcher dawn_dialogue; - // A dialogue already open after the transition needs no movement. - if (wait_until(stream,context,500ms,{{dawn_dialogue}})==0) { - dawn_dialogue_seen=true; - break; - } - BlackScreenWatcher dawn_black; - WhiteScreenWatcher dawn_white; - StartBattleDetector dawn_starting(stream.overlay()); - BattleMenuWatcher dawn_menu(BattleType::TRAINER); - const Milliseconds south=attempt==0 ? 1546ms : 3000ms; - stream.log("[Route 207 Dawn] South approach attempt="+std::to_string(attempt+1)); - const int result=run_until(stream,context, - [south](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,-1},south,500ms); - ctx.wait_for_all_requests(); - },{{dawn_dialogue},{dawn_black},{dawn_white},{dawn_starting},{dawn_menu}}); - if (result==0) dawn_dialogue_seen=true; - else if (result>=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 207 Dawn: Unexpected transition or battle before dialogue.",stream); + }, + {{dawn_dialogue}, {dawn_black}, {dawn_white}, {dawn_starting}, {dawn_menu}}); + if (result == 0){ + dawn_dialogue_seen = true; + }else if (result >= 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 207 Dawn: Unexpected transition or battle before dialogue.", + stream); } - if (!dawn_dialogue_seen) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 207 Dawn: Dialogue not encountered after southward retries.",stream); - stream.log("[Route 207 Dawn] Dialogue detected; south movement cancelled.",COLOR_GREEN); - bool dawn_dialogue_cleared=false; - for (size_t pass=0;pass<6 && !dawn_dialogue_cleared;pass++) { - // A advances the conversation and accepts its default choice. - // Reuse the existing dialogue helper, then guard against a brief - // gap between text boxes being mistaken for the end of the scene. - mash_until_dialogue_ends(stream,context,BUTTON_A,60s); - context.wait_for_all_requests(); - ShortDialogWatcher resumed_dialogue; - if (wait_until(stream,context,2000ms,{{resumed_dialogue}})==0) continue; - const auto frame=stream.video().snapshot(); - OverworldDetector overworld; - dawn_dialogue_cleared=frame && overworld.detect(*frame.frame); - } - if (!dawn_dialogue_cleared) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 207 Dawn: Dialogue did not remain cleared.",stream); - pbf_wait(context, 6000ms); - stream.log("[Route 207 Dawn] Dialogue cleared; continuing to Camper Anthony.",COLOR_GREEN); - // restof206.txt begins after Dawn. Recorded battle/menu inputs are - // replaced by the encounter watchers and existing move-2 battle engine. - segment03_expected_encounter(stream,context,"Route 207 Camper Anthony", - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,-1},1013ms,1667ms); - pbf_move_left_joystick(ctx,{1,0},3309ms,1011ms); - }); - // The recorded approach triggers Lauren next without further steering. - segment03_expected_encounter(stream,context,"Route 207 Picnicker Lauren", - [](ProControllerContext&) {}); - // Justin can be dodged. After the recorded approach, wait three seconds - // for an encounter; otherwise keep the remaining Coronet route. - segment03_expected_encounter(stream,context,"Route 207 Hiker Justin", - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},994ms,508ms); - pbf_move_left_joystick(ctx,{0,1},534ms,457ms); - pbf_move_left_joystick(ctx,{1,0},2076ms,149ms); - },3000ms,false); - // Keep the recorded route after Justin; replace its final blind doorway - // excerpt with the same watched down/right search used at Oreburgh Gate. - bool coronet_entered=segment03_cycling_move(stream,context,1,0,942ms,443ms,"Mount Coronet approach"); - if (!coronet_entered) - coronet_entered=segment03_cycling_move(stream,context,0,-1,776ms,119ms,"Mount Coronet approach"); - if (!coronet_entered) - coronet_entered=segment03_cycling_move(stream,context,1,0,627ms,147ms,"Mount Coronet approach"); - if (!coronet_entered) - coronet_entered=segment03_cycling_move(stream,context,0,1,725ms,1008ms,"Mount Coronet approach"); - if (!coronet_entered) { - enter_cave_with_down_right_search(stream,context,"Mount Coronet west entrance"); - segment03_finish_transition(stream,context,"Mount Coronet west entrance"); - // finish_transition includes 500ms; allow two seconds in total. - pbf_wait(context,1500ms); - context.wait_for_all_requests(); - } - OverworldWatcher coronet_overworld; - if (wait_until(stream,context,10s,{{coronet_overworld}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mount Coronet: Interior not ready after west entrance.",stream); - context.wait_for_all_requests(); } - void coronet1(VideoStream& stream, ProControllerContext& context) { - activate_repel(stream, context, 5); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); - pbf_wait(context, 104ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 852ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 37ms, 0ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 102ms, 0ms); - pbf_move_left_joystick(context, {1.000000, 1.000000}, 78ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 983ms, 0ms); - pbf_wait(context, 1754ms); - pbf_press_button(context, BUTTON_A, 173ms, 0ms); - pbf_wait(context, 1072ms); - pbf_press_button(context, BUTTON_A, 168ms, 0ms); - pbf_wait(context, 155ms); - pbf_press_button(context, BUTTON_A, 142ms, 0ms); - pbf_wait(context, 136ms); - pbf_press_button(context, BUTTON_A, 159ms, 0ms); - pbf_wait(context, 1598ms); - pbf_press_button(context, BUTTON_A, 244ms, 0ms); - pbf_wait(context, 148ms); - pbf_press_button(context, BUTTON_A, 170ms, 0ms); - pbf_wait(context, 587ms); - pbf_press_button(context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 135ms); - pbf_press_button(context, BUTTON_A, 160ms, 0ms); - pbf_wait(context, 321ms); - pbf_press_button(context, BUTTON_A, 211ms, 0ms); - pbf_wait(context, 205ms); - pbf_press_button(context, BUTTON_A, 180ms, 0ms); - pbf_wait(context, 672ms); - pbf_press_button(context, BUTTON_A, 182ms, 0ms); - pbf_wait(context, 122ms); - pbf_press_button(context, BUTTON_A, 172ms, 0ms); - pbf_wait(context, 362ms); - pbf_press_button(context, BUTTON_A, 172ms, 0ms); - pbf_wait(context, 373ms); - pbf_press_button(context, BUTTON_A, 199ms, 0ms); - pbf_wait(context, 150ms); - pbf_press_button(context, BUTTON_A, 183ms, 0ms); - pbf_wait(context, 190ms); - pbf_press_button(context, BUTTON_A, 188ms, 0ms); - pbf_wait(context, 213ms); - pbf_press_button(context, BUTTON_A, 143ms, 0ms); - pbf_wait(context, 7001ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 999ms, 0ms); - pbf_wait(context, 268ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 375ms, 0ms); - pbf_wait(context, 227ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 741ms, 0ms); - pbf_wait(context, 46ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 743ms, 0ms); - pbf_wait(context, 68ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 265ms, 0ms); - pbf_wait(context, 255ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 890ms, 0ms); - pbf_wait(context, 88ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 462ms, 0ms); - pbf_wait(context, 271ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 647ms, 0ms); - pbf_wait(context, 20ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1047ms, 0ms); - pbf_wait(context, 56ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 776ms, 0ms); - pbf_wait(context, 2038ms); - context.wait_for_all_requests(); + if (!dawn_dialogue_seen){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 207 Dawn: Dialogue not encountered after southward retries.", + stream); } - void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context) { - // The expiry routine starts west. Step east first to leave room for - // its west/east pairs, then clear the old Repel before route movement. + stream.log("[Route 207 Dawn] Dialogue detected; south movement cancelled.", COLOR_GREEN); + bool dawn_dialogue_cleared = false; + for (size_t pass = 0; pass < 6 && !dawn_dialogue_cleared; pass++){ + // A advances the conversation and accepts its default choice. + // Reuse the existing dialogue helper, then guard against a brief + // gap between text boxes being mistaken for the end of the scene. + mash_until_dialogue_ends(stream, context, BUTTON_A, 60s); context.wait_for_all_requests(); - pbf_wait(context, 250ms); - pbf_move_left_joystick(context,{1,0},1000ms,0ms); - pbf_wait(context, 250ms); + ShortDialogWatcher resumed_dialogue; + if (wait_until(stream, context, 2000ms, {{resumed_dialogue}}) == 0){ + continue; + } + const auto frame = stream.video().snapshot(); + OverworldDetector overworld; + dawn_dialogue_cleared = frame && overworld.detect(*frame.frame); + } + if (!dawn_dialogue_cleared){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 207 Dawn: Dialogue did not remain cleared.", stream); + } + pbf_wait(context, 6000ms); + stream.log("[Route 207 Dawn] Dialogue cleared; continuing to Camper Anthony.", COLOR_GREEN); + // restof206.txt begins after Dawn. Recorded battle/menu inputs are + // replaced by the encounter watchers and existing move-2 battle engine. + segment03_expected_encounter(stream, context, "Route 207 Camper Anthony", [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 1013ms, 1667ms); + pbf_move_left_joystick(ctx, {1, 0}, 3309ms, 1011ms); + }); + // The recorded approach triggers Lauren next without further steering. + segment03_expected_encounter(stream, context, "Route 207 Picnicker Lauren", [](ProControllerContext&){ + }); + // Justin can be dodged. After the recorded approach, wait three seconds + // for an encounter; otherwise keep the remaining Coronet route. + segment03_expected_encounter( + stream, context, "Route 207 Hiker Justin", + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 994ms, 508ms); + pbf_move_left_joystick(ctx, {0, 1}, 534ms, 457ms); + pbf_move_left_joystick(ctx, {1, 0}, 2076ms, 149ms); + }, + 3000ms, false); + // Keep the recorded route after Justin; replace its final blind doorway + // excerpt with the same watched down/right search used at Oreburgh Gate. + bool coronet_entered = segment03_cycling_move(stream, context, 1, 0, 942ms, 443ms, "Mount Coronet approach"); + if (!coronet_entered){ + coronet_entered = segment03_cycling_move(stream, context, 0, -1, 776ms, 119ms, "Mount Coronet approach"); + } + if (!coronet_entered){ + coronet_entered = segment03_cycling_move(stream, context, 1, 0, 627ms, 147ms, "Mount Coronet approach"); + } + if (!coronet_entered){ + coronet_entered = segment03_cycling_move(stream, context, 0, 1, 725ms, 1008ms, "Mount Coronet approach"); + } + if (!coronet_entered){ + enter_cave_with_down_right_search(stream, context, "Mount Coronet west entrance"); + segment03_finish_transition(stream, context, "Mount Coronet west entrance"); + // finish_transition includes 500ms; allow two seconds in total. + pbf_wait(context, 1500ms); context.wait_for_all_requests(); - // Leave Repel off during the potentially long Hiker sight-line sweeps. - force_repel_expiry(stream,context,RepelRenewalChoice::NO,false,true); - - // Either Hiker may interrupt the crossing. Resume toward the east - // wall after each fight. The wiggles are one encounter-triggering - // attempt: once started, do not restart or finish them after an - // interruption. Keep the resumed east approach watched for a later Hiker. - bool bridge_approach_clear=false,bridge_wiggle_started=false; - size_t bridge_battles=0,approach_dialogues=0; - for (size_t pass=0;pass<6 && !bridge_approach_clear;pass++) { - ShortDialogWatcher dialogue; - StartBattleDetector starting(stream.overlay()); - BattleMenuWatcher menu(BattleType::TRAINER); - const int engaged=run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},4828ms,104ms); - ctx.wait_for_all_requests(); - pbf_wait(ctx,3000ms); - ctx.wait_for_all_requests(); - if (!bridge_wiggle_started) { - // Mark before the first input: an encounter during - // this set fulfills its purpose and consumes the set. - bridge_wiggle_started=true; - for (size_t pair=0;pair<8;pair++) { - pbf_move_left_joystick(ctx,{0,1},500ms,150ms); - pbf_move_left_joystick(ctx,{0,-1},500ms,150ms); - ctx.wait_for_all_requests(); - } - } else { - stream.log("[Route 208 bridge] Wiggle set already used; continuing watched east approach without repeating it."); - } - pbf_wait(ctx,2500ms); - },{{dialogue},{starting},{menu}}); - if (engaged<0) { - bridge_approach_clear=true; - break; - } - if (engaged==0) { - // A short box can be a Repel prompt. Advance it with A (the - // default renewal choice) while watching for actual combat. - // Never send a nonbattle choice arrow to the battle engine. - StartBattleDetector dialogue_starting(stream.overlay()); - BattleMenuWatcher dialogue_menu(BattleType::TRAINER); - bool dialogue_cleared=false; - const int battle_started=run_until(stream,context, - [&](ProControllerContext& ctx) { - mash_until_dialogue_ends(stream,ctx,BUTTON_A,20s); - ctx.wait_for_all_requests(); - pbf_wait(ctx,3000ms); + } + OverworldWatcher coronet_overworld; + if (wait_until(stream, context, 10s, {{coronet_overworld}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mount Coronet: Interior not ready after west entrance.", stream); + } + context.wait_for_all_requests(); +} +void coronet1(VideoStream& stream, ProControllerContext& context){ + activate_repel(stream, context, 5); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); + pbf_wait(context, 104ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 852ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 37ms, 0ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 102ms, 0ms); + pbf_move_left_joystick(context, {1.000000, 1.000000}, 78ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 983ms, 0ms); + pbf_wait(context, 1754ms); + pbf_press_button(context, BUTTON_A, 173ms, 0ms); + pbf_wait(context, 1072ms); + pbf_press_button(context, BUTTON_A, 168ms, 0ms); + pbf_wait(context, 155ms); + pbf_press_button(context, BUTTON_A, 142ms, 0ms); + pbf_wait(context, 136ms); + pbf_press_button(context, BUTTON_A, 159ms, 0ms); + pbf_wait(context, 1598ms); + pbf_press_button(context, BUTTON_A, 244ms, 0ms); + pbf_wait(context, 148ms); + pbf_press_button(context, BUTTON_A, 170ms, 0ms); + pbf_wait(context, 587ms); + pbf_press_button(context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 135ms); + pbf_press_button(context, BUTTON_A, 160ms, 0ms); + pbf_wait(context, 321ms); + pbf_press_button(context, BUTTON_A, 211ms, 0ms); + pbf_wait(context, 205ms); + pbf_press_button(context, BUTTON_A, 180ms, 0ms); + pbf_wait(context, 672ms); + pbf_press_button(context, BUTTON_A, 182ms, 0ms); + pbf_wait(context, 122ms); + pbf_press_button(context, BUTTON_A, 172ms, 0ms); + pbf_wait(context, 362ms); + pbf_press_button(context, BUTTON_A, 172ms, 0ms); + pbf_wait(context, 373ms); + pbf_press_button(context, BUTTON_A, 199ms, 0ms); + pbf_wait(context, 150ms); + pbf_press_button(context, BUTTON_A, 183ms, 0ms); + pbf_wait(context, 190ms); + pbf_press_button(context, BUTTON_A, 188ms, 0ms); + pbf_wait(context, 213ms); + pbf_press_button(context, BUTTON_A, 143ms, 0ms); + pbf_wait(context, 7001ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 999ms, 0ms); + pbf_wait(context, 268ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 375ms, 0ms); + pbf_wait(context, 227ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 741ms, 0ms); + pbf_wait(context, 46ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 743ms, 0ms); + pbf_wait(context, 68ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 265ms, 0ms); + pbf_wait(context, 255ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 890ms, 0ms); + pbf_wait(context, 88ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 462ms, 0ms); + pbf_wait(context, 271ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 647ms, 0ms); + pbf_wait(context, 20ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1047ms, 0ms); + pbf_wait(context, 56ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 776ms, 0ms); + pbf_wait(context, 2038ms); + context.wait_for_all_requests(); +} +void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ + // The expiry routine starts west. Step east first to leave room for + // its west/east pairs, then clear the old Repel before route movement. + context.wait_for_all_requests(); + pbf_wait(context, 250ms); + pbf_move_left_joystick(context, {1, 0}, 1000ms, 0ms); + pbf_wait(context, 250ms); + context.wait_for_all_requests(); + // Leave Repel off during the potentially long Hiker sight-line sweeps. + force_repel_expiry(stream, context, RepelRenewalChoice::NO, false, true); + + // Either Hiker may interrupt the crossing. Resume toward the east + // wall after each fight. The wiggles are one encounter-triggering + // attempt: once started, do not restart or finish them after an + // interruption. Keep the resumed east approach watched for a later Hiker. + bool bridge_approach_clear = false, bridge_wiggle_started = false; + size_t bridge_battles = 0, approach_dialogues = 0; + for (size_t pass = 0; pass < 6 && !bridge_approach_clear; pass++){ + ShortDialogWatcher dialogue; + StartBattleDetector starting(stream.overlay()); + BattleMenuWatcher menu(BattleType::TRAINER); + const int engaged = run_until( + stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 4828ms, 104ms); + ctx.wait_for_all_requests(); + pbf_wait(ctx, 3000ms); + ctx.wait_for_all_requests(); + if (!bridge_wiggle_started){ + // Mark before the first input: an encounter during + // this set fulfills its purpose and consumes the set. + bridge_wiggle_started = true; + for (size_t pair = 0; pair < 8; pair++){ + pbf_move_left_joystick(ctx, {0, 1}, 500ms, 150ms); + pbf_move_left_joystick(ctx, {0, -1}, 500ms, 150ms); ctx.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - OverworldDetector overworld; - ShortDialogDetector short_dialogue; - dialogue_cleared=frame && overworld.detect(*frame.frame) && - !short_dialogue.detect(*frame.frame); - },{{dialogue_starting},{dialogue_menu}}); - if (battle_started<0) { - if (!dialogue_cleared || ++approach_dialogues>2) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 bridge: Approach dialogue did not clear within bounded recovery.",stream); - stream.log("[Route 208 bridge] Nonbattle dialogue cleared; resuming watched crossing."); - continue; + } + }else{ + stream.log( + "[Route 208 bridge] Wiggle set already used; continuing watched east approach without repeating it."); } - } - fight_trainer_using_moves(stream, context, 2, "Route 208 Hiker", false); - if (++bridge_battles>2) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 bridge: Unexpected additional Hiker battle.",stream); - pbf_wait(context,500ms); - context.wait_for_all_requests(); + pbf_wait(ctx, 2500ms); + }, + {{dialogue}, {starting}, {menu}}); + if (engaged < 0){ + bridge_approach_clear = true; + break; } - if (!bridge_approach_clear) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 bridge: Watched east approach did not clear.",stream); - stream.log("[Route 208 bridge] Full east approach clear after " + - std::to_string(bridge_battles) + " battle(s); continuing toward the stairs.",COLOR_GREEN); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 421ms, 0ms); - pbf_wait(context, 178ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1436ms, 0ms); - pbf_wait(context, 81ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1197ms, 0ms); - pbf_wait(context, 298ms); - // Fixed eastward travel can stop beyond the stair column. Center above - // the stairs using the native left-rock pose before moving south. - bool rock_acquisition_timed_out=false; - align_to_overworld_landmarks(stream,context,match_route208_stair_top, - "Route 208 stair-top rocks",false,true,false,nullptr,nullptr,0,0,0, - &rock_acquisition_timed_out); - if (!activate_repel(stream,context,5)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 stair top: Repel activation failed.",stream); - context.wait_for_all_requests(); - if (rock_acquisition_timed_out) { - stream.log("[Route 208 stair fallback] Using east 2000ms, southwest 1500ms, then south trial 1650ms."); - // Keep the recorded inputs explicit for review and calibration. - // Watch this whole recovery phase; cancel its remaining inputs if - // a transition or battle starts before the gate approach. - BlackScreenWatcher fallback_black; - WhiteScreenWatcher fallback_white; - BattleMenuWatcher fallback_battle(BattleType::TRAINER); - const int fallback_interrupted=run_until(stream,context, - [&](ProControllerContext& ctx) { - // East: clamp against the cliff edge. - pbf_move_left_joystick(ctx,{1,0},2000ms,0ms); - ctx.wait_for_all_requests(); - // Southwest: drag toward the stair column. - pbf_move_left_joystick(ctx,{-1,-1},1500ms,0ms); + if (engaged == 0){ + // A short box can be a Repel prompt. Advance it with A (the + // default renewal choice) while watching for actual combat. + // Never send a nonbattle choice arrow to the battle engine. + StartBattleDetector dialogue_starting(stream.overlay()); + BattleMenuWatcher dialogue_menu(BattleType::TRAINER); + bool dialogue_cleared = false; + const int battle_started = run_until( + stream, context, + [&](ProControllerContext& ctx){ + mash_until_dialogue_ends(stream, ctx, BUTTON_A, 20s); ctx.wait_for_all_requests(); - // South: user-requested 1650ms trial; recovery may start farther down the stairs. - pbf_move_left_joystick(ctx,{0,-1},1650ms,0ms); - pbf_wait(ctx,643ms); + pbf_wait(ctx, 3000ms); ctx.wait_for_all_requests(); - segment03_align_lower_stair_lane(stream,ctx); - },{{fallback_black},{fallback_white},{fallback_battle}}); - if (fallback_interrupted>=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 stair fallback: Unexpected transition or battle before west gate approach.",stream); - } else { - // Pass just above the honey tree, between the southern trainer - // and Hannah to the north. Calibrate this 1825ms trial from video. - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1825ms, 0ms); - pbf_wait(context, 643ms); + const auto frame = stream.video().snapshot(); + OverworldDetector overworld; + ShortDialogDetector short_dialogue; + dialogue_cleared = frame && overworld.detect(*frame.frame) && !short_dialogue.detect(*frame.frame); + }, + {{dialogue_starting}, {dialogue_menu}}); + if (battle_started < 0){ + if (!dialogue_cleared || ++approach_dialogues > 2){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Approach dialogue did not clear within bounded recovery.", stream); + } + stream.log("[Route 208 bridge] Nonbattle dialogue cleared; resuming watched crossing."); + continue; + } } - // Entry may occur during an earlier approach leg. Once a fade is - // caught, skip the remaining outdoor approach rather than replaying it - // inside the gatehouse. The existing watcher cancels at black/white, - // waits for GONE500ms, then settles for2s before the next input. - bool hearthome_gate_entered=segment03_cycling_move(stream,context, - 1,0,3500ms,443ms,"Hearthome west gate approach"); - if (!hearthome_gate_entered) - hearthome_gate_entered=segment03_cycling_move(stream,context, - 0,1,250ms,405ms,"Hearthome west gate doorway alignment"); - if (!hearthome_gate_entered) - hearthome_gate_entered=segment03_cycling_move(stream,context, - 1,0,600ms,1617ms,"Hearthome west gate entrance",true); - if (!hearthome_gate_entered) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome west gate: Interior transition not confirmed.",stream); - if (!segment03_cycling_move(stream,context, - 1,0,1800ms,1588ms,"Hearthome west gate exit",true)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome west gate: Exterior transition not confirmed.",stream); - OverworldWatcher hearthome_outside; - if (wait_until(stream,context,10s,{{hearthome_outside}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome west gate: Overworld not ready after exit.",stream); - if (segment03_cycling_move(stream,context, - 1,0,2200ms,6189ms,"Hearthome Buneary approach")) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Buneary approach: Unexpected extra transition.",stream); - // Begin the scene deadline after the approach requests finish, not - // while the recorded movement and neutral waits remain queued. - context.wait_for_all_requests(); - ShortDialogWatcher buneary_dialogue; - if (wait_until(stream,context,15s,{{buneary_dialogue}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Buneary: Dialogue did not start.",stream); - bool buneary_cleared=false; - for (size_t pass=0;pass<6 && !buneary_cleared;pass++) { - mash_until_dialogue_ends(stream,context,BUTTON_A,60s); - context.wait_for_all_requests(); - ShortDialogWatcher resumed_dialogue; - if (wait_until(stream,context,2000ms,{{resumed_dialogue}})==0) continue; - // Let the NPC finish walking away before any expiration movement. - pbf_wait(context,6000ms); - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - OverworldDetector overworld; - ShortDialogDetector dialogue; - buneary_cleared=frame && overworld.detect(*frame.frame) && !dialogue.detect(*frame.frame); + fight_trainer_using_moves(stream, context, 2, "Route 208 Hiker", false); + if (++bridge_battles > 2){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 208 bridge: Unexpected additional Hiker battle.", stream); } - if (!buneary_cleared) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Buneary: Scene did not clear.",stream); - stream.log("[Hearthome Buneary] Scene cleared; expiring Repel without renewal before Center travel.",COLOR_GREEN); - pbf_move_left_joystick(context,{1,0},500ms,0ms); - context.wait_for_all_requests(); - force_repel_expiry(stream,context,RepelRenewalChoice::NO,false,true); - // Use the same whole-approach watcher as the Eterna bicycle shop. - // A doorway reached during recorded travel or alignment cancels all - // remaining outdoor inputs; the room must then be confirmed visually. - bool center_entered=false; - BlackScreenWatcher center_transition; - const int center_entry=run_until(stream,context, - [&](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3350ms, 0ms); - pbf_wait(ctx, 437ms); - // Clear the northbound lamp column before crossing the plaza. - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 250ms, 0ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 3000ms, 0ms); - pbf_wait(ctx, 1582ms); - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1150ms, 0ms); - pbf_wait(ctx, 334ms); - // Repel expired before this travel; no blind response inputs remain. - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 2250ms, 0ms); - pbf_wait(ctx, 304ms); - pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 300ms, 0ms); - pbf_wait(ctx, 174ms); - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); - pbf_move_left_joystick(ctx, {-1.000000, 1.000000}, 100ms, 0ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1800ms, 0ms); - pbf_wait(ctx, 21ms); - pbf_wait(ctx, 221ms); - pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 250ms, 0ms); - pbf_wait(ctx, 59ms); - align_to_overworld_landmarks(stream,ctx,match_hearthome_center_entrance, - "Hearthome Center exterior alignment",false,true,false, - match_hearthome_center_interior,¢er_entered); - if (!center_entered) { - pbf_move_left_joystick(ctx,{0,1},4000ms,0ms); - pbf_wait(ctx,3000ms); - } - },{{center_transition}}); - if (center_entry!=0 && !center_entered) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Center: Entry transition not detected.",stream); - segment03_finish_transition(stream,context,"Hearthome Center entrance"); - pbf_wait(context,1500ms); - context.wait_for_all_requests(); - segment03_confirm_location(stream,context,match_hearthome_center_interior, - "Hearthome Center interior"); - if (!heal_pokemon(stream,context,"Hearthome")) OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT,"Hearthome Center exit not confirmed.",stream); - // Let the exit scene and camera settle before spending the Hall travel - // budget. The old east hold began while the exit was still fading in. - segment03_finish_transition(stream,context,"Hearthome Center heal exit"); - pbf_wait(context,1500ms); + pbf_wait(context, 500ms); context.wait_for_all_requests(); - OverworldWatcher center_exit_ready; - if (wait_until(stream,context,10s,{{center_exit_ready}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Center: Exterior not ready after healing.",stream); - pbf_move_left_joystick(context,{1,0},2092ms,185ms); - // Bring the Hall into view without the old 1004ms north overshoot. - pbf_move_left_joystick(context,{0,1},600ms,500ms); + } + if (!bridge_approach_clear){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Watched east approach did not clear.", stream); + } + stream.log("[Route 208 bridge] Full east approach clear after " + std::to_string(bridge_battles) + + " battle(s); continuing toward the stairs.", + COLOR_GREEN); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 421ms, 0ms); + pbf_wait(context, 178ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1436ms, 0ms); + pbf_wait(context, 81ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1197ms, 0ms); + pbf_wait(context, 298ms); + // Fixed eastward travel can stop beyond the stair column. Center above + // the stairs using the native left-rock pose before moving south. + bool rock_acquisition_timed_out = false; + align_to_overworld_landmarks(stream, context, match_route208_stair_top, "Route 208 stair-top rocks", false, true, + false, nullptr, nullptr, 0, 0, 0, &rock_acquisition_timed_out); + if (!activate_repel(stream, context, 5)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 stair top: Repel activation failed.", stream); + } + context.wait_for_all_requests(); + if (rock_acquisition_timed_out){ + stream.log("[Route 208 stair fallback] Using east 2000ms, southwest 1500ms, then south trial 1650ms."); + // Keep the recorded inputs explicit for review and calibration. + // Watch this whole recovery phase; cancel its remaining inputs if + // a transition or battle starts before the gate approach. + BlackScreenWatcher fallback_black; + WhiteScreenWatcher fallback_white; + BattleMenuWatcher fallback_battle(BattleType::TRAINER); + const int fallback_interrupted = + run_until(stream, context, + [&](ProControllerContext& ctx){ + // East: clamp against the cliff edge. + pbf_move_left_joystick(ctx, {1, 0}, 2000ms, 0ms); + ctx.wait_for_all_requests(); + // Southwest: drag toward the stair column. + pbf_move_left_joystick(ctx, {-1, -1}, 1500ms, 0ms); + ctx.wait_for_all_requests(); + // South: user-requested 1650ms trial; recovery may start farther down the stairs. + pbf_move_left_joystick(ctx, {0, -1}, 1650ms, 0ms); + pbf_wait(ctx, 643ms); + ctx.wait_for_all_requests(); + segment03_align_lower_stair_lane(stream, ctx); + }, + {{fallback_black}, {fallback_white}, {fallback_battle}}); + if (fallback_interrupted >= 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 stair fallback: Unexpected transition or battle before west gate approach.", stream); + } + }else{ + // Pass just above the honey tree, between the southern trainer + // and Hannah to the north. Calibrate this 1825ms trial from video. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1825ms, 0ms); + pbf_wait(context, 643ms); + } + // Entry may occur during an earlier approach leg. Once a fade is + // caught, skip the remaining outdoor approach rather than replaying it + // inside the gatehouse. The existing watcher cancels at black/white, + // waits for GONE500ms, then settles for2s before the next input. + bool hearthome_gate_entered = + segment03_cycling_move(stream, context, 1, 0, 3500ms, 443ms, "Hearthome west gate approach"); + if (!hearthome_gate_entered){ + hearthome_gate_entered = + segment03_cycling_move(stream, context, 0, 1, 250ms, 405ms, "Hearthome west gate doorway alignment"); + } + if (!hearthome_gate_entered){ + hearthome_gate_entered = + segment03_cycling_move(stream, context, 1, 0, 600ms, 1617ms, "Hearthome west gate entrance", true); + } + if (!hearthome_gate_entered){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome west gate: Interior transition not confirmed.", stream); + } + if (!segment03_cycling_move(stream, context, 1, 0, 1800ms, 1588ms, "Hearthome west gate exit", true)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome west gate: Exterior transition not confirmed.", stream); + } + OverworldWatcher hearthome_outside; + if (wait_until(stream, context, 10s, {{hearthome_outside}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome west gate: Overworld not ready after exit.", stream); + } + if (segment03_cycling_move(stream, context, 1, 0, 2200ms, 6189ms, "Hearthome Buneary approach")){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Buneary approach: Unexpected extra transition.", stream); + } + // Begin the scene deadline after the approach requests finish, not + // while the recorded movement and neutral waits remain queued. + context.wait_for_all_requests(); + ShortDialogWatcher buneary_dialogue; + if (wait_until(stream, context, 15s, {{buneary_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Buneary: Dialogue did not start.", stream); + } + bool buneary_cleared = false; + for (size_t pass = 0; pass < 6 && !buneary_cleared; pass++){ + mash_until_dialogue_ends(stream, context, BUTTON_A, 60s); context.wait_for_all_requests(); - align_to_overworld_landmarks(stream,context,match_hearthome_contest_fantina, - "Hearthome Contest Hall Fantina",false,true); - // Repeat the existing short Up/A interaction within the 15s window. - // Dialogue cancels the remaining inputs before any text cleanup. - ShortDialogWatcher fantina_dialogue; - BlackScreenWatcher fantina_black; - WhiteScreenWatcher fantina_white; - const int fantina_started=run_until(stream,context, - [](ProControllerContext& ctx) { - constexpr auto pulse=16ms+100ms+194ms+277ms; - Milliseconds elapsed=0ms; - for (;elapsed+pulse<=15s;elapsed+=pulse) { - pbf_move_left_joystick(ctx,{0,1},16ms,100ms); - pbf_press_button(ctx,BUTTON_A,194ms,277ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx,15s-elapsed); - },{{fantina_dialogue},{fantina_black},{fantina_white}}); - if (fantina_started>0) { - // Fantina's opening camera cut fades to black before the text box. - // The watcher has cancelled Up/A; wait without further movement. - stream.log("[Hearthome Fantina] Opening fade detected; waiting for dialogue."); - segment03_finish_transition(stream,context,"Hearthome Fantina opening"); - ShortDialogWatcher opening_dialogue; - if (wait_until(stream,context,15s,{{opening_dialogue}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Fantina: Opening fade was not followed by dialogue.",stream); - } else if (fantina_started!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Fantina dialogue did not start.",stream); - clear_post_battle_dialogue(stream,context,BUTTON_B); - pbf_wait(context,2000ms); - clear_post_battle_dialogue(stream,context,BUTTON_B); - // Keep Hall entry separate from Fantina's outdoor conversation. - segment03_cycling_move(stream,context,0,1,1179ms,4268ms, - "Hearthome Contest Hall entrance",true); - pbf_press_button(context,BUTTON_A,181ms,219ms); + ShortDialogWatcher resumed_dialogue; + if (wait_until(stream, context, 2000ms, {{resumed_dialogue}}) == 0){ + continue; + } + // Let the NPC finish walking away before any expiration movement. + pbf_wait(context, 6000ms); context.wait_for_all_requests(); - ShortDialogWatcher contest_dialogue; - if (wait_until(stream,context,15s,{{contest_dialogue}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Interior dialogue did not start.",stream); - // Use Buneary's resumed-text and scene-clear checks, with B to clear. - bool contest_cleared=false; - for (size_t pass=0;pass<6 && !contest_cleared;pass++) { - mash_until_dialogue_ends(stream,context,BUTTON_B,60s); - context.wait_for_all_requests(); - ShortDialogWatcher resumed_dialogue; - if (wait_until(stream,context,2000ms,{{resumed_dialogue}})==0) continue; - pbf_wait(context,6000ms); - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - OverworldDetector overworld; - ShortDialogDetector dialogue; - contest_cleared=frame && overworld.detect(*frame.frame) && !dialogue.detect(*frame.frame); + const auto frame = stream.video().snapshot(); + OverworldDetector overworld; + ShortDialogDetector dialogue; + buneary_cleared = frame && overworld.detect(*frame.frame) && !dialogue.detect(*frame.frame); + } + if (!buneary_cleared){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Buneary: Scene did not clear.", stream); + } + stream.log("[Hearthome Buneary] Scene cleared; expiring Repel without renewal before Center travel.", COLOR_GREEN); + pbf_move_left_joystick(context, {1, 0}, 500ms, 0ms); + context.wait_for_all_requests(); + force_repel_expiry(stream, context, RepelRenewalChoice::NO, false, true); + // Use the same whole-approach watcher as the Eterna bicycle shop. + // A doorway reached during recorded travel or alignment cancels all + // remaining outdoor inputs; the room must then be confirmed visually. + bool center_entered = false; + BlackScreenWatcher center_transition; + const int center_entry = run_until( + stream, context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3350ms, 0ms); + pbf_wait(ctx, 437ms); + // Clear the northbound lamp column before crossing the plaza. + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 250ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 3000ms, 0ms); + pbf_wait(ctx, 1582ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 1150ms, 0ms); + pbf_wait(ctx, 334ms); + // Repel expired before this travel; no blind response inputs remain. + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 2250ms, 0ms); + pbf_wait(ctx, 304ms); + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 300ms, 0ms); + pbf_wait(ctx, 174ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); + pbf_move_left_joystick(ctx, {-1.000000, 1.000000}, 100ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1800ms, 0ms); + pbf_wait(ctx, 21ms); + pbf_wait(ctx, 221ms); + pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 250ms, 0ms); + pbf_wait(ctx, 59ms); + align_to_overworld_landmarks(stream, ctx, match_hearthome_center_entrance, + "Hearthome Center exterior alignment", false, true, false, + match_hearthome_center_interior, ¢er_entered); + if (!center_entered){ + pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); + pbf_wait(ctx, 3000ms); + } + }, + {{center_transition}}); + if (center_entry != 0 && !center_entered){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Center: Entry transition not detected.", stream); + } + segment03_finish_transition(stream, context, "Hearthome Center entrance"); + pbf_wait(context, 1500ms); + context.wait_for_all_requests(); + segment03_confirm_location(stream, context, match_hearthome_center_interior, "Hearthome Center interior"); + if (!heal_pokemon(stream, context, "Hearthome")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Center exit not confirmed.", stream); + } + // Let the exit scene and camera settle before spending the Hall travel + // budget. The old east hold began while the exit was still fading in. + segment03_finish_transition(stream, context, "Hearthome Center heal exit"); + pbf_wait(context, 1500ms); + context.wait_for_all_requests(); + OverworldWatcher center_exit_ready; + if (wait_until(stream, context, 10s, {{center_exit_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Center: Exterior not ready after healing.", stream); + } + pbf_move_left_joystick(context, {1, 0}, 2092ms, 185ms); + // Bring the Hall into view without the old 1004ms north overshoot. + pbf_move_left_joystick(context, {0, 1}, 600ms, 500ms); + context.wait_for_all_requests(); + align_to_overworld_landmarks(stream, context, match_hearthome_contest_fantina, "Hearthome Contest Hall Fantina", + false, true); + // Repeat the existing short Up/A interaction within the 15s window. + // Dialogue cancels the remaining inputs before any text cleanup. + ShortDialogWatcher fantina_dialogue; + BlackScreenWatcher fantina_black; + WhiteScreenWatcher fantina_white; + const int fantina_started = + run_until(stream, context, + [](ProControllerContext& ctx){ + constexpr auto pulse = 16ms + 100ms + 194ms + 277ms; + Milliseconds elapsed = 0ms; + for (; elapsed + pulse <= 15s; elapsed += pulse){ + pbf_move_left_joystick(ctx, {0, 1}, 16ms, 100ms); + pbf_press_button(ctx, BUTTON_A, 194ms, 277ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx, 15s - elapsed); + }, + {{fantina_dialogue}, {fantina_black}, {fantina_white}}); + if (fantina_started > 0){ + // Fantina's opening camera cut fades to black before the text box. + // The watcher has cancelled Up/A; wait without further movement. + stream.log("[Hearthome Fantina] Opening fade detected; waiting for dialogue."); + segment03_finish_transition(stream, context, "Hearthome Fantina opening"); + ShortDialogWatcher opening_dialogue; + if (wait_until(stream, context, 15s, {{opening_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Fantina: Opening fade was not followed by dialogue.", + stream); } - if (!contest_cleared) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Interior scene did not clear.",stream); - // Own the exit before handing off the checkpoint. The transition watcher - // cancels southward movement at the fade, leaving the fixed outdoor spawn. - if (!segment03_cycling_move(stream,context,0,-1,903ms,1455ms, - "Hearthome Contest Hall exit",true)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Exit transition not confirmed.",stream); - OverworldWatcher hall_outside; - if (wait_until(stream,context,10s,{{hall_outside}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Overworld not ready after exit.",stream); - // The approach landmark view is not the post-exit doorway view. - // The watched south exit plus restored overworld completes this boundary. - stream.log("[Hearthome Contest Hall] Checkpoint complete: outside doorway, facing south."); - // End 03.04 / start 03.05: just outside the Hall, south-facing, Repel off. + }else if (fantina_started != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Fantina dialogue did not start.", stream); } - - void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context) { - OverworldWatcher hall_start; - if (wait_until(stream,context,10s,{{hall_start}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall checkpoint start: Overworld not ready.",stream); - // butters' recorded Contest Hall-to-rival route. - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1294ms, 0ms); - pbf_wait(context, 132ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3255ms, 0ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 2666ms, 0ms); - pbf_wait(context, 62ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2068ms, 0ms); - pbf_wait(context, 456ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1065ms, 0ms); - fight_trainer_using_moves(stream, context, std::vector{2, 1}, "Barry in Hearthome", true); - // butters' recorded rival-to-Lost Tower route. - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1088ms, 0ms); - pbf_wait(context, 95ms); + clear_post_battle_dialogue(stream, context, BUTTON_B); + pbf_wait(context, 2000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B); + // Keep Hall entry separate from Fantina's outdoor conversation. + segment03_cycling_move(stream, context, 0, 1, 1179ms, 4268ms, "Hearthome Contest Hall entrance", true); + pbf_press_button(context, BUTTON_A, 181ms, 219ms); + context.wait_for_all_requests(); + ShortDialogWatcher contest_dialogue; + if (wait_until(stream, context, 15s, {{contest_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Contest Hall: Interior dialogue did not start.", stream); + } + // Use Buneary's resumed-text and scene-clear checks, with B to clear. + bool contest_cleared = false; + for (size_t pass = 0; pass < 6 && !contest_cleared; pass++){ + mash_until_dialogue_ends(stream, context, BUTTON_B, 60s); context.wait_for_all_requests(); - // Cancel at each gate fade, then allow a defined settling wait. - BlackScreenWatcher east_gate_entry; - if (run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},3446ms,0ms); - pbf_wait(ctx,983ms); - },{{east_gate_entry}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Entrance transition did not start.",stream); - BlackScreenWatcher east_gate_entry_finished(COLOR_RED,{.1,.1,.8,.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{east_gate_entry_finished}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Entrance transition did not finish.",stream); - pbf_wait(context,983ms); + ShortDialogWatcher resumed_dialogue; + if (wait_until(stream, context, 2000ms, {{resumed_dialogue}}) == 0){ + continue; + } + pbf_wait(context, 6000ms); context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + OverworldDetector overworld; + ShortDialogDetector dialogue; + contest_cleared = frame && overworld.detect(*frame.frame) && !dialogue.detect(*frame.frame); + } + if (!contest_cleared){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Interior scene did not clear.", stream); + } + // Own the exit before handing off the checkpoint. The transition watcher + // cancels southward movement at the fade, leaving the fixed outdoor spawn. + if (!segment03_cycling_move(stream, context, 0, -1, 903ms, 1455ms, "Hearthome Contest Hall exit", true)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Exit transition not confirmed.", stream); + } + OverworldWatcher hall_outside; + if (wait_until(stream, context, 10s, {{hall_outside}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Overworld not ready after exit.", stream); + } + // The approach landmark view is not the post-exit doorway view. + // The watched south exit plus restored overworld completes this boundary. + stream.log("[Hearthome Contest Hall] Checkpoint complete: outside doorway, facing south."); + // End 03.04 / start 03.05: just outside the Hall, south-facing, Repel off. +} + +void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context){ + OverworldWatcher hall_start; + if (wait_until(stream, context, 10s, {{hall_start}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall checkpoint start: Overworld not ready.", + stream); + } + // butters' recorded Contest Hall-to-rival route. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1294ms, 0ms); + pbf_wait(context, 132ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3255ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2666ms, 0ms); + pbf_wait(context, 62ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2068ms, 0ms); + pbf_wait(context, 456ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1065ms, 0ms); + fight_trainer_using_moves(stream, context, std::vector{2, 1}, "Barry in Hearthome", true); + // butters' recorded rival-to-Lost Tower route. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1088ms, 0ms); + pbf_wait(context, 95ms); + context.wait_for_all_requests(); + // Cancel at each gate fade, then allow a defined settling wait. + BlackScreenWatcher east_gate_entry; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 3446ms, 0ms); + pbf_wait(ctx, 983ms); + }, + {{east_gate_entry}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Entrance transition did not start.", stream); + } + BlackScreenWatcher east_gate_entry_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{east_gate_entry_finished}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Hearthome east gate: Entrance transition did not finish.", stream); + } + pbf_wait(context, 983ms); + context.wait_for_all_requests(); + + BlackScreenWatcher east_gate_exit; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 2095ms, 0ms); + pbf_wait(ctx, 1660ms); + }, + {{east_gate_exit}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Exit transition did not start.", stream); + } + BlackScreenWatcher east_gate_exit_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{east_gate_exit_finished}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Exit transition did not finish.", stream); + } + pbf_wait(context, 1660ms); + context.wait_for_all_requests(); + stream.log("[Hearthome east gate] Entry and exit transitions finished.", COLOR_GREEN); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1128ms, 0ms); + pbf_wait(context, 7266ms); + // Use the existing Bag helper instead of replaying recorded menu inputs. + if (!activate_repel(stream, context, 8, 0)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 209: Repel activation failed.", stream); + } + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1412ms, 0ms); + pbf_wait(context, 77ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1878ms, 0ms); + pbf_wait(context, 100ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1246ms, 0ms); + pbf_wait(context, 522ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3110ms, 0ms); + pbf_wait(context, 322ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1245ms, 0ms); + pbf_wait(context, 89ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3634ms, 0ms); + pbf_wait(context, 966ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2378ms, 0ms); + pbf_wait(context, 310ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 612ms, 0ms); + pbf_wait(context, 1001ms); + context.wait_for_all_requests(); + // butters' marked road pose: sign frame and rock pair. + align_to_overworld_landmarks(stream, context, match_route209_repel_point, "Route 209 Repel point before expiry", + false, true); + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + align_to_overworld_landmarks(stream, context, match_route209_repel_point, "Route 209 Repel point after renewal", + false, true); + + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1345ms, 0ms); + pbf_wait(context, 309ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 789ms, 0ms); + pbf_wait(context, 456ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1100ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1040ms, 0ms); + pbf_wait(context, 93ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1157ms, 0ms); + pbf_wait(context, 488ms); + // Repel was renewed at the marked road pose; no delayed prompt inputs. + pbf_move_left_joystick(context, {0.000000, 1.000000}, 835ms, 0ms); + pbf_wait(context, 549ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1451ms, 0ms); + pbf_wait(context, 901ms); + context.wait_for_all_requests(); + // Normalize the checkpoint boundary with a complete enter/exit cycle. + // Cancel movement at each fade so it cannot continue onto the next map. + BlackScreenWatcher tower_entry; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 1311ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{tower_entry}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Entry transition did not start.", stream); + } + BlackScreenWatcher tower_entry_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{tower_entry_finished}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Entry transition did not finish.", stream); + } + OverworldWatcher tower_inside; + if (wait_until(stream, context, 10s, {{tower_inside}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Indoor overworld not ready.", stream); + } + + BlackScreenWatcher tower_exit; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 1311ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{tower_exit}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Exit transition did not start.", stream); + } + BlackScreenWatcher tower_exit_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{tower_exit_finished}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Exit transition did not finish.", stream); + } + OverworldWatcher tower_outside; + if (wait_until(stream, context, 10s, {{tower_outside}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower handoff: Outdoor overworld not ready.", stream); + } + context.wait_for_all_requests(); + stream.log("[Lost Tower handoff] Outside doorway, facing south; checkpoint complete.", COLOR_GREEN); +} +void lost_tower(VideoStream& stream, ProControllerContext& context){ + // Checkpoint 03.06 starts outside the doorway, facing south, with Repel active. + // Turn north, then enter using the same single-step timing as Segment 02. + context.wait_for_all_requests(); + BlackScreenWatcher entering_tower; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 2; i++){ + pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); + } + pbf_wait(ctx, 2389ms); + }, + {{entering_tower}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Transition did not start.", stream); + } + BlackScreenWatcher tower_entry_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{tower_entry_finished}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Transition did not finish.", stream); + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + OverworldWatcher tower_entry_ready; + if (wait_until(stream, context, 10s, {{tower_entry_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Overworld not ready.", stream); + } - BlackScreenWatcher east_gate_exit; - if (run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{1,0},2095ms,0ms); - pbf_wait(ctx,1660ms); - },{{east_gate_exit}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Exit transition did not start.",stream); - BlackScreenWatcher east_gate_exit_finished(COLOR_RED,{.1,.1,.8,.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{east_gate_exit_finished}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Exit transition did not finish.",stream); - pbf_wait(context,1660ms); - context.wait_for_all_requests(); - stream.log("[Hearthome east gate] Entry and exit transitions finished.",COLOR_GREEN); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1128ms, 0ms); - pbf_wait(context, 7266ms); - // Use the existing Bag helper instead of replaying recorded menu inputs. - if (!activate_repel(stream, context, 8, 0)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 209: Repel activation failed.", stream); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1412ms, 0ms); - pbf_wait(context, 77ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1878ms, 0ms); - pbf_wait(context, 100ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1246ms, 0ms); - pbf_wait(context, 522ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3110ms, 0ms); - pbf_wait(context, 322ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1245ms, 0ms); - pbf_wait(context, 89ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3634ms, 0ms); - pbf_wait(context, 966ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2378ms, 0ms); - pbf_wait(context, 310ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 612ms, 0ms); - pbf_wait(context, 1001ms); - context.wait_for_all_requests(); - // butters' marked road pose: sign frame and rock pair. - align_to_overworld_landmarks(stream, context, match_route209_repel_point, - "Route 209 Repel point before expiry", false, true); - force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - align_to_overworld_landmarks(stream, context, match_route209_repel_point, - "Route 209 Repel point after renewal", false, true); - - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1345ms, 0ms); - pbf_wait(context, 309ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 789ms, 0ms); - pbf_wait(context, 456ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1100ms, 0ms); - pbf_wait(context, 122ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1040ms, 0ms); - pbf_wait(context, 93ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1157ms, 0ms); - pbf_wait(context, 488ms); - // Repel was renewed at the marked road pose; no delayed prompt inputs. - pbf_move_left_joystick(context, {0.000000, 1.000000}, 835ms, 0ms); - pbf_wait(context, 549ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1451ms, 0ms); - pbf_wait(context, 901ms); - context.wait_for_all_requests(); - // Normalize the checkpoint boundary with a complete enter/exit cycle. - // Cancel movement at each fade so it cannot continue onto the next map. - BlackScreenWatcher tower_entry; - if (run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,1},1311ms,0ms); - pbf_wait(ctx,3000ms); - },{{tower_entry}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Entry transition did not start.",stream); - BlackScreenWatcher tower_entry_finished(COLOR_RED,{.1,.1,.8,.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{tower_entry_finished}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Entry transition did not finish.",stream); - OverworldWatcher tower_inside; - if (wait_until(stream,context,10s,{{tower_inside}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Indoor overworld not ready.",stream); - - BlackScreenWatcher tower_exit; - if (run_until(stream,context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx,{0,-1},1311ms,0ms); - pbf_wait(ctx,3000ms); - },{{tower_exit}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Exit transition did not start.",stream); - BlackScreenWatcher tower_exit_finished(COLOR_RED,{.1,.1,.8,.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{tower_exit_finished}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Exit transition did not finish.",stream); - OverworldWatcher tower_outside; - if (wait_until(stream,context,10s,{{tower_outside}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Outdoor overworld not ready.",stream); - context.wait_for_all_requests(); - stream.log("[Lost Tower handoff] Outside doorway, facing south; checkpoint complete.",COLOR_GREEN); + // butters' route, using the same counted D-pad steps as the Eterna Gym. + // Counts include one facing press whenever the commanded direction changes. + // Keep the same direction across a dialogue stop without another facing press. + struct Move { + DpadPosition dir; + int count; + }; + const Move to_floor2[] = { + {DPAD_UP, 4}, {DPAD_LEFT, 4}, {DPAD_UP, 6}, {DPAD_LEFT, 2}, {DPAD_UP, 3}, {DPAD_RIGHT, 2}, + }; + BlackScreenWatcher stairs_to_floor2; + if (run_until(stream, context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor2}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 2: Stair transition did not start.", stream); + } + segment03_finish_transition(stream, context, "Lost Tower floor 2"); + OverworldWatcher floor2_ready; + if (wait_until(stream, context, 10s, {{floor2_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 2: Overworld not ready after stairs.", stream); } - void lost_tower(VideoStream& stream, ProControllerContext& context) { - // Checkpoint 03.06 starts outside the doorway, facing south, with Repel active. - // Turn north, then enter using the same single-step timing as Segment 02. - context.wait_for_all_requests(); - BlackScreenWatcher entering_tower; - if (run_until(stream, context, - [](ProControllerContext& ctx) { - for (int i = 0; i < 2; i++) { - pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); - } - pbf_wait(ctx, 2389ms); - }, {{entering_tower}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower entry: Transition did not start.", stream); - BlackScreenWatcher tower_entry_finished(COLOR_RED, {.1,.1,.8,.8}, - 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); - if (wait_until(stream, context, 30s, {{tower_entry_finished}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower entry: Transition did not finish.", stream); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - OverworldWatcher tower_entry_ready; - if (wait_until(stream, context, 10s, {{tower_entry_ready}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower entry: Overworld not ready.", stream); - - // butters' route, using the same counted D-pad steps as the Eterna Gym. - // Counts include one facing press whenever the commanded direction changes. - // Keep the same direction across a dialogue stop without another facing press. - struct Move { - DpadPosition dir; - int count; - }; - const Move to_floor2[] = { - {DPAD_UP, 4}, - {DPAD_LEFT, 4}, - {DPAD_UP, 6}, - {DPAD_LEFT, 2}, - {DPAD_UP, 3}, - {DPAD_RIGHT, 2}, - }; - BlackScreenWatcher stairs_to_floor2; - if (run_until(stream, context, - [&](ProControllerContext& ctx) { - for (const auto& move : to_floor2) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, {{stairs_to_floor2}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 2: Stair transition did not start.", stream); - segment03_finish_transition(stream, context, "Lost Tower floor 2"); - OverworldWatcher floor2_ready; - if (wait_until(stream, context, 10s, {{floor2_ready}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 2: Overworld not ready after stairs.", stream); - - const Move to_floor3[] = { - {DPAD_DOWN, 12}, - {DPAD_LEFT, 6}, - {DPAD_UP, 2}, - // Includes the extra left step to clear the gravestone column. - {DPAD_LEFT, 8}, - {DPAD_UP, 8}, - {DPAD_RIGHT, 2}, - {DPAD_UP, 4}, - {DPAD_RIGHT, 4}, - }; - BlackScreenWatcher stairs_to_floor3; - if (run_until(stream, context, - [&](ProControllerContext& ctx) { - for (const auto& move : to_floor3) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, {{stairs_to_floor3}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 3: Stair transition did not start.", stream); - segment03_finish_transition(stream, context, "Lost Tower floor 3"); - OverworldWatcher floor3_ready; - if (wait_until(stream, context, 10s, {{floor3_ready}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 3: Overworld not ready after stairs.", stream); - - const Move to_repel[] = { - {DPAD_DOWN, 6}, - }; - bool repel_renewed = false; - ShortDialogDetector pending_repel_dialogue(COLOR_YELLOW); - for (const auto& move : to_repel) { - for (int i = 0; i < move.count; i++) { - // Never queue another step while an expiry prompt is already visible. - context.wait_for_all_requests(); - const auto frame = stream.video().snapshot(); - if (!frame) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower Repel: No frame before the next down step.", stream); - if (pending_repel_dialogue.detect(*frame.frame)) { - stream.log("[Lost Tower Repel] Pending renewal before down press " + - std::to_string(i + 1) + "; clearing before movement."); - clear_post_battle_dialogue(stream, context, BUTTON_A); - repel_renewed = true; - } - // Watch only one press at a time. Its 80ms hold finishes before the - // fresh dialogue watcher's 250ms confirmation can cancel the rest. - // The triggering step counts; all subsequent steps remain unqueued. - ShortDialogWatcher step_repel_dialogue(COLOR_YELLOW); - const int expiry = run_until(stream, context, - [&](ProControllerContext& ctx) { - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - ctx.wait_for_all_requests(); - pbf_wait(ctx, 500ms); - }, {{step_repel_dialogue}}); - if (expiry == 0) { - stream.log("[Lost Tower Repel] Renewal after down press " + - std::to_string(i + 1) + "/" + std::to_string(move.count) + - "; clearing before the remaining steps."); - clear_post_battle_dialogue(stream, context, BUTTON_A); - repel_renewed = true; - } - context.wait_for_all_requests(); - } - } - // Preserve the bounded final observation if expiry arrives after the last step. - if (!repel_renewed) { - ShortDialogWatcher late_repel_dialogue(COLOR_YELLOW); - if (wait_until(stream, context, 3s, {{late_repel_dialogue}}) == 0) { - stream.log("[Lost Tower Repel] Late renewal dialogue detected; accepting renewal."); - clear_post_battle_dialogue(stream, context, BUTTON_A); - } else { - stream.log("[Lost Tower Repel] No renewal dialogue at the recorded stop; continuing."); - } - } + const Move to_floor3[] = { + {DPAD_DOWN, 12}, + {DPAD_LEFT, 6}, + {DPAD_UP, 2}, + // Includes the extra left step to clear the gravestone column. + {DPAD_LEFT, 8}, + {DPAD_UP, 8}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 4}, + }; + BlackScreenWatcher stairs_to_floor3; + if (run_until(stream, context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor3}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 3: Stair transition did not start.", stream); + } + segment03_finish_transition(stream, context, "Lost Tower floor 3"); + OverworldWatcher floor3_ready; + if (wait_until(stream, context, 10s, {{floor3_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 3: Overworld not ready after stairs.", stream); + } - const Move to_biker[] = { - {DPAD_DOWN, 1}, - {DPAD_RIGHT, 2}, - {DPAD_DOWN, 3}, - {DPAD_LEFT, 2}, - {DPAD_DOWN, 6}, - // Clear the gravestone column before beginning the watched up step. - {DPAD_LEFT, 5}, - {DPAD_UP, 2}, - }; - for (const auto& move : to_biker) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } - } - context.wait_for_all_requests(); - // The final up movement is one facing press and one movement press. - // Stop at a trainer prompt before any later path segment is queued. - constexpr int BIKER_UP_PRESSES = 2; - int biker_up_completed = 0; - // A trainer's walk-up can lock movement before the dialogue box appears. - ShortDialogWatcher corner_biker_dialogue(COLOR_YELLOW); - bool biker_challenged = wait_until(stream, context, 5s, {{corner_biker_dialogue}}) == 0; - ShortDialogDetector pending_biker_dialogue(COLOR_YELLOW); - while (!biker_challenged && biker_up_completed < BIKER_UP_PRESSES) { + const Move to_repel[] = { + {DPAD_DOWN, 6}, + }; + bool repel_renewed = false; + ShortDialogDetector pending_repel_dialogue(COLOR_YELLOW); + for (const auto& move : to_repel){ + for (int i = 0; i < move.count; i++){ + // Never queue another step while an expiry prompt is already visible. context.wait_for_all_requests(); const auto frame = stream.video().snapshot(); - if (!frame) + if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower biker: No frame before the next up press.", stream); - if (pending_biker_dialogue.detect(*frame.frame)) { - biker_challenged = true; - break; + "Lost Tower Repel: No frame before the next down step.", + stream); + } + if (pending_repel_dialogue.detect(*frame.frame)){ + stream.log("[Lost Tower Repel] Pending renewal before down press " + std::to_string(i + 1) + + "; clearing before movement."); + clear_post_battle_dialogue(stream, context, BUTTON_A); + repel_renewed = true; } - // A fresh 250ms dialogue watch cannot cancel the 80ms hold itself. - // Count that press, then preserve any subsequent unqueued presses. - ShortDialogWatcher approaching_biker(COLOR_YELLOW); - const int challenge = run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); - ctx.wait_for_all_requests(); - // Observe the walk-up before another up press can be queued. - pbf_wait(ctx, 5000ms); - }, {{approaching_biker}}); - biker_up_completed++; - if (challenge == 0) { - biker_challenged = true; - break; + // Watch only one press at a time. Its 80ms hold finishes before the + // fresh dialogue watcher's 250ms confirmation can cancel the rest. + // The triggering step counts; all subsequent steps remain unqueued. + ShortDialogWatcher step_repel_dialogue(COLOR_YELLOW); + const int expiry = run_until(stream, context, + [&](ProControllerContext& ctx){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + ctx.wait_for_all_requests(); + pbf_wait(ctx, 500ms); + }, + {{step_repel_dialogue}}); + if (expiry == 0){ + stream.log("[Lost Tower Repel] Renewal after down press " + std::to_string(i + 1) + "/" + + std::to_string(move.count) + "; clearing before the remaining steps."); + clear_post_battle_dialogue(stream, context, BUTTON_A); + repel_renewed = true; } context.wait_for_all_requests(); } - if (biker_challenged) { - stream.log("[Lost Tower biker] Dialogue detected after " + - std::to_string(biker_up_completed) + " up presses; handling the trainer battle."); - fight_trainer_using_moves(stream, context, std::vector{2}, - "Lost Tower biker", false); - clear_post_battle_dialogue(stream, context, BUTTON_B); - - // The trainer conversation turns the player toward the biker. - // Restore north if its facing press was already used before the battle. - if (biker_up_completed > 0) { - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - context.wait_for_all_requests(); - } - for (; biker_up_completed < BIKER_UP_PRESSES; biker_up_completed++) { - pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); - } - context.wait_for_all_requests(); - stream.log("[Lost Tower biker] North-facing approach complete; resuming the recorded path."); - } else { - stream.log("[Lost Tower biker] No dialogue; continuing the recorded path."); + } + // Preserve the bounded final observation if expiry arrives after the last step. + if (!repel_renewed){ + ShortDialogWatcher late_repel_dialogue(COLOR_YELLOW); + if (wait_until(stream, context, 3s, {{late_repel_dialogue}}) == 0){ + stream.log("[Lost Tower Repel] Late renewal dialogue detected; accepting renewal."); + clear_post_battle_dialogue(stream, context, BUTTON_A); + }else{ + stream.log("[Lost Tower Repel] No renewal dialogue at the recorded stop; continuing."); } + } - const Move to_floor4[] = { - {DPAD_UP, 1}, - {DPAD_LEFT, 2}, - {DPAD_UP, 4}, - {DPAD_LEFT, 6}, - {DPAD_UP, 4}, - {DPAD_RIGHT, 3}, - {DPAD_UP, 4}, - {DPAD_RIGHT, 3}, - }; - BlackScreenWatcher stairs_to_floor4; - if (run_until(stream, context, - [&](ProControllerContext& ctx) { - for (const auto& move : to_floor4) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, {{stairs_to_floor4}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 4: Stair transition did not start.", stream); - segment03_finish_transition(stream, context, "Lost Tower floor 4"); - OverworldWatcher floor4_ready; - if (wait_until(stream, context, 10s, {{floor4_ready}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 4: Overworld not ready after stairs.", stream); - - const Move to_floor5[] = { - {DPAD_DOWN, 6}, - {DPAD_LEFT, 8}, - {DPAD_UP, 3}, - {DPAD_LEFT, 2}, - {DPAD_UP, 4}, - {DPAD_RIGHT, 3}, - }; - BlackScreenWatcher stairs_to_floor5; - if (run_until(stream, context, - [&](ProControllerContext& ctx) { - for (const auto& move : to_floor5) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, {{stairs_to_floor5}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 5: Stair transition did not start.", stream); - segment03_finish_transition(stream, context, "Lost Tower floor 5"); - OverworldWatcher floor5_ready; - if (wait_until(stream, context, 10s, {{floor5_ready}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 5: Overworld not ready after stairs.", stream); - - const Move to_rewards[] = { - {DPAD_DOWN, 4}, - {DPAD_LEFT, 5}, - {DPAD_DOWN, 3}, - }; - for (const auto& move : to_rewards) { - for (int i = 0; i < move.count; i++) { - pbf_press_dpad(context, move.dir, 80ms, 300ms); - } + const Move to_biker[] = { + {DPAD_DOWN, 1}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 3}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 6}, + // Clear the gravestone column before beginning the watched up step. + {DPAD_LEFT, 5}, + {DPAD_UP, 2}, + }; + for (const auto& move : to_biker){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); } + } + context.wait_for_all_requests(); + // The final up movement is one facing press and one movement press. + // Stop at a trainer prompt before any later path segment is queued. + constexpr int BIKER_UP_PRESSES = 2; + int biker_up_completed = 0; + // A trainer's walk-up can lock movement before the dialogue box appears. + ShortDialogWatcher corner_biker_dialogue(COLOR_YELLOW); + bool biker_challenged = wait_until(stream, context, 5s, {{corner_biker_dialogue}}) == 0; + ShortDialogDetector pending_biker_dialogue(COLOR_YELLOW); + while (!biker_challenged && biker_up_completed < BIKER_UP_PRESSES){ context.wait_for_all_requests(); - // Use the Segment 01 conversation pattern: start once, then clear with B. + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Lost Tower biker: No frame before the next up press.", stream); + } + if (pending_biker_dialogue.detect(*frame.frame)){ + biker_challenged = true; + break; + } + + // A fresh 250ms dialogue watch cannot cancel the 80ms hold itself. + // Count that press, then preserve any subsequent unqueued presses. + ShortDialogWatcher approaching_biker(COLOR_YELLOW); + const int challenge = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); + ctx.wait_for_all_requests(); + // Observe the walk-up before another up press can be queued. + pbf_wait(ctx, 5000ms); + }, + {{approaching_biker}}); + biker_up_completed++; + if (challenge == 0){ + biker_challenged = true; + break; + } context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_A, 80ms, 100ms); + } + if (biker_challenged){ + stream.log("[Lost Tower biker] Dialogue detected after " + std::to_string(biker_up_completed) + + " up presses; handling the trainer battle."); + fight_trainer_using_moves(stream, context, std::vector{2}, "Lost Tower biker", false); + clear_post_battle_dialogue(stream, context, BUTTON_B); + + // The trainer conversation turns the player toward the biker. + // Restore north if its facing press was already used before the battle. + if (biker_up_completed > 0){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + context.wait_for_all_requests(); + } + for (; biker_up_completed < BIKER_UP_PRESSES; biker_up_completed++){ + pbf_press_dpad(context, DPAD_UP, 80ms, 300ms); + } context.wait_for_all_requests(); - wait_for_dialogue(stream, context, "Lost Tower reward: First box", 30s); - clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); - stream.log("[Lost Tower reward] Dialogue absent for four seconds; opening the Bag."); - if (!use_escape_rope(stream, context, 8)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower: Escape Rope exit was not confirmed.", stream); - // The exterior is visible before the Rope arrival camera finishes settling. - // Keep the following south/left movement out of that arrival animation. - pbf_wait(context, 5000ms); + stream.log("[Lost Tower biker] North-facing approach complete; resuming the recorded path."); + }else{ + stream.log("[Lost Tower biker] No dialogue; continuing the recorded path."); + } + + const Move to_floor4[] = { + {DPAD_UP, 1}, {DPAD_LEFT, 2}, {DPAD_UP, 4}, {DPAD_LEFT, 6}, + {DPAD_UP, 4}, {DPAD_RIGHT, 3}, {DPAD_UP, 4}, {DPAD_RIGHT, 3}, + }; + BlackScreenWatcher stairs_to_floor4; + if (run_until(stream, context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor4}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 4: Stair transition did not start.", stream); + } + segment03_finish_transition(stream, context, "Lost Tower floor 4"); + OverworldWatcher floor4_ready; + if (wait_until(stream, context, 10s, {{floor4_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 4: Overworld not ready after stairs.", stream); + } + + const Move to_floor5[] = { + {DPAD_DOWN, 6}, {DPAD_LEFT, 8}, {DPAD_UP, 3}, {DPAD_LEFT, 2}, {DPAD_UP, 4}, {DPAD_RIGHT, 3}, + }; + BlackScreenWatcher stairs_to_floor5; + if (run_until(stream, context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor5}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 5: Stair transition did not start.", stream); + } + segment03_finish_transition(stream, context, "Lost Tower floor 5"); + OverworldWatcher floor5_ready; + if (wait_until(stream, context, 10s, {{floor5_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 5: Overworld not ready after stairs.", stream); + } + + const Move to_rewards[] = { + {DPAD_DOWN, 4}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 3}, + }; + for (const auto& move : to_rewards){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + } + } + context.wait_for_all_requests(); + // Use the Segment 01 conversation pattern: start once, then clear with B. + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 80ms, 100ms); + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Lost Tower reward: First box", 30s); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + stream.log("[Lost Tower reward] Dialogue absent for four seconds; opening the Bag."); + if (!use_escape_rope(stream, context, 8)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower: Escape Rope exit was not confirmed.", stream); + } + // The exterior is visible before the Rope arrival camera finishes settling. + // Keep the following south/left movement out of that arrival animation. + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + OverworldWatcher rope_exit_ready; + if (wait_until(stream, context, 10s, {{rope_exit_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Lost Tower: Overworld not ready after Escape Rope settling.", stream); + } + context.wait_for_all_requests(); + stream.log("[Lost Tower] Reward dialogue cleared; Escape Rope exit settled.", COLOR_GREEN); +} + +void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context){ + // Recorded Tower-to-Gym path; shared helpers handle prompts and battles. + pbf_move_left_joystick(context, {0.000000, -1.000000}, 336ms, 0ms); + pbf_wait(context, 590ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 327ms, 0ms); + pbf_wait(context, 590ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 745ms, 0ms); + pbf_wait(context, 379ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 77ms, 0ms); + pbf_wait(context, 17ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1167ms, 0ms); + pbf_wait(context, 1882ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 512ms, 0ms); + pbf_wait(context, 171ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 500ms, 0ms); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1701ms, 0ms); + pbf_press_button(context, BUTTON_PLUS, 181ms, 219ms); + // Stay north/south on the open road beside Lost Tower. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, true, true); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 4384ms, 0ms); + pbf_wait(context, 1767ms); + pbf_press_button(context, BUTTON_PLUS, 181ms, 219ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1701ms, 0ms); + pbf_wait(context, 133ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1379ms, 0ms); + pbf_wait(context, 365ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1701ms, 0ms); + pbf_wait(context, 133ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 3179ms, 0ms); + pbf_wait(context, 76ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 3172ms, 0ms); + pbf_wait(context, 28ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 4210ms, 0ms); + pbf_wait(context, 421ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2327ms, 0ms); + pbf_wait(context, 71ms); + // Expiry can interrupt this eastward leg or the following north turn. + // Renew on the clear strip, then repeat the recorded eastward alignment + // against the trees before taking the uninterrupted north leg. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2327ms, 0ms); + pbf_wait(context, 71ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1028ms, 0ms); + pbf_wait(context, 120ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2480ms, 0ms); + pbf_wait(context, 773ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 182ms, 0ms); + pbf_wait(context, 567ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2514ms, 0ms); + pbf_wait(context, 86ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 180ms, 0ms); + pbf_wait(context, 308ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 137ms, 0ms); + pbf_wait(context, 510ms); + pbf_wait(context, 1767ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 500ms, 0ms); + pbf_wait(context, 510ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 383ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 161ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 756ms, 0ms); + pbf_wait(context, 335ms); + // The clear cliff platform permits west/east renewal. Restore its east + // edge afterward so a partial expiry pulse cannot offset the bridge approach. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 756ms, 0ms); + pbf_wait(context, 335ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2176ms, 0ms); + pbf_wait(context, 24ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 826ms, 0ms); + pbf_wait(context, 561ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 957ms, 0ms); + pbf_wait(context, 621ms); + context.wait_for_all_requests(); + ShortDialogWatcher calvin_dialogue; + StartBattleDetector calvin_start(stream.overlay()); + BattleMenuWatcher calvin_menu(BattleType::TRAINER); + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 794ms, 0ms); + pbf_wait(ctx, 15000ms); + }, + {{calvin_dialogue}, {calvin_start}, {calvin_menu}}) < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Calvin: Trainer encounter did not start.", stream); + } + fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ruin Maniac Calvin", false); + // Renew in the clear area after Calvin, before the next walking strip. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + // Cover at most one unfinished 500ms westward expiry pulse. + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1582ms, 0ms); + pbf_wait(context, 406ms); + // The item occupies lane 2. Search only lanes 4 and 3 (numbered from the top). + // Every D-pad press is observed separately: facing changes must not become + // an unverified extra step north. Fixed scenery measures eastward progress. + ShortDialogDetector jogger_dialogue; + BattleMenuDetector jogger_battle(BattleType::TRAINER, COLOR_GREEN); + BlackScreenDetector jogger_transition; + constexpr double lane_pitch = 70.0 / 1080; + constexpr double lane_tolerance = 1.0 / 54; + size_t missing_pose = 0, stale_frames = 0, stalled = 0, lane_changes = 0, lane_presses = 0, arrival_frames = 0, + jogger_battles = 0; + int changing_to = 0; + bool east_pending = false, last_coarse = false, corner_ready = false; + double last_x = 0; + Milliseconds bypass_movement = 0ms; + WallClock fresh_after = current_time(), last_frame_time = WallClock::min(); + auto bypass_started = std::chrono::steady_clock::now(); + for (size_t observation = 0; observation < 120; observation++){ + context.throw_if_cancelled(); context.wait_for_all_requests(); - OverworldWatcher rope_exit_ready; - if (wait_until(stream, context, 10s, {{rope_exit_ready}}) != 0) + if (std::chrono::steady_clock::now() - bypass_started > 90s){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower: Overworld not ready after Escape Rope settling.", stream); - context.wait_for_all_requests(); - stream.log("[Lost Tower] Reward dialogue cleared; Escape Rope exit settled.", COLOR_GREEN); - } - - void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context) { - // Recorded Tower-to-Gym path; shared helpers handle prompts and battles. - pbf_move_left_joystick(context, {0.000000, -1.000000}, 336ms, 0ms); - pbf_wait(context, 590ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 327ms, 0ms); - pbf_wait(context, 590ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 745ms, 0ms); - pbf_wait(context, 379ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 77ms, 0ms); - pbf_wait(context, 17ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1167ms, 0ms); - pbf_wait(context, 1882ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 512ms, 0ms); - pbf_wait(context, 171ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 500ms, 0ms); - pbf_wait(context, 1767ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1701ms, 0ms); - pbf_press_button(context, BUTTON_PLUS, 181ms, 219ms); - // Stay north/south on the open road beside Lost Tower. - force_repel_expiry(stream, context, RepelRenewalChoice::YES, true, true); - pbf_wait(context, 1767ms); - pbf_move_left_joystick(context, { 0.000000, 1.000000 }, 4384ms, 0ms); - pbf_wait(context, 1767ms); - pbf_press_button(context, BUTTON_PLUS, 181ms, 219ms); - pbf_move_left_joystick(context, { -1.000000, -0.000000 }, 1701ms, 0ms); - pbf_wait(context, 133ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1379ms, 0ms); - pbf_wait(context, 365ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1701ms, 0ms); - pbf_wait(context, 133ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 3179ms, 0ms); - pbf_wait(context, 76ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 3172ms, 0ms); - pbf_wait(context, 28ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 4210ms, 0ms); - pbf_wait(context, 421ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2327ms, 0ms); - pbf_wait(context, 71ms); - // Expiry can interrupt this eastward leg or the following north turn. - // Renew on the clear strip, then repeat the recorded eastward alignment - // against the trees before taking the uninterrupted north leg. - force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2327ms, 0ms); - pbf_wait(context, 71ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1028ms, 0ms); - pbf_wait(context, 120ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2480ms, 0ms); - pbf_wait(context, 773ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 182ms, 0ms); - pbf_wait(context, 567ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2514ms, 0ms); - pbf_wait(context, 86ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 180ms, 0ms); - pbf_wait(context, 308ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 137ms, 0ms); - pbf_wait(context, 510ms); - pbf_wait(context, 1767ms); - pbf_move_left_joystick(context, { 1.000000, -0.000000 }, 500ms, 0ms); - pbf_wait(context, 510ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 383ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 161ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 756ms, 0ms); - pbf_wait(context, 335ms); - // The clear cliff platform permits west/east renewal. Restore its east - // edge afterward so a partial expiry pulse cannot offset the bridge approach. - force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 756ms, 0ms); - pbf_wait(context, 335ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2176ms, 0ms); - pbf_wait(context, 24ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 826ms, 0ms); - pbf_wait(context, 561ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 957ms, 0ms); - pbf_wait(context, 621ms); - context.wait_for_all_requests(); - ShortDialogWatcher calvin_dialogue; - StartBattleDetector calvin_start(stream.overlay()); - BattleMenuWatcher calvin_menu(BattleType::TRAINER); - if (run_until(stream,context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx,{1.000000,0.000000},794ms,0ms); - pbf_wait(ctx,15000ms); - },{{calvin_dialogue},{calvin_start},{calvin_menu}})<0) + "Route 215 jogger: Lane search timed out.", stream); + } + const auto frame = stream.video().snapshot(); + if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 Calvin: Trainer encounter did not start.",stream); - fight_trainer_using_moves(stream, context, std::vector{2}, - "Route 215 Ruin Maniac Calvin", false); - // Renew in the clear area after Calvin, before the next walking strip. - force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - // Cover at most one unfinished 500ms westward expiry pulse. - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1582ms, 0ms); - pbf_wait(context, 406ms); - // The item occupies lane 2. Search only lanes 4 and 3 (numbered from the top). - // Every D-pad press is observed separately: facing changes must not become - // an unverified extra step north. Fixed scenery measures eastward progress. - ShortDialogDetector jogger_dialogue; - BattleMenuDetector jogger_battle(BattleType::TRAINER, COLOR_GREEN); - BlackScreenDetector jogger_transition; - constexpr double lane_pitch=70.0/1080; - constexpr double lane_tolerance=1.0/54; - size_t missing_pose=0, stale_frames=0, stalled=0, lane_changes=0, lane_presses=0, arrival_frames=0, jogger_battles=0; - int changing_to=0; - bool east_pending=false, last_coarse=false, corner_ready=false; - double last_x=0; - Milliseconds bypass_movement=0ms; - WallClock fresh_after=current_time(), last_frame_time=WallClock::min(); - auto bypass_started=std::chrono::steady_clock::now(); - for (size_t observation=0;observation<120;observation++){ - context.throw_if_cancelled(); - context.wait_for_all_requests(); - if (std::chrono::steady_clock::now()-bypass_started>90s) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Lane search timed out.",stream); - const auto frame=stream.video().snapshot(); - if (!frame) + "Route 215 jogger: No video frame.", stream); + } + if (frame.timestamp <= fresh_after || frame.timestamp <= last_frame_time){ + if (++stale_frames >= 8){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: No video frame.",stream); - if (frame.timestamp<=fresh_after || frame.timestamp<=last_frame_time){ - if (++stale_frames>=8) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Video is not advancing after movement.",stream); - pbf_wait(context,500ms); - continue; + "Route 215 jogger: Video is not advancing after movement.", + stream); } - last_frame_time=frame.timestamp;stale_frames=0; - if (jogger_dialogue.detect(*frame.frame) || jogger_battle.detect(*frame.frame)){ - if (++jogger_battles>1) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Repeated unexpected encounter.",stream); - // The recorded failure is the jogger's trainer introduction. - // Complete it with the shared move-list handler, then measure - // the current lane again; the trainer may have approached us. - fight_trainer_using_moves(stream,context,std::vector{2}, - "Route 215 jogger",false); - context.wait_for_all_requests();fresh_after=current_time(); - last_frame_time=WallClock::min(); - missing_pose=0;stale_frames=0;stalled=0;arrival_frames=0; - changing_to=0;lane_presses=0;east_pending=false; - bypass_started=std::chrono::steady_clock::now(); - continue; + pbf_wait(context, 500ms); + continue; + } + last_frame_time = frame.timestamp; + stale_frames = 0; + if (jogger_dialogue.detect(*frame.frame) || jogger_battle.detect(*frame.frame)){ + if (++jogger_battles > 1){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 215 jogger: Repeated unexpected encounter.", stream); } - if (jogger_transition.detect(*frame.frame)) + // The recorded failure is the jogger's trainer introduction. + // Complete it with the shared move-list handler, then measure + // the current lane again; the trainer may have approached us. + fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 jogger", false); + context.wait_for_all_requests(); + fresh_after = current_time(); + last_frame_time = WallClock::min(); + missing_pose = 0; + stale_frames = 0; + stalled = 0; + arrival_frames = 0; + changing_to = 0; + lane_presses = 0; + east_pending = false; + bypass_started = std::chrono::steady_clock::now(); + continue; + } + if (jogger_transition.detect(*frame.frame)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Unexpected transition.", stream); + } + const auto pose = match_route215_jogger_corner(*frame.frame); + for (const auto& anchor : pose.landmarks){ + stream.log("[Route 215 jogger] " + anchor.name + " score=" + std::to_string(anchor.score) + + ", margin=" + std::to_string(anchor.margin) + ", accepted=" + std::to_string(anchor.accepted) + + ", offset=(" + std::to_string(anchor.dx) + "," + std::to_string(anchor.dy) + ")"); + } + const int lane = pose.located && std::abs(pose.dy) <= lane_tolerance ? 4 + : pose.located && std::abs(pose.dy - lane_pitch) <= lane_tolerance ? 3 + : 0; + stream.log("[Route 215 jogger] lane=" + std::to_string(lane) + ", coarse=" + std::to_string(pose.coarse) + + ", offset=(" + std::to_string(pose.dx) + "," + std::to_string(pose.dy) + ")"); + if (!lane){ + // Wait out rain/clouds or a settling step; never search vertically + // from a missing or ambiguous pose, including after a blocked step. + arrival_frames = 0; + if (++missing_pose >= 8){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Unexpected transition.",stream); - const auto pose=match_route215_jogger_corner(*frame.frame); - for (const auto& anchor : pose.landmarks) - stream.log("[Route 215 jogger] "+anchor.name+" score="+std::to_string(anchor.score)+ - ", margin="+std::to_string(anchor.margin)+", accepted="+std::to_string(anchor.accepted)+ - ", offset=("+std::to_string(anchor.dx)+","+std::to_string(anchor.dy)+")"); - const int lane=pose.located && std::abs(pose.dy)<=lane_tolerance ? 4 : - pose.located && std::abs(pose.dy-lane_pitch)<=lane_tolerance ? 3 : 0; - stream.log("[Route 215 jogger] lane="+std::to_string(lane)+ - ", coarse="+std::to_string(pose.coarse)+", offset=("+ - std::to_string(pose.dx)+","+std::to_string(pose.dy)+")"); - if (!lane){ - // Wait out rain/clouds or a settling step; never search vertically - // from a missing or ambiguous pose, including after a blocked step. - arrival_frames=0; - if (++missing_pose>=8) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Cannot verify lane 3 or 4.",stream); - pbf_wait(context,500ms); - continue; - } - missing_pose=0; - if (east_pending){ - stalled=pose.coarse==last_coarse && last_x-pose.dx<1.0/256 ? stalled+1 : 0; - east_pending=false; - } - if (changing_to && lane==changing_to){ - changing_to=0;lane_presses=0;stalled=0; - } - const bool near_corner=!pose.coarse && std::abs(pose.dx)<=1.0/128; - if (near_corner && lane==4 && !changing_to){ - if (++arrival_frames<3){pbf_wait(context,250ms);continue;} - force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); - // Expiry preserves lane 4. Cover its possible unfinished 500ms - // west pulse, then settle east against the fence before going south. - pbf_move_left_joystick(context,{1.0,0.0},1000ms,0ms); - context.wait_for_all_requests(); - corner_ready=true; - break; + "Route 215 jogger: Cannot verify lane 3 or 4.", stream); } - arrival_frames=0; - if (!changing_to && ((near_corner && lane==3) || stalled>=2)){ - if (++lane_changes>6) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Both safe lanes remain blocked.",stream); - changing_to=lane==4 ? 3 : 4; - lane_presses=0;stalled=0; - pbf_wait(context,750ms); + pbf_wait(context, 500ms); + continue; + } + missing_pose = 0; + if (east_pending){ + stalled = pose.coarse == last_coarse && last_x - pose.dx < 1.0 / 256 ? stalled + 1 : 0; + east_pending = false; + } + if (changing_to && lane == changing_to){ + changing_to = 0; + lane_presses = 0; + stalled = 0; + } + const bool near_corner = !pose.coarse && std::abs(pose.dx) <= 1.0 / 128; + if (near_corner && lane == 4 && !changing_to){ + if (++arrival_frames < 3){ + pbf_wait(context, 250ms); continue; } - const auto movement_limit=8000ms; - if (changing_to){ - if (lane_presses>=2){ - // A blocked lane change leaves us in the observed lane. - // Wait for the jogger, then try east again without assuming a step. - changing_to=0;lane_presses=0; - pbf_wait(context,750ms); - continue; - } - if (bypass_movement+80ms>movement_limit) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Movement budget exhausted.",stream); - pbf_press_dpad(context,changing_to==3 ? DPAD_UP : DPAD_DOWN,80ms,300ms); - context.wait_for_all_requests();fresh_after=current_time(); - bypass_movement+=80ms;++lane_presses; + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + // Expiry preserves lane 4. Cover its possible unfinished 500ms + // west pulse, then settle east against the fence before going south. + pbf_move_left_joystick(context, {1.0, 0.0}, 1000ms, 0ms); + context.wait_for_all_requests(); + corner_ready = true; + break; + } + arrival_frames = 0; + if (!changing_to && ((near_corner && lane == 3) || stalled >= 2)){ + if (++lane_changes > 6){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Route 215 jogger: Both safe lanes remain blocked.", stream); + } + changing_to = lane == 4 ? 3 : 4; + lane_presses = 0; + stalled = 0; + pbf_wait(context, 750ms); + continue; + } + const auto movement_limit = 8000ms; + if (changing_to){ + if (lane_presses >= 2){ + // A blocked lane change leaves us in the observed lane. + // Wait for the jogger, then try east again without assuming a step. + changing_to = 0; + lane_presses = 0; + pbf_wait(context, 750ms); continue; } - if (!pose.coarse && pose.dx < -1.0/32) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Passed the expected east corner.",stream); - const auto duration=!pose.coarse && std::abs(pose.dx)<=1.0/24 ? 40ms : 160ms; - if (bypass_movement+duration>movement_limit) + if (bypass_movement + 80ms > movement_limit){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Movement budget exhausted.",stream); - last_x=pose.dx;last_coarse=pose.coarse; - // Only a small westward correction is allowed at the verified corner. - pbf_move_left_joystick(context,{!pose.coarse && pose.dx<0 ? -1.0 : 1.0,0.0},duration,0ms); - pbf_wait(context,400ms); - context.wait_for_all_requests();fresh_after=current_time(); - bypass_movement+=duration;east_pending=true; + "Route 215 jogger: Movement budget exhausted.", stream); + } + pbf_press_dpad(context, changing_to == 3 ? DPAD_UP : DPAD_DOWN, 80ms, 300ms); + context.wait_for_all_requests(); + fresh_after = current_time(); + bypass_movement += 80ms; + ++lane_presses; + continue; } - if (!corner_ready) + if (!pose.coarse && pose.dx < -1.0 / 32){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: East corner not confirmed.",stream); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1682ms, 0ms); - pbf_wait(context, 501ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1843ms, 0ms); - pbf_wait(context, 55ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 2431ms, 0ms); - pbf_wait(context, 723ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2075ms, 0ms); - pbf_wait(context, 243ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 500ms, 0ms); - pbf_wait(context, 75ms); - // The corner-to-bridge leg stays below the regular Repel movement budget. - force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); - pbf_wait(context, 1767ms); - // Expiry stays on the clear upper strip. The recorded east crossing - // takes 2392ms; 3000ms also covers an unfinished 500ms west expiry pulse. - // The eastern wall fixes the starting column for Derek's south approach. - pbf_move_left_joystick(context,{1.0,0.0},3000ms,0ms); - pbf_wait(context, 43ms); - context.wait_for_all_requests(); - ShortDialogWatcher derek_dialogue; - StartBattleDetector derek_start(stream.overlay()); - BattleMenuWatcher derek_menu(BattleType::TRAINER); - if (run_until(stream,context, - [](ProControllerContext& ctx){ - // The uninterrupted 1000ms approach ends one step into grass. - // Stop one recorded step earlier on Derek's sightline while - // the existing watchers wait for him to turn toward the player. - pbf_move_left_joystick(ctx,{0.000000,-1.000000},850ms,0ms); - pbf_wait(ctx,15000ms); - },{{derek_dialogue},{derek_start},{derek_menu}})<0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 Derek: Trainer encounter did not start.",stream); - fight_trainer_using_moves(stream, context, std::vector{2}, - "Route 215 Black Belt Derek", false); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1929ms, 0ms); - pbf_wait(context, 5ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2195ms, 0ms); - pbf_wait(context, 88ms); - // Renew on the clear landing below the stairs, then restore its column. - force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); - align_to_overworld_landmarks(stream,context,match_route215_derek_stairs_renewal, - "Route 215 below Derek stairs after renewal",false,true); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 501ms, 0ms); - pbf_wait(context, 390ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 954ms, 0ms); - pbf_wait(context, 310ms); - pbf_move_left_joystick(context, {0.000000, 1.000000}, 1148ms, 0ms); - pbf_wait(context, 453ms); - // Stay west/east on the clear road row; south would enter tall grass. - force_repel_expiry(stream,context,RepelRenewalChoice::YES,false,true); - align_to_overworld_landmarks(stream,context,match_route215_derek_stair_top_renewal, - "Route 215 above Derek stairs after renewal",false,true); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 1855ms, 0ms); - pbf_wait(context, 171ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1385ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2478ms, 0ms); - pbf_wait(context, 355ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 580ms, 0ms); - pbf_wait(context, 392ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 978ms, 0ms); - pbf_wait(context, 201ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1159ms, 0ms); - pbf_wait(context, 122ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 535ms, 0ms); - pbf_wait(context, 234ms); - // West/east follows the clear strip south of the grass before the Ace Trainers. - force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - pbf_wait(context, 1767ms); - // Resume from the road/fence pose shown at renewal in the recording. - // A fixed leftover west input compounds the variable expiry offset. - align_to_overworld_landmarks(stream,context,match_route215_ace_renewal, - "Route 215 before Ace Trainers after renewal",false,true); - context.wait_for_all_requests(); - ShortDialogWatcher dennis_dialogue; - StartBattleDetector dennis_start(stream.overlay()); - BattleMenuWatcher dennis_menu(BattleType::TRAINER); - if (run_until(stream,context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx,{0.000000,-1.000000},1323ms,0ms); - pbf_wait(ctx,106ms); - pbf_move_left_joystick(ctx,{1.000000,0.000000},834ms,0ms); - pbf_wait(ctx,206ms); - pbf_move_left_joystick(ctx,{0.000000,1.000000},221ms,0ms); - pbf_wait(ctx,516ms); - pbf_move_left_joystick(ctx,{1.000000,0.000000},667ms,0ms); - pbf_wait(ctx,15000ms); - },{{dennis_dialogue},{dennis_start},{dennis_menu}})<0) + "Route 215 jogger: Passed the expected east corner.", stream); + } + const auto duration = !pose.coarse && std::abs(pose.dx) <= 1.0 / 24 ? 40ms : 160ms; + if (bypass_movement + duration > movement_limit){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 Dennis: Trainer encounter did not start.",stream); - // Dennis and Maya are separate consecutive battles. - fight_trainer_using_moves(stream, context, std::vector{2}, - "Route 215 Ace Trainer Dennis", false); - fight_trainer_using_moves(stream, context, std::vector{2}, - "Route 215 Ace Trainer Maya", false); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 10884ms, 0ms); - pbf_wait(context, 48ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 2314ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 26ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -0.000000}, 2251ms, 0ms); - pbf_move_left_joystick(context, {1.000000, -1.000000}, 27ms, 0ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 1382ms, 0ms); - pbf_wait(context, 29ms); - pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1035ms, 0ms); - pbf_move_left_joystick(context, {-1.000000, -1.000000}, 75ms, 0ms); - pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); - // Veilstone needs no Repel. Clear the remaining expiry without renewal - // on the paved north/south strip before normalizing the city approach. - force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); - align_to_overworld_landmarks(stream,context,match_veilstone_city_passage, - "Veilstone passage after expiry",false,true,true); - // Replay the successful street approach, then correct onto the western - // stair lane before climbing. Do not use the NPC pair as a wall stop. - pbf_move_left_joystick(context,{0.0,-1.0},1342ms,0ms); - pbf_move_left_joystick(context,{-1.0,0.0},1404ms,0ms); - pbf_move_left_joystick(context,{0.0,-1.0},368ms,0ms); - pbf_move_left_joystick(context,{-1.0,-1.0},90ms,0ms); - pbf_move_left_joystick(context,{-1.0,0.0},1458ms,0ms); - // Expose the western stair base with the first 200ms of the recorded - // north leg; correct its lane before continuing the remaining ascent. - pbf_move_left_joystick(context,{0.0,1.0},200ms,0ms); + "Route 215 jogger: Movement budget exhausted.", stream); + } + last_x = pose.dx; + last_coarse = pose.coarse; + // Only a small westward correction is allowed at the verified corner. + pbf_move_left_joystick(context, {!pose.coarse && pose.dx < 0 ? -1.0 : 1.0, 0.0}, duration, 0ms); + pbf_wait(context, 400ms); context.wait_for_all_requests(); - align_to_overworld_landmarks(stream,context,match_veilstone_dawn_stairs, - "Veilstone western stair approach",false,true); - stream.log("[Veilstone approach] Following the western stairs toward Dawn."); - ShortDialogWatcher dawn_dialogue; - if (run_until(stream,context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx,{0.0,1.0},3023ms,0ms); - pbf_press_button(ctx,BUTTON_A,80ms,100ms); - // Dawn's turn, walk and camera cut precede the first box. - // Keep watching through the cutscene; cancel as soon as it opens. - pbf_wait(ctx,15000ms); - },{{dawn_dialogue}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Veilstone Dawn: Dialogue did not start from the west stair approach.",stream); - clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + fresh_after = current_time(); + bypass_movement += duration; + east_pending = true; + } + if (!corner_ready){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: East corner not confirmed.", stream); + } + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1682ms, 0ms); + pbf_wait(context, 501ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1843ms, 0ms); + pbf_wait(context, 55ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 2431ms, 0ms); + pbf_wait(context, 723ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2075ms, 0ms); + pbf_wait(context, 243ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 500ms, 0ms); + pbf_wait(context, 75ms); + // The corner-to-bridge leg stays below the regular Repel movement budget. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_wait(context, 1767ms); + // Expiry stays on the clear upper strip. The recorded east crossing + // takes 2392ms; 3000ms also covers an unfinished 500ms west expiry pulse. + // The eastern wall fixes the starting column for Derek's south approach. + pbf_move_left_joystick(context, {1.0, 0.0}, 3000ms, 0ms); + pbf_wait(context, 43ms); + context.wait_for_all_requests(); + ShortDialogWatcher derek_dialogue; + StartBattleDetector derek_start(stream.overlay()); + BattleMenuWatcher derek_menu(BattleType::TRAINER); + if (run_until(stream, context, + [](ProControllerContext& ctx){ + // The uninterrupted 1000ms approach ends one step into grass. + // Stop one recorded step earlier on Derek's sightline while + // the existing watchers wait for him to turn toward the player. + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 850ms, 0ms); + pbf_wait(ctx, 15000ms); + }, + {{derek_dialogue}, {derek_start}, {derek_menu}}) < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Derek: Trainer encounter did not start.", stream); + } + fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Black Belt Derek", false); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1929ms, 0ms); + pbf_wait(context, 5ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2195ms, 0ms); + pbf_wait(context, 88ms); + // Renew on the clear landing below the stairs, then restore its column. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + align_to_overworld_landmarks(stream, context, match_route215_derek_stairs_renewal, + "Route 215 below Derek stairs after renewal", false, true); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 501ms, 0ms); + pbf_wait(context, 390ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 954ms, 0ms); + pbf_wait(context, 310ms); + pbf_move_left_joystick(context, {0.000000, 1.000000}, 1148ms, 0ms); + pbf_wait(context, 453ms); + // Stay west/east on the clear road row; south would enter tall grass. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + align_to_overworld_landmarks(stream, context, match_route215_derek_stair_top_renewal, + "Route 215 above Derek stairs after renewal", false, true); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 1855ms, 0ms); + pbf_wait(context, 171ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1385ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2478ms, 0ms); + pbf_wait(context, 355ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 580ms, 0ms); + pbf_wait(context, 392ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 978ms, 0ms); + pbf_wait(context, 201ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1159ms, 0ms); + pbf_wait(context, 122ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 535ms, 0ms); + pbf_wait(context, 234ms); + // West/east follows the clear strip south of the grass before the Ace Trainers. + force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); + pbf_wait(context, 1767ms); + // Resume from the road/fence pose shown at renewal in the recording. + // A fixed leftover west input compounds the variable expiry offset. + align_to_overworld_landmarks(stream, context, match_route215_ace_renewal, + "Route 215 before Ace Trainers after renewal", false, true); + context.wait_for_all_requests(); + ShortDialogWatcher dennis_dialogue; + StartBattleDetector dennis_start(stream.overlay()); + BattleMenuWatcher dennis_menu(BattleType::TRAINER); + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 1323ms, 0ms); + pbf_wait(ctx, 106ms); + pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 834ms, 0ms); + pbf_wait(ctx, 206ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 221ms, 0ms); + pbf_wait(ctx, 516ms); + pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 667ms, 0ms); + pbf_wait(ctx, 15000ms); + }, + {{dennis_dialogue}, {dennis_start}, {dennis_menu}}) < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Dennis: Trainer encounter did not start.", stream); + } + // Dennis and Maya are separate consecutive battles. + fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ace Trainer Dennis", false); + fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ace Trainer Maya", false); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 10884ms, 0ms); + pbf_wait(context, 48ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2314ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 26ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -0.000000}, 2251ms, 0ms); + pbf_move_left_joystick(context, {1.000000, -1.000000}, 27ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 1382ms, 0ms); + pbf_wait(context, 29ms); + pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1035ms, 0ms); + pbf_move_left_joystick(context, {-1.000000, -1.000000}, 75ms, 0ms); + pbf_move_left_joystick(context, {0.000000, -1.000000}, 2190ms, 0ms); + // Veilstone needs no Repel. Clear the remaining expiry without renewal + // on the paved north/south strip before normalizing the city approach. + force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); + align_to_overworld_landmarks(stream, context, match_veilstone_city_passage, "Veilstone passage after expiry", false, + true, true); + // Replay the successful street approach, then correct onto the western + // stair lane before climbing. Do not use the NPC pair as a wall stop. + pbf_move_left_joystick(context, {0.0, -1.0}, 1342ms, 0ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 1404ms, 0ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 368ms, 0ms); + pbf_move_left_joystick(context, {-1.0, -1.0}, 90ms, 0ms); + pbf_move_left_joystick(context, {-1.0, 0.0}, 1458ms, 0ms); + // Expose the western stair base with the first 200ms of the recorded + // north leg; correct its lane before continuing the remaining ascent. + pbf_move_left_joystick(context, {0.0, 1.0}, 200ms, 0ms); + context.wait_for_all_requests(); + align_to_overworld_landmarks(stream, context, match_veilstone_dawn_stairs, "Veilstone western stair approach", + false, true); + stream.log("[Veilstone approach] Following the western stairs toward Dawn."); + ShortDialogWatcher dawn_dialogue; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 3023ms, 0ms); + pbf_press_button(ctx, BUTTON_A, 80ms, 100ms); + // Dawn's turn, walk and camera cut precede the first box. + // Keep watching through the cutscene; cancel as soon as it opens. + pbf_wait(ctx, 15000ms); + }, + {{dawn_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Veilstone Dawn: Dialogue did not start from the west stair approach.", + stream); + } + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); - // The new recording already enters the Gym. Cancel at its first fade. - context.wait_for_all_requests(); - BlackScreenWatcher gym_entry; - if (run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1954ms, 0ms); - pbf_wait(ctx, 223ms); - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 110ms, 0ms); - pbf_wait(ctx, 190ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 910ms, 0ms); - pbf_wait(ctx, 3000ms); - }, {{gym_entry}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Veilstone Gym: Entrance transition did not start.", stream); - segment03_finish_transition(stream, context, "Veilstone Gym entrance"); - // The first visit pans across the Gym before returning to the entrance. - pbf_wait(context, 5000ms); - context.wait_for_all_requests(); - OverworldWatcher gym_ready; - if (wait_until(stream, context, 10s, {{gym_ready}}) != 0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Veilstone Gym: Overworld not ready after the entrance scene.", stream); - context.wait_for_all_requests(); + // The new recording already enters the Gym. Cancel at its first fade. + context.wait_for_all_requests(); + BlackScreenWatcher gym_entry; + if (run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1954ms, 0ms); + pbf_wait(ctx, 223ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 110ms, 0ms); + pbf_wait(ctx, 190ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 910ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{gym_entry}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Gym: Entrance transition did not start.", stream); + } + segment03_finish_transition(stream, context, "Veilstone Gym entrance"); + // The first visit pans across the Gym before returning to the entrance. + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + OverworldWatcher gym_ready; + if (wait_until(stream, context, 10s, {{gym_ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Veilstone Gym: Overworld not ready after the entrance scene.", stream); } + context.wait_for_all_requests(); +} static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ @@ -2075,109 +2284,39 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ }; const Move sequence1[] = { - {DPAD_RIGHT, 8, 0ms}, - {DPAD_UP, 6, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 8, 0ms}, {DPAD_UP, 6, 0ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 1000ms}, {DPAD_UP, 2, 0ms}, }; const Move sequence2[] = { - {DPAD_UP, 5, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_DOWN, 8, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 5, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 2000ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_DOWN, 10, 0ms}, - {DPAD_RIGHT, 6, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 1000ms}, - {DPAD_UP, 6, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 1000ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_DOWN, 13, 0ms}, - {DPAD_LEFT, 3, 0ms}, - {DPAD_UP, 4, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 8, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 4, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_DOWN, 2, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 1000ms}, - {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_DOWN, 6, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_DOWN, 7, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 5, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 5, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 5, 0ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 1000ms}, {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 5, 0ms}, {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 1000ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 2, 0ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 2000ms}, + {DPAD_RIGHT, 1, 0ms}, {DPAD_DOWN, 10, 0ms}, {DPAD_RIGHT, 6, 0ms}, {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 1000ms}, {DPAD_UP, 6, 0ms}, {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 1000ms}, {DPAD_LEFT, 1, 0ms}, + {DPAD_DOWN, 13, 0ms}, {DPAD_LEFT, 3, 0ms}, {DPAD_UP, 4, 0ms}, {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 8, 0ms}, {DPAD_RIGHT, 2, 0ms}, {DPAD_UP, 4, 0ms}, {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 2, 0ms}, {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 1000ms}, {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 4, 0ms}, {DPAD_DOWN, 6, 0ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_DOWN, 7, 0ms}, + {DPAD_RIGHT, 2, 0ms}, {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 5, 0ms}, {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 4, 0ms}, {DPAD_LEFT, 1, 2000ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 2000ms}, {DPAD_DOWN, 5, 0ms}, {DPAD_RIGHT, 4, 0ms}, {DPAD_DOWN, 3, 0ms}, }; const Move sequence3[] = { - {DPAD_RIGHT, 2, 0ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 3000ms}, - {DPAD_DOWN, 3, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_UP, 5, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_LEFT, 8, 0ms}, - {DPAD_UP, 3, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 3000ms}, - {DPAD_UP, 11, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_DOWN, 13, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 2, 0ms}, {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 3000ms}, + {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 4, 0ms}, {DPAD_UP, 5, 0ms}, {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, {DPAD_DOWN, 5, 0ms}, {DPAD_LEFT, 8, 0ms}, {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 3000ms}, {DPAD_UP, 11, 0ms}, {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, {DPAD_DOWN, 13, 0ms}, {DPAD_RIGHT, 4, 0ms}, {DPAD_UP, 3, 0ms}, }; const Move sequence4[] = { - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 9, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 4, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_DOWN, 8, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 2, 0ms}, {DPAD_UP, 9, 0ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 4, 0ms}, {DPAD_RIGHT, 4, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_DOWN, 8, 0ms}, {DPAD_RIGHT, 4, 0ms}, {DPAD_UP, 3, 0ms}, }; const Move sequence5[] = { @@ -2198,7 +2337,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 01 (Black Belt Rafael). - pbf_press_button(context,BUTTON_A,150ms,300ms); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); fight_trainer_using_moves(stream, context, 2, "Black Belt Rafael", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2214,7 +2353,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 02 (Black Belt Jeffrey). - pbf_press_button(context,BUTTON_A,150ms,300ms); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); fight_trainer_using_moves(stream, context, 2, "Black Belt Jeffrey", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2230,7 +2369,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 03 (Black Belt Darren). - pbf_press_button(context,BUTTON_A,150ms,300ms); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); fight_trainer_using_moves(stream, context, 2, "Black Belt Darren", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2246,7 +2385,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 04 (Black Belt Colby). - pbf_press_button(context,BUTTON_A,150ms,300ms); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); fight_trainer_using_moves(stream, context, 1, "Black Belt Colby", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2262,7 +2401,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate the Gym battle. - pbf_press_button(context,BUTTON_A,150ms,300ms); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); fight_trainer_using_moves(stream, context, 1, "Maylene", true); // Clear badge and reward dialogue before handing off inside the Gym. @@ -2270,19 +2409,20 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ stream.log("[Veilstone Gym puzzle] Shared battle cleanup returned; clearing remaining Maylene rewards with B."); clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); stream.log("[Veilstone Gym puzzle] Maylene post-battle dialogue cleared; remaining here for Segment 04."); - } -std::string AutoStory_Segment_03::name() const{ return "03: Second Badge to Third Badge"; } -std::string AutoStory_Segment_03::start_text() const{ return "Start: Second Gym Badge acquired (Eterna City)."; } -std::string AutoStory_Segment_03::end_text() const{ return "End: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } +std::string AutoStory_Segment_03::name() const { + return "03: Second Badge to Third Badge"; +} +std::string AutoStory_Segment_03::start_text() const { + return "Start: Second Gym Badge acquired (Eterna City)."; +} +std::string AutoStory_Segment_03::end_text() const { + return "End: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; +} -void AutoStory_Segment_03::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_03::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -2300,58 +2440,54 @@ void AutoStory_Segment_03::run_segment( } -std::string AutoStory_Checkpoint_0301::name() const{ return "03.01 - Eterna Gym to Jupiter"; } -std::string AutoStory_Checkpoint_0301::start_text() const{ return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; } -std::string AutoStory_Checkpoint_0301::end_text() const{ return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; } +std::string AutoStory_Checkpoint_0301::name() const { + return "03.01 - Eterna Gym to Jupiter"; +} +std::string AutoStory_Checkpoint_0301::start_text() const { + return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; +} +std::string AutoStory_Checkpoint_0301::end_text() const { + return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; +} -void AutoStory_Checkpoint_0301::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Checkpoint_0301::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0301(env, context, options, stats); } -void Checkpoint_0301( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void Checkpoint_0301(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. eterna_gym_to_jupiter(env.console, context); - }, - true - ); + true); } -std::string AutoStory_Checkpoint_0302::name() const { return "03.02 - Jupiter to Mount Coronet"; } -std::string AutoStory_Checkpoint_0302::start_text() const { return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; } -std::string AutoStory_Checkpoint_0302::end_text() const { return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; } - -void AutoStory_Checkpoint_0302::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { +std::string AutoStory_Checkpoint_0302::name() const { + return "03.02 - Jupiter to Mount Coronet"; +} +std::string AutoStory_Checkpoint_0302::start_text() const { + return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; +} +std::string AutoStory_Checkpoint_0302::end_text() const { + return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; +} + +void AutoStory_Checkpoint_0302::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0302(env, context, options, stats); } -void Checkpoint_0302( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { +void Checkpoint_0302(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ // Set daylight once for this outdoor locator strip, including the // Center, Mart return, bicycle shop, and north Route 206 gate. context.wait_for_all_requests(); @@ -2365,94 +2501,85 @@ void Checkpoint_0302( context.wait_for_all_requests(); // Recorded route with feedback battle handling. jupiter_to_coronet(env.console, context); - }, - true - ); + true); +} +std::string AutoStory_Checkpoint_0303::name() const { + return "03.03 - Mount Coronet"; +} +std::string AutoStory_Checkpoint_0303::start_text() const { + return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; } -std::string AutoStory_Checkpoint_0303::name() const { return "03.03 - Mount Coronet"; } -std::string AutoStory_Checkpoint_0303::start_text() const { return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; } -std::string AutoStory_Checkpoint_0303::end_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } - -void AutoStory_Checkpoint_0303::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { +std::string AutoStory_Checkpoint_0303::end_text() const { + return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; +} + +void AutoStory_Checkpoint_0303::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0303(env, context, options, stats); } -void Checkpoint_0303( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { +void Checkpoint_0303(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. coronet1(env.console, context); - }, - true - ); + true); } -std::string AutoStory_Checkpoint_0304::name() const { return "03.04 - Coronet to Hearthome"; } -std::string AutoStory_Checkpoint_0304::start_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } -std::string AutoStory_Checkpoint_0304::end_text() const { return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } - -void AutoStory_Checkpoint_0304::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { +std::string AutoStory_Checkpoint_0304::name() const { + return "03.04 - Coronet to Hearthome"; +} +std::string AutoStory_Checkpoint_0304::start_text() const { + return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; +} +std::string AutoStory_Checkpoint_0304::end_text() const { + return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; +} + +void AutoStory_Checkpoint_0304::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0304(env, context, options, stats); } -void Checkpoint_0304( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { +void Checkpoint_0304(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. coronet_to_heartholme(env.console, context); - }, - true - ); + true); +} +std::string AutoStory_Checkpoint_0305::name() const { + return "03.05 - Hearthome to Lost Tower"; +} +std::string AutoStory_Checkpoint_0305::start_text() const { + return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } -std::string AutoStory_Checkpoint_0305::name() const { return "03.05 - Hearthome to Lost Tower"; } -std::string AutoStory_Checkpoint_0305::start_text() const { return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } -std::string AutoStory_Checkpoint_0305::end_text() const { return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; } - -void AutoStory_Checkpoint_0305::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { +std::string AutoStory_Checkpoint_0305::end_text() const { + return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; +} + +void AutoStory_Checkpoint_0305::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0305(env, context, options, stats); } -void Checkpoint_0305( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { +void Checkpoint_0305(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ // Restore night lighting on every attempt, including after a reset. context.wait_for_all_requests(); env.console.log("[Route 209 locator setup] Setting 22:00; preserving the current date."); @@ -2466,35 +2593,32 @@ void Checkpoint_0305( // Recorded route with feedback battle handling. heartholme_to_lost_tower(env.console, context); // Stop here. Checkpoint 03.06 saves outside and owns the Tower visit. - }, - true - ); + true); } -std::string AutoStory_Checkpoint_0306::name() const { return "03.06 - Lost Tower to Veilstone"; } -std::string AutoStory_Checkpoint_0306::start_text() const { return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; } -std::string AutoStory_Checkpoint_0306::end_text() const { return "Inside Veilstone Gym at the entrance, facing north."; } +std::string AutoStory_Checkpoint_0306::name() const { + return "03.06 - Lost Tower to Veilstone"; +} +std::string AutoStory_Checkpoint_0306::start_text() const { + return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; +} +std::string AutoStory_Checkpoint_0306::end_text() const { + return "Inside Veilstone Gym at the entrance, facing north."; +} -void AutoStory_Checkpoint_0306::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { +void AutoStory_Checkpoint_0306::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0306(env, context, options, stats); } -void Checkpoint_0306( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { +void Checkpoint_0306(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ // Restore night before the route on every attempt, including resets. context.wait_for_all_requests(); env.console.log("[Route 215 locator setup] Setting 22:00; preserving the current date."); @@ -2509,37 +2633,34 @@ void Checkpoint_0306( lost_tower(env.console, context); lost_tower_to_veilstone_gym(env.console, context); }, - true - ); + true); } -std::string AutoStory_Checkpoint_0307::name() const { return "03.07 - Veilstone Gym (start inside at entrance)"; } -std::string AutoStory_Checkpoint_0307::start_text() const { return "Start inside Veilstone Gym at the entrance, facing north."; } -std::string AutoStory_Checkpoint_0307::end_text() const { return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } +std::string AutoStory_Checkpoint_0307::name() const { + return "03.07 - Veilstone Gym (start inside at entrance)"; +} +std::string AutoStory_Checkpoint_0307::start_text() const { + return "Start inside Veilstone Gym at the entrance, facing north."; +} +std::string AutoStory_Checkpoint_0307::end_text() const { + return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; +} -void AutoStory_Checkpoint_0307::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const { +void AutoStory_Checkpoint_0307::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { Checkpoint_0307(env, context, options, stats); } -void Checkpoint_0307( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) { - checkpoint_reattempt_loop(env, context, options, stats, - [&](size_t /*attempt*/) { +void Checkpoint_0307(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, + [&](size_t /*attempt*/){ //#. Veilstone Gym navigation and battle gym_Puzzle_03(env.console, context); }, - true - ); + true); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h index 1c51870efb..494bd84afd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h @@ -8,178 +8,118 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment03_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment03_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_03 : public AutoStory_Segment{ +class AutoStory_Segment_03 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0301 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0301 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0301( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0301(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0302 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0302( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0302(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0303 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0303( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0303(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0304 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0304( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0304(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0305 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0305( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0305(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0306 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0306( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0306(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0307 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0307( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void Checkpoint_0307(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 0ad15a41a7..042c2a1576 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -8,36 +8,42 @@ #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" + #include "PokemonBDSP_AutoStory_Segment_04.h" using namespace std::chrono_literals; -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -std::string AutoStory_Segment_04::name() const{ return "04: Third Badge to Fourth Badge"; } -std::string AutoStory_Segment_04::start_text() const{ return "Start: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } -std::string AutoStory_Segment_04::end_text() const{ return "End: Fourth Gym Badge acquired (Pastoria City)."; } +std::string AutoStory_Segment_04::name() const { + return "04: Third Badge to Fourth Badge"; +} +std::string AutoStory_Segment_04::start_text() const { + return "Start: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; +} +std::string AutoStory_Segment_04::end_text() const { + return "End: Fourth Gym Badge acquired (Pastoria City)."; +} -void AutoStory_Segment_04::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_04::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -67,168 +73,280 @@ void AutoStory_Segment_04::run_segment( } -std::string AutoStory_Checkpoint_0401::name() const{ return "04.01 - leave_gym"; } -std::string AutoStory_Checkpoint_0401::start_text() const{ return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } -std::string AutoStory_Checkpoint_0401::end_text() const{ return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; } -void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0401::name() const { + return "04.01 - leave_gym"; +} +std::string AutoStory_Checkpoint_0401::start_text() const { + return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; +} +std::string AutoStory_Checkpoint_0401::end_text() const { + return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; +} +void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0401(env, context, options, stats); } -std::string AutoStory_Checkpoint_0402::name() const{ return "04.02 - go_to_veilstone_pokemon_center"; } -std::string AutoStory_Checkpoint_0402::start_text() const{ return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; } -std::string AutoStory_Checkpoint_0402::end_text() const{ return "Entered Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0402::name() const { + return "04.02 - go_to_veilstone_pokemon_center"; +} +std::string AutoStory_Checkpoint_0402::start_text() const { + return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; +} +std::string AutoStory_Checkpoint_0402::end_text() const { + return "Entered Veilstone City Pokemon Center."; +} +void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0402(env, context, options, stats); } -std::string AutoStory_Checkpoint_0403::name() const{ return "04.03 - heal_at_pokemon_center (Veilstone)"; } -std::string AutoStory_Checkpoint_0403::start_text() const{ return "Inside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_0403::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0403::name() const { + return "04.03 - heal_at_pokemon_center (Veilstone)"; +} +std::string AutoStory_Checkpoint_0403::start_text() const { + return "Inside Veilstone City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0403::end_text() const { + return "Healed and exited Veilstone City Pokemon Center."; +} +void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0403(env, context, options, stats); } -std::string AutoStory_Checkpoint_0404::name() const{ return "04.04 - go_to_galactic_warehouse"; } -std::string AutoStory_Checkpoint_0404::start_text() const{ return "Outside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_0404::end_text() const{ return "Reached Galactic Warehouse, battle started."; } -void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0404::name() const { + return "04.04 - go_to_galactic_warehouse"; +} +std::string AutoStory_Checkpoint_0404::start_text() const { + return "Outside Veilstone City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0404::end_text() const { + return "Reached Galactic Warehouse, battle started."; +} +void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0404(env, context, options, stats); } -std::string AutoStory_Checkpoint_0405::name() const{ return "04.05 - handle_battle (veilstone_galactic_grunts_1)"; } -std::string AutoStory_Checkpoint_0405::start_text() const{ return "In battle against Galactic Grunt at Veilstone."; } -std::string AutoStory_Checkpoint_0405::end_text() const{ return "Defeated Galactic Grunt at Veilstone."; } -void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0405::name() const { + return "04.05 - handle_battle (veilstone_galactic_grunts_1)"; +} +std::string AutoStory_Checkpoint_0405::start_text() const { + return "In battle against Galactic Grunt at Veilstone."; +} +std::string AutoStory_Checkpoint_0405::end_text() const { + return "Defeated Galactic Grunt at Veilstone."; +} +void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0405(env, context, options, stats); } -std::string AutoStory_Checkpoint_0406::name() const{ return "04.06 - enter_galactic_warehouse"; } -std::string AutoStory_Checkpoint_0406::start_text() const{ return "Outside Galactic Warehouse."; } -std::string AutoStory_Checkpoint_0406::end_text() const{ return "Entered Galactic Warehouse."; } -void AutoStory_Checkpoint_0406::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0406::name() const { + return "04.06 - enter_galactic_warehouse"; +} +std::string AutoStory_Checkpoint_0406::start_text() const { + return "Outside Galactic Warehouse."; +} +std::string AutoStory_Checkpoint_0406::end_text() const { + return "Entered Galactic Warehouse."; +} +void AutoStory_Checkpoint_0406::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0406(env, context, options, stats); } -std::string AutoStory_Checkpoint_0407::name() const{ return "04.07 - exit_galactic_warehouse"; } -std::string AutoStory_Checkpoint_0407::start_text() const{ return "Inside Galactic Warehouse."; } -std::string AutoStory_Checkpoint_0407::end_text() const{ return "Exited Galactic Warehouse with TM Fly."; } -void AutoStory_Checkpoint_0407::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0407::name() const { + return "04.07 - exit_galactic_warehouse"; +} +std::string AutoStory_Checkpoint_0407::start_text() const { + return "Inside Galactic Warehouse."; +} +std::string AutoStory_Checkpoint_0407::end_text() const { + return "Exited Galactic Warehouse with TM Fly."; +} +void AutoStory_Checkpoint_0407::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0407(env, context, options, stats); } -std::string AutoStory_Checkpoint_0408::name() const{ return "04.08 - go_to_route_214"; } -std::string AutoStory_Checkpoint_0408::start_text() const{ return "Outside Galactic Warehouse."; } -std::string AutoStory_Checkpoint_0408::end_text() const{ return "Arrived at Route 214, battle started."; } -void AutoStory_Checkpoint_0408::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0408::name() const { + return "04.08 - go_to_route_214"; +} +std::string AutoStory_Checkpoint_0408::start_text() const { + return "Outside Galactic Warehouse."; +} +std::string AutoStory_Checkpoint_0408::end_text() const { + return "Arrived at Route 214, battle started."; +} +void AutoStory_Checkpoint_0408::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0408(env, context, options, stats); } -std::string AutoStory_Checkpoint_0409::name() const{ return "04.09 - handle_battle (route_214_psychic_abigail)"; } -std::string AutoStory_Checkpoint_0409::start_text() const{ return "In battle against Psychic Abigail on Route 214."; } -std::string AutoStory_Checkpoint_0409::end_text() const{ return "Defeated Psychic Abigail on Route 214."; } -void AutoStory_Checkpoint_0409::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0409::name() const { + return "04.09 - handle_battle (route_214_psychic_abigail)"; +} +std::string AutoStory_Checkpoint_0409::start_text() const { + return "In battle against Psychic Abigail on Route 214."; +} +std::string AutoStory_Checkpoint_0409::end_text() const { + return "Defeated Psychic Abigail on Route 214."; +} +void AutoStory_Checkpoint_0409::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0409(env, context, options, stats); } -std::string AutoStory_Checkpoint_0410::name() const{ return "04.10 - walk_through_route_214"; } -std::string AutoStory_Checkpoint_0410::start_text() const{ return "On Route 214 after Psychic Abigail."; } -std::string AutoStory_Checkpoint_0410::end_text() const{ return "Walked through Route 214."; } -void AutoStory_Checkpoint_0410::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0410::name() const { + return "04.10 - walk_through_route_214"; +} +std::string AutoStory_Checkpoint_0410::start_text() const { + return "On Route 214 after Psychic Abigail."; +} +std::string AutoStory_Checkpoint_0410::end_text() const { + return "Walked through Route 214."; +} +void AutoStory_Checkpoint_0410::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0410(env, context, options, stats); } -std::string AutoStory_Checkpoint_0411::name() const{ return "04.11 - pass_by_valor_lakefront"; } -std::string AutoStory_Checkpoint_0411::start_text() const{ return "End of Route 214."; } -std::string AutoStory_Checkpoint_0411::end_text() const{ return "Passed Valor Lakefront, entered building."; } -void AutoStory_Checkpoint_0411::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0411::name() const { + return "04.11 - pass_by_valor_lakefront"; +} +std::string AutoStory_Checkpoint_0411::start_text() const { + return "End of Route 214."; +} +std::string AutoStory_Checkpoint_0411::end_text() const { + return "Passed Valor Lakefront, entered building."; +} +void AutoStory_Checkpoint_0411::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0411(env, context, options, stats); } -std::string AutoStory_Checkpoint_0412::name() const{ return "04.12 - go_through_building_route_213"; } -std::string AutoStory_Checkpoint_0412::start_text() const{ return "Inside building between Route 214 and Route 213."; } -std::string AutoStory_Checkpoint_0412::end_text() const{ return "Exited building onto Route 213."; } -void AutoStory_Checkpoint_0412::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0412::name() const { + return "04.12 - go_through_building_route_213"; +} +std::string AutoStory_Checkpoint_0412::start_text() const { + return "Inside building between Route 214 and Route 213."; +} +std::string AutoStory_Checkpoint_0412::end_text() const { + return "Exited building onto Route 213."; +} +void AutoStory_Checkpoint_0412::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0412(env, context, options, stats); } -std::string AutoStory_Checkpoint_0413::name() const{ return "04.13 - go_through_route_213"; } -std::string AutoStory_Checkpoint_0413::start_text() const{ return "On Route 213."; } -std::string AutoStory_Checkpoint_0413::end_text() const{ return "Walked through Route 213, entered east Pastoria building."; } -void AutoStory_Checkpoint_0413::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0413::name() const { + return "04.13 - go_through_route_213"; +} +std::string AutoStory_Checkpoint_0413::start_text() const { + return "On Route 213."; +} +std::string AutoStory_Checkpoint_0413::end_text() const { + return "Walked through Route 213, entered east Pastoria building."; +} +void AutoStory_Checkpoint_0413::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0413(env, context, options, stats); } -std::string AutoStory_Checkpoint_0414::name() const{ return "04.14 - go_through_house_east_pastoria"; } -std::string AutoStory_Checkpoint_0414::start_text() const{ return "Inside east Pastoria building."; } -std::string AutoStory_Checkpoint_0414::end_text() const{ return "Exited east Pastoria building into Pastoria City."; } -void AutoStory_Checkpoint_0414::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0414::name() const { + return "04.14 - go_through_house_east_pastoria"; +} +std::string AutoStory_Checkpoint_0414::start_text() const { + return "Inside east Pastoria building."; +} +std::string AutoStory_Checkpoint_0414::end_text() const { + return "Exited east Pastoria building into Pastoria City."; +} +void AutoStory_Checkpoint_0414::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0414(env, context, options, stats); } -std::string AutoStory_Checkpoint_0415::name() const{ return "04.15 - go_through_pastoria"; } -std::string AutoStory_Checkpoint_0415::start_text() const{ return "In Pastoria City."; } -std::string AutoStory_Checkpoint_0415::end_text() const{ return "Entered Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0415::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0415::name() const { + return "04.15 - go_through_pastoria"; +} +std::string AutoStory_Checkpoint_0415::start_text() const { + return "In Pastoria City."; +} +std::string AutoStory_Checkpoint_0415::end_text() const { + return "Entered Pastoria City Pokemon Center."; +} +void AutoStory_Checkpoint_0415::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0415(env, context, options, stats); } -std::string AutoStory_Checkpoint_0416::name() const{ return "04.16 - heal_at_pokemon_center (Pastoria)"; } -std::string AutoStory_Checkpoint_0416::start_text() const{ return "Inside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_0416::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0416::name() const { + return "04.16 - heal_at_pokemon_center (Pastoria)"; +} +std::string AutoStory_Checkpoint_0416::start_text() const { + return "Inside Pastoria City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0416::end_text() const { + return "Healed and exited Pastoria City Pokemon Center."; +} +void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0416(env, context, options, stats); } -std::string AutoStory_Checkpoint_0417::name() const{ return "04.17 - go_to_pastoria_gym"; } -std::string AutoStory_Checkpoint_0417::start_text() const{ return "Outside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_0417::end_text() const{ return "Inside Pastoria Gym at the entrance, facing north."; } -void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0417::name() const { + return "04.17 - go_to_pastoria_gym"; +} +std::string AutoStory_Checkpoint_0417::start_text() const { + return "Outside Pastoria City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0417::end_text() const { + return "Inside Pastoria Gym at the entrance, facing north."; +} +void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0417(env, context, options, stats); } -std::string AutoStory_Checkpoint_0418::name() const{ return "04.18 - Pastoria Gym Puzzle"; } -std::string AutoStory_Checkpoint_0418::start_text() const{ return "Inside Pastoria Gym at the entrance, facing north."; } -std::string AutoStory_Checkpoint_0418::end_text() const{ return "Not implemented: Pastoria Gym puzzle and Crasher Wake battle."; } -void AutoStory_Checkpoint_0418::run_checkpoint( - SingleSwitchProgramEnvironment&, - ProControllerContext& context, - AutoStoryOptions, - AutoStoryStats& -) const{ +std::string AutoStory_Checkpoint_0418::name() const { + return "04.18 - Pastoria Gym Puzzle"; +} +std::string AutoStory_Checkpoint_0418::start_text() const { + return "Inside Pastoria Gym at the entrance, facing north."; +} +std::string AutoStory_Checkpoint_0418::end_text() const { + return "Not implemented: Pastoria Gym puzzle and Crasher Wake battle."; +} +void AutoStory_Checkpoint_0418::run_checkpoint(SingleSwitchProgramEnvironment&, ProControllerContext& context, + AutoStoryOptions, AutoStoryStats&) const { context.wait_for_all_requests(); // Stop before any movement until the puzzle and leader battle are implemented. - throw OperationFailedException( - ErrorReportMode::NO_ERROR_REPORT, - "04.18 - Pastoria Gym Puzzle is not implemented. Stopped inside the Gym." - ); + throw OperationFailedException(ErrorReportMode::NO_ERROR_REPORT, + "04.18 - Pastoria Gym Puzzle is not implemented. Stopped inside the Gym."); } -static void move_back_after_unexpected_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, + const std::string& trainerid){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "route_213_tuber_chelsea") { + if (trainerid == "route_213_tuber_chelsea"){ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 - } else if (trainerid == "route_213_tuber_jared") { + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + }else if (trainerid == "route_213_tuber_jared"){ // Just need to wait, nothing else - } else if (trainerid == "route_213_beauty_cindy") { + }else if (trainerid == "route_213_beauty_cindy"){ pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ } } -static bool handle_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); @@ -241,37 +359,25 @@ static bool handle_battle( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if ( - trainerid == "veilstone_galactic_grunts_1" or - trainerid == "route_213_tuber_chelsea" or - trainerid == "route_213_tuber_jared" or - trainerid == "route_213_beauty_cindy" - ) { + if (trainerid == "veilstone_galactic_grunts_1" or trainerid == "route_213_tuber_chelsea" or + trainerid == "route_213_tuber_jared" or trainerid == "route_213_beauty_cindy"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - } else if ( - trainerid == "route_214_collector_brady" or - trainerid == "route_214_collector_douglas" - ){ + }else if (trainerid == "route_214_collector_brady" or trainerid == "route_214_collector_douglas"){ // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - } else if ( - trainerid == "route_214_psychic_abigail" or - trainerid == "route_214_psychic_mitchell" - ){ + }else if (trainerid == "route_214_psychic_abigail" or trainerid == "route_214_psychic_mitchell"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -280,28 +386,22 @@ static bool handle_battle( context.wait_for_all_requests(); pbf_mash_button(context, BUTTON_B, 2000ms); - if ( - trainerid == "route_213_tuber_chelsea" or - trainerid == "route_213_tuber_jared" or - trainerid == "route_213_beauty_cindy" - ){ + if (trainerid == "route_213_tuber_chelsea" or trainerid == "route_213_tuber_jared" or + trainerid == "route_213_beauty_cindy"){ move_back_after_unexpected_battle(stream, context, trainerid); } stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } -static bool leave_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool leave_gym(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("leave_gym: walking out of gym"); pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 2 - pbf_move_left_joystick(context, {+1, 0}, 300ms, 100ms); // 2 + pbf_move_left_joystick(context, {+1, 0}, 300ms, 100ms); // 2 pbf_press_dpad(context, DPAD_RIGHT, 280ms, 200ms); pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 2 @@ -316,13 +416,11 @@ static bool leave_gym( pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 0ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 0ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: black screen not detected!", COLOR_RED); return false; @@ -331,10 +429,7 @@ static bool leave_gym( return true; } -static bool go_to_veilstone_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_veilstone_pokemon_center(VideoStream& stream, ProControllerContext& context){ DpadState dpad; stream.log("Walking towards the pokemon center"); pbf_wait(context, 4000ms); @@ -342,14 +437,14 @@ static bool go_to_veilstone_pokemon_center( mash_until_dialogue_ends(stream, context, BUTTON_B); context.wait_for_all_requests(); pbf_wait(context, 4000ms); - + pbf_move_left_joystick(context, {0, -1}, 3000ms, 100ms); // 21 pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 14 - pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 4 pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 25 pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 11 pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 12 - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 5 pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 8+ dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); @@ -357,13 +452,11 @@ static bool go_to_veilstone_pokemon_center( /* Entering the Pokemon Center */ BlackScreenOverWatcher black_screen1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen1}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen1}}); if (ret < 0){ stream.log("Didn't enter pokemon center!", COLOR_RED); return false; @@ -373,24 +466,17 @@ static bool go_to_veilstone_pokemon_center( stream.log("go_to_veilstone_pokemon_center: transition confirmed."); return true; - } -static bool heal_at_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_pokemon_center(VideoStream& stream, ProControllerContext& context){ return heal_pokemon(stream, context, "Veilstone"); } -static bool go_to_galactic_warehouse( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_galactic_warehouse(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 6 pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 6+ pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 7 pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 9+ @@ -401,13 +487,11 @@ static bool go_to_galactic_warehouse( context.wait_for_all_requests(); BlackScreenOverWatcher battle_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 12000ms); - }, - {{battle_black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 12000ms); + }, + {{battle_black_screen}}); if (ret < 0){ stream.log("go_to_galactic_warehouse: battle black screen not detected (2)", COLOR_RED); return false; @@ -416,14 +500,11 @@ static bool go_to_galactic_warehouse( return true; } -static bool enter_galactic_warehouse( - VideoStream& stream, - ProControllerContext& context -){ +static bool enter_galactic_warehouse(VideoStream& stream, ProControllerContext& context){ DpadState dpad; pbf_wait(context, 3000ms); context.wait_for_all_requests(); - + pbf_mash_button(context, BUTTON_B, 16000ms); context.wait_for_all_requests(); @@ -432,15 +513,13 @@ static bool enter_galactic_warehouse( repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); context.wait_for_all_requests(); - + BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen_1}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("Entering Galactic warehouse black screen not detected!", COLOR_RED); return false; @@ -449,16 +528,13 @@ static bool enter_galactic_warehouse( return true; } -static bool exit_galactic_warehouse( - VideoStream& stream, - ProControllerContext& context -){ +static bool exit_galactic_warehouse(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 7+ - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ @@ -466,14 +542,12 @@ static bool exit_galactic_warehouse( // Get fly ShortDialogWatcher getting_fly_TM(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 2000ms); - }, - {{getting_fly_TM}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{getting_fly_TM}}); + if (ret < 0){ stream.log("enter_galactic_warehouse_getting_fly_TM: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -487,16 +561,14 @@ static bool exit_galactic_warehouse( pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 5+ dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5, true); - + // Black screen detector here when exiting the warehouse BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_2}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_2}}); if (ret < 0){ stream.log("enter_galactic_warehouse: black screen not detected!", COLOR_RED); return false; @@ -505,10 +577,7 @@ static bool exit_galactic_warehouse( return true; } -static bool go_to_route_214( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_route_214(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); stream.log("Moving towards Route 214"); @@ -531,13 +600,11 @@ static bool go_to_route_214( context.wait_for_all_requests(); BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_1}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); return false; @@ -548,30 +615,26 @@ static bool go_to_route_214( context.wait_for_all_requests(); // Add a black screen detector here when entering the connecting house BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_2}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_2}}); if (ret < 0){ stream.log("go_to_route_214_exit_house: black screen not detected!", COLOR_RED); return false; } stream.log("Exited house"); pbf_wait(context, 1500ms); - + pbf_move_left_joystick(context, {0, -1}, 6000ms, 100ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_route_214: black screen not detected!", COLOR_RED); return false; @@ -580,16 +643,13 @@ static bool go_to_route_214( return true; } -static bool walk_through_route_214( - VideoStream& stream, - ProControllerContext& context -){ +static bool walk_through_route_214(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); stream.log("walk_through_route_214: Continuing on the first part of route 214"); pbf_wait(context, 3000ms); DpadState dpad; - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 pbf_move_left_joystick(context, {+1, 0}, 3000ms, 100ms); // 13+ dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3, true); @@ -598,23 +658,19 @@ static bool walk_through_route_214( { MarkDetector battle_dialogue_1(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); // 13+ - }, - {{battle_dialogue_1}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); // 13+ + }, + {{battle_dialogue_1}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214_collector_brady: black screen not detected!", COLOR_RED); return false; @@ -628,7 +684,7 @@ static bool walk_through_route_214( dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ @@ -638,23 +694,19 @@ static bool walk_through_route_214( { MarkDetector battle_dialogue_2(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_wait(context, 30000ms); - }, - {{battle_dialogue_2}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_wait(context, 30000ms); + }, + {{battle_dialogue_2}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214_psychic_mitchell: black screen not detected!", COLOR_RED); return false; @@ -667,7 +719,7 @@ static bool walk_through_route_214( pbf_wait(context, 3000ms); pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 6+ - + dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ @@ -682,10 +734,7 @@ static bool walk_through_route_214( return true; } -static bool pass_by_valor_lakefront( - VideoStream& stream, - ProControllerContext& context -){ +static bool pass_by_valor_lakefront(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); stream.log("Finishing going through route 214"); pbf_wait(context, 1000ms); @@ -693,18 +742,16 @@ static bool pass_by_valor_lakefront( { ShortDialogWatcher repel_ran_out(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - DpadState dpad; - dpad.last_dir = DPAD_DOWN; - for (int c = 0; c < 20; c++){ - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, true); - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); - } - }, - {{repel_ran_out}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + DpadState dpad; + dpad.last_dir = DPAD_DOWN; + for (int c = 0; c < 20; c++){ + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, true); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); + } + }, + {{repel_ran_out}}); if (ret == 0){ pbf_mash_button(context, BUTTON_A, 2000ms); }; @@ -713,23 +760,21 @@ static bool pass_by_valor_lakefront( pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 20+ dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 6, true); - + pbf_move_left_joystick(context, {0, -1}, 8000ms, 100ms); // 41+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 10+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4+ repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 4, true); context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214: Didn't enter building!", COLOR_RED); return false; @@ -738,21 +783,16 @@ static bool pass_by_valor_lakefront( return true; } -static bool go_through_building_route_213( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_building_route_213(VideoStream& stream, ProControllerContext& context){ stream.log("Going through building between routes 214 and 213"); context.wait_for_all_requests(); pbf_wait(context, 2000ms); ShortDialogWatcher repel_ran_out(COLOR_CYAN); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); - }, - {{repel_ran_out}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{repel_ran_out}}); if (ret == 0){ stream.log("go_through_building_route_214: Not reapplying repel.", COLOR_RED); pbf_mash_button(context, BUTTON_B, 2000ms); @@ -761,13 +801,11 @@ static bool go_through_building_route_213( context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_building_route_214: Didn't exit building!", COLOR_RED); return false; @@ -777,42 +815,35 @@ static bool go_through_building_route_213( return true; } -static bool go_through_route_213( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_route_213(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); stream.log("Going through route 213"); pbf_wait(context, 2000ms); DpadState dpad; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4, true); - + MarkDetector tuber_chelsea_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - }, - {{tuber_chelsea_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + }, + {{tuber_chelsea_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_route_213_tuber_chelsea: Starting battle."); handle_battle(stream, context, "route_213_tuber_chelsea"); context.wait_for_all_requests(); @@ -820,32 +851,28 @@ static bool go_through_route_213( } } context.wait_for_all_requests(); - + MarkDetector tuber_jared_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3 - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - }, - {{tuber_jared_fight}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3 + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + }, + {{tuber_jared_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_jared: Didn't detect battle starting!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_route_213_tuber_jared: Starting battle."); handle_battle(stream, context, "route_213_tuber_jared"); context.wait_for_all_requests(); @@ -856,30 +883,26 @@ static bool go_through_route_213( context.wait_for_all_requests(); pbf_move_left_joystick(context, {-1, 0}, 3600ms, 100ms); // 20+ - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 3 + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 3 pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 13+ MarkDetector beauty_cindy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5000ms, 100ms); // 15+ - }, - {{beauty_cindy_fight}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5000ms, 100ms); // 15+ + }, + {{beauty_cindy_fight}}); if (ret == 0){ BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_beauty_cindy: Didn't detect battle starting!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ context.wait_for_all_requests(); stream.log("go_through_route_213_beauty_cindy: Starting battle."); handle_battle(stream, context, "route_213_beauty_cindy"); @@ -890,7 +913,7 @@ static bool go_through_route_213( context.wait_for_all_requests(); - pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 5 pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 8 activate_repel(stream, context); @@ -899,13 +922,11 @@ static bool go_through_route_213( pbf_wait(context, 1000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213: Didn't enter building!", COLOR_RED); return false; @@ -915,19 +936,14 @@ static bool go_through_route_213( return true; } -static bool go_through_house_east_pastoria( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_house_east_pastoria(VideoStream& stream, ProControllerContext& context){ stream.log("Going through the building at the entrance of Pastoria city"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_east_pastoria: Didn't exit building!", COLOR_RED); return false; @@ -937,38 +953,33 @@ static bool go_through_house_east_pastoria( return true; } -static bool go_through_pastoria( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_pastoria(VideoStream& stream, ProControllerContext& context){ stream.log("Going through Pastoria city and to the Pokemon center"); DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ pbf_move_left_joystick(context, {-1, 0}, 4200ms, 100ms); // 23+ - pbf_move_left_joystick(context, {0, -1}, 500ms, 100ms); // 3 - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 500ms, 100ms); // 3 + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 - pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 - pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 - pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 + }, + {{repel_dialog}}); if (ret == 0){ pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_through_pastoria: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_pastoria: Didn't detect repel dialog box!", COLOR_RED); + return false; } - + context.wait_for_all_requests(); pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 @@ -976,13 +987,11 @@ static bool go_through_pastoria( repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_pastoria: Didn't enter Pokemon center!", COLOR_RED); return false; @@ -992,30 +1001,25 @@ static bool go_through_pastoria( return true; } -static bool go_to_pastoria_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_pastoria_gym(VideoStream& stream, ProControllerContext& context){ stream.log("Going to Pastoria gym"); DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 5 + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 5 pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 8 pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_pastoria_gym: Didn't enter building!", COLOR_RED); return false; @@ -1024,191 +1028,237 @@ static bool go_to_pastoria_gym( return true; } -void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "leave_gym: transition not detected.", env.console); } }, - true - ); + true); } -void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_veilstone_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_veilstone_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_veilstone_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_galactic_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_galactic_warehouse: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_galactic_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_galactic_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_galactic_warehouse: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!exit_galactic_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "exit_galactic_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "exit_galactic_warehouse: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_route_214: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "walk_through_route_214: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!pass_by_valor_lakefront(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "pass_by_valor_lakefront: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "pass_by_valor_lakefront: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_building_route_213: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_east_pastoria: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); } }, - false - ); + false); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h index 4cece07401..feeab61651 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -8,276 +8,217 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment04_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment04_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_04 : public AutoStory_Segment{ +class AutoStory_Segment_04 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0401 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0401 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0402 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0402 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0403 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0403 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0404 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0404 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0405 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0405 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0406 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0406 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0407 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0407 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0408 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0408 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0409 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0409 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0410 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0410 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0411 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0411 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0412 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0412 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0413 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0413 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0414 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0414 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0415 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0415 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0416 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0416 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0417 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0417 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0418 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0418 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index 456eab9845..a4dbaafb32 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -8,33 +8,39 @@ #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" + #include "PokemonBDSP_AutoStory_Segment_05.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -std::string AutoStory_Segment_05::name() const{ return "05: Fourth Badge to Fifth Badge"; } -std::string AutoStory_Segment_05::start_text() const{ return "Start: Fourth Gym Badge acquired (Pastoria City)."; } -std::string AutoStory_Segment_05::end_text() const{ return "End: Fifth Gym Badge acquired (Veilstone City)."; } +std::string AutoStory_Segment_05::name() const { + return "05: Fourth Badge to Fifth Badge"; +} +std::string AutoStory_Segment_05::start_text() const { + return "Start: Fourth Gym Badge acquired (Pastoria City)."; +} +std::string AutoStory_Segment_05::end_text() const { + return "End: Fifth Gym Badge acquired (Veilstone City)."; +} -void AutoStory_Segment_05::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_05::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -63,155 +69,267 @@ void AutoStory_Segment_05::run_segment( } -std::string AutoStory_Checkpoint_0501::name() const{ return "05.01 - go_to_pastoria_pokemon_center"; } -std::string AutoStory_Checkpoint_0501::start_text() const{ return "Fourth Gym Badge acquired (Pastoria City)."; } -std::string AutoStory_Checkpoint_0501::end_text() const{ return "Entered Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0501::name() const { + return "05.01 - go_to_pastoria_pokemon_center"; +} +std::string AutoStory_Checkpoint_0501::start_text() const { + return "Fourth Gym Badge acquired (Pastoria City)."; +} +std::string AutoStory_Checkpoint_0501::end_text() const { + return "Entered Pastoria City Pokemon Center."; +} +void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0501(env, context, options, stats); } -std::string AutoStory_Checkpoint_0502::name() const{ return "05.02 - exit_pastoria_pokemon_center"; } -std::string AutoStory_Checkpoint_0502::start_text() const{ return "Inside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_0502::end_text() const{ return "Healed and exited Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0502::name() const { + return "05.02 - exit_pastoria_pokemon_center"; +} +std::string AutoStory_Checkpoint_0502::start_text() const { + return "Inside Pastoria City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0502::end_text() const { + return "Healed and exited Pastoria City Pokemon Center."; +} +void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0502(env, context, options, stats); } -std::string AutoStory_Checkpoint_0503::name() const{ return "05.03 - pastoria_city_drama"; } -std::string AutoStory_Checkpoint_0503::start_text() const{ return "Outside Pastoria City Pokemon Center."; } -std::string AutoStory_Checkpoint_0503::end_text() const{ return "Pastoria City drama complete, exited east building."; } -void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0503::name() const { + return "05.03 - pastoria_city_drama"; +} +std::string AutoStory_Checkpoint_0503::start_text() const { + return "Outside Pastoria City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0503::end_text() const { + return "Pastoria City drama complete, exited east building."; +} +void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0503(env, context, options, stats); } -std::string AutoStory_Checkpoint_0504::name() const{ return "05.04 - go_through_house_east_pastoria"; } -std::string AutoStory_Checkpoint_0504::start_text() const{ return "Inside east Pastoria building."; } -std::string AutoStory_Checkpoint_0504::end_text() const{ return "Exited east Pastoria building onto Route 213."; } -void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0504::name() const { + return "05.04 - go_through_house_east_pastoria"; +} +std::string AutoStory_Checkpoint_0504::start_text() const { + return "Inside east Pastoria building."; +} +std::string AutoStory_Checkpoint_0504::end_text() const { + return "Exited east Pastoria building onto Route 213."; +} +void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0504(env, context, options, stats); } -std::string AutoStory_Checkpoint_0505::name() const{ return "05.05 - go_through_route_213_chasing"; } -std::string AutoStory_Checkpoint_0505::start_text() const{ return "On Route 213 chasing Galactic Grunts."; } -std::string AutoStory_Checkpoint_0505::end_text() const{ return "Entered east Route 213 building."; } -void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0505::name() const { + return "05.05 - go_through_route_213_chasing"; +} +std::string AutoStory_Checkpoint_0505::start_text() const { + return "On Route 213 chasing Galactic Grunts."; +} +std::string AutoStory_Checkpoint_0505::end_text() const { + return "Entered east Route 213 building."; +} +void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0505(env, context, options, stats); } -std::string AutoStory_Checkpoint_0506::name() const{ return "05.06 - go_through_building_route_213"; } -std::string AutoStory_Checkpoint_0506::start_text() const{ return "Inside building on Route 213."; } -std::string AutoStory_Checkpoint_0506::end_text() const{ return "Exited building onto Route 214."; } -void AutoStory_Checkpoint_0506::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0506::name() const { + return "05.06 - go_through_building_route_213"; +} +std::string AutoStory_Checkpoint_0506::start_text() const { + return "Inside building on Route 213."; +} +std::string AutoStory_Checkpoint_0506::end_text() const { + return "Exited building onto Route 214."; +} +void AutoStory_Checkpoint_0506::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0506(env, context, options, stats); } -std::string AutoStory_Checkpoint_0507::name() const{ return "05.07 - go_through_route_214"; } -std::string AutoStory_Checkpoint_0507::start_text() const{ return "On Route 214 chasing Galactic Grunts."; } -std::string AutoStory_Checkpoint_0507::end_text() const{ return "Flew to Solaceon Town."; } -void AutoStory_Checkpoint_0507::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0507::name() const { + return "05.07 - go_through_route_214"; +} +std::string AutoStory_Checkpoint_0507::start_text() const { + return "On Route 214 chasing Galactic Grunts."; +} +std::string AutoStory_Checkpoint_0507::end_text() const { + return "Flew to Solaceon Town."; +} +void AutoStory_Checkpoint_0507::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0507(env, context, options, stats); } -std::string AutoStory_Checkpoint_0508::name() const{ return "05.08 - go_inside_solaceon_pokemon_center"; } -std::string AutoStory_Checkpoint_0508::start_text() const{ return "In Solaceon Town."; } -std::string AutoStory_Checkpoint_0508::end_text() const{ return "Entered Solaceon Town Pokemon Center."; } -void AutoStory_Checkpoint_0508::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0508::name() const { + return "05.08 - go_inside_solaceon_pokemon_center"; +} +std::string AutoStory_Checkpoint_0508::start_text() const { + return "In Solaceon Town."; +} +std::string AutoStory_Checkpoint_0508::end_text() const { + return "Entered Solaceon Town Pokemon Center."; +} +void AutoStory_Checkpoint_0508::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0508(env, context, options, stats); } -std::string AutoStory_Checkpoint_0509::name() const{ return "05.09 - heal_at_solaceon_pokemon_center"; } -std::string AutoStory_Checkpoint_0509::start_text() const{ return "Inside Solaceon Town Pokemon Center."; } -std::string AutoStory_Checkpoint_0509::end_text() const{ return "Healed and exited Solaceon Town Pokemon Center."; } -void AutoStory_Checkpoint_0509::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0509::name() const { + return "05.09 - heal_at_solaceon_pokemon_center"; +} +std::string AutoStory_Checkpoint_0509::start_text() const { + return "Inside Solaceon Town Pokemon Center."; +} +std::string AutoStory_Checkpoint_0509::end_text() const { + return "Healed and exited Solaceon Town Pokemon Center."; +} +void AutoStory_Checkpoint_0509::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0509(env, context, options, stats); } -std::string AutoStory_Checkpoint_0510::name() const{ return "05.10 - go_to_the_psyduck_route_210"; } -std::string AutoStory_Checkpoint_0510::start_text() const{ return "Outside Solaceon Town Pokemon Center."; } -std::string AutoStory_Checkpoint_0510::end_text() const{ return "Reached Celestic Town Pokemon Center via Route 210."; } -void AutoStory_Checkpoint_0510::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0510::name() const { + return "05.10 - go_to_the_psyduck_route_210"; +} +std::string AutoStory_Checkpoint_0510::start_text() const { + return "Outside Solaceon Town Pokemon Center."; +} +std::string AutoStory_Checkpoint_0510::end_text() const { + return "Reached Celestic Town Pokemon Center via Route 210."; +} +void AutoStory_Checkpoint_0510::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0510(env, context, options, stats); } -std::string AutoStory_Checkpoint_0511::name() const{ return "05.11 - heal_at_celestic_pokemon_center"; } -std::string AutoStory_Checkpoint_0511::start_text() const{ return "Inside Celestic Town Pokemon Center."; } -std::string AutoStory_Checkpoint_0511::end_text() const{ return "Healed and exited Celestic Town Pokemon Center."; } -void AutoStory_Checkpoint_0511::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0511::name() const { + return "05.11 - heal_at_celestic_pokemon_center"; +} +std::string AutoStory_Checkpoint_0511::start_text() const { + return "Inside Celestic Town Pokemon Center."; +} +std::string AutoStory_Checkpoint_0511::end_text() const { + return "Healed and exited Celestic Town Pokemon Center."; +} +void AutoStory_Checkpoint_0511::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0511(env, context, options, stats); } -std::string AutoStory_Checkpoint_0512::name() const{ return "05.12 - celestic_town_drama"; } -std::string AutoStory_Checkpoint_0512::start_text() const{ return "In Celestic Town."; } -std::string AutoStory_Checkpoint_0512::end_text() const{ return "Entered Celestic Town cave."; } -void AutoStory_Checkpoint_0512::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0512::name() const { + return "05.12 - celestic_town_drama"; +} +std::string AutoStory_Checkpoint_0512::start_text() const { + return "In Celestic Town."; +} +std::string AutoStory_Checkpoint_0512::end_text() const { + return "Entered Celestic Town cave."; +} +void AutoStory_Checkpoint_0512::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0512(env, context, options, stats); } -std::string AutoStory_Checkpoint_0513::name() const{ return "05.13 - discover_the_paintings"; } -std::string AutoStory_Checkpoint_0513::start_text() const{ return "Inside Celestic Town cave."; } -std::string AutoStory_Checkpoint_0513::end_text() const{ return "Discovered paintings and exited cave."; } -void AutoStory_Checkpoint_0513::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0513::name() const { + return "05.13 - discover_the_paintings"; +} +std::string AutoStory_Checkpoint_0513::start_text() const { + return "Inside Celestic Town cave."; +} +std::string AutoStory_Checkpoint_0513::end_text() const { + return "Discovered paintings and exited cave."; +} +void AutoStory_Checkpoint_0513::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0513(env, context, options, stats); } -std::string AutoStory_Checkpoint_0514::name() const{ return "05.14 - talk_to_cyrus_and_fly_hearthome"; } -std::string AutoStory_Checkpoint_0514::start_text() const{ return "Outside Celestic Town cave."; } -std::string AutoStory_Checkpoint_0514::end_text() const{ return "Entered Hearthome City Pokemon Center."; } -void AutoStory_Checkpoint_0514::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0514::name() const { + return "05.14 - talk_to_cyrus_and_fly_hearthome"; +} +std::string AutoStory_Checkpoint_0514::start_text() const { + return "Outside Celestic Town cave."; +} +std::string AutoStory_Checkpoint_0514::end_text() const { + return "Entered Hearthome City Pokemon Center."; +} +void AutoStory_Checkpoint_0514::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0514(env, context, options, stats); } -std::string AutoStory_Checkpoint_0515::name() const{ return "05.15 - heal_at_hearthome_pokemon_center"; } -std::string AutoStory_Checkpoint_0515::start_text() const{ return "Inside Hearthome City Pokemon Center."; } -std::string AutoStory_Checkpoint_0515::end_text() const{ return "Healed and exited Hearthome City Pokemon Center."; } -void AutoStory_Checkpoint_0515::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0515::name() const { + return "05.15 - heal_at_hearthome_pokemon_center"; +} +std::string AutoStory_Checkpoint_0515::start_text() const { + return "Inside Hearthome City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0515::end_text() const { + return "Healed and exited Hearthome City Pokemon Center."; +} +void AutoStory_Checkpoint_0515::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0515(env, context, options, stats); } -std::string AutoStory_Checkpoint_0516::name() const{ return "05.16 - get_into_hearthome_gym"; } -std::string AutoStory_Checkpoint_0516::start_text() const{ return "Outside Hearthome City gym."; } -std::string AutoStory_Checkpoint_0516::end_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_0516::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0516::name() const { + return "05.16 - get_into_hearthome_gym"; +} +std::string AutoStory_Checkpoint_0516::start_text() const { + return "Outside Hearthome City gym."; +} +std::string AutoStory_Checkpoint_0516::end_text() const { + return "Fifth Gym Badge acquired (Hearthome City)."; +} +void AutoStory_Checkpoint_0516::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0516(env, context, options, stats); } -std::string AutoStory_Checkpoint_0517::name() const{ return "05.17 - Hearthome Gym Puzzle"; } -std::string AutoStory_Checkpoint_0517::start_text() const{ return "Outside Hearthome City gym."; } -std::string AutoStory_Checkpoint_0517::end_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_0517::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0517::name() const { + return "05.17 - Hearthome Gym Puzzle"; +} +std::string AutoStory_Checkpoint_0517::start_text() const { + return "Outside Hearthome City gym."; +} +std::string AutoStory_Checkpoint_0517::end_text() const { + return "Fifth Gym Badge acquired (Hearthome City)."; +} +void AutoStory_Checkpoint_0517::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0517(env, context, options, stats); } -static void move_back_after_unexpected_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, + const std::string& trainerid){ stream.log("Starting battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "route_213_tuber_chelsea") { + if (trainerid == "route_213_tuber_chelsea"){ DpadState dpad; - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6); - } else if (trainerid == "route_213_tuber_jared") { + }else if (trainerid == "route_213_tuber_jared"){ // Just need to wait, nothing else - } else if (trainerid == "route_213_beauty_cindy") { + }else if (trainerid == "route_213_beauty_cindy"){ pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ - } else if (trainerid == "route_210_ace_ernest") { + }else if (trainerid == "route_210_ace_ernest"){ pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ } } -static bool handle_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); @@ -224,42 +342,29 @@ static bool handle_battle( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if ( - trainerid == "pastoria_clint" or - trainerid == "route_213_tuber_chelsea" or - trainerid == "route_213_tuber_jared" or - trainerid == "route_213_beauty_cindy" or - trainerid == "route_210_veteran_brian" - ) { + if (trainerid == "pastoria_clint" or trainerid == "route_213_tuber_chelsea" or + trainerid == "route_213_tuber_jared" or trainerid == "route_213_beauty_cindy" or + trainerid == "route_210_veteran_brian"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - } else if ( - trainerid == "route_214_galactic_grunt" or - trainerid == "route_210_rancher_marco" or - trainerid == "route_210_breeder_amber" or - trainerid == "route_210_blackbelt_adam" - ){ + }else if (trainerid == "route_214_galactic_grunt" or trainerid == "route_210_rancher_marco" or + trainerid == "route_210_breeder_amber" or trainerid == "route_210_blackbelt_adam"){ // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - - } else if ( - trainerid == "route_210_ace_alyssa" or - trainerid == "route_210_birdkeeper_brianna" or - trainerid == "celestic_town_galactic_grunt" - ){ + + }else if (trainerid == "route_210_ace_alyssa" or trainerid == "route_210_birdkeeper_brianna" or + trainerid == "celestic_town_galactic_grunt"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -270,19 +375,16 @@ static bool handle_battle( return true; } -static bool leave_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool leave_gym(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("leave_gym: walking out of gym"); - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ - pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ pbf_move_left_joystick(context, {0, -1}, 6000ms, 100ms); // 34+ pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ @@ -290,13 +392,11 @@ static bool leave_gym( dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: Didn't enter building!", COLOR_RED); return false; @@ -305,49 +405,38 @@ static bool leave_gym( return true; } -static bool go_to_pastoria_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_pastoria_pokemon_center(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("go_to_pastoria_pokemon_center: going to pokemon center"); - pbf_move_left_joystick(context, {+1, 0}, 900ms, 100ms); // 6 + pbf_move_left_joystick(context, {+1, 0}, 900ms, 100ms); // 6 pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}} - ); - if (ret1 < 0){ - stream.log("go_to_pastoria_pokemon_centert: Didn't detect transition!", COLOR_RED); - return false; - } - stream.log("go_to_pastoria_pokemon_center: transition confirmed."); + int ret1 = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); + if (ret1 < 0){ + stream.log("go_to_pastoria_pokemon_centert: Didn't detect transition!", COLOR_RED); + return false; + } + stream.log("go_to_pastoria_pokemon_center: transition confirmed."); return true; } -static bool exit_pastoria_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool exit_pastoria_pokemon_center(VideoStream& stream, ProControllerContext& context){ return heal_pokemon(stream, context, "Pastoria"); } -static bool pastoria_city_drama( - VideoStream& stream, - ProControllerContext& context -){ +static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -356,23 +445,21 @@ static bool pastoria_city_drama( repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2, true); pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 10 - pbf_move_left_joystick(context, {0, +1}, 500ms, 100ms); // 3 + pbf_move_left_joystick(context, {0, +1}, 500ms, 100ms); // 3 pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); // 7 pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 2+ context.wait_for_all_requests(); { ShortDialogWatcher talk_to_grunt_1(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 2000ms); - }, - {{talk_to_grunt_1}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{talk_to_grunt_1}}); + if (ret < 0){ stream.log("pastoria_city_drama_talk_to_grunt_1: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -380,7 +467,7 @@ static bool pastoria_city_drama( pbf_mash_button(context, BUTTON_B, 8000ms); }; } - + context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -391,27 +478,23 @@ static bool pastoria_city_drama( { ShortDialogWatcher talk_to_grunt_2(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 2000ms); - }, - {{talk_to_grunt_2}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{talk_to_grunt_2}}); + if (ret < 0){ stream.log("pastoria_city_drama_talk_to_grunt_2: dialog box not detected!", COLOR_RED); } if (ret == 0){ stream.log("pastoria_city_drama_talk_to_grunt_2: talking to grunt!"); - + BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 200000ms); - }, - {{black_screen}} - ); + int ret1 = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 200000ms); + }, + {{black_screen}}); if (ret1 < 0){ stream.log("pastoria_city_drama_talk_to_clint: Didn't detect start of battle with Clint!", COLOR_RED); return false; @@ -421,46 +504,39 @@ static bool pastoria_city_drama( pbf_mash_button(context, BUTTON_B, 3000ms); }; } - + context.wait_for_all_requests(); pbf_wait(context, 3000ms); - + dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("pastoria_city_drama: transition not detected!", COLOR_RED); return false; } stream.log("pastoria_city_drama: transition detected."); } - + return true; } -static bool go_through_house_east_pastoria( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_house_east_pastoria(VideoStream& stream, ProControllerContext& context){ stream.log("Going through the building at the entrance of Pastoria city"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_east_pastoria: Didn't exit building!", COLOR_RED); return false; @@ -470,10 +546,7 @@ static bool go_through_house_east_pastoria( return true; } -static bool go_through_route_213_chasing( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_route_213_chasing(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -490,26 +563,22 @@ static bool go_through_route_213_chasing( repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5, true); MarkDetector beauty_cindy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 15+ - }, - {{beauty_cindy_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 15+ + }, + {{beauty_cindy_fight}}); if (ret == 0){ BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_beauty_cindy: Didn't detect battle starting!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ context.wait_for_all_requests(); stream.log("go_through_route_213_beauty_cindy: Starting battle."); handle_battle(stream, context, "route_213_beauty_cindy"); @@ -520,13 +589,13 @@ static bool go_through_route_213_chasing( context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 4 pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 9 pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 4 - pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 3+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 4 + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2, true); @@ -534,15 +603,13 @@ static bool go_through_route_213_chasing( context.wait_for_all_requests(); ShortDialogWatcher talk_to_grunt(COLOR_BLUE); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt}} - ); - if (ret < 0) { + ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}}); + if (ret < 0){ stream.log("go_through_route_213_chasing_talk_to_grunt: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -557,53 +624,47 @@ static bool go_through_route_213_chasing( context.wait_for_all_requests(); ShortDialogWatcher repel_dialog(COLOR_RED); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); - }, - {{repel_dialog}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); + }, + {{repel_dialog}}); if (ret == 0){ pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_through_route_213: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_route_213: Didn't detect repel dialog box!", COLOR_RED); + return false; } context.wait_for_all_requests(); pbf_wait(context, 2000ms); pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - + MarkDetector tuber_jared_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 17 - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 3 - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - }, - {{tuber_jared_fight}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 17 + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 3 + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + }, + {{tuber_jared_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_jared: Didn't detect battle starting!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_route_213_tuber_jared: Starting battle."); handle_battle(stream, context, "route_213_tuber_jared"); context.wait_for_all_requests(); @@ -614,69 +675,58 @@ static bool go_through_route_213_chasing( context.wait_for_all_requests(); MarkDetector tuber_chelsea_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - DpadState dpad; - pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ - dpad.last_dir = DPAD_LEFT; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6, true); - }, - {{tuber_chelsea_fight}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + DpadState dpad; + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6, true); + }, + {{tuber_chelsea_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!"); - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_route_213_tuber_chelsea: Starting battle."); handle_battle(stream, context, "route_213_tuber_chelsea"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } } - + BlackScreenOverWatcher route_213_east_building(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{route_213_east_building}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{route_213_east_building}}); if (ret < 0){ stream.log("go_through_route_213_east_building: Didn't detect entering the building!", COLOR_RED); return false; }; - + return true; } -static bool go_through_building_route_213( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_building_route_213(VideoStream& stream, ProControllerContext& context){ stream.log("Going through building between routes 214 and 213"); context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 30000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 30000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_building_route_214: Didn't exit building!", COLOR_RED); return false; @@ -686,10 +736,7 @@ static bool go_through_building_route_213( return true; } -static bool go_through_route_214( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_route_214(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -706,15 +753,13 @@ static bool go_through_route_214( context.wait_for_all_requests(); ShortDialogWatcher talk_to_grunt(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}}); + if (ret < 0){ stream.log("go_through_route_214_talk_to_grunt: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -730,32 +775,28 @@ static bool go_through_route_214( repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); ShortDialogWatcher talk_to_grunt_2(COLOR_BLUE); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt_2}} - ); - if (ret < 0) { + ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt_2}}); + if (ret < 0){ stream.log("go_through_route_214_talk_to_grunt_2: dialog box not detected!", COLOR_RED); } if (ret == 0){ stream.log("go_through_route_214_talk_to_grunt_2: talking to grunt!"); context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_214_galactic_grunt: Didn't detect battle starting!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_route_214_galactic_grunt: Starting battle."); handle_battle(stream, context, "route_214_galactic_grunt"); context.wait_for_all_requests(); @@ -769,15 +810,13 @@ static bool go_through_route_214( repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1, true); ShortDialogWatcher talk_to_cynthia(COLOR_BLUE); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 10000ms); - ssf_mash1_button(context, BUTTON_A, 10000ms); - }, - {{talk_to_cynthia}} - ); - if (ret < 0) { + ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + {{talk_to_cynthia}}); + if (ret < 0){ stream.log("go_through_route_214_talk_to_cynthia: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -793,46 +832,35 @@ static bool go_through_route_214( return true; } -static bool go_inside_solaceon_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_inside_solaceon_pokemon_center(VideoStream& stream, ProControllerContext& context){ stream.log("go_inside_solaceon_pokemon_center: going inside"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 200000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 200000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_inside_solaceon_pokemon_center: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_inside_solaceon_pokemon_center: black screen detected!", COLOR_GREEN); return true; } return true; } -static bool heal_at_solaceon_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_solaceon_pokemon_center(VideoStream& stream, ProControllerContext& context){ stream.log("heal_at_solaceon_pokemon_center: healing pokemon"); context.wait_for_all_requests(); heal_pokemon(stream, context, "Solaceon"); return true; } -static bool go_to_the_psyduck_route_210( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -857,30 +885,26 @@ static bool go_to_the_psyduck_route_210( { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); - }, - {{battle_dialogue}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); stream.log("go_to_the_psyduck_route_210_rancher_marco: marker detected!", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_rancher_marco: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_rancher_marco"); } } @@ -891,41 +915,37 @@ static bool go_to_the_psyduck_route_210( pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ tall grass - { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); ShortDialogWatcher battle_dialogue_2(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - DpadState dpad; - pbf_move_left_joystick(context, {0, +1}, 6600ms, 100ms); // 22+ tall grass - - dpad.last_dir = DPAD_UP; - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{battle_dialogue}, {battle_dialogue_2}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + DpadState dpad; + pbf_move_left_joystick(context, {0, +1}, 6600ms, + 100ms); // 22+ tall grass + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{battle_dialogue}, {battle_dialogue_2}}); if (ret >= 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); stream.log("go_to_the_psyduck_route_210_breeder_amber: marker detected!", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_breeder_amber: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_breeder_amber"); } } @@ -938,15 +958,13 @@ static bool go_to_the_psyduck_route_210( repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); ShortDialogWatcher talk_to_psyducks(COLOR_BLUE); - int ret_psyducks = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_psyducks}} - ); - if (ret_psyducks < 0) { + int ret_psyducks = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_psyducks}}); + if (ret_psyducks < 0){ stream.log("go_to_the_psyduck_route_210_talk_to_psyducks: dialog box not detected!", COLOR_RED); } if (ret_psyducks == 0){ @@ -964,22 +982,20 @@ static bool go_to_the_psyduck_route_210( activate_repel(stream, context); - pbf_move_left_joystick(context, {0, +1}, 8000ms, 100ms); // 29+ tall grass + pbf_move_left_joystick(context, {0, +1}, 8000ms, 100ms); // 29+ tall grass repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_the_psyduck_route_210: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ + }else if (ret < 0){ stream.log("go_to_the_psyduck_route_210: Didn't detect repel dialog box!", COLOR_RED); return false; } @@ -999,32 +1015,28 @@ static bool go_to_the_psyduck_route_210( { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, +1}, 10000ms, 100ms); - }, - {{battle_dialogue}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, +1}, 10000ms, 100ms); + }, + {{battle_dialogue}}); + if (ret < 0){ stream.log("go_to_the_psyduck_route_210_ace_alyssa: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_ace_alyssa: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_ace_alyssa"); } } @@ -1041,29 +1053,25 @@ static bool go_to_the_psyduck_route_210( { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ - }, - {{battle_dialogue}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_ace_ernest: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_ace_ernest"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1078,29 +1086,25 @@ static bool go_to_the_psyduck_route_210( { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); - }, - {{battle_dialogue}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_blackbelt_adam: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_blackbelt_adam"); } } @@ -1110,20 +1114,18 @@ static bool go_to_the_psyduck_route_210( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_the_psyduck_route_210: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ + }else if (ret < 0){ stream.log("go_to_the_psyduck_route_210: Didn't detect repel dialog box!", COLOR_RED); return false; } @@ -1140,29 +1142,25 @@ static bool go_to_the_psyduck_route_210( { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); - }, - {{battle_dialogue}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_birdkeeper_brianna: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_birdkeeper_brianna"); } } @@ -1176,29 +1174,25 @@ static bool go_to_the_psyduck_route_210( { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); - }, - {{battle_dialogue}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_veteran_brian: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_210_veteran_brian"); } } @@ -1212,38 +1206,30 @@ static bool go_to_the_psyduck_route_210( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_the_psyduck_route_210_enter_celestic_town_pokemon_center: black screen not detected!", COLOR_RED); + stream.log("go_to_the_psyduck_route_210_enter_celestic_town_pokemon_center: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ return true; } - } return true; } -static bool heal_at_celestic_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_celestic_pokemon_center(VideoStream& stream, ProControllerContext& context){ stream.log("heal_at_celestic_pokemon_center: healing pokemon"); context.wait_for_all_requests(); heal_pokemon(stream, context, "Celestic"); return true; } -static bool celestic_town_drama( - VideoStream& stream, - ProControllerContext& context -){ +static bool celestic_town_drama(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1254,15 +1240,13 @@ static bool celestic_town_drama( { ShortDialogWatcher talk_to_cynthias_grandma(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_cynthias_grandma}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_cynthias_grandma}}); + if (ret < 0){ stream.log("celestic_town_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -1277,32 +1261,28 @@ static bool celestic_town_drama( dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); pbf_move_left_joystick(context, {0, +1}, 3600ms, 100ms); // 18+ - pbf_move_left_joystick(context, {-1, 0}, 200ms, 100ms); // 1 + pbf_move_left_joystick(context, {-1, 0}, 200ms, 100ms); // 1 { ShortDialogWatcher talk_to_the_grunt(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {-1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_the_grunt}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {-1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_the_grunt}}); + if (ret < 0){ stream.log("celestic_town_talk_to_the_grunt: dialog box not detected!", COLOR_RED); } if (ret == 0){ stream.log("celestic_town_talk_to_the_grunt: talking to the grunt!"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret1 = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret1 < 0){ stream.log("celestic_town_talk_to_the_grunt: Didn't detect start of battle with Clint!", COLOR_RED); return false; @@ -1316,16 +1296,14 @@ static bool celestic_town_drama( context.wait_for_all_requests(); dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); - + { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("celestic_town_enter_cave: black screen not detected!", COLOR_RED); return false; @@ -1336,10 +1314,7 @@ static bool celestic_town_drama( } -static bool discover_the_paintings( - VideoStream& stream, - ProControllerContext& context -){ +static bool discover_the_paintings(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1349,22 +1324,20 @@ static bool discover_the_paintings( pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 8+ pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ - pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); { ShortDialogWatcher talk_to_cynthias_grandma(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_cynthias_grandma}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_cynthias_grandma}}); + if (ret < 0){ stream.log("discover_the_paintings_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); } if (ret == 0){ @@ -1375,9 +1348,9 @@ static bool discover_the_paintings( context.wait_for_all_requests(); pbf_wait(context, 5000ms); - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2+ pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 7+ dpad.last_dir = DPAD_DOWN; @@ -1385,13 +1358,11 @@ static bool discover_the_paintings( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("discover_the_paintings: black screen for exiting the building not detected!", COLOR_RED); return false; @@ -1401,10 +1372,7 @@ static bool discover_the_paintings( return true; } -static bool talk_to_cyrus_and_fly_hearthome( - VideoStream& stream, - ProControllerContext& context -){ +static bool talk_to_cyrus_and_fly_hearthome(VideoStream& stream, ProControllerContext& context){ pbf_press_button(context, BUTTON_L, 80ms, 300ms); pbf_press_button(context, BUTTON_L, 80ms, 300ms); context.wait_for_all_requests(); @@ -1425,15 +1393,14 @@ static bool talk_to_cyrus_and_fly_hearthome( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("talk_to_cyrus_and_fly_hearthome: black screen for entering pokemon center not detected!", COLOR_RED); + stream.log("talk_to_cyrus_and_fly_hearthome: black screen for entering pokemon center not detected!", + COLOR_RED); return false; } } @@ -1441,20 +1408,14 @@ static bool talk_to_cyrus_and_fly_hearthome( } -static bool heal_at_hearthome_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_hearthome_pokemon_center(VideoStream& stream, ProControllerContext& context){ stream.log("heal_at_hearthome_pokemon_center: healing pokemon"); context.wait_for_all_requests(); heal_pokemon(stream, context, "Hearthome"); return true; } -static bool get_into_hearthome_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool get_into_hearthome_gym(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1467,13 +1428,11 @@ static bool get_into_hearthome_gym( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("get_into_hearthome_gym: black screen not detected!", COLOR_RED); return false; @@ -1483,191 +1442,239 @@ static bool get_into_hearthome_gym( return true; } -void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_pastoria_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_pastoria_pokemon_center: transition not detected.", + env.console); } }, - true - ); + true); } -void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!exit_pastoria_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "exit_pastoria_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "exit_pastoria_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!pastoria_city_drama(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_east_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_east_pastoria: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_213_chasing(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_213_chasing: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_213_chasing: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_building_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_building_route_213: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_inside_solaceon_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_solaceon_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_the_psyduck_route_210(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_the_psyduck_route_210: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_the_psyduck_route_210: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_celestic_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!celestic_town_drama(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!discover_the_paintings(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "discover_the_paintings: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "discover_the_paintings: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_hearthome_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_into_hearthome_gym: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_into_hearthome_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_into_hearthome_gym: transition not detected.", + env.console); } }, - false - ); + false); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h index b454a9da65..f5f53a21c1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h @@ -8,263 +8,208 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment05_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment05_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_05 : public AutoStory_Segment{ +class AutoStory_Segment_05 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0501 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0501 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0502 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0502 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0503 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0503 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0504 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0504 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0505 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0505 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0506 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0506 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0507 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0507 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0508 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0508 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0509 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0509 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0510 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0510 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0511 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0511 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0512 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0512 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0513 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0513 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0514 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0514 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0515 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0515 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0516 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0516 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0517 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0517 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 630a572d77..03ca3b0a7b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -8,33 +8,39 @@ #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" + #include "PokemonBDSP_AutoStory_Segment_06.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -std::string AutoStory_Segment_06::name() const{ return "06: Fifth Badge to Sixth Badge"; } -std::string AutoStory_Segment_06::start_text() const{ return "Start: Fifth Gym Badge acquired (Veilstone City)."; } -std::string AutoStory_Segment_06::end_text() const{ return "End: Sixth Gym Badge acquired (Canalave City)."; } +std::string AutoStory_Segment_06::name() const { + return "06: Fifth Badge to Sixth Badge"; +} +std::string AutoStory_Segment_06::start_text() const { + return "Start: Fifth Gym Badge acquired (Veilstone City)."; +} +std::string AutoStory_Segment_06::end_text() const { + return "End: Sixth Gym Badge acquired (Canalave City)."; +} -void AutoStory_Segment_06::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_06::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -57,105 +63,175 @@ void AutoStory_Segment_06::run_segment( } -std::string AutoStory_Checkpoint_0601::name() const{ return "06.01 - cynthia_and_fly_to_jubilife"; } -std::string AutoStory_Checkpoint_0601::start_text() const{ return "Fifth Gym Badge acquired (Hearthome City)."; } -std::string AutoStory_Checkpoint_0601::end_text() const{ return "Flew to Jubilife City."; } -void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0601::name() const { + return "06.01 - cynthia_and_fly_to_jubilife"; +} +std::string AutoStory_Checkpoint_0601::start_text() const { + return "Fifth Gym Badge acquired (Hearthome City)."; +} +std::string AutoStory_Checkpoint_0601::end_text() const { + return "Flew to Jubilife City."; +} +void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0601(env, context, options, stats); } -std::string AutoStory_Checkpoint_0602::name() const{ return "06.02 - enter_jubilife_pokemon_center"; } -std::string AutoStory_Checkpoint_0602::start_text() const{ return "In Jubilife City."; } -std::string AutoStory_Checkpoint_0602::end_text() const{ return "Entered Jubilife City Pokemon Center."; } -void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0602::name() const { + return "06.02 - enter_jubilife_pokemon_center"; +} +std::string AutoStory_Checkpoint_0602::start_text() const { + return "In Jubilife City."; +} +std::string AutoStory_Checkpoint_0602::end_text() const { + return "Entered Jubilife City Pokemon Center."; +} +void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0602(env, context, options, stats); } -std::string AutoStory_Checkpoint_0603::name() const{ return "06.03 - heal_and_exit_jubilife_pokemon_center"; } -std::string AutoStory_Checkpoint_0603::start_text() const{ return "Inside Jubilife City Pokemon Center."; } -std::string AutoStory_Checkpoint_0603::end_text() const{ return "Healed and exited Jubilife City Pokemon Center."; } -void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0603::name() const { + return "06.03 - heal_and_exit_jubilife_pokemon_center"; +} +std::string AutoStory_Checkpoint_0603::start_text() const { + return "Inside Jubilife City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0603::end_text() const { + return "Healed and exited Jubilife City Pokemon Center."; +} +void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0603(env, context, options, stats); } -std::string AutoStory_Checkpoint_0604::name() const{ return "06.04 - move_to_jubilife_west_exit"; } -std::string AutoStory_Checkpoint_0604::start_text() const{ return "Outside Jubilife City Pokemon Center."; } -std::string AutoStory_Checkpoint_0604::end_text() const{ return "Entered west exit building of Jubilife City."; } -void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0604::name() const { + return "06.04 - move_to_jubilife_west_exit"; +} +std::string AutoStory_Checkpoint_0604::start_text() const { + return "Outside Jubilife City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0604::end_text() const { + return "Entered west exit building of Jubilife City."; +} +void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0604(env, context, options, stats); } -std::string AutoStory_Checkpoint_0605::name() const{ return "06.05 - go_through_jubilife_west_exit"; } -std::string AutoStory_Checkpoint_0605::start_text() const{ return "Inside Jubilife City west exit building."; } -std::string AutoStory_Checkpoint_0605::end_text() const{ return "Exited onto Route 218."; } -void AutoStory_Checkpoint_0605::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0605::name() const { + return "06.05 - go_through_jubilife_west_exit"; +} +std::string AutoStory_Checkpoint_0605::start_text() const { + return "Inside Jubilife City west exit building."; +} +std::string AutoStory_Checkpoint_0605::end_text() const { + return "Exited onto Route 218."; +} +void AutoStory_Checkpoint_0605::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0605(env, context, options, stats); } -std::string AutoStory_Checkpoint_0606::name() const{ return "06.06 - go_through_route_218"; } -std::string AutoStory_Checkpoint_0606::start_text() const{ return "On Route 218."; } -std::string AutoStory_Checkpoint_0606::end_text() const{ return "Entered building at end of Route 218."; } -void AutoStory_Checkpoint_0606::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0606::name() const { + return "06.06 - go_through_route_218"; +} +std::string AutoStory_Checkpoint_0606::start_text() const { + return "On Route 218."; +} +std::string AutoStory_Checkpoint_0606::end_text() const { + return "Entered building at end of Route 218."; +} +void AutoStory_Checkpoint_0606::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0606(env, context, options, stats); } -std::string AutoStory_Checkpoint_0607::name() const{ return "06.07 - go_through_building_route_218"; } -std::string AutoStory_Checkpoint_0607::start_text() const{ return "Inside building at end of Route 218."; } -std::string AutoStory_Checkpoint_0607::end_text() const{ return "Exited building into Canalave City."; } -void AutoStory_Checkpoint_0607::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0607::name() const { + return "06.07 - go_through_building_route_218"; +} +std::string AutoStory_Checkpoint_0607::start_text() const { + return "Inside building at end of Route 218."; +} +std::string AutoStory_Checkpoint_0607::end_text() const { + return "Exited building into Canalave City."; +} +void AutoStory_Checkpoint_0607::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0607(env, context, options, stats); } -std::string AutoStory_Checkpoint_0608::name() const{ return "06.08 - go_to_clint_and_pokemon_center"; } -std::string AutoStory_Checkpoint_0608::start_text() const{ return "In Canalave City."; } -std::string AutoStory_Checkpoint_0608::end_text() const{ return "Battled Clint and entered Canalave City Pokemon Center."; } -void AutoStory_Checkpoint_0608::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0608::name() const { + return "06.08 - go_to_clint_and_pokemon_center"; +} +std::string AutoStory_Checkpoint_0608::start_text() const { + return "In Canalave City."; +} +std::string AutoStory_Checkpoint_0608::end_text() const { + return "Battled Clint and entered Canalave City Pokemon Center."; +} +void AutoStory_Checkpoint_0608::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0608(env, context, options, stats); } -std::string AutoStory_Checkpoint_0609::name() const{ return "06.09 - heal_and_exit_canalave_pokemon_center"; } -std::string AutoStory_Checkpoint_0609::start_text() const{ return "Inside Canalave City Pokemon Center."; } -std::string AutoStory_Checkpoint_0609::end_text() const{ return "Healed and exited Canalave City Pokemon Center."; } -void AutoStory_Checkpoint_0609::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0609::name() const { + return "06.09 - heal_and_exit_canalave_pokemon_center"; +} +std::string AutoStory_Checkpoint_0609::start_text() const { + return "Inside Canalave City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0609::end_text() const { + return "Healed and exited Canalave City Pokemon Center."; +} +void AutoStory_Checkpoint_0609::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0609(env, context, options, stats); } -std::string AutoStory_Checkpoint_0610::name() const{ return "06.10 - go_to_canalave_gym"; } -std::string AutoStory_Checkpoint_0610::start_text() const{ return "Outside Canalave City Pokemon Center."; } -std::string AutoStory_Checkpoint_0610::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } -void AutoStory_Checkpoint_0610::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0610::name() const { + return "06.10 - go_to_canalave_gym"; +} +std::string AutoStory_Checkpoint_0610::start_text() const { + return "Outside Canalave City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0610::end_text() const { + return "Sixth Gym Badge acquired (Canalave City)."; +} +void AutoStory_Checkpoint_0610::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0610(env, context, options, stats); } -std::string AutoStory_Checkpoint_0611::name() const{ return "06.11 - Canalave Gym Puzzle"; } -std::string AutoStory_Checkpoint_0611::start_text() const{ return "Outside Canalave City Pokemon Center."; } -std::string AutoStory_Checkpoint_0611::end_text() const{ return "Sixth Gym Badge acquired (Canalave City)."; } -void AutoStory_Checkpoint_0611::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0611::name() const { + return "06.11 - Canalave Gym Puzzle"; +} +std::string AutoStory_Checkpoint_0611::start_text() const { + return "Outside Canalave City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0611::end_text() const { + return "Sixth Gym Badge acquired (Canalave City)."; +} +void AutoStory_Checkpoint_0611::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0611(env, context, options, stats); } -static void move_back_after_unexpected_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, + const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "route_218_sailor_skyler") { - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ - pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 1+ + if (trainerid == "route_218_sailor_skyler"){ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 1+ pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ } } -static bool handle_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); @@ -168,43 +244,34 @@ static bool handle_battle( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if ( - trainerid == "route_218_sailor_skyler" - ) { + if (trainerid == "route_218_sailor_skyler"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - } else if ( - trainerid == "canalave_clint" - ) { + }else if (trainerid == "canalave_clint"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; } } - + context.wait_for_all_requests(); pbf_mash_button(context, BUTTON_B, 2000ms); stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } -static bool test( - VideoStream& stream, - ProControllerContext& context -){ +static bool test(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -213,10 +280,7 @@ static bool test( return true; } -static bool cynthia_and_fly_to_jubilife( - VideoStream& stream, - ProControllerContext& context -){ +static bool cynthia_and_fly_to_jubilife(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -247,10 +311,7 @@ static bool cynthia_and_fly_to_jubilife( return true; } -static bool enter_jubilife_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool enter_jubilife_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -258,13 +319,11 @@ static bool enter_jubilife_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_jubilife_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -273,10 +332,7 @@ static bool enter_jubilife_pokemon_center( return true; } -static bool heal_and_exit_jubilife_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_and_exit_jubilife_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -285,30 +341,25 @@ static bool heal_and_exit_jubilife_pokemon_center( return true; } -static bool move_to_jubilife_west_exit( - VideoStream& stream, - ProControllerContext& context -){ +static bool move_to_jubilife_west_exit(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("move_to_jubilife_west_exit: moving towards building..."); - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 6 + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 6 pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 18 { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); // 46 - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); // 46 + }, + {{black_screen}}); if (ret < 0){ stream.log("move_to_jubilife_west_exit: transition not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("move_to_jubilife_west_exit: transition detected.", COLOR_GREEN); } } @@ -316,10 +367,7 @@ static bool move_to_jubilife_west_exit( return true; } -static bool go_through_jubilife_west_exit( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_jubilife_west_exit(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -327,17 +375,15 @@ static bool go_through_jubilife_west_exit( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_jubilife_west_exit: transition not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_jubilife_west_exit: transition detected.", COLOR_GREEN); } } @@ -345,10 +391,7 @@ static bool go_through_jubilife_west_exit( return true; } -static bool go_through_route_218( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_route_218(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -370,31 +413,28 @@ static bool go_through_route_218( { MarkDetector sailor_skyler_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); // 28++ (getting out of the water) - pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ - }, - {{sailor_skyler_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 6000ms, + 100ms); // 28++ (getting out of the water) + pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ + }, + {{sailor_skyler_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_218_sailor_skyler: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_218_sailor_skyler"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -402,33 +442,28 @@ static bool go_through_route_218( } } } - + dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_218: transition not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_route_218: transition detected.", COLOR_GREEN); } } return true; } -static bool go_through_building_route_218( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_building_route_218(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -436,17 +471,15 @@ static bool go_through_building_route_218( { ShortDialogWatcher talk_to_scientist(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{talk_to_scientist}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{talk_to_scientist}}); if (ret < 0){ stream.log("go_through_building_route_218_scientist: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_building_route_218_scientist: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 12000ms); context.wait_for_all_requests(); @@ -455,27 +488,22 @@ static bool go_through_building_route_218( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_building_route_218: transition not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_through_building_route_218: transition detected.", COLOR_GREEN); } } return true; } -static bool go_to_clint_and_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -484,26 +512,24 @@ static bool go_to_clint_and_pokemon_center( pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 7+ pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 5+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 4+ pbf_move_left_joystick(context, {0, +1}, 4200ms, 100ms); // 21+ dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_clint_and_pokemon_center: Not activating repel again.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_to_clint_and_pokemon_center: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_clint_and_pokemon_center: Didn't detect repel dialog box!", COLOR_RED); + return false; } } @@ -514,33 +540,29 @@ static bool go_to_clint_and_pokemon_center( { ShortDialogWatcher talk_to_clint(COLOR_BLUE); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{talk_to_clint}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{talk_to_clint}}); if (ret < 0){ stream.log("go_to_clint_and_pokemon_center_clint: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_to_clint_and_pokemon_center_clint: dialog box detected, starting battle.", COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_clint_and_pokemon_center_clint: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "canalave_clint"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -559,13 +581,11 @@ static bool go_to_clint_and_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_clint_and_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -575,10 +595,7 @@ static bool go_to_clint_and_pokemon_center( return true; } -static bool heal_and_exit_canalave_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_and_exit_canalave_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -587,10 +604,7 @@ static bool heal_and_exit_canalave_pokemon_center( return true; } -static bool go_to_canalave_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_canalave_gym(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -598,7 +612,7 @@ static bool go_to_canalave_gym( stream.log("go_to_canalave_gym: going for the gym..."); repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2, false); - + pbf_move_left_joystick(context, {-1, 0}, 5000ms, 100ms); // 26+ pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 9 dpad.last_dir = DPAD_DOWN; @@ -606,13 +620,11 @@ static bool go_to_canalave_gym( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_canalave_gym: black screen not detected!", COLOR_RED); return false; @@ -620,133 +632,161 @@ static bool go_to_canalave_gym( stream.log("go_to_canalave_gym: entered gym", COLOR_GREEN); }; return true; - } - -void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ /*if (!test(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "test: transition not detected.", env.console); }*/ if (!cynthia_and_fly_to_jubilife(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "cynthia_and_fly_to_jubilife: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "cynthia_and_fly_to_jubilife: transition not detected.", + env.console); } }, - true - ); + true); } -void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_jubilife_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_jubilife_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_jubilife_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "move_to_jubilife_west_exit: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "move_to_jubilife_west_exit: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_jubilife_west_exit: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_jubilife_west_exit: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_218(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_building_route_218: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_building_route_218: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_clint_and_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_clint_and_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_clint_and_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); } }, - false - ); + false); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h index 81e8fd423d..17820d197a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h @@ -8,179 +8,142 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment06_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment06_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_06 : public AutoStory_Segment{ +class AutoStory_Segment_06 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0601 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0601 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0602 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0602 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0603 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0603 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0604 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0604 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0605 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0605 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0606 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0606 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0607 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0607 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0608 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0608 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0609 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0609 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0610 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0610 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0611 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0611 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 1750d193d7..9254617a68 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -5,38 +5,43 @@ * */ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Battle.h" -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" + #include "PokemonBDSP_AutoStory_Segment_07.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -std::string AutoStory_Segment_07::name() const{ return "07: Sixth Badge to Seventh Badge"; } -std::string AutoStory_Segment_07::start_text() const{ return "Start: Sixth Gym Badge acquired (Canalave City)."; } -std::string AutoStory_Segment_07::end_text() const{ return "End: Seventh Gym Badge acquired (Snowpoint City)."; } +std::string AutoStory_Segment_07::name() const { + return "07: Sixth Badge to Seventh Badge"; +} +std::string AutoStory_Segment_07::start_text() const { + return "Start: Sixth Gym Badge acquired (Canalave City)."; +} +std::string AutoStory_Segment_07::end_text() const { + return "End: Seventh Gym Badge acquired (Snowpoint City)."; +} -void AutoStory_Segment_07::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_07::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -67,164 +72,290 @@ void AutoStory_Segment_07::run_segment( } -std::string AutoStory_Checkpoint_0701::name() const{ return "07.01 - leave_gym"; } -std::string AutoStory_Checkpoint_0701::start_text() const{ return "Inside Canalave City gym after sixth badge."; } -std::string AutoStory_Checkpoint_0701::end_text() const{ return "Exited Canalave City gym."; } -void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0701::name() const { + return "07.01 - leave_gym"; +} +std::string AutoStory_Checkpoint_0701::start_text() const { + return "Inside Canalave City gym after sixth badge."; +} +std::string AutoStory_Checkpoint_0701::end_text() const { + return "Exited Canalave City gym."; +} +void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0701(env, context, options, stats); } -std::string AutoStory_Checkpoint_0702::name() const{ return "07.02 - go_to_the_library"; } -std::string AutoStory_Checkpoint_0702::start_text() const{ return "Outside Canalave City gym."; } -std::string AutoStory_Checkpoint_0702::end_text() const{ return "Entered Canalave City library."; } -void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0702::name() const { + return "07.02 - go_to_the_library"; +} +std::string AutoStory_Checkpoint_0702::start_text() const { + return "Outside Canalave City gym."; +} +std::string AutoStory_Checkpoint_0702::end_text() const { + return "Entered Canalave City library."; +} +void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0702(env, context, options, stats); } -std::string AutoStory_Checkpoint_0703::name() const{ return "07.03 - drama_at_the_library"; } -std::string AutoStory_Checkpoint_0703::start_text() const{ return "Inside Canalave City library."; } -std::string AutoStory_Checkpoint_0703::end_text() const{ return "Library drama complete, exited library."; } -void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0703::name() const { + return "07.03 - drama_at_the_library"; +} +std::string AutoStory_Checkpoint_0703::start_text() const { + return "Inside Canalave City library."; +} +std::string AutoStory_Checkpoint_0703::end_text() const { + return "Library drama complete, exited library."; +} +void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0703(env, context, options, stats); } -std::string AutoStory_Checkpoint_0704::name() const{ return "07.04 - talk_fly_and_enter_mount_coronet"; } -std::string AutoStory_Checkpoint_0704::start_text() const{ return "Outside Canalave City library."; } -std::string AutoStory_Checkpoint_0704::end_text() const{ return "Entered Mt. Coronet."; } -void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0704::name() const { + return "07.04 - talk_fly_and_enter_mount_coronet"; +} +std::string AutoStory_Checkpoint_0704::start_text() const { + return "Outside Canalave City library."; +} +std::string AutoStory_Checkpoint_0704::end_text() const { + return "Entered Mt. Coronet."; +} +void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0704(env, context, options, stats); } -std::string AutoStory_Checkpoint_0705::name() const{ return "07.05 - go_through_mount_coronet"; } -std::string AutoStory_Checkpoint_0705::start_text() const{ return "Inside Mt. Coronet."; } -std::string AutoStory_Checkpoint_0705::end_text() const{ return "Exited Mt. Coronet."; } -void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0705::name() const { + return "07.05 - go_through_mount_coronet"; +} +std::string AutoStory_Checkpoint_0705::start_text() const { + return "Inside Mt. Coronet."; +} +std::string AutoStory_Checkpoint_0705::end_text() const { + return "Exited Mt. Coronet."; +} +void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0705(env, context, options, stats); } -std::string AutoStory_Checkpoint_0706::name() const{ return "07.06 - go_through_route_216"; } -std::string AutoStory_Checkpoint_0706::start_text() const{ return "On Route 216."; } -std::string AutoStory_Checkpoint_0706::end_text() const{ return "Entered small chalet on Route 216."; } -void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0706::name() const { + return "07.06 - go_through_route_216"; +} +std::string AutoStory_Checkpoint_0706::start_text() const { + return "On Route 216."; +} +std::string AutoStory_Checkpoint_0706::end_text() const { + return "Entered small chalet on Route 216."; +} +void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0706(env, context, options, stats); } -std::string AutoStory_Checkpoint_0707::name() const{ return "07.07 - heal_small_house_route_216"; } -std::string AutoStory_Checkpoint_0707::start_text() const{ return "Inside small chalet on Route 216."; } -std::string AutoStory_Checkpoint_0707::end_text() const{ return "Rested in chalet and exited."; } -void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0707::name() const { + return "07.07 - heal_small_house_route_216"; +} +std::string AutoStory_Checkpoint_0707::start_text() const { + return "Inside small chalet on Route 216."; +} +std::string AutoStory_Checkpoint_0707::end_text() const { + return "Rested in chalet and exited."; +} +void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0707(env, context, options, stats); } -std::string AutoStory_Checkpoint_0708::name() const{ return "07.08 - continue_through_route_216"; } -std::string AutoStory_Checkpoint_0708::start_text() const{ return "Outside chalet on Route 216."; } -std::string AutoStory_Checkpoint_0708::end_text() const{ return "Entered Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0708::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0708::name() const { + return "07.08 - continue_through_route_216"; +} +std::string AutoStory_Checkpoint_0708::start_text() const { + return "Outside chalet on Route 216."; +} +std::string AutoStory_Checkpoint_0708::end_text() const { + return "Entered Veilstone City Pokemon Center."; +} +void AutoStory_Checkpoint_0708::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0708(env, context, options, stats); } -std::string AutoStory_Checkpoint_0709::name() const{ return "07.09 - heal_and_exit_veilstone_pokemon_center"; } -std::string AutoStory_Checkpoint_0709::start_text() const{ return "Inside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_0709::end_text() const{ return "Healed and exited Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0709::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0709::name() const { + return "07.09 - heal_and_exit_veilstone_pokemon_center"; +} +std::string AutoStory_Checkpoint_0709::start_text() const { + return "Inside Veilstone City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0709::end_text() const { + return "Healed and exited Veilstone City Pokemon Center."; +} +void AutoStory_Checkpoint_0709::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0709(env, context, options, stats); } -std::string AutoStory_Checkpoint_0710::name() const{ return "07.10 - go_to_house_south_veilstone"; } -std::string AutoStory_Checkpoint_0710::start_text() const{ return "Outside Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_0710::end_text() const{ return "Entered house south of Veilstone City."; } -void AutoStory_Checkpoint_0710::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0710::name() const { + return "07.10 - go_to_house_south_veilstone"; +} +std::string AutoStory_Checkpoint_0710::start_text() const { + return "Outside Veilstone City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0710::end_text() const { + return "Entered house south of Veilstone City."; +} +void AutoStory_Checkpoint_0710::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0710(env, context, options, stats); } -std::string AutoStory_Checkpoint_0711::name() const{ return "07.11 - go_through_house_south_veilstone"; } -std::string AutoStory_Checkpoint_0711::start_text() const{ return "Inside house south of Veilstone City."; } -std::string AutoStory_Checkpoint_0711::end_text() const{ return "Exited house onto Route 214."; } -void AutoStory_Checkpoint_0711::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0711::name() const { + return "07.11 - go_through_house_south_veilstone"; +} +std::string AutoStory_Checkpoint_0711::start_text() const { + return "Inside house south of Veilstone City."; +} +std::string AutoStory_Checkpoint_0711::end_text() const { + return "Exited house onto Route 214."; +} +void AutoStory_Checkpoint_0711::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0711(env, context, options, stats); } -std::string AutoStory_Checkpoint_0712::name() const{ return "07.12 - walk_through_route_214"; } -std::string AutoStory_Checkpoint_0712::start_text() const{ return "On Route 214 heading south."; } -std::string AutoStory_Checkpoint_0712::end_text() const{ return "Entered Valor Lakefront."; } -void AutoStory_Checkpoint_0712::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0712::name() const { + return "07.12 - walk_through_route_214"; +} +std::string AutoStory_Checkpoint_0712::start_text() const { + return "On Route 214 heading south."; +} +std::string AutoStory_Checkpoint_0712::end_text() const { + return "Entered Valor Lakefront."; +} +void AutoStory_Checkpoint_0712::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0712(env, context, options, stats); } -std::string AutoStory_Checkpoint_0713::name() const{ return "07.13 - go_through_lake_valor"; } -std::string AutoStory_Checkpoint_0713::start_text() const{ return "At Valor Lakefront."; } -std::string AutoStory_Checkpoint_0713::end_text() const{ return "Flew to Twinleaf Town and entered own house."; } -void AutoStory_Checkpoint_0713::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0713::name() const { + return "07.13 - go_through_lake_valor"; +} +std::string AutoStory_Checkpoint_0713::start_text() const { + return "At Valor Lakefront."; +} +std::string AutoStory_Checkpoint_0713::end_text() const { + return "Flew to Twinleaf Town and entered own house."; +} +void AutoStory_Checkpoint_0713::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0713(env, context, options, stats); } -std::string AutoStory_Checkpoint_0714::name() const{ return "07.14 - heal_at_home"; } -std::string AutoStory_Checkpoint_0714::start_text() const{ return "Inside own house in Twinleaf Town."; } -std::string AutoStory_Checkpoint_0714::end_text() const{ return "Healed at home and exited."; } -void AutoStory_Checkpoint_0714::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0714::name() const { + return "07.14 - heal_at_home"; +} +std::string AutoStory_Checkpoint_0714::start_text() const { + return "Inside own house in Twinleaf Town."; +} +std::string AutoStory_Checkpoint_0714::end_text() const { + return "Healed at home and exited."; +} +void AutoStory_Checkpoint_0714::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0714(env, context, options, stats); } -std::string AutoStory_Checkpoint_0715::name() const{ return "07.15 - go_to_lake_verity"; } -std::string AutoStory_Checkpoint_0715::start_text() const{ return "Outside own house in Twinleaf Town."; } -std::string AutoStory_Checkpoint_0715::end_text() const{ return "Entered Verity Lakefront."; } -void AutoStory_Checkpoint_0715::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0715::name() const { + return "07.15 - go_to_lake_verity"; +} +std::string AutoStory_Checkpoint_0715::start_text() const { + return "Outside own house in Twinleaf Town."; +} +std::string AutoStory_Checkpoint_0715::end_text() const { + return "Entered Verity Lakefront."; +} +void AutoStory_Checkpoint_0715::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0715(env, context, options, stats); } -std::string AutoStory_Checkpoint_0716::name() const{ return "07.16 - commander_mars_and_snowpoint"; } -std::string AutoStory_Checkpoint_0716::start_text() const{ return "At Lake Verity."; } -std::string AutoStory_Checkpoint_0716::end_text() const{ return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_0716::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0716::name() const { + return "07.16 - commander_mars_and_snowpoint"; +} +std::string AutoStory_Checkpoint_0716::start_text() const { + return "At Lake Verity."; +} +std::string AutoStory_Checkpoint_0716::end_text() const { + return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; +} +void AutoStory_Checkpoint_0716::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0716(env, context, options, stats); } -std::string AutoStory_Checkpoint_0717::name() const{ return "07.17 - heal_at_snowpoint_pokemon_center"; } -std::string AutoStory_Checkpoint_0717::start_text() const{ return "Inside Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_0717::end_text() const{ return "Healed and exited Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_0717::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0717::name() const { + return "07.17 - heal_at_snowpoint_pokemon_center"; +} +std::string AutoStory_Checkpoint_0717::start_text() const { + return "Inside Snowpoint City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0717::end_text() const { + return "Healed and exited Snowpoint City Pokemon Center."; +} +void AutoStory_Checkpoint_0717::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0717(env, context, options, stats); } -std::string AutoStory_Checkpoint_0718::name() const{ return "07.18 - go_to_snowpoint_gym"; } -std::string AutoStory_Checkpoint_0718::start_text() const{ return "Outside Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_0718::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -void AutoStory_Checkpoint_0718::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0718::name() const { + return "07.18 - go_to_snowpoint_gym"; +} +std::string AutoStory_Checkpoint_0718::start_text() const { + return "Outside Snowpoint City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0718::end_text() const { + return "Seventh Gym Badge acquired (Snowpoint City)."; +} +void AutoStory_Checkpoint_0718::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0718(env, context, options, stats); } -std::string AutoStory_Checkpoint_0719::name() const{ return "07.19 - Snowpoint Gym Battle"; } -std::string AutoStory_Checkpoint_0719::start_text() const{ return "Outside Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_0719::end_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -void AutoStory_Checkpoint_0719::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const{ +std::string AutoStory_Checkpoint_0719::name() const { + return "07.19 - Snowpoint Gym Battle"; +} +std::string AutoStory_Checkpoint_0719::start_text() const { + return "Outside Snowpoint City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0719::end_text() const { + return "Seventh Gym Badge acquired (Snowpoint City)."; +} +void AutoStory_Checkpoint_0719::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0719(env, context, options, stats); } -static void move_back_after_unexpected_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, + const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "route_216_ace_maria") { + if (trainerid == "route_216_ace_maria"){ pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 8 - } else if (trainerid == "route_215_skier_edward") { + }else if (trainerid == "route_215_skier_edward"){ pbf_move_left_joystick(context, {-1, 0}, 8100ms, 100ms); // 27++ (snow) - } else if (trainerid == "route_217_skier_lexie") { + }else if (trainerid == "route_217_skier_lexie"){ pbf_move_left_joystick(context, {0, +1}, 2100ms, 100ms); // 7++ (snow) - } else if (trainerid == "route_217_blackbelt_luke") { + }else if (trainerid == "route_217_blackbelt_luke"){ pbf_move_left_joystick(context, {0, +1}, 5100ms, 100ms); // 17++ (snow) } } -static bool handle_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); @@ -237,56 +368,41 @@ static bool handle_battle( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if ( - trainerid == "route_216_ace_maria" or - trainerid == "route_216_skier_edward" or - trainerid == "route_217_skier_lexie" or - trainerid == "route_217_blackbelt_luke" or - trainerid == "route_217_ace_olivia" or - trainerid == "verity_lakefront_grunt_1" or - trainerid == "verity_lakefront_grunt_3" - ) { + if (trainerid == "route_216_ace_maria" or trainerid == "route_216_skier_edward" or + trainerid == "route_217_skier_lexie" or trainerid == "route_217_blackbelt_luke" or + trainerid == "route_217_ace_olivia" or trainerid == "verity_lakefront_grunt_1" or + trainerid == "verity_lakefront_grunt_3"){ // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - } else if ( - trainerid == "route_217_ace_dalton" - ) { + }else if (trainerid == "route_217_ace_dalton"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - } else if ( - trainerid == "verity_lakefront_grunt_2" or - trainerid == "verity_lakefront_grunt_4" or - trainerid == "verity_lakefront_commander_mars" - ) { + }else if (trainerid == "verity_lakefront_grunt_2" or trainerid == "verity_lakefront_grunt_4" or + trainerid == "verity_lakefront_commander_mars"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; } } - + context.wait_for_all_requests(); pbf_mash_button(context, BUTTON_B, 2000ms); stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } -static bool leave_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool leave_gym(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -294,13 +410,11 @@ static bool leave_gym( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: black screen not detected!", COLOR_RED); return false; @@ -310,10 +424,7 @@ static bool leave_gym( return true; } -static bool go_to_the_library( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_the_library(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -326,7 +437,7 @@ static bool go_to_the_library( if (ret < 0){ stream.log("go_to_the_library_talk_to_clint: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_to_the_library_talk_to_clint: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 7000ms); context.wait_for_all_requests(); @@ -337,20 +448,18 @@ static bool go_to_the_library( repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); pbf_move_left_joystick(context, {-1, 0}, 1600ms, 100ms); // 8+ pbf_move_left_joystick(context, {0, +1}, 3800ms, 100ms); // 19+ - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_library: black screen not detected!", COLOR_RED); return false; @@ -360,10 +469,7 @@ static bool go_to_the_library( return true; } -static bool drama_at_the_library( - VideoStream& stream, - ProControllerContext& context -){ +static bool drama_at_the_library(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -374,13 +480,11 @@ static bool drama_at_the_library( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -390,19 +494,17 @@ static bool drama_at_the_library( context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 3+ pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 7 - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -419,7 +521,7 @@ static bool drama_at_the_library( if (ret < 0){ stream.log("drama_at_the_library_talk_to_clint: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("drama_at_the_library_talk_to_clint: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 45000ms); context.wait_for_all_requests(); @@ -435,13 +537,11 @@ static bool drama_at_the_library( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -455,17 +555,15 @@ static bool drama_at_the_library( dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 7 - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -478,13 +576,11 @@ static bool drama_at_the_library( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -494,10 +590,7 @@ static bool drama_at_the_library( return true; } -static bool talk_fly_and_enter_mount_coronet( - VideoStream& stream, - ProControllerContext& context -){ +static bool talk_fly_and_enter_mount_coronet(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -510,7 +603,7 @@ static bool talk_fly_and_enter_mount_coronet( if (ret < 0){ stream.log("talk_fly_and_enter_mount_coronet_talk_to_rowan: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("talk_fly_and_enter_mount_coronet_talk_to_rowan: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 21000ms); context.wait_for_all_requests(); @@ -526,10 +619,10 @@ static bool talk_fly_and_enter_mount_coronet( stream.log("talk_fly_and_enter_mount_coronet: going towards Mt. Coronet..."); pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {-1, 0}, 1800ms, 100ms); // 9+ - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 11); @@ -546,13 +639,11 @@ static bool talk_fly_and_enter_mount_coronet( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("talk_fly_and_enter_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -562,10 +653,7 @@ static bool talk_fly_and_enter_mount_coronet( return true; } -static bool go_through_mount_coronet( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -588,13 +676,11 @@ static bool go_through_mount_coronet( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -607,23 +693,21 @@ static bool go_through_mount_coronet( stream.log("go_through_mount_coronet: continuing through Mt. Coronet..."); pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_mount_coronet: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_through_mount_coronet: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_mount_coronet: Didn't detect repel dialog box!", COLOR_RED); + return false; } } context.wait_for_all_requests(); @@ -631,13 +715,11 @@ static bool go_through_mount_coronet( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -653,13 +735,11 @@ static bool go_through_mount_coronet( { WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{white_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{white_screen}}); if (ret < 0){ stream.log("go_through_mount_coronet: white screen not detected!", COLOR_RED); return false; @@ -670,10 +750,7 @@ static bool go_through_mount_coronet( return true; } -static bool go_through_route_216( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_route_216(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -681,55 +758,49 @@ static bool go_through_route_216( stream.log("go_through_route_216: going through the snow..."); pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 5+ - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 2+ { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 9600ms, 100ms); // 48+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 9600ms, 100ms); // 48+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_route_216: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_through_route_216: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_route_216: Didn't detect repel dialog box!", COLOR_RED); + return false; } } - + context.wait_for_all_requests(); pbf_wait(context, 1000ms); { MarkDetector ace_maria_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3200ms, 100ms); // 16 - }, - {{ace_maria_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3200ms, 100ms); // 16 + }, + {{ace_maria_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_216_ace_maria: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_216_ace_maria"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -744,29 +815,26 @@ static bool go_through_route_216( { MarkDetector skier_edward_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 7500ms, 100ms); // 25++ (snow) - }, - {{skier_edward_fight}} - ); + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 7500ms, 100ms); // 25++ (snow) + }, + {{skier_edward_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_216_skier_edward: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_216_skier_edward"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -781,13 +849,11 @@ static bool go_through_route_216( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_216: black screen not detected!", COLOR_RED); return false; @@ -798,10 +864,7 @@ static bool go_through_route_216( return true; } -static bool heal_small_house_route_216( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_small_house_route_216(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -813,27 +876,23 @@ static bool heal_small_house_route_216( { ShortDialogWatcher sleep_in_a_bed(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{sleep_in_a_bed}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{sleep_in_a_bed}}); if (ret == 0){ stream.log("heal_small_house_route_216_sleep_in_a_bed: taking a nap...", COLOR_GREEN); const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - int arrow_ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 10000ms); - }, - {{arrow}} - ); + int arrow_ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 10000ms); + }, + {{arrow}}); if (arrow_ret != 0){ stream.log("heal_small_house_route_216_sleep_in_a_bed: selection arrow not found.", COLOR_RED); return false; @@ -842,9 +901,9 @@ static bool heal_small_house_route_216( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_mash_button(context, BUTTON_B, 6000ms); - } else if (ret < 0){ - stream.log("heal_small_house_route_216_sleep_in_a_bed: Didn't find the bed!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("heal_small_house_route_216_sleep_in_a_bed: Didn't find the bed!", COLOR_RED); + return false; } } @@ -857,13 +916,11 @@ static bool heal_small_house_route_216( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_small_house_route_216: black screen not detected!", COLOR_RED); return false; @@ -874,10 +931,7 @@ static bool heal_small_house_route_216( return true; } -static bool continue_through_route_216( - VideoStream& stream, - ProControllerContext& context -){ +static bool continue_through_route_216(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -892,25 +946,23 @@ static bool continue_through_route_216( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("continue_through_route_216: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("continue_through_route_216: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("continue_through_route_216: Didn't detect repel dialog box!", COLOR_RED); + return false; } } pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10++ (Snow) dpad.last_dir = DPAD_UP; - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); // Snow + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); // Snow pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 4++ (Snow) dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 1); // Snow @@ -918,29 +970,26 @@ static bool continue_through_route_216( { MarkDetector ace_dalton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 7500ms, 100ms); // 25++ (snow) - }, - {{ace_dalton_fight}} - ); + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 7500ms, 100ms); // 25++ (snow) + }, + {{ace_dalton_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_ace_dalton: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_217_ace_dalton"); } } @@ -954,20 +1003,18 @@ static bool continue_through_route_216( { ShortDialogWatcher pick_up_TM100(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, -1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{pick_up_TM100}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_TM100}}); if (ret == 0){ stream.log("continue_through_route_216: Picking up TM100.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 10000ms); - } else if (ret < 0){ - stream.log("continue_through_route_216: Didn't detect TM100 dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("continue_through_route_216: Didn't detect TM100 dialog box!", COLOR_RED); + return false; } } @@ -976,29 +1023,26 @@ static bool continue_through_route_216( { MarkDetector skier_lexie_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 6000ms, 100ms); // 20++ (snow) - }, - {{skier_lexie_fight}} - ); + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 6000ms, 100ms); // 20++ (snow) + }, + {{skier_lexie_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_skier_lexie: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_217_skier_lexie"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1008,42 +1052,39 @@ static bool continue_through_route_216( } context.wait_for_all_requests(); - + dpad.last_dir = DPAD_UP; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 2); // Snow - repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 3); // Snow + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 2); // Snow + repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 3); // Snow pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 2++ (snow) pbf_move_left_joystick(context, {0, +1}, 2700ms, 100ms); // 9++ (snow) dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 800ms, 2); // Snow - repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 1); // Snow + repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 1); // Snow { MarkDetector blackbelt_luke_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 5400ms, 100ms); // 21++ (snow) - }, - {{blackbelt_luke_fight}} - ); + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5400ms, 100ms); // 21++ (snow) + }, + {{blackbelt_luke_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_blackbelt_luke: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_217_blackbelt_luke"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1062,29 +1103,25 @@ static bool continue_through_route_216( { MarkDetector ace_olivia_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{ace_olivia_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{ace_olivia_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_ace_olivia: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_217_ace_olivia"); } } @@ -1099,7 +1136,7 @@ static bool continue_through_route_216( repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 11); pbf_move_left_joystick(context, {+1, 0}, 7800ms, 100ms); // 26++ (snow) dpad.last_dir = DPAD_RIGHT; - repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 2); // Snow + repeat_dpad(context, dpad, DPAD_UP, 80ms, 800ms, 2); // Snow pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 8++ (snow) pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 8++ (snow) pbf_move_left_joystick(context, {+1, 0}, 2700ms, 100ms); // 9++ (snow) @@ -1114,13 +1151,11 @@ static bool continue_through_route_216( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216: black screen not detected!", COLOR_RED); return false; @@ -1131,10 +1166,7 @@ static bool continue_through_route_216( return true; } -static bool heal_and_exit_veilstone_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_and_exit_veilstone_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1144,10 +1176,7 @@ static bool heal_and_exit_veilstone_pokemon_center( } -static bool go_to_house_south_veilstone( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1158,20 +1187,18 @@ static bool go_to_house_south_veilstone( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ - pbf_move_left_joystick(context, {+1, 0}, 4000ms, 100ms); // 20+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ + pbf_move_left_joystick(context, {+1, 0}, 4000ms, 100ms); // 20+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_house_south_veilstone: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_to_house_south_veilstone: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_house_south_veilstone: Didn't detect repel dialog box!", COLOR_RED); + return false; } } @@ -1195,27 +1222,21 @@ static bool go_to_house_south_veilstone( { BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_1}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); return false; } } - - return true; + return true; } -static bool go_through_house_south_veilstone( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_house_south_veilstone(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1223,34 +1244,29 @@ static bool go_through_house_south_veilstone( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); return false; } stream.log("go_through_house_south_veilstone: exited the house", COLOR_GREEN); } - + return true; } -static bool walk_through_route_214( - VideoStream& stream, - ProControllerContext& context -){ +static bool walk_through_route_214(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("walk_through_route_214: going through route 214..."); - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {0, -1}, 3800ms, 100ms); // 19+ dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8); @@ -1262,14 +1278,14 @@ static bool walk_through_route_214( dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ - + dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1, true); pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ @@ -1283,19 +1299,17 @@ static bool walk_through_route_214( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 11000ms, 100ms); // 55+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("walk_through_route_214: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("walk_through_route_214: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("walk_through_route_214: Didn't detect repel dialog box!", COLOR_RED); + return false; } } @@ -1309,13 +1323,11 @@ static bool walk_through_route_214( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214: black screen not detected!", COLOR_RED); return false; @@ -1326,10 +1338,7 @@ static bool walk_through_route_214( return true; } -static bool go_through_lake_valor( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_lake_valor(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1353,24 +1362,22 @@ static bool go_through_lake_valor( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_lake_valor: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_through_lake_valor: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_lake_valor: Didn't detect repel dialog box!", COLOR_RED); + return false; } } context.wait_for_all_requests(); @@ -1381,20 +1388,17 @@ static bool go_through_lake_valor( // @GumGum: Here comes the rest of the code - // And this comes after having defeated the commander and having exited the cave fly_to(stream, context, FlyPoint::TwinleafTown); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_lake_valor: black screen not detected!", COLOR_RED); return false; @@ -1405,10 +1409,7 @@ static bool go_through_lake_valor( return true; } -static bool heal_at_home( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_home(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1420,20 +1421,18 @@ static bool heal_at_home( { ShortDialogWatcher mom_heal_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{mom_heal_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{mom_heal_dialog}}); if (ret == 0){ stream.log("heal_at_home: Sleeping...", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 4000ms); - } else if (ret < 0){ - stream.log("heal_at_home: Didn't detect mom dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("heal_at_home: Didn't detect mom dialog box!", COLOR_RED); + return false; } } context.wait_for_all_requests(); @@ -1446,13 +1445,11 @@ static bool heal_at_home( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_at_home: black screen not detected!", COLOR_RED); return false; @@ -1463,10 +1460,7 @@ static bool heal_at_home( return true; } -static bool go_to_lake_verity( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_lake_verity(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1483,13 +1477,11 @@ static bool go_to_lake_verity( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_lake_verity: black screen not detected!", COLOR_RED); return false; @@ -1499,10 +1491,7 @@ static bool go_to_lake_verity( return true; } -static bool commander_mars_and_snowpoint( - VideoStream& stream, - ProControllerContext& context -){ +static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1529,50 +1518,44 @@ static bool commander_mars_and_snowpoint( { MarkDetector grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 - }, - {{grunt_1_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 + }, + {{grunt_1_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_1: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "verity_lakefront_grunt_1"); } } } - + context.wait_for_all_requests(); pbf_wait(context, 2000ms); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "verity_lakefront_grunt_2"); } } @@ -1591,29 +1574,25 @@ static bool commander_mars_and_snowpoint( { MarkDetector grunt_3_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 - }, - {{grunt_3_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 + }, + {{grunt_3_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_3: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "verity_lakefront_grunt_3"); } } @@ -1624,17 +1603,15 @@ static bool commander_mars_and_snowpoint( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "verity_lakefront_grunt_4"); } } @@ -1648,34 +1625,30 @@ static bool commander_mars_and_snowpoint( { ShortDialogWatcher commander_mars_fight(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{commander_mars_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{commander_mars_fight}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint: Didn't detect Commander Mars dialog box!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ stream.log("commander_mars_and_snowpoint: Battling Commander Mars...", COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_commander_mars: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "verity_lakefront_commander_mars"); } } @@ -1695,13 +1668,11 @@ static bool commander_mars_and_snowpoint( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); return false; @@ -1712,10 +1683,7 @@ static bool commander_mars_and_snowpoint( return true; } -static bool heal_at_snowpoint_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_snowpoint_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 3000ms); context.wait_for_all_requests(); @@ -1726,10 +1694,7 @@ static bool heal_at_snowpoint_pokemon_center( return true; } -static bool go_to_snowpoint_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_snowpoint_gym(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1744,13 +1709,11 @@ static bool go_to_snowpoint_gym( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); return false; @@ -1762,215 +1725,261 @@ static bool go_to_snowpoint_gym( } - - -void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "leave_gym: transition not detected.", env.console); } }, - true - ); + true); } -void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_the_library(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!drama_at_the_library(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_fly_and_enter_mount_coronet(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_mount_coronet: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_route_216(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_small_house_route_216(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_small_house_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_small_house_route_216: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!continue_through_route_216(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "continue_through_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "continue_through_route_216: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_house_south_veilstone: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "walk_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "walk_through_route_214: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_lake_valor(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_home(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_home: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_home: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_lake_verity(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!commander_mars_and_snowpoint(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "commander_mars_and_snowpoint: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "commander_mars_and_snowpoint: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); } }, - false - ); + false); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h index d7d1f93c8b..55cd7ad00b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h @@ -8,291 +8,230 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment07_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment07_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_07 : public AutoStory_Segment{ +class AutoStory_Segment_07 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0701 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0701 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0702 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0702 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0703 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0703 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0704 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0704 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0705 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0705 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0706 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0706 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0707 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0707 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0708 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0708 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0709 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0709 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0710 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0710 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0711 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0711 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0712 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0712 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0713 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0713 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0714 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0714 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0715 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0715 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0716 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0716 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0717 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0717 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0718 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0718 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0719 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0719 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index a50bed240e..1be1bba576 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -5,28 +5,32 @@ * */ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" -#include "../PokemonBDSP_AutoStory.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" + #include "PokemonBDSP_AutoStory_Segment_08.h" using namespace std::chrono_literals; -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // --------------------------------------------------------------------------- @@ -78,16 +82,18 @@ static void exit_legendary_encounter(VideoStream& /*stream*/, ProControllerConte // Segment / Checkpoint classes // --------------------------------------------------------------------------- -std::string AutoStory_Segment_08::name() const{ return "08: Seventh Badge to Eighth Badge"; } -std::string AutoStory_Segment_08::start_text() const{ return "Start: Seventh Gym Badge acquired (Snowpoint City)."; } -std::string AutoStory_Segment_08::end_text() const{ return "End: Eighth Gym Badge acquired (Sunyshore City)."; } +std::string AutoStory_Segment_08::name() const { + return "08: Seventh Badge to Eighth Badge"; +} +std::string AutoStory_Segment_08::start_text() const { + return "Start: Seventh Gym Badge acquired (Snowpoint City)."; +} +std::string AutoStory_Segment_08::end_text() const { + return "End: Eighth Gym Badge acquired (Sunyshore City)."; +} -void AutoStory_Segment_08::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_08::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -123,37 +129,36 @@ void AutoStory_Segment_08::run_segment( } -std::string AutoStory_Checkpoint_021::name() const{ return "021 - " + AutoStory_Segment_08().name(); } -std::string AutoStory_Checkpoint_021::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -std::string AutoStory_Checkpoint_021::end_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } +std::string AutoStory_Checkpoint_021::name() const { + return "021 - " + AutoStory_Segment_08().name(); +} +std::string AutoStory_Checkpoint_021::start_text() const { + return "Seventh Gym Badge acquired (Snowpoint City)."; +} +std::string AutoStory_Checkpoint_021::end_text() const { + return "Eighth Gym Badge acquired (Sunyshore City)."; +} // ------------------------------------------------------------------ Functions logic --------------------------------------------------------- // -static void move_back_after_unexpected_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, + const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "veilstone_warehouse_warehouse_grunt_1") { + if (trainerid == "veilstone_warehouse_warehouse_grunt_1"){ pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ - } else if (trainerid == "galactic_hq_grunt_1") { + }else if (trainerid == "galactic_hq_grunt_1"){ pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ - } else if (trainerid == "route_207_hiker_justin") { + }else if (trainerid == "route_207_hiker_justin"){ pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 3+ } } -static bool handle_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); @@ -166,58 +171,42 @@ static bool handle_battle( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if ( - trainerid == "veilstone_warehouse_warehouse_grunt_2" or - trainerid == "galactic_hq_grunt_4" or - trainerid == "galactic_hq_grunt_5" - ) { + if (trainerid == "veilstone_warehouse_warehouse_grunt_2" or trainerid == "galactic_hq_grunt_4" or + trainerid == "galactic_hq_grunt_5"){ // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - } else if ( - trainerid == "galactic_hq_grunt_1" or - trainerid == "galactic_hq_double_grunts_1" or - trainerid == "route_222_sailor_luther" - ) { + }else if (trainerid == "galactic_hq_grunt_1" or trainerid == "galactic_hq_double_grunts_1" or + trainerid == "route_222_sailor_luther"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - } else if ( - trainerid == "veilstone_warehouse_warehouse_grunt_1" or - trainerid == "galactic_hq_scientist_fredrick" or - trainerid == "galactic_hq_grunt_2" or - trainerid == "galactic_hq_grunt_3" or - trainerid == "galactic_hq_cyrus" or - trainerid == "galactic_hq_commander_saturn" or - trainerid == "route_207_hiker_justin" - ) { + }else if (trainerid == "veilstone_warehouse_warehouse_grunt_1" or trainerid == "galactic_hq_scientist_fredrick" or + trainerid == "galactic_hq_grunt_2" or trainerid == "galactic_hq_grunt_3" or + trainerid == "galactic_hq_cyrus" or trainerid == "galactic_hq_commander_saturn" or + trainerid == "route_207_hiker_justin"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; } } - + context.wait_for_all_requests(); pbf_mash_button(context, BUTTON_B, 2000ms); stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } -static bool leave_gym( - VideoStream& stream, - ProControllerContext& context -){ +static bool leave_gym(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -225,13 +214,11 @@ static bool leave_gym( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: black screen not detected!", COLOR_RED); return false; @@ -241,10 +228,7 @@ static bool leave_gym( return true; } -static bool heal_at_snowpoint_city_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_snowpoint_city_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -257,13 +241,11 @@ static bool heal_at_snowpoint_city_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_at_snowpoint_city_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -273,18 +255,11 @@ static bool heal_at_snowpoint_city_pokemon_center( return true; } -static bool heal_and_exit( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -){ +static bool heal_and_exit(VideoStream& stream, ProControllerContext& context, const std::string& label){ return heal_pokemon(stream, context, label); } -static bool go_to_lake_acuity( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_lake_acuity(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -310,20 +285,18 @@ static bool go_to_lake_acuity( context.wait_for_all_requests(); pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {-1, 0}, 8400ms, 100ms); // 28++ (snow) dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_lake_acuity: black screen not detected!", COLOR_RED); return false; @@ -333,10 +306,7 @@ static bool go_to_lake_acuity( return true; } -static bool talk_to_clint_then_go_veilstone( - VideoStream& stream, - ProControllerContext& context -){ +static bool talk_to_clint_then_go_veilstone(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -346,10 +316,12 @@ static bool talk_to_clint_then_go_veilstone( ShortDialogWatcher listen_to_clint_and_jupiter(COLOR_BLUE); int ret = wait_until(stream, context, std::chrono::seconds(30), {{listen_to_clint_and_jupiter}}); if (ret < 0){ - stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box not detected!", COLOR_RED); + stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box not detected!", + COLOR_RED); return false; - } else if (ret == 0) { - stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box detected.", COLOR_GREEN); + }else if (ret == 0){ + stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box detected.", + COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 22000ms); context.wait_for_all_requests(); } @@ -362,13 +334,11 @@ static bool talk_to_clint_then_go_veilstone( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("talk_to_clint_then_go_veilstone: black screen not detected!", COLOR_RED); return false; @@ -379,10 +349,7 @@ static bool talk_to_clint_then_go_veilstone( } -static bool get_key_and_enter_warehouse( - VideoStream& stream, - ProControllerContext& context -){ +static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -399,19 +366,17 @@ static bool get_key_and_enter_warehouse( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_mount_coronet: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_through_mount_coronet: Didn't detect repel dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_mount_coronet: Didn't detect repel dialog box!", COLOR_RED); + return false; } } @@ -421,20 +386,18 @@ static bool get_key_and_enter_warehouse( { ShortDialogWatcher talk_to_grunt(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}}); if (ret == 0){ stream.log("get_key_and_enter_warehouse: Talking to the grunt.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 9000ms); - } else if (ret < 0){ - stream.log("get_key_and_enter_warehouse: Didn't detect grunt dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("get_key_and_enter_warehouse: Didn't detect grunt dialog box!", COLOR_RED); + return false; } } @@ -443,20 +406,18 @@ static bool get_key_and_enter_warehouse( { ShortDialogWatcher pick_up_key(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{pick_up_key}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_key}}); if (ret == 0){ stream.log("get_key_and_enter_warehouse: Got the key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 5000ms); - } else if (ret < 0){ - stream.log("get_key_and_enter_warehouse: Didn't detect key pickup dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("get_key_and_enter_warehouse: Didn't detect key pickup dialog box!", COLOR_RED); + return false; } } @@ -477,13 +438,11 @@ static bool get_key_and_enter_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("get_key_and_enter_warehouse: black screen not detected!", COLOR_RED); return false; @@ -493,10 +452,7 @@ static bool get_key_and_enter_warehouse( return true; } -static bool go_through_warehouse( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_through_warehouse(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -508,20 +464,18 @@ static bool go_through_warehouse( { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_warehouse: Opened the door.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); - } else if (ret < 0){ - stream.log("go_through_warehouse: Didn't detect door dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_warehouse: Didn't detect door dialog box!", COLOR_RED); + return false; } } @@ -530,13 +484,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -554,31 +506,27 @@ static bool go_through_warehouse( { MarkDetector warehouse_grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {+1, 0}, 10800ms, 100ms); // 54+ - }, - {{warehouse_grunt_1_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 10800ms, 100ms); // 54+ + }, + {{warehouse_grunt_1_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("veilstone_warehouse_warehouse_grunt_1: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "veilstone_warehouse_warehouse_grunt_1"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -589,34 +537,30 @@ static bool go_through_warehouse( context.wait_for_all_requests(); pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1 - + { MarkDetector warehouse_grunt_2_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 13000ms, 100ms); // 54+ - }, - {{warehouse_grunt_2_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 13000ms, 100ms); // 54+ + }, + {{warehouse_grunt_2_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("veilstone_warehouse_warehouse_grunt_2: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "veilstone_warehouse_warehouse_grunt_2"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -631,13 +575,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -655,13 +597,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -678,32 +618,28 @@ static bool go_through_warehouse( BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); MarkDetector galactic_hq_grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ - pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6 - }, - {{black_screen},{galactic_hq_grunt_1_fight}} - ); - if (ret == 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6 + }, + {{black_screen}, {galactic_hq_grunt_1_fight}}); + if (ret == 0){ stream.log("go_through_warehouse: got into Galactic HQ 1F, second room.", COLOR_GREEN); - } else if (ret == 1){ + }else if (ret == 1){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen_2}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen_2}}); if (ret < 0){ stream.log("galactic_hq_grunt_1: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_grunt_1"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -715,13 +651,11 @@ static bool go_through_warehouse( { BlackScreenFlashWatcher room_transition(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{room_transition}} - ); + int ret1 = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{room_transition}}); if (ret1 < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -738,13 +672,11 @@ static bool go_through_warehouse( { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -759,13 +691,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -776,18 +706,16 @@ static bool go_through_warehouse( context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {-1, 0}, 1400ms, 100ms); // 6+ { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -803,13 +731,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -826,30 +752,26 @@ static bool go_through_warehouse( { MarkDetector galactic_hq_scientist_fredrick_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 6 - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 6+ - }, - {{galactic_hq_scientist_fredrick_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 6+ + }, + {{galactic_hq_scientist_fredrick_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_scientist_fredrick: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_scientist_fredrick"); } } @@ -862,13 +784,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -887,20 +807,18 @@ static bool go_through_warehouse( { ShortDialogWatcher pick_up_galactic_key(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{pick_up_galactic_key}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_galactic_key}}); if (ret == 0){ stream.log("go_through_warehouse: Picked up the Galactic key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_through_warehouse: Didn't detect grunt dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_warehouse: Didn't detect grunt dialog box!", COLOR_RED); + return false; } } @@ -912,20 +830,18 @@ static bool go_through_warehouse( { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_warehouse: Opened the first door with the Galactic Key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); - } else if (ret < 0){ - stream.log("go_through_warehouse: Didn't detect door opening dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_warehouse: Didn't detect door opening dialog box!", COLOR_RED); + return false; } } @@ -939,13 +855,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -960,13 +874,11 @@ static bool go_through_warehouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -978,10 +890,7 @@ static bool go_through_warehouse( } -static bool heal_at_veilstone_pokemon_center( - VideoStream& stream, - ProControllerContext& context -){ +static bool heal_at_veilstone_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -994,13 +903,11 @@ static bool heal_at_veilstone_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_at_veilstone_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -1011,10 +918,7 @@ static bool heal_at_veilstone_pokemon_center( return true; } -static bool go_to_galactic_hq( - VideoStream& stream, - ProControllerContext& context -){ +static bool go_to_galactic_hq(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1035,15 +939,14 @@ static bool go_to_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_galactic_hq, entering the Galactic HQ from the front door: black screen not detected!", COLOR_RED); + stream.log("go_to_galactic_hq, entering the Galactic HQ from the front door: black screen not detected!", + COLOR_RED); return false; } stream.log("go_to_galactic_hq: entered the Galactic HQ from the front door.", COLOR_GREEN); @@ -1052,11 +955,7 @@ static bool go_to_galactic_hq( return true; } -static bool go_through_galactic_hq( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ // ------- This has returned one error and I couldn't reproduce it. It was somewhere at the other comment I left behind ----------- // DpadState dpad; @@ -1070,20 +969,18 @@ static bool go_through_galactic_hq( { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_galactic_hq: Opened the door.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); - } else if (ret < 0){ - stream.log("go_through_galactic_hq, opening the door: Didn't detect door dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_galactic_hq, opening the door: Didn't detect door dialog box!", COLOR_RED); + return false; } } @@ -1093,15 +990,14 @@ static bool go_through_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_galactic_hq, entering Galactic HQ's 2F, first room: black screen not detected!", COLOR_RED); + stream.log("go_through_galactic_hq, entering Galactic HQ's 2F, first room: black screen not detected!", + COLOR_RED); return false; } stream.log("go_through_galactic_hq: entered Galactic HQ's 2F, first room.", COLOR_GREEN); @@ -1118,42 +1014,38 @@ static bool go_through_galactic_hq( MarkDetector galactic_hq_grunt_2_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); // First run to the right, and see grunt 2 detects you - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - }, - {{galactic_hq_grunt_2_fight}} - ); - if (ret < 0) { + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + }, + {{galactic_hq_grunt_2_fight}}); + if (ret < 0){ // If it doesn't, move on, but consider that grunt_2 hasn't been done stream.log("galactic_hq_grunt_2: mark not detected, moving on..."); - } else if (ret == 0){ + }else if (ret == 0){ // If it does, handle the fight and consider that grunt_2 has been done context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_grunt_2"); grunt_2_done = true; } } } - + context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1163,42 +1055,39 @@ static bool go_through_galactic_hq( dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); - if (options.help_mode == HelpMode::OUTSIDE_HELP) { - if (!grunt_2_done) { + if (options.help_mode == HelpMode::OUTSIDE_HELP){ + if (!grunt_2_done){ { // Now run downwards and check if either mark is detected (it should) MarkDetector galactic_hq_grunt_2_3_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 16+ - }, - {{galactic_hq_grunt_2_3_fight}} - ); + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 16+ + }, + {{galactic_hq_grunt_2_3_fight}}); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (ret < 0 ) { + if (ret < 0){ // Mark isn't detected, there is an issue stream.log("galactic_hq_grunt_2_3: mark not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_grunt_2"); } } @@ -1206,21 +1095,19 @@ static bool go_through_galactic_hq( } context.wait_for_all_requests(); - + { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); - ssf_mash1_button(context, BUTTON_A, 10000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ context.wait_for_all_requests(); handle_battle(stream, context, "galactic_hq_grunt_3"); @@ -1237,17 +1124,15 @@ static bool go_through_galactic_hq( dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7); pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); // 13+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1262,13 +1147,11 @@ static bool go_through_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1282,33 +1165,30 @@ static bool go_through_galactic_hq( dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); - if (options.help_mode == HelpMode::OUTSIDE_HELP) { + if (options.help_mode == HelpMode::OUTSIDE_HELP){ { MarkDetector galactic_hq_grunt_4_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); // 5+ - }, - {{galactic_hq_grunt_4_fight}} - ); + int ret = + run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); // 5+ + }, + {{galactic_hq_grunt_4_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 3000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_4: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_grunt_4"); } } @@ -1316,17 +1196,15 @@ static bool go_through_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 10000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 10000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_5: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_grunt_5"); } } @@ -1341,13 +1219,11 @@ static bool go_through_galactic_hq( { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1364,13 +1240,11 @@ static bool go_through_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1379,7 +1253,7 @@ static bool go_through_galactic_hq( }; // ---------------------------------------- Until here ---------------------------------------- // - + context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1388,20 +1262,18 @@ static bool go_through_galactic_hq( { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_galactic_hq: Opened the door with the Galactic Key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); - } else if (ret < 0){ - stream.log("go_through_galactic_hq: Didn't detect door opening dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_galactic_hq: Didn't detect door opening dialog box!", COLOR_RED); + return false; } } @@ -1410,33 +1282,29 @@ static bool go_through_galactic_hq( { ShortDialogWatcher cyrus_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 5+ - }, - {{cyrus_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 5+ + }, + {{cyrus_dialog}}); if (ret == 0){ stream.log("go_through_galactic_hq: Cyrus dialog box detected.", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_cyrus: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_cyrus"); } } } - + context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1444,7 +1312,7 @@ static bool go_through_galactic_hq( context.wait_for_all_requests(); - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 6+ dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 2); @@ -1452,13 +1320,11 @@ static bool go_through_galactic_hq( { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1478,13 +1344,11 @@ static bool go_through_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1501,32 +1365,28 @@ static bool go_through_galactic_hq( { ShortDialogWatcher talk_to_commander_saturn(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_commander_saturn}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_commander_saturn}}); if (ret < 0){ stream.log("go_through_galactic_hq: Didn't detect Commander Saturn dialog box!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ stream.log("go_through_galactic_hq: Starting the battle with Commander Saturn.", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_commander_saturn: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "galactic_hq_commander_saturn"); } } @@ -1543,21 +1403,19 @@ static bool go_through_galactic_hq( { ShortDialogWatcher unlock_guardians(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{unlock_guardians}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{unlock_guardians}}); if (ret == 0){ stream.log("go_through_galactic_hq: Released the Lake Guardians.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); pbf_mash_button(context, BUTTON_B, 8000ms); - } else if (ret < 0){ - stream.log("go_through_galactic_hq: Didn't detect button dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_galactic_hq: Didn't detect button dialog box!", COLOR_RED); + return false; } } @@ -1571,13 +1429,11 @@ static bool go_through_galactic_hq( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1593,13 +1449,11 @@ static bool go_through_galactic_hq( { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1618,13 +1472,11 @@ static bool go_through_galactic_hq( { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1641,13 +1493,11 @@ static bool go_through_galactic_hq( { WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{white_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{white_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: white screen not detected!", COLOR_RED); return false; @@ -1659,11 +1509,7 @@ static bool go_through_galactic_hq( } -static bool fly_and_heal_at_oreburgh( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool fly_and_heal_at_oreburgh(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1676,13 +1522,11 @@ static bool fly_and_heal_at_oreburgh( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("fly_and_heal_at_oreburgh: black screen not detected!", COLOR_RED); return false; @@ -1693,11 +1537,7 @@ static bool fly_and_heal_at_oreburgh( return true; } -static bool go_to_mount_coronet( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1727,29 +1567,25 @@ static bool go_to_mount_coronet( { MarkDetector hiker_justin_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 5000ms, 100ms); // 11+ - }, - {{hiker_justin_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5000ms, 100ms); // 11+ + }, + {{hiker_justin_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_mount_coronet, route_207_hiker_justin: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_207_hiker_justin"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1763,13 +1599,11 @@ static bool go_to_mount_coronet( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -1781,11 +1615,7 @@ static bool go_to_mount_coronet( } -static bool go_through_mount_coronet( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1796,11 +1626,7 @@ static bool go_through_mount_coronet( return true; } -static bool fly_and_heal_at_veilstone( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool fly_and_heal_at_veilstone(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1812,13 +1638,11 @@ static bool fly_and_heal_at_veilstone( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("fly_and_heal_at_veilstone: black screen not detected!", COLOR_RED); return false; @@ -1829,18 +1653,14 @@ static bool fly_and_heal_at_veilstone( return true; } -static bool go_to_house_south_veilstone( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("go_to_house_south_veilstone: heading South..."); - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ dpad.last_dir = DPAD_LEFT; @@ -1861,27 +1681,22 @@ static bool go_to_house_south_veilstone( { BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_1}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); return false; } } - + return true; } -static bool go_through_house_south_veilstone( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_through_house_south_veilstone(VideoStream& stream, ProControllerContext& context, + AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1889,35 +1704,29 @@ static bool go_through_house_south_veilstone( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); return false; } stream.log("go_through_house_south_veilstone: exited the house", COLOR_GREEN); } - + return true; } -static bool go_to_house_west_sunyshore( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("go_to_house_west_sunyshore: going towards Sunyshore City..."); - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {0, -1}, 3800ms, 100ms); // 19+ dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8); @@ -1929,14 +1738,14 @@ static bool go_to_house_west_sunyshore( dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 + pbf_move_left_joystick(context, {-1, 0}, 400ms, 100ms); // 2 pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 5+ pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 8+ dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); pbf_move_left_joystick(context, {0, -1}, 1600ms, 100ms); // 8+ - + dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); pbf_move_left_joystick(context, {0, -1}, 2200ms, 100ms); // 11+ @@ -1950,20 +1759,18 @@ static bool go_to_house_west_sunyshore( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_house_west_sunyshore: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_to_house_west_sunyshore: Didn't detect repel dialog box (1)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_house_west_sunyshore: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; } } @@ -1981,20 +1788,18 @@ static bool go_to_house_west_sunyshore( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 7400ms, 100ms); // 37+ - pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 7400ms, 100ms); // 37+ + pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_house_west_sunyshore: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_to_house_west_sunyshore: Didn't detect repel dialog box (2)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_house_west_sunyshore: Didn't detect repel dialog box (2)!", COLOR_RED); + return false; } } @@ -2003,9 +1808,9 @@ static bool go_to_house_west_sunyshore( dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3); pbf_move_left_joystick(context, {+1, 0}, 1400ms, 100ms); // 7+ - pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {+1, 0}, 5200ms, 100ms); // 26+ - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ use_HM(stream, context, "Rock Smash"); @@ -2017,29 +1822,25 @@ static bool go_to_house_west_sunyshore( { MarkDetector route_222_sailor_luther_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); // 5+ - }, - {{route_222_sailor_luther_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); // 5+ + }, + {{route_222_sailor_luther_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_house_west_sunyshore_route_222_sailor_luther: black screen not detected!", COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_222_sailor_luther"); } } @@ -2054,28 +1855,23 @@ static bool go_to_house_west_sunyshore( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_house_west_sunyshore: black screen not detected!", COLOR_RED); return false; } stream.log("go_to_house_west_sunyshore: entered the house", COLOR_GREEN); } - + return true; } -static bool go_through_house_west_sunyshore( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_through_house_west_sunyshore(VideoStream& stream, ProControllerContext& context, + AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -2083,28 +1879,22 @@ static bool go_through_house_west_sunyshore( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); return false; } stream.log("go_through_house_south_veilstone: exited the house", COLOR_GREEN); } - + return true; } -static bool go_to_sunyshore_lighthouse( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_to_sunyshore_lighthouse(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -2117,7 +1907,7 @@ static bool go_to_sunyshore_lighthouse( if (ret < 0){ stream.log("go_to_house_west_sunyshore, listen_to_flint: dialog box not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("go_to_house_west_sunyshore, listen_to_flint: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 15000ms); context.wait_for_all_requests(); @@ -2140,28 +1930,23 @@ static bool go_to_sunyshore_lighthouse( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_west_sunyshore: black screen not detected!", COLOR_RED); return false; } stream.log("go_through_house_west_sunyshore: entered the lighthouse", COLOR_GREEN); } - + return true; } -static bool talk_to_volkner_at_the_lighthouse( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool talk_to_volkner_at_the_lighthouse(VideoStream& stream, ProControllerContext& context, + AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 1000ms); @@ -2174,7 +1959,7 @@ static bool talk_to_volkner_at_the_lighthouse( if (ret < 0){ stream.log("talk_to_volkner_at_the_lighthouse: black screen exiting elevator not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("talk_to_volkner_at_the_lighthouse: exited the elevator.", COLOR_GREEN); } } @@ -2190,41 +1975,38 @@ static bool talk_to_volkner_at_the_lighthouse( { ShortDialogWatcher talk_to_volkner(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_volkner}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_volkner}}); if (ret == 0){ stream.log("talk_to_volkner_at_the_lighthouse: Talking to Volkner.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 11000ms); - } else if (ret < 0){ - stream.log("talk_to_volkner_at_the_lighthouse: Didn't detect Volkner dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("talk_to_volkner_at_the_lighthouse: Didn't detect Volkner dialog box!", COLOR_RED); + return false; } } context.wait_for_all_requests(); - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {0, -1}, 1400ms, 100ms); // 7+ dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 4); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("talk_to_volkner_at_the_lighthouse: entering elevator to exit black screen not detected!", COLOR_RED); + stream.log("talk_to_volkner_at_the_lighthouse: entering elevator to exit black screen not detected!", + COLOR_RED); return false; } stream.log("talk_to_volkner_at_the_lighthouse: entered the elevator", COLOR_GREEN); @@ -2239,7 +2021,7 @@ static bool talk_to_volkner_at_the_lighthouse( if (ret < 0){ stream.log("talk_to_volkner_at_the_lighthouse: black screen exiting elevator not detected!", COLOR_RED); return false; - } else if (ret == 0) { + }else if (ret == 0){ stream.log("talk_to_volkner_at_the_lighthouse: exited the elevator.", COLOR_GREEN); } } @@ -2247,11 +2029,7 @@ static bool talk_to_volkner_at_the_lighthouse( return true; } -static bool go_to_sunyshore_gym( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_to_sunyshore_gym(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 1000ms); @@ -2267,24 +2045,22 @@ static bool go_to_sunyshore_gym( dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 6); pbf_move_left_joystick(context, {-1, 0}, 6800ms, 100ms); // 34+ - pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ { ShortDialogWatcher talk_to_flint(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_flint}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_flint}}); if (ret == 0){ stream.log("go_to_sunyshore_gym: Talking to Flint.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 9000ms); - } else if (ret < 0){ - stream.log("go_to_sunyshore_gym: Didn't detect Flint dialog box!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_sunyshore_gym: Didn't detect Flint dialog box!", COLOR_RED); + return false; } } @@ -2295,13 +2071,11 @@ static bool go_to_sunyshore_gym( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_sunyshore_gym: black screen entering the gym not detected!", COLOR_RED); return false; @@ -2313,651 +2087,751 @@ static bool go_to_sunyshore_gym( } - // ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // -void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "leave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "leave_gym: transition not detected.", env.console); } }, - true - ); + true); } -void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Snowpoint City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_snowpoint_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_snowpoint_city: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_lake_acuity(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_clint_then_go_veilstone(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_veilstone_city: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!get_key_and_enter_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_key_and_enter_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_key_and_enter_warehouse: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_at_veilstone_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_veilstone_city: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_galactic_hq(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_galactic_hq(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_galactic_hq: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_galactic_hq: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_oreburgh(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "fly_and_heal_at_oreburgh: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "fly_and_heal_at_oreburgh: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Oreburgh City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_oreburgh_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_oreburgh_city: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_mount_coronet(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_mount_coronet: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "fly_and_heal_at_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "fly_and_heal_at_veilstone: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_veilstone_city: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_veilstone_city: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_house_south_veilstone: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_house_west_sunyshore(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_house_west_sunyshore: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_house_west_sunyshore: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_house_west_sunyshore(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_lighthouse(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_lighthouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_sunyshore_lighthouse: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0825( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0825(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0826( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0826(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); } }, - false - ); + false); } - -std::string AutoStory_Checkpoint_0801::name() const{ return "08.01 - Gym badge acquired"; } -std::string AutoStory_Checkpoint_0801::start_text() const{ return "Seventh Gym Badge acquired (Snowpoint City)."; } -std::string AutoStory_Checkpoint_0801::end_text() const{ return "Leave Snowpoint Gym."; } -void AutoStory_Checkpoint_0801::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0801::name() const { + return "08.01 - Gym badge acquired"; +} +std::string AutoStory_Checkpoint_0801::start_text() const { + return "Seventh Gym Badge acquired (Snowpoint City)."; +} +std::string AutoStory_Checkpoint_0801::end_text() const { + return "Leave Snowpoint Gym."; +} +void AutoStory_Checkpoint_0801::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0801(env, context, options, stats); } -std::string AutoStory_Checkpoint_0802::name() const{ return "08.02 - Left Snowpoint Gym"; } -std::string AutoStory_Checkpoint_0802::start_text() const{ return "Left Snowpoint Gym."; } -std::string AutoStory_Checkpoint_0802::end_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -void AutoStory_Checkpoint_0802::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0802::name() const { + return "08.02 - Left Snowpoint Gym"; +} +std::string AutoStory_Checkpoint_0802::start_text() const { + return "Left Snowpoint Gym."; +} +std::string AutoStory_Checkpoint_0802::end_text() const { + return "Arrived at Snowpoint City Pokémon Center."; +} +void AutoStory_Checkpoint_0802::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0802(env, context, options, stats); } -std::string AutoStory_Checkpoint_0803::name() const{ return "08.03 - Arrived at Snowpoint Pokemon Center"; } -std::string AutoStory_Checkpoint_0803::start_text() const{ return "Arrived at Snowpoint City Pokémon Center."; } -std::string AutoStory_Checkpoint_0803::end_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_0803::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0803::name() const { + return "08.03 - Arrived at Snowpoint Pokemon Center"; +} +std::string AutoStory_Checkpoint_0803::start_text() const { + return "Arrived at Snowpoint City Pokémon Center."; +} +std::string AutoStory_Checkpoint_0803::end_text() const { + return "Healed at Snowpoint City Pokemon Center."; +} +void AutoStory_Checkpoint_0803::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0803(env, context, options, stats); } -std::string AutoStory_Checkpoint_0804::name() const{ return "08.04 - Healed at Snowpoint City Pokemon Center"; } -std::string AutoStory_Checkpoint_0804::start_text() const{ return "Healed at Snowpoint City Pokemon Center."; } -std::string AutoStory_Checkpoint_0804::end_text() const{ return "Entered Lake Acuity Lakefront."; } -void AutoStory_Checkpoint_0804::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0804::name() const { + return "08.04 - Healed at Snowpoint City Pokemon Center"; +} +std::string AutoStory_Checkpoint_0804::start_text() const { + return "Healed at Snowpoint City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0804::end_text() const { + return "Entered Lake Acuity Lakefront."; +} +void AutoStory_Checkpoint_0804::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0804(env, context, options, stats); } -std::string AutoStory_Checkpoint_0805::name() const{ return "08.05 - Entered Acuity Lakefront"; } -std::string AutoStory_Checkpoint_0805::start_text() const{ return "Entered Lake Acuity Lakefront."; } -std::string AutoStory_Checkpoint_0805::end_text() const{ return "Arrived at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0805::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0805::name() const { + return "08.05 - Entered Acuity Lakefront"; +} +std::string AutoStory_Checkpoint_0805::start_text() const { + return "Entered Lake Acuity Lakefront."; +} +std::string AutoStory_Checkpoint_0805::end_text() const { + return "Arrived at Veilstone City Pokemon Center."; +} +void AutoStory_Checkpoint_0805::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0805(env, context, options, stats); } -std::string AutoStory_Checkpoint_0806::name() const{ return "08.06 - Arrived at Veilstone Pokemon Center"; } -std::string AutoStory_Checkpoint_0806::start_text() const{ return "Arrived at Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_0806::end_text() const{ return "Healed at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0806::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0806::name() const { + return "08.06 - Arrived at Veilstone Pokemon Center"; +} +std::string AutoStory_Checkpoint_0806::start_text() const { + return "Arrived at Veilstone City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0806::end_text() const { + return "Healed at Veilstone City Pokemon Center."; +} +void AutoStory_Checkpoint_0806::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0806(env, context, options, stats); } -std::string AutoStory_Checkpoint_0807::name() const{ return "08.07 - Healed at Veilstone Pokemon Center"; } -std::string AutoStory_Checkpoint_0807::start_text() const{ return "Healed at Veilstone City Pokemon Center."; } -std::string AutoStory_Checkpoint_0807::end_text() const{ return "Entered Team Galactic Warehouse."; } -void AutoStory_Checkpoint_0807::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0807::name() const { + return "08.07 - Healed at Veilstone Pokemon Center"; +} +std::string AutoStory_Checkpoint_0807::start_text() const { + return "Healed at Veilstone City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0807::end_text() const { + return "Entered Team Galactic Warehouse."; +} +void AutoStory_Checkpoint_0807::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0807(env, context, options, stats); } -std::string AutoStory_Checkpoint_0808::name() const{ return "08.08 - Entered Team Galactic Warehouse"; } -std::string AutoStory_Checkpoint_0808::start_text() const{ return "Entered Team Galactic Warehouse."; } -std::string AutoStory_Checkpoint_0808::end_text() const{ return "Got out of the warehouse with the key."; } -void AutoStory_Checkpoint_0808::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0808::name() const { + return "08.08 - Entered Team Galactic Warehouse"; +} +std::string AutoStory_Checkpoint_0808::start_text() const { + return "Entered Team Galactic Warehouse."; +} +std::string AutoStory_Checkpoint_0808::end_text() const { + return "Got out of the warehouse with the key."; +} +void AutoStory_Checkpoint_0808::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0808(env, context, options, stats); } -std::string AutoStory_Checkpoint_0809::name() const{ return "08.09 - Fly back to Veilstone Pokemon Center for healing"; } -std::string AutoStory_Checkpoint_0809::start_text() const{ return "Got out of the warehouse with the key."; } -std::string AutoStory_Checkpoint_0809::end_text() const{ return "Entered Veilstone Pokemon Center for healing."; } -void AutoStory_Checkpoint_0809::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0809::name() const { + return "08.09 - Fly back to Veilstone Pokemon Center for healing"; +} +std::string AutoStory_Checkpoint_0809::start_text() const { + return "Got out of the warehouse with the key."; +} +std::string AutoStory_Checkpoint_0809::end_text() const { + return "Entered Veilstone Pokemon Center for healing."; +} +void AutoStory_Checkpoint_0809::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0809(env, context, options, stats); } -std::string AutoStory_Checkpoint_0810::name() const{ return "08.10 - Heal Pokemon"; } -std::string AutoStory_Checkpoint_0810::start_text() const{ return "Entered Veilstone Pokemon Center for healing."; } -std::string AutoStory_Checkpoint_0810::end_text() const{ return "Exited Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_0810::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0810::name() const { + return "08.10 - Heal Pokemon"; +} +std::string AutoStory_Checkpoint_0810::start_text() const { + return "Entered Veilstone Pokemon Center for healing."; +} +std::string AutoStory_Checkpoint_0810::end_text() const { + return "Exited Veilstone Pokemon Center."; +} +void AutoStory_Checkpoint_0810::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0810(env, context, options, stats); } -std::string AutoStory_Checkpoint_0811::name() const{ return "08.11 - Go to Galactic HQ"; } -std::string AutoStory_Checkpoint_0811::start_text() const{ return "Exited Veilstone Pokemon Center."; } -std::string AutoStory_Checkpoint_0811::end_text() const{ return "Entered Galactic HQ."; } -void AutoStory_Checkpoint_0811::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0811::name() const { + return "08.11 - Go to Galactic HQ"; +} +std::string AutoStory_Checkpoint_0811::start_text() const { + return "Exited Veilstone Pokemon Center."; +} +std::string AutoStory_Checkpoint_0811::end_text() const { + return "Entered Galactic HQ."; +} +void AutoStory_Checkpoint_0811::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0811(env, context, options, stats); } -std::string AutoStory_Checkpoint_0812::name() const{ return "08.12 - Deal with Cyrus at the Galactic HQ"; } -std::string AutoStory_Checkpoint_0812::start_text() const{ return "Entered Galactic HQ."; } -std::string AutoStory_Checkpoint_0812::end_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } -void AutoStory_Checkpoint_0812::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0812::name() const { + return "08.12 - Deal with Cyrus at the Galactic HQ"; +} +std::string AutoStory_Checkpoint_0812::start_text() const { + return "Entered Galactic HQ."; +} +std::string AutoStory_Checkpoint_0812::end_text() const { + return "Dealt with Cyrus and left the Galactic HQ."; +} +void AutoStory_Checkpoint_0812::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0812(env, context, options, stats); } -std::string AutoStory_Checkpoint_0813::name() const{ return "08.13 - Fly to Oreburgh City"; } -std::string AutoStory_Checkpoint_0813::start_text() const{ return "Dealt with Cyrus and left the Galactic HQ."; } -std::string AutoStory_Checkpoint_0813::end_text() const{ return "Flew to Oreburgh city and entered Pokemon Center."; } -void AutoStory_Checkpoint_0813::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0813::name() const { + return "08.13 - Fly to Oreburgh City"; +} +std::string AutoStory_Checkpoint_0813::start_text() const { + return "Dealt with Cyrus and left the Galactic HQ."; +} +std::string AutoStory_Checkpoint_0813::end_text() const { + return "Flew to Oreburgh city and entered Pokemon Center."; +} +void AutoStory_Checkpoint_0813::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0813(env, context, options, stats); } -std::string AutoStory_Checkpoint_0814::name() const{ return "08.14 - Heal Pokemon at Oreburgh City Pokemon Center"; } -std::string AutoStory_Checkpoint_0814::start_text() const{ return "Flew to Oreburgh City and entered Pokemon Center."; } -std::string AutoStory_Checkpoint_0814::end_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } -void AutoStory_Checkpoint_0814::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0814::name() const { + return "08.14 - Heal Pokemon at Oreburgh City Pokemon Center"; +} +std::string AutoStory_Checkpoint_0814::start_text() const { + return "Flew to Oreburgh City and entered Pokemon Center."; +} +std::string AutoStory_Checkpoint_0814::end_text() const { + return "Healed Pokemon at Oreburgh City Pokemon Center."; +} +void AutoStory_Checkpoint_0814::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0814(env, context, options, stats); } -std::string AutoStory_Checkpoint_0815::name() const{ return "08.15 - Go from Oreburgh City to Mount Coronet entrance"; } -std::string AutoStory_Checkpoint_0815::start_text() const{ return "Healed Pokemon at Oreburgh City Pokemon Center."; } -std::string AutoStory_Checkpoint_0815::end_text() const{ return "Entered Mount Coronet."; } -void AutoStory_Checkpoint_0815::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0815::name() const { + return "08.15 - Go from Oreburgh City to Mount Coronet entrance"; +} +std::string AutoStory_Checkpoint_0815::start_text() const { + return "Healed Pokemon at Oreburgh City Pokemon Center."; +} +std::string AutoStory_Checkpoint_0815::end_text() const { + return "Entered Mount Coronet."; +} +void AutoStory_Checkpoint_0815::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0815(env, context, options, stats); } -std::string AutoStory_Checkpoint_0816::name() const{ return "08.16 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; } -std::string AutoStory_Checkpoint_0816::start_text() const{ return "Entered Mount Coronet."; } -std::string AutoStory_Checkpoint_0816::end_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } -void AutoStory_Checkpoint_0816::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0816::name() const { + return "08.16 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; +} +std::string AutoStory_Checkpoint_0816::start_text() const { + return "Entered Mount Coronet."; +} +std::string AutoStory_Checkpoint_0816::end_text() const { + return "Exited Mt. Coronet, having dealt with Cyrus."; +} +void AutoStory_Checkpoint_0816::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0816(env, context, options, stats); } -std::string AutoStory_Checkpoint_0817::name() const{ return "08.17 - Fly to Veilstone City"; } -std::string AutoStory_Checkpoint_0817::start_text() const{ return "Exited Mt. Coronet, having dealt with Cyrus."; } -std::string AutoStory_Checkpoint_0817::end_text() const{ return "Entered the Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_0817::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0817::name() const { + return "08.17 - Fly to Veilstone City"; +} +std::string AutoStory_Checkpoint_0817::start_text() const { + return "Exited Mt. Coronet, having dealt with Cyrus."; +} +std::string AutoStory_Checkpoint_0817::end_text() const { + return "Entered the Veilstone Pokemon Center."; +} +void AutoStory_Checkpoint_0817::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0817(env, context, options, stats); } -std::string AutoStory_Checkpoint_0818::name() const{ return "08.18 - Heal and exit"; } -std::string AutoStory_Checkpoint_0818::start_text() const{ return "Entered the Veilstone Pokemon Center."; } -std::string AutoStory_Checkpoint_0818::end_text() const{ return "Exited the Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_0818::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0818::name() const { + return "08.18 - Heal and exit"; +} +std::string AutoStory_Checkpoint_0818::start_text() const { + return "Entered the Veilstone Pokemon Center."; +} +std::string AutoStory_Checkpoint_0818::end_text() const { + return "Exited the Veilstone Pokemon Center."; +} +void AutoStory_Checkpoint_0818::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0818(env, context, options, stats); } -std::string AutoStory_Checkpoint_0819::name() const{ return "08.19 - Go to house South of Veilstone City"; } -std::string AutoStory_Checkpoint_0819::start_text() const{ return "Exited the Veilstone Pokemon Center."; } -std::string AutoStory_Checkpoint_0819::end_text() const{ return "Arrived at the house South of Veilstone City."; } -void AutoStory_Checkpoint_0819::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0819::name() const { + return "08.19 - Go to house South of Veilstone City"; +} +std::string AutoStory_Checkpoint_0819::start_text() const { + return "Exited the Veilstone Pokemon Center."; +} +std::string AutoStory_Checkpoint_0819::end_text() const { + return "Arrived at the house South of Veilstone City."; +} +void AutoStory_Checkpoint_0819::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0819(env, context, options, stats); } -std::string AutoStory_Checkpoint_0820::name() const{ return "08.20 - Go through the house South of Veilstone City"; } -std::string AutoStory_Checkpoint_0820::start_text() const{ return "Arrived at the house South of Veilstone City."; } -std::string AutoStory_Checkpoint_0820::end_text() const{ return "Exited the house South of Veilstone City."; } -void AutoStory_Checkpoint_0820::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0820::name() const { + return "08.20 - Go through the house South of Veilstone City"; +} +std::string AutoStory_Checkpoint_0820::start_text() const { + return "Arrived at the house South of Veilstone City."; +} +std::string AutoStory_Checkpoint_0820::end_text() const { + return "Exited the house South of Veilstone City."; +} +void AutoStory_Checkpoint_0820::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0820(env, context, options, stats); } -std::string AutoStory_Checkpoint_0821::name() const{ return "08.21 - Go through routes 214 and 222 to the house West of Sunyshore City"; } -std::string AutoStory_Checkpoint_0821::start_text() const{ return "Exited house South of Veilstone City."; } -std::string AutoStory_Checkpoint_0821::end_text() const{ return "Entered the house West of Sunyshore City."; } -void AutoStory_Checkpoint_0821::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0821::name() const { + return "08.21 - Go through routes 214 and 222 to the house West of Sunyshore City"; +} +std::string AutoStory_Checkpoint_0821::start_text() const { + return "Exited house South of Veilstone City."; +} +std::string AutoStory_Checkpoint_0821::end_text() const { + return "Entered the house West of Sunyshore City."; +} +void AutoStory_Checkpoint_0821::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0821(env, context, options, stats); } -std::string AutoStory_Checkpoint_0822::name() const{ return "08.22 - Go through the house West of Sunyshore City"; } -std::string AutoStory_Checkpoint_0822::start_text() const{ return "Entered the house West of Sunyshore City."; } -std::string AutoStory_Checkpoint_0822::end_text() const{ return "Entered Sunyshore City."; } -void AutoStory_Checkpoint_0822::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0822::name() const { + return "08.22 - Go through the house West of Sunyshore City"; +} +std::string AutoStory_Checkpoint_0822::start_text() const { + return "Entered the house West of Sunyshore City."; +} +std::string AutoStory_Checkpoint_0822::end_text() const { + return "Entered Sunyshore City."; +} +void AutoStory_Checkpoint_0822::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0822(env, context, options, stats); } -std::string AutoStory_Checkpoint_0823::name() const{ return "08.23 - Go to Sunyshore City's lighthouse"; } -std::string AutoStory_Checkpoint_0823::start_text() const{ return "Entered Sunyshore City."; } -std::string AutoStory_Checkpoint_0823::end_text() const{ return "Entered Sunyshore City's lighthouse."; } -void AutoStory_Checkpoint_0823::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0823::name() const { + return "08.23 - Go to Sunyshore City's lighthouse"; +} +std::string AutoStory_Checkpoint_0823::start_text() const { + return "Entered Sunyshore City."; +} +std::string AutoStory_Checkpoint_0823::end_text() const { + return "Entered Sunyshore City's lighthouse."; +} +void AutoStory_Checkpoint_0823::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0823(env, context, options, stats); } -std::string AutoStory_Checkpoint_0824::name() const{ return "08.24 - Talk to Volkner at the lighthouse"; } -std::string AutoStory_Checkpoint_0824::start_text() const{ return "Entered Sunyshore City's lighthouse."; } -std::string AutoStory_Checkpoint_0824::end_text() const{ return "Exited Sunyshore City's lighthouse."; } -void AutoStory_Checkpoint_0824::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0824::name() const { + return "08.24 - Talk to Volkner at the lighthouse"; +} +std::string AutoStory_Checkpoint_0824::start_text() const { + return "Entered Sunyshore City's lighthouse."; +} +std::string AutoStory_Checkpoint_0824::end_text() const { + return "Exited Sunyshore City's lighthouse."; +} +void AutoStory_Checkpoint_0824::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0824(env, context, options, stats); } -std::string AutoStory_Checkpoint_0825::name() const{ return "08.25 - Go to the Sunyshore City gym"; } -std::string AutoStory_Checkpoint_0825::start_text() const{ return "Exited Sunyshore City's lighthouse."; } -std::string AutoStory_Checkpoint_0825::end_text() const{ return "Entered Sunyshore City's gym."; } -void AutoStory_Checkpoint_0825::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0825::name() const { + return "08.25 - Go to the Sunyshore City gym"; +} +std::string AutoStory_Checkpoint_0825::start_text() const { + return "Exited Sunyshore City's lighthouse."; +} +std::string AutoStory_Checkpoint_0825::end_text() const { + return "Entered Sunyshore City's gym."; +} +void AutoStory_Checkpoint_0825::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0825(env, context, options, stats); } -std::string AutoStory_Checkpoint_0826::name() const{ return "08.26 - Sunnyshore Gym Puzzle"; } -std::string AutoStory_Checkpoint_0826::start_text() const{ return "Exited Sunyshore City's lighthouse."; } -std::string AutoStory_Checkpoint_0826::end_text() const{ return "Entered Sunyshore City's gym."; } -void AutoStory_Checkpoint_0826::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0826::name() const { + return "08.26 - Sunnyshore Gym Puzzle"; +} +std::string AutoStory_Checkpoint_0826::start_text() const { + return "Exited Sunyshore City's lighthouse."; +} +std::string AutoStory_Checkpoint_0826::end_text() const { + return "Entered Sunyshore City's gym."; +} +void AutoStory_Checkpoint_0826::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0826(env, context, options, stats); } -void AutoStory_Checkpoint_021::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Checkpoint_021::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_021(env, context, options, stats); } -void checkpoint_021( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_021(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 08 gameplay logic (seventh badge area) }, - true - ); + true); } -void checkpoint_021_legendary( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ approach_legendary(env.console, context); enter_legendary_battle(env.console, context); @@ -2966,8 +2840,7 @@ void checkpoint_021_legendary( confirm_legendary_catch(env.console, context); exit_legendary_encounter(env.console, context); }, - true - ); + true); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index 39fff41a6f..5eccc15225 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -8,426 +8,327 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment08_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment08_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_08 : public AutoStory_Segment{ +class AutoStory_Segment_08 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0801 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0801 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0801( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); - -class AutoStory_Checkpoint_0802 : public AutoStory_Checkpoint{ +void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); + +class AutoStory_Checkpoint_0802 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0803 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0803 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0804 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0804 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0805 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0805 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0806 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0806 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0807 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0807 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0808 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0808 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0809 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0809 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0810 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0810 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0811 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0811 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0812 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0812 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0813 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0813 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0814 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0814 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0815 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0815 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0816 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0816 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0817 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0817 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0818 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0818 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0819 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0819 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0820 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0820 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0821 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0821 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0822 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0822 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0823 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0823 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0824 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0824 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0825 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0825 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0825(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0825(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0826 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0826 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0826(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_0826(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; // start: seventh badge acquired // end: legendary encounter (Spear Pillar / Mt. Coronet) -void checkpoint_021( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_021(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); // start: legendary encounter // end: eighth Gym Badge acquired (Sunyshore City) -void checkpoint_021_legendary( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 7e45b5a8b4..8701e21242 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -5,40 +5,46 @@ * */ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" #include "PokemonBDSP/Inference/PokemonBDSP_ShopDetectors.h" -#include "../PokemonBDSP_AutoStory.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "../Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" + #include "PokemonBDSP_AutoStory_Segment_09.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -std::string AutoStory_Segment_09::name() const{ return "09: Eighth Badge to Champion"; } -std::string AutoStory_Segment_09::start_text() const{ return "Start: Eighth Gym Badge acquired (Sunyshore City)."; } -std::string AutoStory_Segment_09::end_text() const{ return "End: Elite Four and Champion defeated, credits roll."; } +std::string AutoStory_Segment_09::name() const { + return "09: Eighth Badge to Champion"; +} +std::string AutoStory_Segment_09::start_text() const { + return "Start: Eighth Gym Badge acquired (Sunyshore City)."; +} +std::string AutoStory_Segment_09::end_text() const { + return "End: Elite Four and Champion defeated, credits roll."; +} -void AutoStory_Segment_09::run_segment( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +void AutoStory_Segment_09::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -63,31 +69,24 @@ void AutoStory_Segment_09::run_segment( env.console.log("End Segment " + name(), COLOR_GREEN); } -static void move_back_after_unexpected_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, + const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); - if (trainerid == "route_223_swimmer_miranda") { + if (trainerid == "route_223_swimmer_miranda"){ pbf_move_left_joystick(context, {0, +1}, 3800ms, 100ms); // 19+ - } else if (trainerid == "route_223_swimmer_crystal") { + }else if (trainerid == "route_223_swimmer_crystal"){ pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ - } else if (trainerid == "route_223_swimmer_gabrielle") { + }else if (trainerid == "route_223_swimmer_gabrielle"){ pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ - } else if (trainerid == "route_223_swimmer_troy") { + }else if (trainerid == "route_223_swimmer_troy"){ pbf_move_left_joystick(context, {0, +1}, 800ms, 100ms); // 3+ } } -static bool handle_battle( - VideoStream& stream, - ProControllerContext& context, - const std::string& trainerid -){ +static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); stream.log("Battling " + trainerid + "...", COLOR_GREEN); BattleMenuWatcher battle_menu(BattleType::TRAINER); @@ -100,60 +99,43 @@ static bool handle_battle( pbf_press_button(context, BUTTON_A, 80ms, 300ms); pbf_wait(context, 500ms); - if ( - trainerid == "victory_road_ace_mariah" or - trainerid == "victory_road_blackbelt_miles" or - trainerid == "victory_road_dragontamer_andre" or - trainerid == "victory_road_veteran_edgar" - ) { + if (trainerid == "victory_road_ace_mariah" or trainerid == "victory_road_blackbelt_miles" or + trainerid == "victory_road_dragontamer_andre" or trainerid == "victory_road_veteran_edgar"){ // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - } else if ( - trainerid == "route_223_swimmer_miranda" or - trainerid == "route_223_swimmer_oscar" or - trainerid == "route_223_swimmer_crystal" or - trainerid == "route_223_swimmer_gabrielle" or - trainerid == "route_223_swimmer_troy" or - trainerid == "victory_road_ace_sydney" or - trainerid == "victory_road_psychic_valencia" - ) { + }else if (trainerid == "route_223_swimmer_miranda" or trainerid == "route_223_swimmer_oscar" or + trainerid == "route_223_swimmer_crystal" or trainerid == "route_223_swimmer_gabrielle" or + trainerid == "route_223_swimmer_troy" or trainerid == "victory_road_ace_sydney" or + trainerid == "victory_road_psychic_valencia"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - } else if ( - trainerid == "victory_road_ace_omar" or - trainerid == "victory_road_veteran_clayton" or - trainerid == "victory_road_dragontamer_clinton" - ) { + }else if (trainerid == "victory_road_ace_omar" or trainerid == "victory_road_veteran_clayton" or + trainerid == "victory_road_dragontamer_clinton"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; } } - + context.wait_for_all_requests(); pbf_mash_button(context, BUTTON_B, 2000ms); stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } -static bool enter_sunyshore_pokemon_center( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerContext& context, + AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -166,13 +148,11 @@ static bool enter_sunyshore_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_sunyshore_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -183,16 +163,14 @@ static bool enter_sunyshore_pokemon_center( return true; } -static bool go_to_victory_road_pokemon_center( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& context, + AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); - stream.log("go_to_victory_road_pokemon_center: going through Victory Road's outdoor and heading to the Pokemon Center..."); + stream.log( + "go_to_victory_road_pokemon_center: going through Victory Road's outdoor and heading to the Pokemon Center..."); // Going to the lady who gives you HM Waterfall @@ -208,19 +186,17 @@ static bool go_to_victory_road_pokemon_center( { MarkDetector jasmine_dialog(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 6800ms, 100ms); // 34+ - }, - {{jasmine_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 6800ms, 100ms); // 34+ + }, + {{jasmine_dialog}}); if (ret == 0){ stream.log("go_to_victory_road_pokemon_center: talking to Jasmine.", COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); pbf_mash_button(context, BUTTON_B, 14000ms); - } else if (ret < 0) { + }else if (ret < 0){ stream.log("go_to_victory_road_pokemon_center: Jasmine dialog box not detected!", COLOR_RED); return false; } @@ -229,12 +205,12 @@ static bool go_to_victory_road_pokemon_center( context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ use_HM(stream, context, "Surf"); context.wait_for_all_requests(); - + activate_repel(stream, context); context.wait_for_all_requests(); @@ -247,29 +223,26 @@ static bool go_to_victory_road_pokemon_center( { MarkDetector route_223_swimmer_miranda_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{route_223_swimmer_miranda_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{route_223_swimmer_miranda_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_miranda: black screen not detected!", COLOR_RED); + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_miranda: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_miranda"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -285,29 +258,26 @@ static bool go_to_victory_road_pokemon_center( { MarkDetector route_223_swimmer_oscar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{route_223_swimmer_oscar_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{route_223_swimmer_oscar_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_oscar: black screen not detected!", COLOR_RED); + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_oscar: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_oscar"); } } @@ -321,29 +291,26 @@ static bool go_to_victory_road_pokemon_center( { MarkDetector route_223_swimmer_crystal_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ - }, - {{route_223_swimmer_crystal_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ + }, + {{route_223_swimmer_crystal_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_crystal: black screen not detected!", COLOR_RED); + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_crystal: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_crystal"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -356,20 +323,18 @@ static bool go_to_victory_road_pokemon_center( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_victory_road_pokemon_center: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center: Didn't detect repel dialog box (1)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; } } @@ -380,10 +345,10 @@ static bool go_to_victory_road_pokemon_center( dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 2); repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {0, +1}, 1600ms, 100ms); // 4++ (getting out of the water) pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ context.wait_for_all_requests(); use_HM(stream, context, "Surf"); @@ -404,35 +369,32 @@ static bool go_to_victory_road_pokemon_center( { MarkDetector route_223_swimmer_gabrielle_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - DpadState dpad; - dpad.last_dir = DPAD_RIGHT; - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - }, - {{route_223_swimmer_gabrielle_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + DpadState dpad; + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + }, + {{route_223_swimmer_gabrielle_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_gabrielle: black screen not detected!", COLOR_RED); + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_gabrielle: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_gabrielle"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -445,7 +407,7 @@ static bool go_to_victory_road_pokemon_center( dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 3); - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ pbf_move_left_joystick(context, {0, +1}, 1400ms, 100ms); // 7+ dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 5); @@ -453,32 +415,29 @@ static bool go_to_victory_road_pokemon_center( { MarkDetector route_223_swimmer_troy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - }, - {{route_223_swimmer_troy_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + }, + {{route_223_swimmer_troy_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_troy: black screen not detected!", COLOR_RED); + stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_troy: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_troy"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -503,20 +462,18 @@ static bool go_to_victory_road_pokemon_center( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_victory_road_pokemon_center: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("go_to_victory_road_pokemon_center: Didn't detect repel dialog box (1)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_to_victory_road_pokemon_center: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; } } @@ -529,13 +486,11 @@ static bool go_to_victory_road_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -546,19 +501,11 @@ static bool go_to_victory_road_pokemon_center( return true; } -static bool heal_and_exit( - VideoStream& stream, - ProControllerContext& context, - const std::string& label -){ +static bool heal_and_exit(VideoStream& stream, ProControllerContext& context, const std::string& label){ return heal_pokemon(stream, context, label); } -static bool enter_victory_road_cave( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool enter_victory_road_cave(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -570,13 +517,11 @@ static bool enter_victory_road_cave( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_victory_road_cave: black screen not detected!", COLOR_RED); return false; @@ -587,11 +532,7 @@ static bool enter_victory_road_cave( return true; } -static bool go_through_victory_road_cave( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool go_through_victory_road_cave(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -604,15 +545,15 @@ static bool go_through_victory_road_cave( pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16+ pbf_move_left_joystick(context, {+1, 0}, 3800ms, 100ms); // 19+ pbf_move_left_joystick(context, {0, +1}, 3000ms, 100ms); // 15+ - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ use_HM(stream, context, "Rock Climb"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 1+ pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); @@ -622,29 +563,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_ace_mariah_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_ace_mariah_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_mariah_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_ace_mariah: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_ace_mariah: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_ace_mariah"); } } @@ -656,20 +594,18 @@ static bool go_through_victory_road_cave( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (1)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; } } @@ -680,13 +616,11 @@ static bool go_through_victory_road_cave( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (1)!", COLOR_RED); return false; @@ -698,14 +632,14 @@ static bool go_through_victory_road_cave( pbf_wait(context, 2000ms); pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ context.wait_for_all_requests(); use_HM(stream, context, "Rock Smash"); context.wait_for_all_requests(); - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ - pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 400ms, 100ms); // 1+ pbf_move_left_joystick(context, {0, -1}, 1200ms, 100ms); // 6+ context.wait_for_all_requests(); @@ -719,29 +653,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_ace_omar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_ace_omar_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_omar_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_ace_omar: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_ace_omar: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_ace_omar"); } } @@ -751,10 +682,10 @@ static bool go_through_victory_road_cave( pbf_wait(context, 2000ms); pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 1++ (move boulders) - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ context.wait_for_all_requests(); use_HM(stream, context, "Rock Smash"); @@ -764,7 +695,7 @@ static bool go_through_victory_road_cave( dpad.last_dir = DPAD_UP; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); pbf_move_left_joystick(context, {+1, 0}, 1500ms, 100ms); // 4++ (move after boulder moving) - pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 2+ context.wait_for_all_requests(); use_HM(stream, context, "Rock Smash"); @@ -777,29 +708,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_ace_sydney_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_ace_sydney_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_sydney_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_ace_sydney: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_ace_sydney: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_ace_sydney"); } } @@ -808,7 +736,7 @@ static bool go_through_victory_road_cave( context.wait_for_all_requests(); pbf_wait(context, 2000ms); - pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {0, +1}, 600ms, 100ms); // 2+ pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ dpad.last_dir = DPAD_RIGHT; @@ -818,29 +746,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_veteran_clayton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_veteran_clayton_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_veteran_clayton_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_veteran_clayton: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_veteran_clayton: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_veteran_clayton"); } } @@ -853,20 +778,18 @@ static bool go_through_victory_road_cave( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (1)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; } } @@ -878,13 +801,11 @@ static bool go_through_victory_road_cave( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (2)!", COLOR_RED); return false; @@ -913,29 +834,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_blackbelt_miles_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_blackbelt_miles_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_blackbelt_miles_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_blackbelt_miles: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_blackbelt_miles: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_blackbelt_miles"); } } @@ -946,13 +864,11 @@ static bool go_through_victory_road_cave( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (3)!", COLOR_RED); return false; @@ -973,34 +889,31 @@ static bool go_through_victory_road_cave( context.wait_for_all_requests(); pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 3+ + pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 3+ { MarkDetector victory_road_psychic_valencia_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_psychic_valencia_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_psychic_valencia_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_psychic_valencia: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_psychic_valencia: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_psychic_valencia"); } } @@ -1018,22 +931,20 @@ static bool go_through_victory_road_cave( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (2)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (2)!", COLOR_RED); + return false; } } @@ -1053,29 +964,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_dragontamer_andre_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_dragontamer_andre_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_dragontamer_andre_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_dragontamer_andre: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_dragontamer_andre: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_dragontamer_andre"); } } @@ -1092,19 +1000,17 @@ static bool go_through_victory_road_cave( context.wait_for_all_requests(); pbf_move_left_joystick(context, {-1, 0}, 1200ms, 100ms); // 6+ pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 17++ (getting out of the water) - pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 600ms, 100ms); // 2+ dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 2); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (4)!", COLOR_RED); return false; @@ -1119,20 +1025,18 @@ static bool go_through_victory_road_cave( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3000ms, 100ms); // 15+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3000ms, 100ms); // 15+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); - } else if (ret < 0){ - stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (2)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("go_through_victory_road_cave: Didn't detect repel dialog box (2)!", COLOR_RED); + return false; } } @@ -1143,29 +1047,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_veteran_edgar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_veteran_edgar_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_veteran_edgar_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_veteran_edgar: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_veteran_edgar: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_veteran_edgar"); } } @@ -1189,29 +1090,26 @@ static bool go_through_victory_road_cave( { MarkDetector victory_road_dragontamer_clinton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_dragontamer_clinton_fight}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_dragontamer_clinton_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}} - ); + ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ - stream.log("go_through_victory_road_cave, victory_road_dragontamer_clinton: black screen not detected!", COLOR_RED); + stream.log("go_through_victory_road_cave, victory_road_dragontamer_clinton: black screen not detected!", + COLOR_RED); return false; - } else if (ret == 0){ + }else if (ret == 0){ handle_battle(stream, context, "victory_road_dragontamer_clinton"); } } @@ -1226,13 +1124,11 @@ static bool go_through_victory_road_cave( { WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{white_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{white_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: white screen not detected!", COLOR_RED); return false; @@ -1243,11 +1139,7 @@ static bool go_through_victory_road_cave( return true; } -static bool enter_elite_4_pokemon_center( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool enter_elite_4_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); //pbf_wait(context, 2000ms); @@ -1271,13 +1163,11 @@ static bool enter_elite_4_pokemon_center( { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_elite_4_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -1288,11 +1178,7 @@ static bool enter_elite_4_pokemon_center( return true; } -static bool ready_in_front_of_elite_4( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool ready_in_front_of_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 6000ms); @@ -1301,20 +1187,18 @@ static bool ready_in_front_of_elite_4( { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until( - stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}} - ); + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("ready_in_front_of_elite_4: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); - } else if (ret < 0){ - stream.log("ready_in_front_of_elite_4: Didn't detect repel dialog box (1)!", COLOR_RED); - return false; + }else if (ret < 0){ + stream.log("ready_in_front_of_elite_4: Didn't detect repel dialog box (1)!", COLOR_RED); + return false; } } @@ -1341,21 +1225,13 @@ static bool ready_in_front_of_elite_4( pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 1+ - buy_items(stream, context, - { - {ShopItem::FullRestore, BUY_MAX} - }, - options.language - ); + buy_items(stream, context, {{ShopItem::FullRestore, BUY_MAX}}, options.language); return true; } -static bool get_into_the_elite_4_entrance( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool get_into_the_elite_4_entrance(VideoStream& stream, ProControllerContext& context, + AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1364,11 +1240,7 @@ static bool get_into_the_elite_4_entrance( return true; } -static bool deal_with_aaron_elite_4( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool deal_with_aaron_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1377,11 +1249,7 @@ static bool deal_with_aaron_elite_4( return true; } -static bool deal_with_bertha_elite_4( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool deal_with_bertha_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1390,11 +1258,7 @@ static bool deal_with_bertha_elite_4( return true; } -static bool deal_with_flint_elite_4( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool deal_with_flint_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1403,11 +1267,7 @@ static bool deal_with_flint_elite_4( return true; } -static bool deal_with_lucian_elite_4( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool deal_with_lucian_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1416,11 +1276,7 @@ static bool deal_with_lucian_elite_4( return true; } -static bool deal_with_cynthia_and_finish( - VideoStream& stream, - ProControllerContext& context, - AutoStoryOptions options -){ +static bool deal_with_cynthia_and_finish(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1429,396 +1285,394 @@ static bool deal_with_cynthia_and_finish( return true; } -void checkpoint_0901( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0901(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_sunyshore_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_sunyshore_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_sunyshore_pokemon_center: transition not detected.", + env.console); } }, - true - ); + true); } -void checkpoint_0902( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0902(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Sunyshore City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_1: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_1: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0903( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0903(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_to_victory_road_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0904( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0904(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Victory Road")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "heal_and_exit_2: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_2: transition not detected.", env.console); } }, - false - ); + false); } -void checkpoint_0905( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0905(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_victory_road_cave(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_victory_road_cave: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_victory_road_cave: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0906( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0906(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!go_through_victory_road_cave(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "go_through_victory_road_cave: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_victory_road_cave: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0907( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0907(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!enter_elite_4_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "enter_elite_4_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_elite_4_pokemon_center: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0908( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0908(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!ready_in_front_of_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "ready_in_front_of_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "ready_in_front_of_elite_4: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0909( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0909(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!get_into_the_elite_4_entrance(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "get_into_the_elite_4_entrance: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_into_the_elite_4_entrance: transition not detected.", + env.console); } }, - true - ); + true); } -void checkpoint_0910( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0910(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_aaron_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_aaron_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_aaron_elite_4: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0911( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0911(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_bertha_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_bertha_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_bertha_elite_4: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0912( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0912(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_flint_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_flint_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_flint_elite_4: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0913( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0913(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_lucian_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_lucian_elite_4: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_lucian_elite_4: transition not detected.", + env.console); } }, - false - ); + false); } -void checkpoint_0914( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -){ - checkpoint_reattempt_loop(env, context, options, stats, +void checkpoint_0914(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, context, options, stats, [&](size_t /*attempt*/){ if (!deal_with_cynthia_and_finish(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "deal_with_cynthia_and_finish: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_cynthia_and_finish: transition not detected.", + env.console); } }, - false - ); -} - -std::string AutoStory_Checkpoint_0901::name() const{ return "09.01 - Leave the gym and fly to the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_0901::start_text() const{ return "Eighth Gym Badge acquired (Sunyshore City)."; } -std::string AutoStory_Checkpoint_0901::end_text() const{ return "Enter the Sunyshore Pokemon Center."; } -void AutoStory_Checkpoint_0901::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ + false); +} + +std::string AutoStory_Checkpoint_0901::name() const { + return "09.01 - Leave the gym and fly to the Sunyshore Pokemon Center."; +} +std::string AutoStory_Checkpoint_0901::start_text() const { + return "Eighth Gym Badge acquired (Sunyshore City)."; +} +std::string AutoStory_Checkpoint_0901::end_text() const { + return "Enter the Sunyshore Pokemon Center."; +} +void AutoStory_Checkpoint_0901::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0901(env, context, options, stats); } -std::string AutoStory_Checkpoint_0902::name() const{ return "09.02 - Heal the Pokemon at the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_0902::start_text() const{ return "Entered the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_0902::end_text() const{ return "Exit the Sunyshore Pokemon Center."; } -void AutoStory_Checkpoint_0902::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0902::name() const { + return "09.02 - Heal the Pokemon at the Sunyshore Pokemon Center."; +} +std::string AutoStory_Checkpoint_0902::start_text() const { + return "Entered the Sunyshore Pokemon Center."; +} +std::string AutoStory_Checkpoint_0902::end_text() const { + return "Exit the Sunyshore Pokemon Center."; +} +void AutoStory_Checkpoint_0902::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0902(env, context, options, stats); } -std::string AutoStory_Checkpoint_0903::name() const{ return "09.03 - Start at the Sunyshore Pokemon Center and go to the Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_0903::start_text() const{ return "Exited the Sunyshore Pokemon Center."; } -std::string AutoStory_Checkpoint_0903::end_text() const{ return "Enter the Victory Road Pokemon Center."; } -void AutoStory_Checkpoint_0903::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0903::name() const { + return "09.03 - Start at the Sunyshore Pokemon Center and go to the Victory Road Pokemon Center."; +} +std::string AutoStory_Checkpoint_0903::start_text() const { + return "Exited the Sunyshore Pokemon Center."; +} +std::string AutoStory_Checkpoint_0903::end_text() const { + return "Enter the Victory Road Pokemon Center."; +} +void AutoStory_Checkpoint_0903::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0903(env, context, options, stats); } -std::string AutoStory_Checkpoint_0904::name() const{ return "09.04 - Heal at the Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_0904::start_text() const{ return "Entered Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_0904::end_text() const{ return "Exit Victory Road Pokemon Center."; } -void AutoStory_Checkpoint_0904::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0904::name() const { + return "09.04 - Heal at the Victory Road Pokemon Center."; +} +std::string AutoStory_Checkpoint_0904::start_text() const { + return "Entered Victory Road Pokemon Center."; +} +std::string AutoStory_Checkpoint_0904::end_text() const { + return "Exit Victory Road Pokemon Center."; +} +void AutoStory_Checkpoint_0904::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0904(env, context, options, stats); } -std::string AutoStory_Checkpoint_0905::name() const{ return "09.05 - Enter the Victory Road cave."; } -std::string AutoStory_Checkpoint_0905::start_text() const{ return "Exited Victory Road Pokemon Center."; } -std::string AutoStory_Checkpoint_0905::end_text() const{ return "Enter the Victory Road cave."; } -void AutoStory_Checkpoint_0905::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0905::name() const { + return "09.05 - Enter the Victory Road cave."; +} +std::string AutoStory_Checkpoint_0905::start_text() const { + return "Exited Victory Road Pokemon Center."; +} +std::string AutoStory_Checkpoint_0905::end_text() const { + return "Enter the Victory Road cave."; +} +void AutoStory_Checkpoint_0905::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0905(env, context, options, stats); } -std::string AutoStory_Checkpoint_0906::name() const{ return "09.06 - Go through the Victory Road cave."; } -std::string AutoStory_Checkpoint_0906::start_text() const{ return "Entered Victory Road cave."; } -std::string AutoStory_Checkpoint_0906::end_text() const{ return "Exit Victory Road cave."; } -void AutoStory_Checkpoint_0906::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0906::name() const { + return "09.06 - Go through the Victory Road cave."; +} +std::string AutoStory_Checkpoint_0906::start_text() const { + return "Entered Victory Road cave."; +} +std::string AutoStory_Checkpoint_0906::end_text() const { + return "Exit Victory Road cave."; +} +void AutoStory_Checkpoint_0906::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0906(env, context, options, stats); } -std::string AutoStory_Checkpoint_0907::name() const{ return "09.07 - Go through the end of Victory Road into the Elite 4 building"; } -std::string AutoStory_Checkpoint_0907::start_text() const{ return "Exited Victory Road cave."; } -std::string AutoStory_Checkpoint_0907::end_text() const{ return "Enter the Elite 4 building."; } -void AutoStory_Checkpoint_0907::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0907::name() const { + return "09.07 - Go through the end of Victory Road into the Elite 4 building"; +} +std::string AutoStory_Checkpoint_0907::start_text() const { + return "Exited Victory Road cave."; +} +std::string AutoStory_Checkpoint_0907::end_text() const { + return "Enter the Elite 4 building."; +} +void AutoStory_Checkpoint_0907::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0907(env, context, options, stats); } -std::string AutoStory_Checkpoint_0908::name() const{ return "09.08 - Enter the Elite 4 building and get ready to enter."; } -std::string AutoStory_Checkpoint_0908::start_text() const{ return "Entered the Elite 4 building."; } -std::string AutoStory_Checkpoint_0908::end_text() const{ return "Ready in front of the Elite 4."; } -void AutoStory_Checkpoint_0908::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0908::name() const { + return "09.08 - Enter the Elite 4 building and get ready to enter."; +} +std::string AutoStory_Checkpoint_0908::start_text() const { + return "Entered the Elite 4 building."; +} +std::string AutoStory_Checkpoint_0908::end_text() const { + return "Ready in front of the Elite 4."; +} +void AutoStory_Checkpoint_0908::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0908(env, context, options, stats); } -std::string AutoStory_Checkpoint_0909::name() const{ return "09.09 - Enter the first room of the Elite 4."; } -std::string AutoStory_Checkpoint_0909::start_text() const{ return "Readied in front of the Elite 4."; } -std::string AutoStory_Checkpoint_0909::end_text() const{ return "Enter the Elite 4's first room."; } -void AutoStory_Checkpoint_0909::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0909::name() const { + return "09.09 - Enter the first room of the Elite 4."; +} +std::string AutoStory_Checkpoint_0909::start_text() const { + return "Readied in front of the Elite 4."; +} +std::string AutoStory_Checkpoint_0909::end_text() const { + return "Enter the Elite 4's first room."; +} +void AutoStory_Checkpoint_0909::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0909(env, context, options, stats); } -std::string AutoStory_Checkpoint_0910::name() const{ return "09.10 - Deal with Aaron and go to the second room."; } -std::string AutoStory_Checkpoint_0910::start_text() const{ return "Entered the Elite 4's first room."; } -std::string AutoStory_Checkpoint_0910::end_text() const{ return "Deal with Aaron and enter the second room."; } -void AutoStory_Checkpoint_0910::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0910::name() const { + return "09.10 - Deal with Aaron and go to the second room."; +} +std::string AutoStory_Checkpoint_0910::start_text() const { + return "Entered the Elite 4's first room."; +} +std::string AutoStory_Checkpoint_0910::end_text() const { + return "Deal with Aaron and enter the second room."; +} +void AutoStory_Checkpoint_0910::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0910(env, context, options, stats); } -std::string AutoStory_Checkpoint_0911::name() const{ return "09.11 - Deal with Bertha and go to the third room."; } -std::string AutoStory_Checkpoint_0911::start_text() const{ return "Dealt with Aaron and entered the second room."; } -std::string AutoStory_Checkpoint_0911::end_text() const{ return "Deal with Bertha and enter the third room."; } -void AutoStory_Checkpoint_0911::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0911::name() const { + return "09.11 - Deal with Bertha and go to the third room."; +} +std::string AutoStory_Checkpoint_0911::start_text() const { + return "Dealt with Aaron and entered the second room."; +} +std::string AutoStory_Checkpoint_0911::end_text() const { + return "Deal with Bertha and enter the third room."; +} +void AutoStory_Checkpoint_0911::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0911(env, context, options, stats); } -std::string AutoStory_Checkpoint_0912::name() const{ return "09.12 - Deal with Flint and go to the fourth room."; } -std::string AutoStory_Checkpoint_0912::start_text() const{ return "Dealt with Bertha and entered the third room."; } -std::string AutoStory_Checkpoint_0912::end_text() const{ return "Deal with Flint and enter the fourth room."; } -void AutoStory_Checkpoint_0912::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0912::name() const { + return "09.12 - Deal with Flint and go to the fourth room."; +} +std::string AutoStory_Checkpoint_0912::start_text() const { + return "Dealt with Bertha and entered the third room."; +} +std::string AutoStory_Checkpoint_0912::end_text() const { + return "Deal with Flint and enter the fourth room."; +} +void AutoStory_Checkpoint_0912::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0912(env, context, options, stats); } -std::string AutoStory_Checkpoint_0913::name() const{ return "09.13 - Deal with Lucian and go to the last room."; } -std::string AutoStory_Checkpoint_0913::start_text() const{ return "Dealt with Flint and entered the fourth room."; } -std::string AutoStory_Checkpoint_0913::end_text() const{ return "Deal with Lucian and enter the last room."; } -void AutoStory_Checkpoint_0913::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0913::name() const { + return "09.13 - Deal with Lucian and go to the last room."; +} +std::string AutoStory_Checkpoint_0913::start_text() const { + return "Dealt with Flint and entered the fourth room."; +} +std::string AutoStory_Checkpoint_0913::end_text() const { + return "Deal with Lucian and enter the last room."; +} +void AutoStory_Checkpoint_0913::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0913(env, context, options, stats); } -std::string AutoStory_Checkpoint_0914::name() const{ return "09.14 - Deal with Cynthia and finish the game."; } -std::string AutoStory_Checkpoint_0914::start_text() const{ return "Dealt with Lucian and entered the last room."; } -std::string AutoStory_Checkpoint_0914::end_text() const{ return "Deal with Cynthia and finish the game."; } -void AutoStory_Checkpoint_0914::run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -) const{ +std::string AutoStory_Checkpoint_0914::name() const { + return "09.14 - Deal with Cynthia and finish the game."; +} +std::string AutoStory_Checkpoint_0914::start_text() const { + return "Dealt with Lucian and entered the last room."; +} +std::string AutoStory_Checkpoint_0914::end_text() const { + return "Deal with Cynthia and finish the game."; +} +void AutoStory_Checkpoint_0914::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const { checkpoint_0914(env, context, options, stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h index 3010af027e..b21fde71ca 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h @@ -8,298 +8,182 @@ #ifndef PokemonAutomation_PokemonBDSP_AutoStory_Segment09_H #define PokemonAutomation_PokemonBDSP_AutoStory_Segment09_H -#include "../PokemonBDSP_AutoStoryTools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -class AutoStory_Segment_09 : public AutoStory_Segment{ +class AutoStory_Segment_09 : public AutoStory_Segment { public: - virtual std::string name() const override; + 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, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -class AutoStory_Checkpoint_0901 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0901 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0901( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0901(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0902 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0902 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0902( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0902(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0903 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0903 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0903( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0903(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0904 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0904 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0904( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0904(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0905 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0905 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0905( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0905(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0906 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0906 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0906( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0906(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0907 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0907 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0907( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0907(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0908 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0908 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0908( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0908(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0909 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0909 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0909( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0909(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0910 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0910 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0910( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0910(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0911 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0911 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0911( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0911(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0912 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0912 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0912( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0912(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0913 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0913 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0913( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0913(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); -class AutoStory_Checkpoint_0914 : public AutoStory_Checkpoint{ +class AutoStory_Checkpoint_0914 : public AutoStory_Checkpoint { public: - virtual std::string name() const override; + virtual std::string name() const override; virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats - ) const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, + AutoStoryOptions options, AutoStoryStats& stats) const override; }; -void checkpoint_0914( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats -); +void checkpoint_0914(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp index 16c75ff79c..cd031880ec 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -4,37 +4,44 @@ * */ +#include #include #include #include -#include #include -#include +#include + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" -#include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/Images/SolidColorTest.h" #include "CommonTools/OCR/OCR_Routines.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "Pokemon/Pokemon_StatsCalculation.h" + #include "Pokemon/Inference/Pokemon_NameReader.h" -#include "VideoGames/PokemonSwSh/Resources/PokemonSwSh_TypeMatchup.h" +#include "Pokemon/Pokemon_StatsCalculation.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleOpponentNameReader.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_ShopItemNameReader.h" #include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.h" #include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" + +#include "VideoGames/PokemonSwSh/Resources/PokemonSwSh_TypeMatchup.h" + #include "PokemonBDSP_AutoStory_Battle.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { using namespace Pokemon; using namespace PokemonSwSh; @@ -45,24 +52,20 @@ using namespace std::chrono_literals; // run_battle_press_A // --------------------------------------------------------------------------- -void run_battle_press_A( - const ProgramInfo& /*info*/, - VideoStream& stream, - ProControllerContext& context -){ +void run_battle_press_A(const ProgramInfo& /*info*/, VideoStream& stream, ProControllerContext& context){ EndBattleWatcher end_battle; - run_until( - stream, context, - [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, - {{end_battle}} - ); + run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{end_battle}}); OverworldWatcher overworld; - run_until( - stream, context, - [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, - {{overworld}} - ); + run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{overworld}}); } @@ -71,31 +74,25 @@ void run_battle_press_A( // --------------------------------------------------------------------------- void handle_unexpected_battle( - const ProgramInfo& info, - VideoStream& stream, - ProControllerContext& context, + const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, std::function&& action, std::function&& recovery_action, - size_t max_attempts -){ + size_t max_attempts){ for (size_t attempt = 0; attempt < max_attempts; attempt++){ StartBattleDetector start_battle(stream.overlay()); - int result = run_until( - stream, context, - [&](ProControllerContext& ctx){ action(info, stream, ctx); }, - {{start_battle}} - ); + int result = run_until(stream, context, + [&](ProControllerContext& ctx){ + action(info, stream, ctx); + }, + {{start_battle}}); if (result < 0){ return; } run_battle_press_A(info, stream, context); recovery_action(info, stream, context); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "handle_unexpected_battle: exceeded max attempts", - stream - ); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "handle_unexpected_battle: exceeded max attempts", stream); } @@ -103,7 +100,7 @@ void handle_unexpected_battle( // Internal helpers for handle_battle_with_outside_help // --------------------------------------------------------------------------- -static constexpr int TORTERRA_ATK = 238; +static constexpr int TORTERRA_ATK = 238; static constexpr int TORTERRA_SPATK = 170; // ⚠️ Estimated PP regions (fight menu, 2×2 move grid); require calibration. @@ -118,7 +115,9 @@ static int ocr_pp_slot(Logger& logger, const ImageViewRGB32& frame, const ImageF ImageViewRGB32 region = extract_box_reference(frame, box); std::string text = OCR::ocr_read(Language::English, region, OCR::PageSegMode::SINGLE_LINE); text.erase(0, text.find_first_not_of(" \t\r\n")); - if (!text.empty()) text.erase(text.find_last_not_of(" \t\r\n") + 1); + if (!text.empty()){ + text.erase(text.find_last_not_of(" \t\r\n") + 1); + } size_t slash = text.find('/'); std::string num_str = (slash != std::string::npos) ? text.substr(0, slash) : text; @@ -129,7 +128,7 @@ static int ocr_pp_slot(Logger& logger, const ImageViewRGB32& frame, const ImageF } try { return std::stoi(num_str.substr(digit_pos)); - } catch (...) { + } catch (...){ logger.log("PP OCR: parse failed \"" + num_str + "\"", COLOR_YELLOW); return -1; } @@ -138,28 +137,31 @@ static int ocr_pp_slot(Logger& logger, const ImageViewRGB32& frame, const ImageF static std::array read_all_pp(Logger& logger, VideoStream& stream){ VideoSnapshot frame = stream.video().snapshot(); std::array result; - for (int i = 0; i < 4; i++) result[i] = ocr_pp_slot(logger, frame, PP_BOX[i]); + for (int i = 0; i < 4; i++){ + result[i] = ocr_pp_slot(logger, frame, PP_BOX[i]); + } return result; } -static std::pair damage_range(int level, int base_power, int atk, int def, double type_mult){ - if (base_power == 0) return {0, 0}; +static std::pair damage_range(int level, int base_power, int atk, int def, double type_mult){ + if (base_power == 0){ + return {0, 0}; + } int base = (((2 * level) / 5 + 2) * base_power * atk / def) / 50 + 2; return {(int)(base * 85.0 / 100.0 * type_mult), (int)(base * type_mult)}; } struct MoveScore { - int lower_sum = 0; - int upper_sum = 0; - bool ohko_all = false; + int lower_sum = 0; + int upper_sum = 0; + bool ohko_all = false; }; -static MoveScore score_move( - const BDSPMoveData& mdata, - const std::vector& on_field, - const BDSPTrainerData& trainer_data -){ - if (mdata.category == MoveCategory::STATUS) return {}; +static MoveScore score_move(const BDSPMoveData& mdata, const std::vector& on_field, + const BDSPTrainerData& trainer_data){ + if (mdata.category == MoveCategory::STATUS){ + return {}; + } int atk_stat = (mdata.category == MoveCategory::PHYSICAL) ? TORTERRA_ATK : TORTERRA_SPATK; @@ -167,37 +169,58 @@ static MoveScore score_move( score.ohko_all = !on_field.empty(); for (const std::string& slug : on_field){ - if (slug.empty()){ score.ohko_all = false; continue; } + if (slug.empty()){ + score.ohko_all = false; + continue; + } const BDSPTrainerPokemon* tp = nullptr; for (const BDSPTrainer& trainer : trainer_data.trainers){ for (const BDSPTrainerPokemon& mon : trainer.pokemon){ - if (mon.species == slug){ tp = &mon; break; } + if (mon.species == slug){ + tp = &mon; + break; + } + } + if (tp){ + break; } - if (tp) break; } - if (!tp){ score.ohko_all = false; continue; } + if (!tp){ + score.ohko_all = false; + continue; + } const BDSPPokemonData& pdata = bdsp_pokemon(slug); - uint16_t hp = calc_stats_hp(pdata.hp, tp->level, tp->ivs.hp, tp->evs.hp); - uint16_t def = (mdata.category == MoveCategory::PHYSICAL) - ? calc_stats_nonhp(pdata.defense, tp->level, tp->ivs.defense, tp->evs.defense, NatureAdjustment::NEUTRAL) - : calc_stats_nonhp(pdata.spdef, tp->level, tp->ivs.spdef, tp->evs.spdef, NatureAdjustment::NEUTRAL); + uint16_t hp = calc_stats_hp(pdata.hp, tp->level, tp->ivs.hp, tp->evs.hp); + uint16_t def = + (mdata.category == MoveCategory::PHYSICAL) + ? calc_stats_nonhp(pdata.defense, tp->level, tp->ivs.defense, tp->evs.defense, + NatureAdjustment::NEUTRAL) + : calc_stats_nonhp(pdata.spdef, tp->level, tp->ivs.spdef, tp->evs.spdef, NatureAdjustment::NEUTRAL); double mult = type_multiplier(mdata.type, pdata.type[0], pdata.type[1]); - if (mdata.spread && trainer_data.battle_type == BDSPBattleType::DOUBLES) mult *= 0.75; + if (mdata.spread && trainer_data.battle_type == BDSPBattleType::DOUBLES){ + mult *= 0.75; + } auto [lo, hi] = damage_range(100, (int)mdata.base_power, atk_stat, (int)def, mult); score.lower_sum += lo; score.upper_sum += hi; - if (lo < (int)hp) score.ohko_all = false; + if (lo < (int)hp){ + score.ohko_all = false; + } } return score; } static void navigate_to_slot(ProControllerContext& context, int slot){ - if (slot == 1 || slot == 3) pbf_press_dpad(context, DPAD_RIGHT, 10ms, 40ms); - if (slot == 2 || slot == 3) pbf_press_dpad(context, DPAD_DOWN, 10ms, 40ms); + if (slot == 1 || slot == 3){ + pbf_press_dpad(context, DPAD_RIGHT, 10ms, 40ms); + } + if (slot == 2 || slot == 3){ + pbf_press_dpad(context, DPAD_DOWN, 10ms, 40ms); + } } @@ -205,26 +228,27 @@ static void navigate_to_slot(ProControllerContext& context, int slot){ // handle_battle_with_outside_help // --------------------------------------------------------------------------- -void handle_battle_with_outside_help( - const ProgramInfo& /*info*/, - VideoStream& stream, - ProControllerContext& context, - const std::string& trainer_id, - const AutoStoryOptions& options -){ +void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& stream, ProControllerContext& context, + const std::string& trainer_id, const AutoStoryOptions& options){ Logger& logger = stream.logger(); const BDSPTrainerData& trainer_data = bdsp_trainer(trainer_id); const OutsideHelpConfig& help = options.outside_help; std::array move_slugs; - for (int i = 0; i < 4; i++) move_slugs[i] = torterra_move_slug(help.attacks[i]); + for (int i = 0; i < 4; i++){ + move_slugs[i] = torterra_move_slug(help.attacks[i]); + } std::array pp_remaining; - for (int i = 0; i < 4; i++) pp_remaining[i] = (int)bdsp_move(move_slugs[i]).pp; + for (int i = 0; i < 4; i++){ + pp_remaining[i] = (int)bdsp_move(move_slugs[i]).pp; + } std::vector on_field; for (const BDSPTrainer& trainer : trainer_data.trainers){ - if (!trainer.pokemon.empty()) on_field.push_back(trainer.pokemon[0].species); + if (!trainer.pokemon.empty()){ + on_field.push_back(trainer.pokemon[0].species); + } } bool battle_active = true; @@ -233,9 +257,12 @@ void handle_battle_with_outside_help( // Wait for the lead Pokemon's battle menu { BattleMenuWatcher menu(BattleType::TRAINER); - EndBattleWatcher end; + EndBattleWatcher end; int r = wait_until(stream, context, 120s, {{menu}, {end}}); - if (r == 1 || r < 0){ battle_active = false; break; } + if (r == 1 || r < 0){ + battle_active = false; + break; + } } // Open Fight menu @@ -247,45 +274,63 @@ void handle_battle_with_outside_help( { auto ocr_pp = read_all_pp(logger, stream); for (int i = 0; i < 4; i++){ - if (ocr_pp[i] >= 0) pp_remaining[i] = ocr_pp[i]; - else logger.log("PP OCR failed slot " + std::to_string(i) + "; using tracked", COLOR_YELLOW); + if (ocr_pp[i] >= 0){ + pp_remaining[i] = ocr_pp[i]; + }else{ + logger.log("PP OCR failed slot " + std::to_string(i) + "; using tracked", COLOR_YELLOW); + } } } // Check for all-zero PP (Struggle) bool all_zero = true; - for (int pp : pp_remaining) if (pp > 0){ all_zero = false; break; } + for (int pp : pp_remaining){ + if (pp > 0){ + all_zero = false; + break; + } + } if (all_zero){ logger.log("All PP at 0 — using Struggle", COLOR_YELLOW); pbf_press_button(context, BUTTON_B, 10ms, 60ms); context.wait_for_all_requests(); - } else { + }else{ // Score all usable moves - int best_slot = -1, best_hi = -1; + int best_slot = -1, best_hi = -1; bool best_ohko = false; for (int i = 0; i < 4; i++){ - if (pp_remaining[i] <= 0) continue; + if (pp_remaining[i] <= 0){ + continue; + } MoveScore s = score_move(bdsp_move(move_slugs[i]), on_field, trainer_data); - if (bdsp_move(move_slugs[i]).category == MoveCategory::STATUS) continue; + if (bdsp_move(move_slugs[i]).category == MoveCategory::STATUS){ + continue; + } - bool prefer = (best_slot < 0) - || (s.ohko_all && !best_ohko) - || (s.ohko_all && best_ohko && s.upper_sum < best_hi) - || (!s.ohko_all && !best_ohko && s.upper_sum > best_hi); + bool prefer = (best_slot < 0) || (s.ohko_all && !best_ohko) || + (s.ohko_all && best_ohko && s.upper_sum < best_hi) || + (!s.ohko_all && !best_ohko && s.upper_sum > best_hi); - if (prefer){ best_slot = i; best_hi = s.upper_sum; best_ohko = s.ohko_all; } + if (prefer){ + best_slot = i; + best_hi = s.upper_sum; + best_ohko = s.ohko_all; + } } if (best_slot < 0){ - for (int i = 0; i < 4; i++) if (pp_remaining[i] > 0){ best_slot = i; break; } + for (int i = 0; i < 4; i++){ + if (pp_remaining[i] > 0){ + best_slot = i; + break; + } + } } - logger.log( - "Move slot " + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ")" - + (best_ohko ? " [OHKO]" : " [best effort]"), - COLOR_WHITE - ); + logger.log("Move slot " + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ")" + + (best_ohko ? " [OHKO]" : " [best effort]"), + COLOR_WHITE); navigate_to_slot(context, best_slot); pbf_mash_button(context, BUTTON_A, 500ms); @@ -293,9 +338,7 @@ void handle_battle_with_outside_help( pp_remaining[best_slot]--; // Single-target in doubles: confirm default target - if (!bdsp_move(move_slugs[best_slot]).spread - && trainer_data.battle_type == BDSPBattleType::DOUBLES) - { + if (!bdsp_move(move_slugs[best_slot]).spread && trainer_data.battle_type == BDSPBattleType::DOUBLES){ pbf_press_button(context, BUTTON_A, 10ms, 60ms); context.wait_for_all_requests(); } @@ -309,32 +352,32 @@ void handle_battle_with_outside_help( { BattleMenuWatcher menu2(BattleType::TRAINER); ExperienceGainWatcher exp; - EndBattleWatcher end; + EndBattleWatcher end; int event = wait_until(stream, context, 60s, {{menu2}, {exp}, {end}}); if (event == 2 || event < 0){ battle_active = false; - } else if (event == 1){ + }else if (event == 1){ // XP gain — mash through, then update on_field via OCR BattleMenuWatcher menu3(BattleType::TRAINER); - EndBattleWatcher end2; - int after = run_until( - stream, context, - [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, - {{menu3}, {end2}} - ); + EndBattleWatcher end2; + int after = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{menu3}, {end2}}); if (after == 1 || after < 0){ battle_active = false; - } else { + }else{ // Update on_field via OCR VideoSnapshot frame = stream.video().snapshot(); - std::vector names = read_battle_opponent_names( - logger, options.language, frame, trainer_data.battle_type - ); + std::vector names = + read_battle_opponent_names(logger, options.language, frame, trainer_data.battle_type); for (size_t i = 0; i < names.size() && i < on_field.size(); i++){ if (!names[i].empty() && names[i] != on_field[i]){ - logger.log("Slot " + std::to_string(i) + ": " + on_field[i] + " → " + names[i], COLOR_WHITE); + logger.log("Slot " + std::to_string(i) + ": " + on_field[i] + " → " + names[i], + COLOR_WHITE); on_field[i] = names[i]; } } @@ -346,11 +389,11 @@ void handle_battle_with_outside_help( // Mash through XP/dialog until overworld OverworldWatcher overworld; - run_until( - stream, context, - [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, - {{overworld}} - ); + run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{overworld}}); } @@ -359,7 +402,7 @@ void handle_battle_with_outside_help( // --------------------------------------------------------------------------- // ⚠️ Estimated player-side HP bar region — calibrate against a real capture. -static const ImageFloatBox PLAYER_HP_BAR {0.057, 0.652, 0.168, 0.008}; +static const ImageFloatBox PLAYER_HP_BAR{0.057, 0.652, 0.168, 0.008}; // ⚠️ Estimated player-side status badge region — calibrate against a real capture. static const ImageFloatBox PLAYER_STATUS_BOX{0.140, 0.710, 0.080, 0.018}; @@ -371,19 +414,23 @@ static const ImageFloatBox BATTLE_BAG_ITEM_NAME_BOX{0.470, 0.150, 0.350, 0.050}; // Scans each pixel column; a column with high colour saturation counts as "HP present". static double read_player_hp_ratio(const ImageViewRGB32& frame){ ImageViewRGB32 strip = extract_box_reference(frame, PLAYER_HP_BAR); - int total = (int)strip.width(); + int total = (int)strip.width(); int mid_row = (int)(strip.height() / 2); - if (total <= 0) return 1.0; + if (total <= 0){ + return 1.0; + } int colored_cols = 0; for (int x = 0; x < total; x++){ uint32_t px = strip.pixel(x, mid_row); uint8_t r = (px >> 16) & 0xFF; - uint8_t g = (px >> 8) & 0xFF; - uint8_t b = px & 0xFF; + uint8_t g = (px >> 8) & 0xFF; + uint8_t b = px & 0xFF; int mx = std::max({(int)r, (int)g, (int)b}); int mn = std::min({(int)r, (int)g, (int)b}); - if (mx - mn > 30) colored_cols++; + if (mx - mn > 30){ + colored_cols++; + } } return (double)colored_cols / total; } @@ -396,14 +443,10 @@ static bool detect_player_status(const ImageViewRGB32& frame){ // Cycle through battle-bag pockets with BUTTON_R until the highlighted item // matches "full-restore", then use it on the first pokemon. // Called while the battle-bag is already open (navigate to Bag from battle menu first). -static void use_full_restore_in_open_bag( - VideoStream& stream, - ProControllerContext& context, - Language language -){ +static void use_full_restore_in_open_bag(VideoStream& stream, ProControllerContext& context, Language language){ Logger& logger = stream.logger(); static constexpr size_t MAX_POCKETS = 9; - static constexpr size_t MAX_SCROLL = 25; + static constexpr size_t MAX_SCROLL = 25; // Cycle pockets until we see Full Restore in the highlighted row bool pocket_found = false; @@ -411,16 +454,16 @@ static void use_full_restore_in_open_bag( context.wait_for(500ms); VideoSnapshot frame = stream.video().snapshot(); ImageViewRGB32 region = extract_box_reference(frame, BATTLE_BAG_ITEM_NAME_BOX); - OCR::StringMatchResult result = ShopItemNameReader::instance().read_substring( - logger, language, region - ); + OCR::StringMatchResult result = ShopItemNameReader::instance().read_substring(logger, language, region); for (const auto& [score, match] : result.results){ if (match.token == "full-restore" && score <= ShopItemNameReader::MAX_LOG10P){ pocket_found = true; break; } } - if (pocket_found) break; + if (pocket_found){ + break; + } pbf_press_button(context, BUTTON_R, 10ms, 400ms); } @@ -433,9 +476,7 @@ static void use_full_restore_in_open_bag( context.wait_for(400ms); VideoSnapshot frame = stream.video().snapshot(); ImageViewRGB32 region = extract_box_reference(frame, BATTLE_BAG_ITEM_NAME_BOX); - OCR::StringMatchResult result = ShopItemNameReader::instance().read_substring( - logger, language, region - ); + OCR::StringMatchResult result = ShopItemNameReader::instance().read_substring(logger, language, region); bool found = false; for (const auto& [score, match] : result.results){ if (match.token == "full-restore" && score <= ShopItemNameReader::MAX_LOG10P){ @@ -464,17 +505,13 @@ static void use_full_restore_in_open_bag( // Pokemon(bottom-left) Run(bottom-right). Cursor starts at Fight. static void open_battle_bag(ProControllerContext& context){ pbf_press_dpad(context, DPAD_RIGHT, 10ms, 200ms); - pbf_press_button(context, BUTTON_A, 10ms, 800ms); + pbf_press_button(context, BUTTON_A, 10ms, 800ms); context.wait_for(500ms); } // Use a Full Restore on the first party pokemon from the battle menu. // Returns immediately after item use; the caller is responsible for resuming the turn. -static void use_full_restore_in_battle( - VideoStream& stream, - ProControllerContext& context, - Language language -){ +static void use_full_restore_in_battle(VideoStream& stream, ProControllerContext& context, Language language){ open_battle_bag(context); use_full_restore_in_open_bag(stream, context, language); // Mash B to ensure we're back at the battle menu @@ -484,30 +521,32 @@ static void use_full_restore_in_battle( // OCR the opponent name box and restrict matches to `candidates`. // Returns the best matching slug in candidates, or the best unrestricted match // if no candidate qualifies. -static std::string ocr_opponent_restricted( - Logger& logger, - Language language, - const ImageViewRGB32& frame, - const ImageFloatBox& box, - const std::vector& candidates -){ +static std::string ocr_opponent_restricted(Logger& logger, Language language, const ImageViewRGB32& frame, + const ImageFloatBox& box, const std::vector& candidates){ using namespace Pokemon; ImageViewRGB32 cropped = extract_box_reference(frame, box); - OCR::StringMatchResult result = PokemonNameReader::instance().read_substring( - logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS() - ); - if (result.results.empty()) return ""; + OCR::StringMatchResult result = + PokemonNameReader::instance().read_substring(logger, language, cropped, OCR::BLACK_OR_WHITE_TEXT_FILTERS()); + if (result.results.empty()){ + return ""; + } // Prefer the best candidate-restricted match for (const auto& [score, match] : result.results){ - if (score > PokemonNameReader::MAX_LOG10P) break; + if (score > PokemonNameReader::MAX_LOG10P){ + break; + } for (const std::string& c : candidates){ - if (match.token == c) return c; + if (match.token == c){ + return c; + } } } // Fallback: best unrestricted match (if it meets threshold) const auto& best = result.results.begin(); - if (best->first <= PokemonNameReader::MAX_LOG10P) return best->second.token; + if (best->first <= PokemonNameReader::MAX_LOG10P){ + return best->second.token; + } return ""; } @@ -526,32 +565,34 @@ struct BattlePlan { // Returns the Intimidate attack-stage multiplier for `count` Intimidate activations. // Stage formula: 2 / (2 + count), capped at 6 drops. static double intimidate_atk_mult(int count){ - if (count <= 0) return 1.0; + if (count <= 0){ + return 1.0; + } int capped = std::min(count, 6); return 2.0 / (2.0 + capped); } // Score move `slot` (0-3) against a single opponent pokemon. // Returns {lower_damage, upper_damage, is_ohko}. -static std::tuple score_move_expert( - const BDSPMoveData& mdata, - const BDSPTrainerPokemon& tp, - int effective_atk, - int effective_spatk, - bool doubles -){ - if (mdata.category == MoveCategory::STATUS) return {0, 0, false}; +static std::tuple score_move_expert(const BDSPMoveData& mdata, const BDSPTrainerPokemon& tp, + int effective_atk, int effective_spatk, bool doubles){ + if (mdata.category == MoveCategory::STATUS){ + return {0, 0, false}; + } const BDSPPokemonData& pdata = bdsp_pokemon(tp.species); - uint16_t hp = calc_stats_hp(pdata.hp, tp.level, tp.ivs.hp, tp.evs.hp); + uint16_t hp = calc_stats_hp(pdata.hp, tp.level, tp.ivs.hp, tp.evs.hp); int atk_stat = (mdata.category == MoveCategory::PHYSICAL) ? effective_atk : effective_spatk; - uint16_t def = (mdata.category == MoveCategory::PHYSICAL) - ? calc_stats_nonhp(pdata.defense, tp.level, tp.ivs.defense, tp.evs.defense, NatureAdjustment::NEUTRAL) - : calc_stats_nonhp(pdata.spdef, tp.level, tp.ivs.spdef, tp.evs.spdef, NatureAdjustment::NEUTRAL); + uint16_t def = + (mdata.category == MoveCategory::PHYSICAL) + ? calc_stats_nonhp(pdata.defense, tp.level, tp.ivs.defense, tp.evs.defense, NatureAdjustment::NEUTRAL) + : calc_stats_nonhp(pdata.spdef, tp.level, tp.ivs.spdef, tp.evs.spdef, NatureAdjustment::NEUTRAL); double mult = type_multiplier(mdata.type, pdata.type[0], pdata.type[1]); - if (mdata.spread && doubles) mult *= 0.75; + if (mdata.spread && doubles){ + mult *= 0.75; + } // Attacker level is always 100 (helper Pokemon); defender level is tp.level auto [lo, hi] = damage_range(100, (int)mdata.base_power, atk_stat, (int)def, mult); @@ -559,15 +600,9 @@ static std::tuple score_move_expert( return {lo, hi, ohko}; } -static BattlePlan build_expert_plan( - Logger& logger, - const std::array& move_slugs, - const std::array& current_pp, - const std::vector& all_trainers, - Trainer current_trainer, - int base_atk, - int base_spatk -){ +static BattlePlan build_expert_plan(Logger& logger, const std::array& move_slugs, + const std::array& current_pp, const std::vector& all_trainers, + Trainer current_trainer, int base_atk, int base_spatk){ // Running PP budget across the full sequence std::array pp = current_pp; @@ -581,7 +616,7 @@ static BattlePlan build_expert_plan( for (Trainer t : all_trainers){ const BDSPTrainerData& tdata = bdsp_trainer(trainer_slug(t)); bool is_current = (t == current_trainer); - bool doubles = (tdata.battle_type == BDSPBattleType::DOUBLES); + bool doubles = (tdata.battle_type == BDSPBattleType::DOUBLES); for (const BDSPTrainer& side : tdata.trainers){ // Check first-out pokemon for Intimidate (activates before we attack) @@ -590,20 +625,24 @@ static BattlePlan build_expert_plan( } } - int eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); - int eff_spatk = base_spatk; // Intimidate doesn't affect SpAtk + int eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); + int eff_spatk = base_spatk; // Intimidate doesn't affect SpAtk for (const BDSPTrainer& side : tdata.trainers){ for (const BDSPTrainerPokemon& tp : side.pokemon){ // Find best move for this pokemon - int best_slot = -1; - int best_hi = -1; + int best_slot = -1; + int best_hi = -1; bool best_ohko = false; for (int i = 0; i < 4; i++){ - if (pp[i] <= 0) continue; + if (pp[i] <= 0){ + continue; + } const BDSPMoveData& mdata = bdsp_move(move_slugs[i]); - if (mdata.category == MoveCategory::STATUS) continue; + if (mdata.category == MoveCategory::STATUS){ + continue; + } auto [lo, hi, ohko] = score_move_expert(mdata, tp, eff_atk, eff_spatk, doubles); (void)lo; @@ -612,21 +651,18 @@ static BattlePlan build_expert_plan( uint8_t acc = mdata.accuracy; if (acc > 0 && acc < 100){ int adj = 100 - 2 * (100 - acc); - logger.log( - move_slugs[i] + " vs " + tp.species + ": accuracy=" + - std::to_string(acc) + "% (adjusted=" + std::to_string(adj) + "%)", - COLOR_YELLOW - ); + logger.log(move_slugs[i] + " vs " + tp.species + ": accuracy=" + std::to_string(acc) + + "% (adjusted=" + std::to_string(adj) + "%)", + COLOR_YELLOW); } - bool prefer = (best_slot < 0) - || (ohko && !best_ohko) // new OHKO > non-OHKO - || (ohko && best_ohko && hi < best_hi) // both OHKO: lower overkill - || (!ohko && !best_ohko && hi > best_hi); // no OHKO: higher damage + bool prefer = (best_slot < 0) || (ohko && !best_ohko) // new OHKO > non-OHKO + || (ohko && best_ohko && hi < best_hi) // both OHKO: lower overkill + || (!ohko && !best_ohko && hi > best_hi); // no OHKO: higher damage if (prefer){ best_slot = i; - best_hi = hi; + best_hi = hi; best_ohko = ohko; } } @@ -634,15 +670,12 @@ static BattlePlan build_expert_plan( if (best_slot < 0){ // All PP at 0 — Struggle will be used in practice logger.log("PP exhausted for " + tp.species + " — Struggle expected", COLOR_YELLOW); - } else { + }else{ pp[best_slot]--; - logger.log( - std::string(is_current ? "[plan] " : "[pre-plan] ") + - tp.species + " → slot " + std::to_string(best_slot) + - " (" + move_slugs[best_slot] + ")" + - (best_ohko ? " [OHKO]" : " [best effort, hi=" + std::to_string(best_hi) + "]"), - COLOR_WHITE - ); + logger.log(std::string(is_current ? "[plan] " : "[pre-plan] ") + tp.species + " → slot " + + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ")" + + (best_ohko ? " [OHKO]" : " [best effort, hi=" + std::to_string(best_hi) + "]"), + COLOR_WHITE); if (is_current){ // Only override if this species hasn't been planned yet // (in case the same species appears twice — first occurrence wins) @@ -671,38 +704,33 @@ static BattlePlan build_expert_plan( // --------------------------------------------------------------------------- // Snapshot the current frame, OCR on-field names, and update `on_field`. -static void update_on_field( - Logger& logger, - Language language, - VideoStream& stream, - BDSPBattleType battle_type, - const std::vector& candidates, - std::vector& on_field -){ +static void update_on_field(Logger& logger, Language language, VideoStream& stream, BDSPBattleType battle_type, + const std::vector& candidates, std::vector& on_field){ VideoSnapshot frame = stream.video().snapshot(); if (battle_type == BDSPBattleType::SINGLES){ static const ImageFloatBox BOX(0.505, 0.010, 0.420, 0.085); std::string name = ocr_opponent_restricted(logger, language, frame, BOX, candidates); - if (!name.empty() && !on_field.empty()) on_field[0] = name; - } else { + if (!name.empty() && !on_field.empty()){ + on_field[0] = name; + } + }else{ static const ImageFloatBox BOX_L(0.505, 0.010, 0.200, 0.085); static const ImageFloatBox BOX_R(0.710, 0.010, 0.260, 0.085); std::string l = ocr_opponent_restricted(logger, language, frame, BOX_L, candidates); std::string r = ocr_opponent_restricted(logger, language, frame, BOX_R, candidates); - if (!l.empty() && on_field.size() > 0) on_field[0] = l; - if (!r.empty() && on_field.size() > 1) on_field[1] = r; + if (!l.empty() && on_field.size() > 0){ + on_field[0] = l; + } + if (!r.empty() && on_field.size() > 1){ + on_field[1] = r; + } } } -void handle_expert_battle( - const ProgramInfo& /*info*/, - VideoStream& stream, - ProControllerContext& context, - const std::vector& all_trainers, - Trainer current_trainer, - const AutoStoryOptions& options -){ +void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProControllerContext& context, + const std::vector& all_trainers, Trainer current_trainer, + const AutoStoryOptions& options){ using namespace Pokemon; Logger& logger = stream.logger(); @@ -712,12 +740,14 @@ void handle_expert_battle( // Move slugs for the helper's 4 moves std::array move_slugs; - for (int i = 0; i < 4; i++) move_slugs[i] = torterra_move_slug(help.attacks[i]); + for (int i = 0; i < 4; i++){ + move_slugs[i] = torterra_move_slug(help.attacks[i]); + } // Helper stats — 0IV/0EV at level 100, neutral nature const BDSPPokemonData& helper_base = bdsp_pokemon("torterra"); - int base_atk = (int)calc_stats_nonhp(helper_base.attack, 100, 0, 0, NatureAdjustment::NEUTRAL); - int base_spatk = (int)calc_stats_nonhp(helper_base.spatk, 100, 0, 0, NatureAdjustment::NEUTRAL); + int base_atk = (int)calc_stats_nonhp(helper_base.attack, 100, 0, 0, NatureAdjustment::NEUTRAL); + int base_spatk = (int)calc_stats_nonhp(helper_base.spatk, 100, 0, 0, NatureAdjustment::NEUTRAL); // Candidate species slugs for restricted OCR (all pokemon in current trainer's party) std::vector candidates; @@ -729,7 +759,9 @@ void handle_expert_battle( // PP tracking — initialised to max PP from JSON; OCR updates each turn std::array pp_remaining; - for (int i = 0; i < 4; i++) pp_remaining[i] = (int)bdsp_move(move_slugs[i]).pp; + for (int i = 0; i < 4; i++){ + pp_remaining[i] = (int)bdsp_move(move_slugs[i]).pp; + } // Intimidate tracking across turns int intimidate_count = 0; @@ -749,9 +781,11 @@ void handle_expert_battle( bool battle_active = true; { BattleMenuWatcher menu(BattleType::TRAINER); - EndBattleWatcher end; + EndBattleWatcher end; int r = wait_until(stream, context, 60s, {{menu}, {end}}); - if (r != 0 || r < 0) battle_active = false; + if (r != 0 || r < 0){ + battle_active = false; + } } // ── Main battle loop ────────────────────────────────────────────────────── @@ -763,14 +797,12 @@ void handle_expert_battle( // 1. Check HP / status → use Full Restore if needed { VideoSnapshot frame = stream.video().snapshot(); - double hp = read_player_hp_ratio(frame); - bool has_status = detect_player_status(frame); + double hp = read_player_hp_ratio(frame); + bool has_status = detect_player_status(frame); if (hp < 0.33 || has_status){ - logger.log( - "Full Restore needed (HP=" + std::to_string((int)(hp * 100)) + - "%, status=" + (has_status ? "yes" : "no") + ")", - COLOR_WHITE - ); + logger.log("Full Restore needed (HP=" + std::to_string((int)(hp * 100)) + + "%, status=" + (has_status ? "yes" : "no") + ")", + COLOR_WHITE); use_full_restore_in_battle(stream, context, options.language); } } @@ -782,18 +814,18 @@ void handle_expert_battle( { auto ocr_pp = read_all_pp(logger, stream); for (int i = 0; i < 4; i++){ - if (ocr_pp[i] >= 0) pp_remaining[i] = ocr_pp[i]; - else logger.log("PP OCR failed slot " + std::to_string(i), COLOR_YELLOW); + if (ocr_pp[i] >= 0){ + pp_remaining[i] = ocr_pp[i]; + }else{ + logger.log("PP OCR failed slot " + std::to_string(i), COLOR_YELLOW); + } } } // 3. Build plan on first turn (after we have real PP values from OCR) if (!plan_built){ - plan = build_expert_plan( - logger, move_slugs, pp_remaining, - all_trainers, current_trainer, - base_atk, base_spatk - ); + plan = build_expert_plan(logger, move_slugs, pp_remaining, all_trainers, current_trainer, base_atk, + base_spatk); plan_built = true; } @@ -805,19 +837,18 @@ void handle_expert_battle( { int eff_atk = base_atk; for (const std::string& slug : on_field){ - if (slug.empty() || intimidate_seen.count(slug)) continue; + if (slug.empty() || intimidate_seen.count(slug)){ + continue; + } for (const BDSPTrainer& side : trainer_data.trainers){ for (const BDSPTrainerPokemon& tp : side.pokemon){ if (tp.species == slug && tp.has_intimidate){ intimidate_count++; intimidate_seen.insert(slug); eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); - logger.log( - slug + " has Intimidate — ATK stage -" + - std::to_string(intimidate_count) + - " (effective ATK=" + std::to_string(eff_atk) + ")", - COLOR_YELLOW - ); + logger.log(slug + " has Intimidate — ATK stage -" + std::to_string(intimidate_count) + + " (effective ATK=" + std::to_string(eff_atk) + ")", + COLOR_YELLOW); } } } @@ -828,45 +859,58 @@ void handle_expert_battle( int best_slot = -1; if (!on_field.empty() && !on_field[0].empty()){ auto it = plan.move_for_species.find(on_field[0]); - if (it != plan.move_for_species.end() && pp_remaining[it->second] > 0) + if (it != plan.move_for_species.end() && pp_remaining[it->second] > 0){ best_slot = it->second; + } } if (best_slot < 0){ - logger.log("Plan miss for " + (on_field.empty() ? "?" : on_field[0]) + - " — live scoring", COLOR_YELLOW); - int eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); + logger.log("Plan miss for " + (on_field.empty() ? "?" : on_field[0]) + " — live scoring", COLOR_YELLOW); + int eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); int eff_spatk = base_spatk; - int best_hi = -1; + int best_hi = -1; bool best_ohko = false; for (int i = 0; i < 4; i++){ - if (pp_remaining[i] <= 0) continue; + if (pp_remaining[i] <= 0){ + continue; + } const BDSPMoveData& mdata = bdsp_move(move_slugs[i]); - if (mdata.category == MoveCategory::STATUS) continue; + if (mdata.category == MoveCategory::STATUS){ + continue; + } - int hi = 0; bool ohko = false; + int hi = 0; + bool ohko = false; if (!on_field.empty() && !on_field[0].empty()){ for (const BDSPTrainer& side : trainer_data.trainers){ for (const BDSPTrainerPokemon& tp : side.pokemon){ - if (tp.species != on_field[0]) continue; + if (tp.species != on_field[0]){ + continue; + } auto [lo, h, ok] = score_move_expert(mdata, tp, eff_atk, eff_spatk, doubles); - hi = h; ohko = ok; + hi = h; + ohko = ok; } } } - bool prefer = (best_slot < 0) - || (ohko && !best_ohko) - || (ohko && best_ohko && hi < best_hi) - || (!ohko && !best_ohko && hi > best_hi); + bool prefer = (best_slot < 0) || (ohko && !best_ohko) || (ohko && best_ohko && hi < best_hi) || + (!ohko && !best_ohko && hi > best_hi); - if (prefer){ best_slot = i; best_hi = hi; best_ohko = ohko; } + if (prefer){ + best_slot = i; + best_hi = hi; + best_ohko = ohko; + } } // Last resort: first move with any PP remaining if (best_slot < 0){ for (int i = 0; i < 4; i++){ - if (pp_remaining[i] > 0){ best_slot = i; break; } + if (pp_remaining[i] > 0){ + best_slot = i; + break; + } } } } @@ -876,13 +920,10 @@ void handle_expert_battle( logger.log("All PP at 0 — Struggle", COLOR_YELLOW); pbf_press_button(context, BUTTON_B, 10ms, 60ms); // close fight menu context.wait_for_all_requests(); - } else { - logger.log( - "Move slot " + std::to_string(best_slot) + - " (" + move_slugs[best_slot] + ") vs " + - (on_field.empty() ? "?" : on_field[0]), - COLOR_WHITE - ); + }else{ + logger.log("Move slot " + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ") vs " + + (on_field.empty() ? "?" : on_field[0]), + COLOR_WHITE); navigate_to_slot(context, best_slot); pbf_mash_button(context, BUTTON_A, 500ms); context.wait_for_all_requests(); @@ -900,31 +941,30 @@ void handle_expert_battle( // 8. Wait for next event: next battle menu, XP gain, or battle end { - BattleMenuWatcher menu2(BattleType::TRAINER); + BattleMenuWatcher menu2(BattleType::TRAINER); ExperienceGainWatcher exp; - EndBattleWatcher end; + EndBattleWatcher end; int event = wait_until(stream, context, 60s, {{menu2}, {exp}, {end}}); if (event == 2 || event < 0){ // Battle over break; - } else if (event == 1){ + }else if (event == 1){ // XP gain — mash until the NEXT battle menu appears (or battle ends) BattleMenuWatcher menu3(BattleType::TRAINER); - EndBattleWatcher end2; - int after = run_until( - stream, context, - [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, - {{menu3}, {end2}} - ); + EndBattleWatcher end2; + int after = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{menu3}, {end2}}); if (after != 0){ // Battle ended during XP screens break; } // Update on-field now that the next pokemon is visible - update_on_field(logger, options.language, stream, - trainer_data.battle_type, candidates, on_field); + update_on_field(logger, options.language, stream, trainer_data.battle_type, candidates, on_field); for (size_t i = 0; i < on_field.size(); i++){ logger.log("On-field[" + std::to_string(i) + "] = " + on_field[i], COLOR_WHITE); } @@ -937,11 +977,11 @@ void handle_expert_battle( // Mash through any remaining XP/dialog until we're back in the overworld OverworldWatcher overworld; - run_until( - stream, context, - [](ProControllerContext& ctx){ pbf_mash_button(ctx, BUTTON_A, 120000ms); }, - {{overworld}} - ); + run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{overworld}}); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h index 76850c624d..2409113f20 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h @@ -17,14 +17,17 @@ #include #include #include + #include "CommonFramework/Tools/VideoStream.h" + #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" + #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation{ - struct ProgramInfo; -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +struct ProgramInfo; +namespace NintendoSwitch { +namespace PokemonBDSP { // --------------------------------------------------------------------------- @@ -35,11 +38,7 @@ namespace PokemonBDSP{ // This does not attempt to catch — it simply presses A to select the first move // repeatedly until the battle ends. // After the black-screen transition and experience gain, returns to the caller. -void run_battle_press_A( - const ProgramInfo& info, - VideoStream& stream, - ProControllerContext& context -); +void run_battle_press_A(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context); // --------------------------------------------------------------------------- @@ -53,13 +52,10 @@ void run_battle_press_A( // The whole sequence retries up to `max_attempts` times. // Throws OperationFailedException if max_attempts is exceeded. void handle_unexpected_battle( - const ProgramInfo& info, - VideoStream& stream, - ProControllerContext& context, + const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, std::function&& action, std::function&& recovery_action, - size_t max_attempts = 5 -); + size_t max_attempts = 5); // --------------------------------------------------------------------------- @@ -75,15 +71,8 @@ void handle_unexpected_battle( // 4. Mashes A to cover target selection and partner's move. // 5. After XP gain, updates on-field opponents via OCR. // Returns when the battle ends and the player is back in the overworld. -void handle_battle_with_outside_help( - const ProgramInfo& info, - VideoStream& stream, - ProControllerContext& context, - const std::string& trainer_id, - const AutoStoryOptions& options -); - - +void handle_battle_with_outside_help(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, + const std::string& trainer_id, const AutoStoryOptions& options); // --------------------------------------------------------------------------- @@ -104,14 +93,9 @@ void handle_battle_with_outside_help( // 5. Selects the pre-planned optimal move and executes it. // 6. After XP gain, OCRs the new opponent and loops. // Returns when the battle ends and the player is back in the overworld. -void handle_expert_battle( - const ProgramInfo& info, - VideoStream& stream, - ProControllerContext& context, - const std::vector& all_trainers, - Trainer current_trainer, - const AutoStoryOptions& options -); +void handle_expert_battle(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, + const std::vector& all_trainers, Trainer current_trainer, + const AutoStoryOptions& options); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp index 99d341353f..5d1fde2c18 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.cpp @@ -5,37 +5,37 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" + #include "CommonTools/Async/InferenceRoutines.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" + #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" + #include "PokemonBDSP_AutoStory_Dialog.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { -void clear_dialog( - VideoStream& stream, - ProControllerContext& context, - ClearDialogMode mode, - uint16_t timeout_sec -){ +void clear_dialog(VideoStream& stream, ProControllerContext& context, ClearDialogMode mode, uint16_t timeout_sec){ // TODO: implement // - use ShortDialogWatcher + (optionally) ShortDialogPromptDetector // + OverworldDetector (from Detect/) with run_until<> // - loop mashing A until stop condition fires - (void)stream; (void)context; (void)mode; (void)timeout_sec; + (void)stream; + (void)context; + (void)mode; + (void)timeout_sec; } -void wait_for_dialog( - VideoStream& stream, - ProControllerContext& context, - uint16_t timeout_sec -){ +void wait_for_dialog(VideoStream& stream, ProControllerContext& context, uint16_t timeout_sec){ // TODO: implement // - use wait_until() with ShortDialogWatcher - (void)stream; (void)context; (void)timeout_sec; + (void)stream; + (void)context; + (void)timeout_sec; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h index 750a40e3a4..50fc7d7204 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Dialog.h @@ -13,20 +13,22 @@ #define PokemonAutomation_PokemonBDSP_AutoStory_Dialog_H #include + #include "CommonFramework/Tools/VideoStream.h" + #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" -namespace PokemonAutomation{ - struct ProgramInfo; -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +struct ProgramInfo; +namespace NintendoSwitch { +namespace PokemonBDSP { // --------------------------------------------------------------------------- // Stop conditions for clear_dialog() // --------------------------------------------------------------------------- -enum class ClearDialogMode{ +enum class ClearDialogMode { // Stop once the dialog has closed and the overworld is detected. STOP_OVERWORLD, @@ -46,20 +48,11 @@ enum class ClearDialogMode{ // Uses ShortDialogWatcher as the primary "still in dialog" signal. // Throws OperationFailedException if no dialog is ever detected, or if // the stop condition is not met within `timeout_sec` seconds. -void clear_dialog( - VideoStream& stream, - ProControllerContext& context, - ClearDialogMode mode, - uint16_t timeout_sec = 60 -); +void clear_dialog(VideoStream& stream, ProControllerContext& context, ClearDialogMode mode, uint16_t timeout_sec = 60); // Block until a short dialog box appears, then return. // Throws OperationFailedException if no dialog appears within `timeout_sec`. -void wait_for_dialog( - VideoStream& stream, - ProControllerContext& context, - uint16_t timeout_sec = 30 -); +void wait_for_dialog(VideoStream& stream, ProControllerContext& context, uint16_t timeout_sec = 30); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp index 6ecf21f39e..0456f9fc6e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp @@ -5,48 +5,46 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" + #include "CommonTools/Async/InferenceRoutines.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_StartBattleDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" + #include "PokemonBDSP_AutoStory_Navigation.h" -namespace PokemonAutomation{ -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { void walk_steps(ProControllerContext& context, uint16_t direction, uint16_t steps){ // TODO: implement — press D-pad `direction` for `steps` tiles - (void)context; (void)direction; (void)steps; + (void)context; + (void)direction; + (void)steps; } -void walk_until_dialog( - VideoStream& stream, - ProControllerContext& context, - uint16_t direction, - uint16_t timeout_sec -){ +void walk_until_dialog(VideoStream& stream, ProControllerContext& context, uint16_t direction, uint16_t timeout_sec){ // TODO: implement — walk in direction; stop when ShortDialogWatcher fires - (void)stream; (void)context; (void)direction; (void)timeout_sec; + (void)stream; + (void)context; + (void)direction; + (void)timeout_sec; } -void walk_until_battle( - VideoStream& stream, - ProControllerContext& context, - uint16_t direction, - uint16_t timeout_sec -){ +void walk_until_battle(VideoStream& stream, ProControllerContext& context, uint16_t direction, uint16_t timeout_sec){ // TODO: implement — walk in direction; stop when StartBattleDetector fires - (void)stream; (void)context; (void)direction; (void)timeout_sec; + (void)stream; + (void)context; + (void)direction; + (void)timeout_sec; } -void recalibrate_after_battle( - const ProgramInfo& /*info*/, - VideoStream& /*stream*/, - ProControllerContext& context, - std::function&& recovery_action -){ +void recalibrate_after_battle(const ProgramInfo& /*info*/, VideoStream& /*stream*/, ProControllerContext& context, + std::function&& recovery_action){ // TODO: implement — wait for overworld, then run recovery_action recovery_action(context); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h index 186f1c01d4..150e5283ef 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h @@ -12,13 +12,15 @@ #define PokemonAutomation_PokemonBDSP_AutoStory_Navigation_H #include + #include "CommonFramework/Tools/VideoStream.h" + #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" -namespace PokemonAutomation{ - struct ProgramInfo; -namespace NintendoSwitch{ -namespace PokemonBDSP{ +namespace PokemonAutomation { +struct ProgramInfo; +namespace NintendoSwitch { +namespace PokemonBDSP { // --------------------------------------------------------------------------- @@ -36,22 +38,14 @@ void walk_steps(ProControllerContext& context, uint16_t direction, uint16_t step // Walk in `direction` until a short dialog box is detected, or `timeout_sec` elapses. // Throws OperationFailedException on timeout. -void walk_until_dialog( - VideoStream& stream, - ProControllerContext& context, - uint16_t direction, - uint16_t timeout_sec = 30 -); +void walk_until_dialog(VideoStream& stream, ProControllerContext& context, uint16_t direction, + uint16_t timeout_sec = 30); // Walk in `direction` until a battle starts (StartBattleDetector fires), // or `timeout_sec` elapses. // Throws OperationFailedException on timeout. -void walk_until_battle( - VideoStream& stream, - ProControllerContext& context, - uint16_t direction, - uint16_t timeout_sec = 30 -); +void walk_until_battle(VideoStream& stream, ProControllerContext& context, uint16_t direction, + uint16_t timeout_sec = 30); // --------------------------------------------------------------------------- @@ -68,12 +62,8 @@ void walk_until_battle( // on the calling segment and is passed as a lambda. // `recovery_action` should move the player from wherever they ended up back // to a defined starting tile for the current checkpoint. -void recalibrate_after_battle( - const ProgramInfo& info, - VideoStream& stream, - ProControllerContext& context, - std::function&& recovery_action -); +void recalibrate_after_battle(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, + std::function&& recovery_action); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index 9c6cdf9111..bb14ca1900 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -6,19 +6,14 @@ * */ - -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" -#include "CommonFramework/GlobalAutoPaths.h" #include -#include -#include #include - -#include "PokemonBDSP_AutoStory_Segment_00_tools.h" -#include "PokemonBDSP_Autostory_Segment_01_tools.h" +#include +#include #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" @@ -30,1938 +25,1924 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" - #include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" #include "NintendoSwitch/Programs/DateSpam/NintendoSwitch_HomeToDateTime.h" #include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" - -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_ReceivePokemonDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_ExperienceGainDetector.h" - +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_ReceivePokemonDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_SelectionArrow.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" - -#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" #include "PokemonBDSP/Programs/PokemonBDSP_BasicCatcher.h" #include "PokemonBDSP/Programs/PokemonBDSP_GameEntry.h" +#include "PokemonBDSP/Programs/PokemonBDSP_GameNavigation.h" + +#include "PokemonBDSP_Autostory_Segment_01_tools.h" + +#include "PokemonBDSP_AutoStory_Segment_00_tools.h" using namespace std::chrono_literals; namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - - // --------------------------------------------------------------------------- - // BattleDialogWatcher - // --------------------------------------------------------------------------- - - BattleDialogWatcher::BattleDialogWatcher(Color color) - : DetectorToFinder( - "BattleDialogWatcher", - std::chrono::milliseconds(250), - color - ) - { - } +namespace NintendoSwitch { +namespace PokemonBDSP { - // --------------------------------------------------------------------------- - // Poketech Clown dialog helper - // --------------------------------------------------------------------------- - - int detect_clown_choice_arrow( - VideoOverlay& overlay, - VideoSnapshot snapshot - ) { - SelectionArrowFinder top_arrow( - overlay, - { 0.62, 0.37, 0.08, 0.10 }, - COLOR_RED - ); - - SelectionArrowFinder bottom_arrow( - overlay, - { 0.62, 0.48, 0.08, 0.10 }, - COLOR_RED - ); - - if (top_arrow.detect(snapshot)) { - return 0; - } +// --------------------------------------------------------------------------- +// BattleDialogWatcher +// --------------------------------------------------------------------------- - if (bottom_arrow.detect(snapshot)) { - return 1; - } +BattleDialogWatcher::BattleDialogWatcher(Color color) + : DetectorToFinder("BattleDialogWatcher", std::chrono::milliseconds(250), color){ +} - return -1; - } +// --------------------------------------------------------------------------- +// Poketech Clown dialog helper +// --------------------------------------------------------------------------- - void clown_dialog_helper( - VideoStream& stream, - ProControllerContext& context, - DpadPosition direction - ) { - stream.log( - "[AutoStory] Starting clown dialog helper...", - COLOR_BLUE - ); +int detect_clown_choice_arrow(VideoOverlay& overlay, VideoSnapshot snapshot){ + SelectionArrowFinder top_arrow(overlay, {0.62, 0.37, 0.08, 0.10}, COLOR_RED); - const auto overall_deadline = - std::chrono::steady_clock::now() + 30s; + SelectionArrowFinder bottom_arrow(overlay, {0.62, 0.48, 0.08, 0.10}, COLOR_RED); - // ------------------------------------------------- - // Phase 1: Move + interact until dialog is detected. - // ------------------------------------------------- - ShortDialogWatcher dialog(COLOR_YELLOW); - bool dialog_started = false; + if (top_arrow.detect(snapshot)){ + return 0; + } - while (!dialog_started) { + if (bottom_arrow.detect(snapshot)){ + return 1; + } - if (std::chrono::steady_clock::now() > overall_deadline) { - stream.log( - "[AutoStory] Clown helper timed out finding dialog.", - COLOR_RED - ); - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] Clown helper timed out finding dialog.", stream); - } + return -1; +} - context.wait_for_all_requests(); - pbf_press_dpad(context, direction, 80ms, 200ms); - pbf_press_button(context, BUTTON_A, 80ms, 500ms); +void clown_dialog_helper(VideoStream& stream, ProControllerContext& context, DpadPosition direction){ + stream.log("[AutoStory] Starting clown dialog helper...", COLOR_BLUE); - if (wait_until(stream, context, 1500ms, { {dialog} }) == 0) { - dialog_started = true; - stream.log("[AutoStory] Dialog detected.", COLOR_GREEN); - } - } + const auto overall_deadline = std::chrono::steady_clock::now() + 30s; - // ------------------------------------------------- - // Phase 2: A mash for 2 seconds. - // ------------------------------------------------- - stream.log("[AutoStory] Mashing A to advance dialog...", COLOR_BLUE); - pbf_mash_button(context, BUTTON_A, 4000ms); - context.wait_for_all_requests(); + // ------------------------------------------------- + // Phase 1: Move + interact until dialog is detected. + // ------------------------------------------------- + ShortDialogWatcher dialog(COLOR_YELLOW); + bool dialog_started = false; - // ------------------------------------------------- - // Phase 3: Clear remaining dialog. - // ------------------------------------------------- - pbf_mash_button(context, BUTTON_B, 4000ms); + while (!dialog_started){ - context.wait_for_all_requests(); + if (std::chrono::steady_clock::now() > overall_deadline){ + stream.log("[AutoStory] Clown helper timed out finding dialog.", COLOR_RED); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "[AutoStory] Clown helper timed out finding dialog.", stream); + } - stream.log( - "[AutoStory] Clown dialog helper complete.", - COLOR_GREEN - ); - } - // --------------------------------------------------------------------------- - // Time helper - // --------------------------------------------------------------------------- - - // Options chrome and position detectors are independent of setting values. - class TutorialOptionsDetector { - public: - bool detect(const ImageViewRGB32& screen) const { - ImageViewRGB32 panel = extract_box_reference(screen, ImageFloatBox{0.04, 0.065, 0.93, 0.70}); - size_t white = 0; - for (size_t y = 0; y < panel.height(); y += 4) - for (size_t x = 0; x < panel.width(); x += 4) { - uint32_t p = panel.pixel(x, y); - if (((p >> 16) & 255) > 225 && ((p >> 8) & 255) > 225 && (p & 255) > 225) white++; - } - double samples = (double)(((panel.width() + 3) / 4) * ((panel.height() + 3) / 4)); - // Distinguish Options from the icon menu and bag using its - // blue heading and full-width white settings panel. - ImageViewRGB32 heading = extract_box_reference(screen, ImageFloatBox{0.01, 0.005, 0.20, 0.025}); - size_t blue = 0, total = 0; - for (size_t y = 0; y < heading.height(); y++) - for (size_t x = 0; x < heading.width(); x++) { - uint32_t p = heading.pixel(x, y); - int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; - blue += b > r + 20 && g > r + 10; - total++; - } - return samples > 0 && white / samples > 0.70 && total > 0 && blue > total / 4; - } - }; - - class TutorialOptionPositionDetector { - public: - // Locate the two long red horizontal lines surrounding the - // selected row. Scrolling changes their position automatically. - double selected_row(const ImageViewRGB32& screen) const { - int first = -1, last = -1; - for (size_t y = (size_t)(screen.height() * 0.08); y < screen.height() * 0.75; y++) { - size_t red = 0, total = 0; - for (size_t x = (size_t)(screen.width() * 0.08); x < screen.width() * 0.90; x += 2) { - uint32_t p = screen.pixel(x, y); - int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; - red += r > 150 && r > g + 40 && r > b + 40; - total++; - } - if (total && red > total * 0.70) { - if (first < 0) first = (int)y; - last = (int)y; - } - } - if (first < 0 || last - first < screen.height() * 0.04 || last - first > screen.height() * 0.12) return -1; - return (first + last) / (2.0 * screen.height()); + context.wait_for_all_requests(); + pbf_press_dpad(context, direction, 80ms, 200ms); + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + + if (wait_until(stream, context, 1500ms, {{dialog}}) == 0){ + dialog_started = true; + stream.log("[AutoStory] Dialog detected.", COLOR_GREEN); + } + } + + // ------------------------------------------------- + // Phase 2: A mash for 2 seconds. + // ------------------------------------------------- + stream.log("[AutoStory] Mashing A to advance dialog...", COLOR_BLUE); + pbf_mash_button(context, BUTTON_A, 4000ms); + context.wait_for_all_requests(); + + // ------------------------------------------------- + // Phase 3: Clear remaining dialog. + // ------------------------------------------------- + pbf_mash_button(context, BUTTON_B, 4000ms); + + context.wait_for_all_requests(); + + stream.log("[AutoStory] Clown dialog helper complete.", COLOR_GREEN); +} +// --------------------------------------------------------------------------- +// Time helper +// --------------------------------------------------------------------------- + +// Options chrome and position detectors are independent of setting values. +class TutorialOptionsDetector { +public: + bool detect(const ImageViewRGB32& screen) const { + ImageViewRGB32 panel = extract_box_reference(screen, ImageFloatBox{0.04, 0.065, 0.93, 0.70}); + size_t white = 0; + for (size_t y = 0; y < panel.height(); y += 4){ + for (size_t x = 0; x < panel.width(); x += 4){ + uint32_t p = panel.pixel(x, y); + if (((p >> 16) & 255) > 225 && ((p >> 8) & 255) > 225 && (p & 255) > 225){ + white++; } - // Locate selected red value text separately from the row border. - bool selected_value(const ImageViewRGB32& screen, double y) const { - ImageViewRGB32 value = extract_box_reference(screen, ImageFloatBox{0.33, y - 0.025, 0.54, 0.05}); - size_t red = 0; - for (size_t py = 0; py < value.height(); py++) - for (size_t px = 0; px < value.width(); px++) { - uint32_t p = value.pixel(px, py); - int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; - red += r > g + 20 && r > b + 20; - } - return red > value.width() * value.height() * 0.0005; + } + } + double samples = (double)(((panel.width() + 3) / 4) * ((panel.height() + 3) / 4)); + // Distinguish Options from the icon menu and bag using its + // blue heading and full-width white settings panel. + ImageViewRGB32 heading = extract_box_reference(screen, ImageFloatBox{0.01, 0.005, 0.20, 0.025}); + size_t blue = 0, total = 0; + for (size_t y = 0; y < heading.height(); y++){ + for (size_t x = 0; x < heading.width(); x++){ + uint32_t p = heading.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + blue += b > r + 20 && g > r + 10; + total++; + } + } + return samples > 0 && white / samples > 0.70 && total > 0 && blue > total / 4; + } +}; + +class TutorialOptionPositionDetector { +public: + // Locate the two long red horizontal lines surrounding the + // selected row. Scrolling changes their position automatically. + double selected_row(const ImageViewRGB32& screen) const { + int first = -1, last = -1; + for (size_t y = (size_t)(screen.height() * 0.08); y < screen.height() * 0.75; y++){ + size_t red = 0, total = 0; + for (size_t x = (size_t)(screen.width() * 0.08); x < screen.width() * 0.90; x += 2){ + uint32_t p = screen.pixel(x, y); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + red += r > 150 && r > g + 40 && r > b + 40; + total++; + } + if (total && red > total * 0.70){ + if (first < 0){ + first = (int)y; } - }; - - - // Compare binary visual shapes with small alignment tolerance. - // No text recognition is needed for fixed English option labels. - static double option_image_score(const ImageViewRGB32& screen, - const ImageFloatBox& box, const ImageRGB32& reference, bool red_only) { - ImageRGB32 actual = extract_box_reference(screen, box).scale_to(384, 32); - ImageRGB32 expected = reference.scale_to(384, 32); - auto ink = [red_only](uint32_t p){ - int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; - return red_only ? r > g + 20 && r > b + 20 : r + g + b < 500; - }; - double best = 0; - for (int dy = -2; dy <= 2; dy++) for (int dx = -2; dx <= 2; dx++) { - size_t overlap = 0, count = 0; - for (int y = 2; y < 30; y++) for (int x = 2; x < 382; x++) { - bool a = ink(actual.pixel(x + dx, y + dy)); - bool b = ink(expected.pixel(x, y)); - overlap += a && b; - count += a + b; - } - if (count) best = std::max(best, 2.0 * overlap / count); + last = (int)y; + } + } + if (first < 0 || last - first < screen.height() * 0.04 || last - first > screen.height() * 0.12){ + return -1; + } + return (first + last) / (2.0 * screen.height()); + } + // Locate selected red value text separately from the row border. + bool selected_value(const ImageViewRGB32& screen, double y) const { + ImageViewRGB32 value = extract_box_reference(screen, ImageFloatBox{0.33, y - 0.025, 0.54, 0.05}); + size_t red = 0; + for (size_t py = 0; py < value.height(); py++){ + for (size_t px = 0; px < value.width(); px++){ + uint32_t p = value.pixel(px, py); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + red += r > g + 20 && r > b + 20; + } + } + return red > value.width() * value.height() * 0.0005; + } +}; + + +// Compare binary visual shapes with small alignment tolerance. +// No text recognition is needed for fixed English option labels. +static double option_image_score(const ImageViewRGB32& screen, const ImageFloatBox& box, const ImageRGB32& reference, + bool red_only){ + ImageRGB32 actual = extract_box_reference(screen, box).scale_to(384, 32); + ImageRGB32 expected = reference.scale_to(384, 32); + auto ink = [red_only](uint32_t p){ + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + return red_only ? r > g + 20 && r > b + 20 : r + g + b < 500; + }; + double best = 0; + for (int dy = -2; dy <= 2; dy++){ + for (int dx = -2; dx <= 2; dx++){ + size_t overlap = 0, count = 0; + for (int y = 2; y < 30; y++){ + for (int x = 2; x < 382; x++){ + bool a = ink(actual.pixel(x + dx, y + dy)); + bool b = ink(expected.pixel(x, y)); + overlap += a && b; + count += a + b; } - return best; } + if (count){ + best = std::max(best, 2.0 * overlap / count); + } + } + } + return best; +} - static bool autosave_off_highlight(const ImageViewRGB32& screen, double y) { - auto red_count = [&](const ImageFloatBox& box){ - ImageViewRGB32 image = extract_box_reference(screen, box); - size_t red = 0; - for (size_t py = 0; py < image.height(); py++) - for (size_t px = 0; px < image.width(); px++) { - uint32_t p = image.pixel(px, py); - int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; - red += r > g + 30 && r > b + 30; - } - return red; - }; - size_t off = red_count({0.63, y - 0.022, 0.055, 0.044}); - size_t on = red_count({0.34, y - 0.022, 0.055, 0.044}); - // Require a clear Off highlight and reject any competing On highlight. - return off >= 10 && on * 10 < off; +static bool autosave_off_highlight(const ImageViewRGB32& screen, double y){ + auto red_count = [&](const ImageFloatBox& box){ + ImageViewRGB32 image = extract_box_reference(screen, box); + size_t red = 0; + for (size_t py = 0; py < image.height(); py++){ + for (size_t px = 0; px < image.width(); px++){ + uint32_t p = image.pixel(px, py); + int r = (p >> 16) & 255, g = (p >> 8) & 255, b = p & 255; + red += r > g + 30 && r > b + 30; } + } + return red; + }; + size_t off = red_count({0.63, y - 0.022, 0.055, 0.044}); + size_t on = red_count({0.34, y - 0.022, 0.055, 0.044}); + // Require a clear Off highlight and reject any competing On highlight. + return off >= 10 && on * 10 < off; +} - static void verify_tutorial_settings_once(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - MenuDetector menu; - VideoOverlaySet detector_overlays(stream.overlay()); - menu.make_overlays(detector_overlays); - detector_overlays.add(COLOR_CYAN, {0.04, 0.065, 0.93, 0.70}, ""); - detector_overlays.add(COLOR_BLUE, {0.01, 0.005, 0.20, 0.025}, ""); - detector_overlays.add(COLOR_RED, {0.08, 0.08, 0.82, 0.67}, ""); - auto initial = stream.video().snapshot(); - if (!initial || !menu.detect(initial)) { - pbf_press_button(context, BUTTON_X, 80ms, 1000ms); - context.wait_for_all_requests(); - } - MenuWatcher menu_open; - if (wait_until(stream, context, 5s, {{menu_open}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Main menu not detected.", stream); - } - stream.log("[Tutorial settings] Main menu detected; settling for 500ms before Options navigation."); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - TutorialOptionsDetector options_screen; - TutorialOptionPositionDetector position; - // Only two icons are available at this initial checkpoint. - // Verify the main menu before navigating to the neighboring icon. - bool options_open = false; - for (size_t attempt = 0; attempt < 2; attempt++) { - pbf_press_dpad(context, attempt == 0 ? DPAD_RIGHT : DPAD_LEFT, 80ms, 350ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - context.wait_for_all_requests(); - auto frame = stream.video().snapshot(); - if (frame && options_screen.detect(frame)) { - options_open = true; - break; - } - pbf_press_button(context, BUTTON_B, 80ms, 1000ms); +static void verify_tutorial_settings_once(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + MenuDetector menu; + VideoOverlaySet detector_overlays(stream.overlay()); + menu.make_overlays(detector_overlays); + detector_overlays.add(COLOR_CYAN, {0.04, 0.065, 0.93, 0.70}, ""); + detector_overlays.add(COLOR_BLUE, {0.01, 0.005, 0.20, 0.025}, ""); + detector_overlays.add(COLOR_RED, {0.08, 0.08, 0.82, 0.67}, ""); + auto initial = stream.video().snapshot(); + if (!initial || !menu.detect(initial)){ + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + context.wait_for_all_requests(); + } + MenuWatcher menu_open; + if (wait_until(stream, context, 5s, {{menu_open}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Main menu not detected.", stream); + } + stream.log("[Tutorial settings] Main menu detected; settling for 500ms before Options navigation."); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + TutorialOptionsDetector options_screen; + TutorialOptionPositionDetector position; + // Only two icons are available at this initial checkpoint. + // Verify the main menu before navigating to the neighboring icon. + bool options_open = false; + for (size_t attempt = 0; attempt < 2; attempt++){ + pbf_press_dpad(context, attempt == 0 ? DPAD_RIGHT : DPAD_LEFT, 80ms, 350ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + if (frame && options_screen.detect(frame)){ + options_open = true; + break; + } + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + MenuWatcher back_to_menu; + if (wait_until(stream, context, 5s, {{back_to_menu}}) != 0){ + break; + } + } + if (!options_open){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Options screen not detected.", stream); + } + stream.log("[Tutorial settings] Options screen detected."); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + struct Setting { + const char* label; + const char* value; + }; + const Setting settings[] = { + {"textspeed", "fast"}, {"changewindow", "type1"}, {"battleeffects", "off"}, {"battlestyle", "set"}, + {"sendtoboxes", "automatic"}, {"givenicknames", "dontgive"}, {"gyroscope", "use"}, {"autosave", "off"}, + {"shownicknames", "show"}, {"backgroundmusic", "8"}, {"soundeffects", "8"}, + }; + for (size_t row = 0; row < sizeof(settings) / sizeof(settings[0]); row++){ + // Detect the selected row afresh after every navigation/correction. + double y = -1; + const std::string base = RESOURCE_PATH() + "PokemonBDSP/Autostory/Options/" + settings[row].label; + const ImageRGB32 label_reference(base + "_label.png"); + const ImageRGB32 value_reference(base + "_value.png"); + bool slider_reset = false; + bool correct = false; + for (size_t attempt = 0; attempt < 30; attempt++){ + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + if (frame && options_screen.detect(frame)){ + y = position.selected_row(frame); + }else{ + y = -1; + } + const bool value_visible = y >= 0 && position.selected_value(frame, y); + const double label_score = + y >= 0 ? option_image_score(frame, {0.08, y - 0.025, 0.24, 0.05}, label_reference, false) : 0; + stream.log("[Tutorial settings] Expected row=" + std::string(settings[row].label) + + " row_y=" + std::to_string(y) + " highlighted_value=" + std::to_string(value_visible) + + " label_match=" + std::to_string(label_score)); + if (y < 0 || !value_visible || label_score < 0.80){ + // A menu can be detected while its row/text is still + // fading in. Re-read without directional inputs first. + if (attempt < 4){ + pbf_wait(context, 200ms); context.wait_for_all_requests(); - MenuWatcher back_to_menu; - if (wait_until(stream, context, 5s, {{back_to_menu}}) != 0) break; - } - if (!options_open) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Options screen not detected.", stream); + continue; } - stream.log("[Tutorial settings] Options screen detected."); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - struct Setting { const char* label; const char* value; }; - const Setting settings[] = { - {"textspeed", "fast"}, {"changewindow", "type1"}, - {"battleeffects", "off"}, {"battlestyle", "set"}, - {"sendtoboxes", "automatic"}, {"givenicknames", "dontgive"}, - {"gyroscope", "use"}, {"autosave", "off"}, - {"shownicknames", "show"}, {"backgroundmusic", "8"}, - {"soundeffects", "8"}, - }; - for (size_t row = 0; row < sizeof(settings) / sizeof(settings[0]); row++) { - // Detect the selected row afresh after every navigation/correction. - double y = -1; - const std::string base = RESOURCE_PATH() + "PokemonBDSP/Autostory/Options/" + settings[row].label; - const ImageRGB32 label_reference(base + "_label.png"); - const ImageRGB32 value_reference(base + "_value.png"); - bool slider_reset = false; - bool correct = false; - for (size_t attempt = 0; attempt < 30; attempt++) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Unable to confirm expected row " + std::string(settings[row].label), stream); + } + // These are regular video-overlay boxes, so the existing + // overlay display control can show/hide every active ROI. + OverlayBoxScope row_overlay(stream.overlay(), COLOR_RED, {0.07, y - 0.04, 0.84, 0.08}, ""); + OverlayBoxScope label_overlay(stream.overlay(), COLOR_YELLOW, {0.08, y - 0.025, 0.24, 0.05}, ""); + OverlayBoxScope value_overlay(stream.overlay(), COLOR_GREEN, {0.33, y - 0.025, 0.54, 0.05}, ""); + VideoOverlaySet autosave_rois(stream.overlay()); + if (row == 7){ + autosave_rois.add(COLOR_GREEN, {0.63, y - 0.022, 0.055, 0.044}); + autosave_rois.add(COLOR_RED, {0.34, y - 0.022, 0.055, 0.044}); + } + double score; + if (row >= 9){ + // Compare the numeric value rather than the long slider: + // neighboring slider levels otherwise look too similar. + ImageViewRGB32 number = value_reference.sub_image( + value_reference.width() * 82 / 100, 0, value_reference.width() - value_reference.width() * 82 / 100, + value_reference.height()); + ImageRGB32 number_reference = number.scale_to(96, 32); + score = option_image_score(frame, {0.7728, y - 0.025, 0.0972, 0.05}, number_reference, true); + }else{ + score = option_image_score(frame, {0.33, y - 0.025, 0.54, 0.05}, value_reference, true); + } + stream.log("[Tutorial settings] " + std::string(settings[row].label) + + " target match=" + std::to_string(score)); + // The reference recording uses inactive pale-red text; + // the focused live row has a thicker dark-red glyph mask. + // Verified Fast scores 0.734 while Slow/Normal score 0. + if (score >= 0.65 && (row != 7 || autosave_off_highlight(frame, y))){ + if (row == 7){ + for (size_t confirmation = 0; confirmation < 3; confirmation++){ + pbf_wait(context, 200ms); context.wait_for_all_requests(); - auto frame = stream.video().snapshot(); - if (frame && options_screen.detect(frame)) y = position.selected_row(frame); - else y = -1; - const bool value_visible = y >= 0 && position.selected_value(frame, y); - const double label_score = y >= 0 ? option_image_score(frame, - {0.08, y - 0.025, 0.24, 0.05}, label_reference, false) : 0; - stream.log("[Tutorial settings] Expected row=" + std::string(settings[row].label) + - " row_y=" + std::to_string(y) + " highlighted_value=" + std::to_string(value_visible) + - " label_match=" + std::to_string(label_score)); - if (y < 0 || !value_visible || label_score < 0.80) { - // A menu can be detected while its row/text is still - // fading in. Re-read without directional inputs first. - if (attempt < 4) { - pbf_wait(context, 200ms); - context.wait_for_all_requests(); - continue; - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Unable to confirm expected row " + std::string(settings[row].label), stream); + auto check = stream.video().snapshot(); + double check_y = check ? position.selected_row(check) : -1; + if (!check || !options_screen.detect(check) || check_y < 0 || + option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, label_reference, false) < + 0.80 || + !autosave_off_highlight(check, check_y)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Autosave Off could not be confirmed. Stopping before movement.", stream); } - // These are regular video-overlay boxes, so the existing - // overlay display control can show/hide every active ROI. - OverlayBoxScope row_overlay(stream.overlay(), COLOR_RED, - {0.07, y - 0.04, 0.84, 0.08}, ""); - OverlayBoxScope label_overlay(stream.overlay(), COLOR_YELLOW, - {0.08, y - 0.025, 0.24, 0.05}, ""); - OverlayBoxScope value_overlay(stream.overlay(), COLOR_GREEN, - {0.33, y - 0.025, 0.54, 0.05}, ""); - VideoOverlaySet autosave_rois(stream.overlay()); - if (row == 7) { - autosave_rois.add(COLOR_GREEN, {0.63, y - 0.022, 0.055, 0.044}); - autosave_rois.add(COLOR_RED, {0.34, y - 0.022, 0.055, 0.044}); - } - double score; - if (row >= 9) { - // Compare the numeric value rather than the long slider: - // neighboring slider levels otherwise look too similar. - ImageViewRGB32 number = value_reference.sub_image( - value_reference.width() * 82 / 100, 0, - value_reference.width() - value_reference.width() * 82 / 100, - value_reference.height()); - ImageRGB32 number_reference = number.scale_to(96, 32); - score = option_image_score(frame, - {0.7728, y - 0.025, 0.0972, 0.05}, number_reference, true); - } else { - score = option_image_score(frame, - {0.33, y - 0.025, 0.54, 0.05}, value_reference, true); - } - stream.log("[Tutorial settings] " + std::string(settings[row].label) + - " target match=" + std::to_string(score)); - // The reference recording uses inactive pale-red text; - // the focused live row has a thicker dark-red glyph mask. - // Verified Fast scores 0.734 while Slow/Normal score 0. - if (score >= 0.65 && (row != 7 || autosave_off_highlight(frame, y))) { - if (row == 7) { - for (size_t confirmation = 0; confirmation < 3; confirmation++) { - pbf_wait(context, 200ms); - context.wait_for_all_requests(); - auto check = stream.video().snapshot(); - double check_y = check ? position.selected_row(check) : -1; - if (!check || !options_screen.detect(check) || check_y < 0 || - option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, label_reference, false) < 0.80 || - !autosave_off_highlight(check, check_y)) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Autosave Off could not be confirmed. Stopping before movement.", stream); - } - } - } - stream.log("[Tutorial settings] Verified " + std::string(settings[row].label) + "=" + settings[row].value); - correct = true; - break; - } - stream.log("[Tutorial settings] Correcting " + std::string(settings[row].label)); - if (row >= 9 && !slider_reset) { - // Sliders clamp at zero. Establish a known endpoint, - // then increment; verify the target image afterward. - for (size_t step = 0; step < 12; step++) - pbf_press_dpad(context, DPAD_LEFT, 80ms, 100ms); - slider_reset = true; - } else { - pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); - } - context.wait_for_all_requests(); - } - if (!correct) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Could not apply " + std::string(settings[row].label), stream); } - if (row + 1 < sizeof(settings) / sizeof(settings[0])) - pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); - } - // Confirm settings before exiting: A A A A B B. - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - pbf_press_button(context, BUTTON_B, 80ms, 1000ms); - pbf_press_button(context, BUTTON_B, 80ms, 1000ms); - context.wait_for_all_requests(); - OverworldWatcher overworld; - if (wait_until(stream, context, 10s, {{overworld}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Did not return to the overworld after saving options.", stream); } - // Reopen the saved Options screen and independently verify Autosave. - // No route movement is permitted until this persisted-value check passes. - pbf_press_button(context, BUTTON_X, 80ms, 1000ms); - context.wait_for_all_requests(); - MenuWatcher verify_menu; - if (wait_until(stream, context, 5s, {{verify_menu}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Autosave verification: Main menu not detected.", stream); + stream.log("[Tutorial settings] Verified " + std::string(settings[row].label) + "=" + + settings[row].value); + correct = true; + break; + } + stream.log("[Tutorial settings] Correcting " + std::string(settings[row].label)); + if (row >= 9 && !slider_reset){ + // Sliders clamp at zero. Establish a known endpoint, + // then increment; verify the target image afterward. + for (size_t step = 0; step < 12; step++){ + pbf_press_dpad(context, DPAD_LEFT, 80ms, 100ms); } - bool reopened = false; - for (size_t attempt = 0; attempt < 2; attempt++) { + slider_reset = true; + }else{ + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); + } + context.wait_for_all_requests(); + } + if (!correct){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Could not apply " + std::string(settings[row].label), stream); + } + if (row + 1 < sizeof(settings) / sizeof(settings[0])){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); + } + } + // Confirm settings before exiting: A A A A B B. + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + OverworldWatcher overworld; + if (wait_until(stream, context, 10s, {{overworld}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Did not return to the overworld after saving options.", stream); + } + // Reopen the saved Options screen and independently verify Autosave. + // No route movement is permitted until this persisted-value check passes. + pbf_press_button(context, BUTTON_X, 80ms, 1000ms); + context.wait_for_all_requests(); + MenuWatcher verify_menu; + if (wait_until(stream, context, 5s, {{verify_menu}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Autosave verification: Main menu not detected.", stream); + } + bool reopened = false; + for (size_t attempt = 0; attempt < 2; attempt++){ + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + context.wait_for_all_requests(); + auto check = stream.video().snapshot(); + if (check && options_screen.detect(check)){ + reopened = true; + break; + } + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + MenuWatcher return_menu; + if (wait_until(stream, context, 5s, {{return_menu}}) != 0){ + break; + } + pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); + } + if (!reopened){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Autosave verification: Could not reopen saved Options.", stream); + } + // Options starts on Text Speed; Autosave is seven rows below it. + for (size_t step = 0; step < 7; step++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); + } + context.wait_for_all_requests(); + const ImageRGB32 autosave_label(RESOURCE_PATH() + "PokemonBDSP/Autostory/Options/autosave_label.png"); + for (size_t confirmation = 0; confirmation < 3; confirmation++){ + auto check = stream.video().snapshot(); + double check_y = check ? position.selected_row(check) : -1; + if (!check || !options_screen.detect(check) || check_y < 0 || + option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, autosave_label, false) < 0.80 || + !autosave_off_highlight(check, check_y)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Saved Autosave setting is not verified Off. Stopping before movement.", stream); + } + pbf_wait(context, 200ms); + context.wait_for_all_requests(); + } + stream.log("[Tutorial settings] Autosave = Off: confirmed after reopening saved Options (3 frames)."); + // Confirm settings before exiting: A A A A B B. + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 1200ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + pbf_press_button(context, BUTTON_B, 80ms, 1000ms); + context.wait_for_all_requests(); + OverworldWatcher verified_exit; + if (wait_until(stream, context, 10s, {{verified_exit}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Autosave verification: Could not return to the overworld.", stream); + } + // Report inferred values explicitly: these are visual matches, + // allowing the user to compare our conclusions with the screen. + stream.log("[Tutorial settings] Completed. Values believed applied (visual matches before saving):"); + stream.log( + "[Tutorial settings] Text Speed = Fast; Change Window = Type 1; Battle Effects = Off; Battle Style = Set."); + stream.log("[Tutorial settings] Send to Boxes = Automatic; Give Nicknames = Don't give; Gyroscope = Use."); + stream.log("[Tutorial settings] Autosave = Off; Show Nicknames = Show; Background Music = 8; Sound Effects = 8."); +} + + +void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context){ + // Settings-only recovery: initial attempt plus three retries. + // Never permit route movement unless the complete check succeeds. + constexpr size_t MAX_RETRIES = 3; + for (size_t attempt = 0; attempt <= MAX_RETRIES; attempt++){ + stream.log("[Tutorial settings] Attempt " + std::to_string(attempt + 1) + "/4."); + try { + verify_tutorial_settings_once(stream, context); + return; + } catch (OperationFailedException& error){ + stream.log("[Tutorial settings] Attempt failed: " + error.message(), COLOR_ORANGE); + if (attempt == MAX_RETRIES){ + stream.log("[Tutorial settings] All three retries exhausted; stopping before movement.", COLOR_RED); + throw; + } + // Back out of Options, save prompts, bag, or menu without + // moving the player or resetting/loading the game. + stream.log("[Tutorial settings] Returning to overworld before retry."); + context.wait_for_all_requests(); + TutorialOptionsDetector options_screen; + const auto failed_frame = stream.video().snapshot(); + if (failed_frame && options_screen.detect(failed_frame)){ + // Finish the known Options save/exit sequence before + // observing state; never cancel it on an Options frame + // misclassified as overworld by the generic detector. + for (size_t press = 0; press < 4; press++){ pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - context.wait_for_all_requests(); - auto check = stream.video().snapshot(); - if (check && options_screen.detect(check)) { reopened = true; break; } - pbf_press_button(context, BUTTON_B, 80ms, 1000ms); - context.wait_for_all_requests(); - MenuWatcher return_menu; - if (wait_until(stream, context, 5s, {{return_menu}}) != 0) break; - pbf_press_dpad(context, DPAD_RIGHT, 80ms, 350ms); - } - if (!reopened) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Autosave verification: Could not reopen saved Options.", stream); } - // Options starts on Text Speed; Autosave is seven rows below it. - for (size_t step = 0; step < 7; step++) - pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); - context.wait_for_all_requests(); - const ImageRGB32 autosave_label(RESOURCE_PATH() + "PokemonBDSP/Autostory/Options/autosave_label.png"); - for (size_t confirmation = 0; confirmation < 3; confirmation++) { - auto check = stream.video().snapshot(); - double check_y = check ? position.selected_row(check) : -1; - if (!check || !options_screen.detect(check) || check_y < 0 || - option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, autosave_label, false) < 0.80 || - !autosave_off_highlight(check, check_y)) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Saved Autosave setting is not verified Off. Stopping before movement.", stream); + } + pbf_mash_button(context, BUTTON_B, 3000ms); + context.wait_for_all_requests(); + OverworldDetector overworld; + MenuDetector menu; + BlackScreenDetector black; + bool recovered = false; + size_t confirmations = 0; + for (size_t check = 0; check < 15; check++){ + const auto frame = stream.video().snapshot(); + const bool clear = frame && !options_screen.detect(frame) && !menu.detect(frame) && + !black.detect(frame) && bag_pocket_position(frame) == 0 && overworld.detect(frame); + if (clear){ + if (++confirmations >= 3){ + recovered = true; + break; } pbf_wait(context, 200ms); - context.wait_for_all_requests(); + }else{ + confirmations = 0; + pbf_mash_button(context, BUTTON_B, 500ms); } - stream.log("[Tutorial settings] Autosave = Off: confirmed after reopening saved Options (3 frames)."); - // Confirm settings before exiting: A A A A B B. - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - pbf_press_button(context, BUTTON_A, 80ms, 1200ms); - pbf_press_button(context, BUTTON_B, 80ms, 1000ms); - pbf_press_button(context, BUTTON_B, 80ms, 1000ms); context.wait_for_all_requests(); - OverworldWatcher verified_exit; - if (wait_until(stream, context, 10s, {{verified_exit}}) != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Autosave verification: Could not return to the overworld.", stream); - } - // Report inferred values explicitly: these are visual matches, - // allowing the user to compare our conclusions with the screen. - stream.log("[Tutorial settings] Completed. Values believed applied (visual matches before saving):"); - stream.log("[Tutorial settings] Text Speed = Fast; Change Window = Type 1; Battle Effects = Off; Battle Style = Set."); - stream.log("[Tutorial settings] Send to Boxes = Automatic; Give Nicknames = Don't give; Gyroscope = Use."); - stream.log("[Tutorial settings] Autosave = Off; Show Nicknames = Show; Background Music = 8; Sound Effects = 8."); - } - - - void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context) { - // Settings-only recovery: initial attempt plus three retries. - // Never permit route movement unless the complete check succeeds. - constexpr size_t MAX_RETRIES = 3; - for (size_t attempt = 0; attempt <= MAX_RETRIES; attempt++) { - stream.log("[Tutorial settings] Attempt " + std::to_string(attempt + 1) + "/4."); - try { - verify_tutorial_settings_once(stream, context); - return; - } catch (OperationFailedException& error) { - stream.log("[Tutorial settings] Attempt failed: " + error.message(), COLOR_ORANGE); - if (attempt == MAX_RETRIES) { - stream.log("[Tutorial settings] All three retries exhausted; stopping before movement.", COLOR_RED); - throw; - } - // Back out of Options, save prompts, bag, or menu without - // moving the player or resetting/loading the game. - stream.log("[Tutorial settings] Returning to overworld before retry."); - context.wait_for_all_requests(); - TutorialOptionsDetector options_screen; - const auto failed_frame = stream.video().snapshot(); - if (failed_frame && options_screen.detect(failed_frame)) { - // Finish the known Options save/exit sequence before - // observing state; never cancel it on an Options frame - // misclassified as overworld by the generic detector. - for (size_t press = 0; press < 4; press++) - pbf_press_button(context, BUTTON_A, 80ms, 1000ms); - } - pbf_mash_button(context, BUTTON_B, 3000ms); - context.wait_for_all_requests(); - OverworldDetector overworld; - MenuDetector menu; - BlackScreenDetector black; - bool recovered = false; - size_t confirmations = 0; - for (size_t check = 0; check < 15; check++) { - const auto frame = stream.video().snapshot(); - const bool clear = frame && !options_screen.detect(frame) && - !menu.detect(frame) && !black.detect(frame) && bag_pocket_position(frame) == 0 && overworld.detect(frame); - if (clear) { - if (++confirmations >= 3) { recovered = true; break; } - pbf_wait(context, 200ms); - } else { - confirmations = 0; - pbf_mash_button(context, BUTTON_B, 500ms); - } - context.wait_for_all_requests(); - } - stream.log(recovered ? "[Tutorial settings] Recovery confirmed outside Options/menu; retrying." - : "[Tutorial settings] Recovery not confirmed; next attempt will recheck the screen."); - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - } - } } + stream.log(recovered ? "[Tutorial settings] Recovery confirmed outside Options/menu; retrying." + : "[Tutorial settings] Recovery not confirmed; next attempt will recheck the screen."); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + } + } +} - void set_time_fast( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - const DateTime& target, - bool preserve_date - ) { - env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); - - // 1) Home - NintendoSwitch::go_home(env.console, context); - - // 2) Navigate to Date/Time settings quickly. - NintendoSwitch::home_to_date_time(env.console, context, true); - - // 3) Enter date editor. - pbf_press_button(context, BUTTON_A, 80ms, 240ms); - - // 4) Wait for date screen and set exact time. - NintendoSwitch::DateChangeWatcher date_reader(env.console); - int ret = wait_until( - env.console, context, 10s, - { date_reader } - ); - if (ret < 0) { - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to enter Date Change screen.", - env.console - ); - } +void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const DateTime& target, + bool preserve_date){ + env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); - { - VideoOverlaySet overlays(env.console.overlay()); - date_reader.make_overlays(overlays); - DateTime effective_target = target; - if (preserve_date) { - context.wait_for_all_requests(); - const auto snapshot = env.console.video().snapshot(); - if (!snapshot) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Clock-only setup: No date-editor frame.", env.console); - const DateTime current = date_reader.read_date(env.console, snapshot.frame).second; - if (current.year < 2000 || current.year > 2060 || current.month < 1 || current.month > 12 || - current.day < 1 || current.day > 31) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Clock-only setup: Could not reliably read the current console date.", env.console); - effective_target.year = current.year; - effective_target.month = current.month; - effective_target.day = current.day; - env.log("[AutoStory] Clock-only target=" + std::to_string((int)target.hour) + ":" + - std::to_string((int)target.minute) + "; preserving date=" + std::to_string(current.year) + "-" + - std::to_string((int)current.month) + "-" + std::to_string((int)current.day) + "."); - } - date_reader.set_date(env.program_info(), env.console, context, effective_target); - } + // 1) Home + NintendoSwitch::go_home(env.console, context); - // 5) Commit date. - pbf_press_button(context, BUTTON_A, 160ms, 240ms); + // 2) Navigate to Date/Time settings quickly. + NintendoSwitch::home_to_date_time(env.console, context, true); - // 6) Back to game. - pbf_press_button( - context, BUTTON_HOME, 160ms, - ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0 - ); - NintendoSwitch::resume_game_from_home(env.console, context, false); + // 3) Enter date editor. + pbf_press_button(context, BUTTON_A, 80ms, 240ms); - context.wait_for_all_requests(); - context.wait_for(500ms); - } + // 4) Wait for date screen and set exact time. + NintendoSwitch::DateChangeWatcher date_reader(env.console); + int ret = wait_until(env.console, context, 10s, {date_reader}); + if (ret < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Failed to enter Date Change screen.", env.console); + } - - // --------------------------------------------------------------------------- - // Natalie battle - // --------------------------------------------------------------------------- - static void select_battle_move_slot(VideoStream& stream, ProControllerContext& context, - BattleType battle_type, size_t target, const std::string& label, - bool confirm_default_target = false, size_t current_move = 1) { - BattleMenuDetector commands(battle_type); - VideoOverlaySet overlays(stream.overlay()); - commands.make_overlays(overlays); - overlays.add(COLOR_GREEN, {0.90, 0.48, 0.075, 0.505}); - // Reenter Fight from an already-open list without assuming - // that this resets the remembered move cursor. - if (confirm_default_target) { - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - if (frame && detect_battle_move_menu(frame).move_count) { - pbf_press_button(context,BUTTON_B,80ms,500ms); - } - } - bool fight = false; - size_t confirmed = 0; - for (size_t check = 0; check < 30; check++) { - context.wait_for_all_requests(); - auto frame = stream.video().snapshot(); - BattleMenuOption selected = frame ? commands.selected_option(frame) : BattleMenuOption::UNKNOWN; - stream.log("[" + label + "] Command cursor=" + std::to_string((int)selected + 1) + "; target Battle=1."); - if (selected == BattleMenuOption::BATTLE) { - if (++confirmed >= 3) { fight = true; break; } - pbf_wait(context, 100ms); - } else { - confirmed = 0; - if (selected == BattleMenuOption::UNKNOWN) pbf_wait(context, 150ms); - else pbf_press_dpad(context, DPAD_UP, 80ms, 250ms); - } - } - if (!fight) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Could not confirm Battle command; A not pressed.", stream); - pbf_press_button(context,BUTTON_A,80ms,confirm_default_target ? 1000ms : 500ms); - if (confirm_default_target) { - // Reuse the farming move/target confirmation sequence, but - // navigate relative to this battle's retained move cursor. - // Reopening Fight does not reset it to move 1. - size_t available=0,stable=0; - for (size_t check=0;check<20 && stable<3;check++) { - context.wait_for_all_requests(); - const auto frame=stream.video().snapshot(); - const auto moves=frame ? detect_battle_move_menu(frame) : BattleMoveMenuState{}; - if (moves.move_count && moves.move_count==available) stable++; - else {available=moves.move_count;stable=moves.move_count ? 1:0;} - if (stable<3) pbf_wait(context,100ms); - } - if (stable<3 || available "+std::to_string(target)+"."); - for (size_t slot=current_move;slottarget;slot--) pbf_press_dpad(context,DPAD_UP,80ms,400ms); - pbf_press_button(context,BUTTON_A,80ms,1000ms); - pbf_press_button(context,BUTTON_A,80ms,3000ms); - return; - } - confirmed = 0; - size_t previous_move_count = 0, stable_move_count = 0; - for (size_t check = 0; check < 30; check++) { - context.wait_for_all_requests(); - auto frame = stream.video().snapshot(); - const BattleMoveMenuState moves = frame ? detect_battle_move_menu(frame) : BattleMoveMenuState{}; - const int slot = (int)moves.selected_slot; - VideoOverlaySet move_overlays(stream.overlay()); - for (size_t i = 0; i < moves.move_count; i++) - move_overlays.add(moves.selected_slot == i + 1 ? COLOR_GREEN : COLOR_YELLOW, moves.buttons[i]); - stream.log("[" + label + "] Available moves=" + std::to_string(moves.move_count) + - "; selected slot=" + std::to_string(slot) + "/4; target=" + std::to_string(target) + "."); - if (moves.move_count == previous_move_count && moves.move_count > 0) stable_move_count++; - else { previous_move_count = moves.move_count; stable_move_count = 1; confirmed = 0; } - if (moves.move_count > 0 && moves.move_count < target && stable_move_count >= 3) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Move slot " + std::to_string(target) + " is absent; no move confirmed.", stream); - } - if (slot == (int)target && stable_move_count >= 3) { - if (++confirmed >= 3) { - stream.log("[" + label + "] Move " + std::to_string(target) + " confirmed; using it."); - pbf_press_button(context, BUTTON_A, 80ms, 3000ms); - return; - } - pbf_wait(context, 100ms); - } else { - confirmed = 0; - if (slot == 0 || slot == (int)target) pbf_wait(context, 150ms); - else pbf_press_dpad(context, slot < (int)target ? DPAD_DOWN : DPAD_UP, 80ms, 250ms); - } - context.wait_for_all_requests(); - } + { + VideoOverlaySet overlays(env.console.overlay()); + date_reader.make_overlays(overlays); + DateTime effective_target = target; + if (preserve_date){ + context.wait_for_all_requests(); + const auto snapshot = env.console.video().snapshot(); + if (!snapshot){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Could not confirm move " + std::to_string(target) + "; A not pressed.", stream); + "Clock-only setup: No date-editor frame.", env.console); } - - static void select_natalie_move_three(VideoStream& stream, ProControllerContext& context) { - select_battle_move_slot(stream, context, BattleType::TRAINER, 3, "Natalie"); + const DateTime current = date_reader.read_date(env.console, snapshot.frame).second; + if (current.year < 2000 || current.year > 2060 || current.month < 1 || current.month > 12 || + current.day < 1 || current.day > 31){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Clock-only setup: Could not reliably read the current console date.", env.console); } + effective_target.year = current.year; + effective_target.month = current.month; + effective_target.day = current.day; + env.log("[AutoStory] Clock-only target=" + std::to_string((int)target.hour) + ":" + + std::to_string((int)target.minute) + "; preserving date=" + std::to_string(current.year) + "-" + + std::to_string((int)current.month) + "-" + std::to_string((int)current.day) + "."); + } + date_reader.set_date(env.program_info(), env.console, context, effective_target); + } - void fight_Natalie( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log( - "[AutoStory] Starting Natalie battle...", - COLOR_BLUE - ); + // 5) Commit date. + pbf_press_button(context, BUTTON_A, 160ms, 240ms); - // Allow battle transition to finish. - pbf_wait(context, 5000ms); + // 6) Back to game. + pbf_press_button(context, BUTTON_HOME, 160ms, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0); + NintendoSwitch::resume_game_from_home(env.console, context, false); - bool battle_menu_seen = false; + context.wait_for_all_requests(); + context.wait_for(500ms); +} - // State machine loop. - while (true) { - context.wait_for_all_requests(); +// --------------------------------------------------------------------------- +// Natalie battle +// --------------------------------------------------------------------------- +static void select_battle_move_slot(VideoStream& stream, ProControllerContext& context, BattleType battle_type, + size_t target, const std::string& label, bool confirm_default_target = false, + size_t current_move = 1){ + BattleMenuDetector commands(battle_type); + VideoOverlaySet overlays(stream.overlay()); + commands.make_overlays(overlays); + overlays.add(COLOR_GREEN, {0.90, 0.48, 0.075, 0.505}); + // Reenter Fight from an already-open list without assuming + // that this resets the remembered move cursor. + if (confirm_default_target){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (frame && detect_battle_move_menu(frame).move_count){ + pbf_press_button(context, BUTTON_B, 80ms, 500ms); + } + } + bool fight = false; + size_t confirmed = 0; + for (size_t check = 0; check < 30; check++){ + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + BattleMenuOption selected = frame ? commands.selected_option(frame) : BattleMenuOption::UNKNOWN; + stream.log("[" + label + "] Command cursor=" + std::to_string((int)selected + 1) + "; target Battle=1."); + if (selected == BattleMenuOption::BATTLE){ + if (++confirmed >= 3){ + fight = true; + break; + } + pbf_wait(context, 100ms); + }else{ + confirmed = 0; + if (selected == BattleMenuOption::UNKNOWN){ + pbf_wait(context, 150ms); + }else{ + pbf_press_dpad(context, DPAD_UP, 80ms, 250ms); + } + } + } + if (!fight){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Could not confirm Battle command; A not pressed.", stream); + } + pbf_press_button(context, BUTTON_A, 80ms, confirm_default_target ? 1000ms : 500ms); + if (confirm_default_target){ + // Reuse the farming move/target confirmation sequence, but + // navigate relative to this battle's retained move cursor. + // Reopening Fight does not reset it to move 1. + size_t available = 0, stable = 0; + for (size_t check = 0; check < 20 && stable < 3; check++){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + const auto moves = frame ? detect_battle_move_menu(frame) : BattleMoveMenuState{}; + if (moves.move_count && moves.move_count == available){ + stable++; + }else{ + available = moves.move_count; + stable = moves.move_count ? 1 : 0; + } + if (stable < 3){ + pbf_wait(context, 100ms); + } + } + if (stable < 3 || available < target || available < current_move){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Requested move position is not available.", stream); + } + stream.log("[" + label + "] Retained move cursor " + std::to_string(current_move) + " -> " + + std::to_string(target) + "."); + for (size_t slot = current_move; slot < target; slot++){ + pbf_press_dpad(context, DPAD_DOWN, 80ms, 400ms); + } + for (size_t slot = current_move; slot > target; slot--){ + pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); + } + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); + pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + return; + } + confirmed = 0; + size_t previous_move_count = 0, stable_move_count = 0; + for (size_t check = 0; check < 30; check++){ + context.wait_for_all_requests(); + auto frame = stream.video().snapshot(); + const BattleMoveMenuState moves = frame ? detect_battle_move_menu(frame) : BattleMoveMenuState{}; + const int slot = (int)moves.selected_slot; + VideoOverlaySet move_overlays(stream.overlay()); + for (size_t i = 0; i < moves.move_count; i++){ + move_overlays.add(moves.selected_slot == i + 1 ? COLOR_GREEN : COLOR_YELLOW, moves.buttons[i]); + } + stream.log("[" + label + "] Available moves=" + std::to_string(moves.move_count) + + "; selected slot=" + std::to_string(slot) + "/4; target=" + std::to_string(target) + "."); + if (moves.move_count == previous_move_count && moves.move_count > 0){ + stable_move_count++; + }else{ + previous_move_count = moves.move_count; + stable_move_count = 1; + confirmed = 0; + } + if (moves.move_count > 0 && moves.move_count < target && stable_move_count >= 3){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Move slot " + std::to_string(target) + " is absent; no move confirmed.", stream); + } + if (slot == (int)target && stable_move_count >= 3){ + if (++confirmed >= 3){ + stream.log("[" + label + "] Move " + std::to_string(target) + " confirmed; using it."); + pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + return; + } + pbf_wait(context, 100ms); + }else{ + confirmed = 0; + if (slot == 0 || slot == (int)target){ + pbf_wait(context, 150ms); + }else{ + pbf_press_dpad(context, slot < (int)target ? DPAD_DOWN : DPAD_UP, 80ms, 250ms); + } + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Could not confirm move " + std::to_string(target) + "; A not pressed.", stream); +} - BattleMenuWatcher battle_menu(BattleType::TRAINER); - EndBattleWatcher end_battle; - - // Detect move-learning arrows. - SelectionArrowFinder learn_move( - stream.overlay(), - {0.50, 0.62, 0.40, 0.18}, - COLOR_YELLOW - ); - - int ret = run_until( - stream, - context, - [](ProControllerContext& context) { - // Continuously clear: - // EXP gain - // level up - // faint text - // send-out text - // battle narration - pbf_mash_button( - context, - BUTTON_B, - 120000ms - ); - }, - { - {battle_menu}, - battle_menu_seen - ? PeriodicInferenceCallback{end_battle} - : PeriodicInferenceCallback{}, - {learn_move}, - } - ); +static void select_natalie_move_three(VideoStream& stream, ProControllerContext& context){ + select_battle_move_slot(stream, context, BattleType::TRAINER, 3, "Natalie"); +} - switch (ret) { +void fight_Natalie(VideoStream& stream, ProControllerContext& context){ + stream.log("[AutoStory] Starting Natalie battle...", COLOR_BLUE); - // ------------------------------------------------- - // Battle menu detected. - // ------------------------------------------------- - case 0: - { - stream.log( - "[AutoStory] Battle menu detected.", - COLOR_BLUE - ); + // Allow battle transition to finish. + pbf_wait(context, 5000ms); - battle_menu_seen = true; + bool battle_menu_seen = false; - select_natalie_move_three(stream, context); + // State machine loop. + while (true){ - break; - } + context.wait_for_all_requests(); - // ------------------------------------------------- - // Battle finished. - // ------------------------------------------------- - case 1: - { - stream.log( - "[AutoStory] Natalie battle complete.", - COLOR_GREEN - ); + BattleMenuWatcher battle_menu(BattleType::TRAINER); + EndBattleWatcher end_battle; - pbf_mash_button( - context, - BUTTON_B, - 5000ms - ); + // Detect move-learning arrows. + SelectionArrowFinder learn_move(stream.overlay(), {0.50, 0.62, 0.40, 0.18}, COLOR_YELLOW); - return; - } + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + // Continuously clear: + // EXP gain + // level up + // faint text + // send-out text + // battle narration + pbf_mash_button(context, BUTTON_B, 120000ms); + }, + { + {battle_menu}, + battle_menu_seen ? PeriodicInferenceCallback{end_battle} : PeriodicInferenceCallback{}, + {learn_move}, + }); - // ------------------------------------------------- - // Move learning detected. - // ------------------------------------------------- - case 2: - { - stream.log( - "[AutoStory] Learn move detected.", - COLOR_ORANGE - ); - - // Always refuse move learning. - pbf_move_right_joystick( - context, - { 0, -1 }, - 160ms, - 840ms - ); - - pbf_press_button( - context, - BUTTON_A, - 160ms, - 840ms - ); + switch (ret){ - break; - } + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: { + stream.log("[AutoStory] Battle menu detected.", COLOR_BLUE); - // ------------------------------------------------- - // Timeout. - // ------------------------------------------------- - default: - { - stream.log( - "[AutoStory] Natalie battle timed out.", - COLOR_RED - ); - - pbf_mash_button( - context, - BUTTON_B, - 5000ms - ); - - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] Natalie battle timed out.", stream); - } + battle_menu_seen = true; - } - } - } + select_natalie_move_three(stream, context); - // --------------------------------------------------------------------------- - // Starly battle - // --------------------------------------------------------------------------- + break; + } - void fight_starly(VideoStream& stream, ProControllerContext& context) { - stream.log("[AutoStory] Starting Starly battle...", COLOR_BLUE); + // ------------------------------------------------- + // Battle finished. + // ------------------------------------------------- + case 1: { + stream.log("[AutoStory] Natalie battle complete.", COLOR_GREEN); - // Declare OUTSIDE the loop so accumulated detector state - // (trigger counts, black-screen transitions) is preserved across frames. - // STARTER type: player has no Pokeballs, so ball-count areas won't - // be white. STARTER skips that check and only verifies status bars - // and the Fight button color. - BattleMenuWatcher battle_menu(BattleType::STARTER, COLOR_YELLOW); - EndBattleWatcher end_battle; + pbf_mash_button(context, BUTTON_B, 5000ms); - while (true) { - context.wait_for_all_requests(); + return; + } - // Phase 1: wait for either battle menu or battle end. - int ret = run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - { - {battle_menu}, - {end_battle}, - } - ); + // ------------------------------------------------- + // Move learning detected. + // ------------------------------------------------- + case 2: { + stream.log("[AutoStory] Learn move detected.", COLOR_ORANGE); - switch (ret) { - case 0: - { - stream.log("[AutoStory] Battle menu detected. Selecting move...", COLOR_BLUE); + // Always refuse move learning. + pbf_move_right_joystick(context, {0, -1}, 160ms, 840ms); - // Select Fight, then move slot 1. - pbf_press_button(context, BUTTON_A, 80ms, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 3000ms); - // Loop back - mash B through animations until next menu or end. - break; - } - case 1: - { - stream.log("[AutoStory] Starly battle complete.", COLOR_GREEN); - - // Mash through XP / level-up / dialog screens back to overworld. - OverworldWatcher overworld; - int cleanup_ret = run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - { {overworld} } - ); - if (cleanup_ret != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Starly battle: Overworld cleanup timed out.", stream); - } - return; - } - default: - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Starly battle timed out.", stream); - } - } - } + pbf_press_button(context, BUTTON_A, 160ms, 840ms); - // --------------------------------------------------------------------------- - // Tristan battle - // --------------------------------------------------------------------------- - static void encounter_one_wild_pokemon(VideoStream& stream, ProControllerContext& context) { - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); + break; + } - bool encounter_found = false; - const auto deadline = std::chrono::steady_clock::now() + 180s; + // ------------------------------------------------- + // Timeout. + // ------------------------------------------------- + default: { + stream.log("[AutoStory] Natalie battle timed out.", COLOR_RED); - // Wiggle left/right until battle starts. - while (!encounter_found && std::chrono::steady_clock::now() < deadline) { - BlackScreenWatcher battle_start; - BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); + pbf_mash_button(context, BUTTON_B, 5000ms); - // Turn left. - pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); - context.wait_for_all_requests(); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "[AutoStory] Natalie battle timed out.", stream); + } + } + } +} - int ret = wait_until( - stream, - context, - 1500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - } - ); +// --------------------------------------------------------------------------- +// Starly battle +// --------------------------------------------------------------------------- + +void fight_starly(VideoStream& stream, ProControllerContext& context){ + stream.log("[AutoStory] Starting Starly battle...", COLOR_BLUE); + + // Declare OUTSIDE the loop so accumulated detector state + // (trigger counts, black-screen transitions) is preserved across frames. + // STARTER type: player has no Pokeballs, so ball-count areas won't + // be white. STARTER skips that check and only verifies status bars + // and the Fight button color. + BattleMenuWatcher battle_menu(BattleType::STARTER, COLOR_YELLOW); + EndBattleWatcher end_battle; + + while (true){ + context.wait_for_all_requests(); + + // Phase 1: wait for either battle menu or battle end. + int ret = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + { + {battle_menu}, + {end_battle}, + }); + + switch (ret){ + case 0: { + stream.log("[AutoStory] Battle menu detected. Selecting move...", COLOR_BLUE); + + // Select Fight, then move slot 1. + pbf_press_button(context, BUTTON_A, 80ms, 500ms); + pbf_press_button(context, BUTTON_A, 80ms, 3000ms); + // Loop back - mash B through animations until next menu or end. + break; + } + case 1: { + stream.log("[AutoStory] Starly battle complete.", COLOR_GREEN); + + // Mash through XP / level-up / dialog screens back to overworld. + OverworldWatcher overworld; + int cleanup_ret = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}}); + if (cleanup_ret != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Starly battle: Overworld cleanup timed out.", stream); + } + return; + } + default: + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Starly battle timed out.", + stream); + } + } +} - if (ret >= 0) { - encounter_found = true; - break; - } +// --------------------------------------------------------------------------- +// Tristan battle +// --------------------------------------------------------------------------- +static void encounter_one_wild_pokemon(VideoStream& stream, ProControllerContext& context){ + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + + bool encounter_found = false; + const auto deadline = std::chrono::steady_clock::now() + 180s; + + // Wiggle left/right until battle starts. + while (!encounter_found && std::chrono::steady_clock::now() < deadline){ + BlackScreenWatcher battle_start; + BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + + // Turn left. + pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); + context.wait_for_all_requests(); + + int ret = wait_until(stream, context, 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + }); + + if (ret >= 0){ + encounter_found = true; + break; + } - // Turn right. - pbf_move_left_joystick(context, {1, 0}, 120ms, 200ms); - context.wait_for_all_requests(); + // Turn right. + pbf_move_left_joystick(context, {1, 0}, 120ms, 200ms); + context.wait_for_all_requests(); - ret = wait_until( - stream, - context, - 1500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - } - ); + ret = wait_until(stream, context, 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + }); - if (ret >= 0) { - encounter_found = true; - break; - } - } + if (ret >= 0){ + encounter_found = true; + break; + } + } - if (!encounter_found) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Wild encounter: Left/right wiggle did not find a battle within three minutes.", stream); - stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); + if (!encounter_found){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Wild encounter: Left/right wiggle did not find a battle within three minutes.", stream); + } + stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); - // Wait until the actual battle menu appears. - BattleMenuWatcher confirm_menu(BattleType::STANDARD); + // Wait until the actual battle menu appears. + BattleMenuWatcher confirm_menu(BattleType::STANDARD); - int menu_ret = wait_until( - stream, - context, - 15s, - { - {confirm_menu}, - } - ); - - if (menu_ret < 0) { - stream.log( - "[AutoStory] Failed to detect battle menu.", - COLOR_RED - ); - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] Failed to detect battle menu.", stream); - } + int menu_ret = wait_until(stream, context, 15s, + { + {confirm_menu}, + }); - stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); + if (menu_ret < 0){ + stream.log("[AutoStory] Failed to detect battle menu.", COLOR_RED); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "[AutoStory] Failed to detect battle menu.", stream); + } - context.wait_for_all_requests(); - pbf_wait(context, 500ms); + stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); - } + context.wait_for_all_requests(); + pbf_wait(context, 500ms); +} - static void confirm_battle_overworld(VideoStream& stream, ProControllerContext& context, - const std::string& label) { - OverworldDetector overworld; - ShortDialogDetector dialogue(COLOR_YELLOW); - BlackScreenDetector black; - VideoOverlaySet overlays(stream.overlay()); - overworld.make_overlays(overlays); - dialogue.make_overlays(overlays); - black.make_overlays(overlays); - const auto deadline = std::chrono::steady_clock::now() + 180s; - auto quiet_since = std::chrono::steady_clock::now(); - bool finished = false; - while (std::chrono::steady_clock::now() < deadline) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); - const auto now = std::chrono::steady_clock::now(); - if (dialogue.detect(frame) || black.detect(frame) || !overworld.detect(frame)) { - quiet_since = now; - } else if (now - quiet_since >= 4s) { - finished = true; - break; - } - pbf_mash_button(context, BUTTON_B, 100ms); - context.wait_for_all_requests(); - } - if (!finished) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Dialogue-free overworld not confirmed for four seconds.", stream); - context.wait_for_all_requests(); - } +static void confirm_battle_overworld(VideoStream& stream, ProControllerContext& context, const std::string& label){ + OverworldDetector overworld; + ShortDialogDetector dialogue(COLOR_YELLOW); + BlackScreenDetector black; + VideoOverlaySet overlays(stream.overlay()); + overworld.make_overlays(overlays); + dialogue.make_overlays(overlays); + black.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 180s; + auto quiet_since = std::chrono::steady_clock::now(); + bool finished = false; + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + } + const auto now = std::chrono::steady_clock::now(); + if (dialogue.detect(frame) || black.detect(frame) || !overworld.detect(frame)){ + quiet_since = now; + }else if (now - quiet_since >= 4s){ + finished = true; + break; + } + pbf_mash_button(context, BUTTON_B, 100ms); + context.wait_for_all_requests(); + } + if (!finished){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Dialogue-free overworld not confirmed for four seconds.", + stream); + } + context.wait_for_all_requests(); +} - static void fight_using_move_list( - VideoStream& stream, ProControllerContext& context, - BattleType battle_type, const std::vector& move_slots, - const std::string& label, bool confirm_default_target, bool post_battle_dialogue_cleanup - ) { - if (move_slots.empty() || std::any_of(move_slots.begin(),move_slots.end(), - [](size_t slot) { return slot<1 || slot>4; })) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Supply a nonempty move list containing only positions 1 through 4.",stream); - } - size_t move_index=0; - // The first Fight menu starts at move 1. Later turns retain - // this Pokemon's previous selection; a new battle resets it. - size_t current_move=1; - stream.log( - "[AutoStory] Starting " + label + " battle...", - COLOR_BLUE - ); +static void fight_using_move_list(VideoStream& stream, ProControllerContext& context, BattleType battle_type, + const std::vector& move_slots, const std::string& label, + bool confirm_default_target, bool post_battle_dialogue_cleanup){ + if (move_slots.empty() || std::any_of(move_slots.begin(), move_slots.end(), [](size_t slot){ + return slot < 1 || slot > 4; + })){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Supply a nonempty move list containing only positions 1 through 4.", stream); + } + size_t move_index = 0; + // The first Fight menu starts at move 1. Later turns retain + // this Pokemon's previous selection; a new battle resets it. + size_t current_move = 1; + stream.log("[AutoStory] Starting " + label + " battle...", COLOR_BLUE); + + // Scripted battles wait on the menu detector below, so no + // extra fixed delay is needed after a caller confirms it. + // Preserve the existing early-game transition delay. + if (!confirm_default_target){ + pbf_wait(context, 5000ms); + } - // Scripted battles wait on the menu detector below, so no - // extra fixed delay is needed after a caller confirms it. - // Preserve the existing early-game transition delay. - if (!confirm_default_target) pbf_wait(context,5000ms); + bool battle_menu_seen = false; + EndBattleWatcher end_battle; - bool battle_menu_seen = false; - EndBattleWatcher end_battle; + while (true){ - while (true) { + context.wait_for_all_requests(); - context.wait_for_all_requests(); + BattleMenuWatcher battle_menu(battle_type); + // Reuse the farming handlers' move-learning prompt recovery. + SelectionArrowFinder learn_move(stream.overlay(), {0.50, 0.62, 0.40, 0.18}, COLOR_YELLOW); - BattleMenuWatcher battle_menu(battle_type); - // Reuse the farming handlers' move-learning prompt recovery. - SelectionArrowFinder learn_move(stream.overlay(),{0.50,0.62,0.40,0.18},COLOR_YELLOW); - - int ret = run_until( - stream, - context, - [](ProControllerContext& context) { - // Clear narration with B while watching for commands. - // A here can open Fight before selection begins. - pbf_mash_button(context,BUTTON_B,120000ms); - }, - { - {battle_menu}, - battle_menu_seen - ? PeriodicInferenceCallback{end_battle} - : PeriodicInferenceCallback{}, - confirm_default_target ? PeriodicInferenceCallback{learn_move} : PeriodicInferenceCallback{}, - } - ); + int ret = run_until( + stream, context, + [](ProControllerContext& context){ + // Clear narration with B while watching for commands. + // A here can open Fight before selection begins. + pbf_mash_button(context, BUTTON_B, 120000ms); + }, + { + {battle_menu}, + battle_menu_seen ? PeriodicInferenceCallback{end_battle} : PeriodicInferenceCallback{}, + confirm_default_target ? PeriodicInferenceCallback{learn_move} : PeriodicInferenceCallback{}, + }); + + switch (ret){ + + // ------------------------------------------------- + // Battle menu detected. + // ------------------------------------------------- + case 0: + battle_menu_seen = true; + + stream.log("[AutoStory] Battle menu detected.", COLOR_BLUE); + + stream.log("[" + label + "] Move list position " + std::to_string(move_index + 1) + ": use move " + + std::to_string(move_slots[move_index]) + "."); + select_battle_move_slot(stream, context, battle_type, move_slots[move_index], label, confirm_default_target, + current_move); + current_move = move_slots[move_index]; + // Advance once per move selection; retain the final entry. + if (move_index + 1 < move_slots.size()){ + move_index++; + } - switch (ret) { + break; - // ------------------------------------------------- - // Battle menu detected. - // ------------------------------------------------- - case 0: - battle_menu_seen = true; + // ------------------------------------------------- + // End battle detected. + // ------------------------------------------------- + case 1: { + stream.log("[AutoStory] " + label + " battle complete.", COLOR_GREEN); - stream.log( - "[AutoStory] Battle menu detected.", - COLOR_BLUE - ); - - stream.log("["+label+"] Move list position "+std::to_string(move_index+1)+ - ": use move "+std::to_string(move_slots[move_index])+"."); - select_battle_move_slot(stream, context, battle_type, move_slots[move_index], label, - confirm_default_target,current_move); - current_move=move_slots[move_index]; - // Advance once per move selection; retain the final entry. - if (move_index+1(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}}); + if (cleanup_ret != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Post-battle cleanup timed out.", stream); + } + context.wait_for_all_requests(); + confirm_battle_overworld(stream, context, label + " cleanup"); + return; + } - break; + case 2: + stream.log("[" + label + "] Declining move learning; preserving scripted move positions."); + pbf_move_right_joystick(context, {0, -1}, 160ms, 840ms); + pbf_press_button(context, BUTTON_ZL, 160ms, 840ms); + break; - // ------------------------------------------------- - // End battle detected. - // ------------------------------------------------- - case 1: { - stream.log( - "[AutoStory] " + label + " battle complete.", - COLOR_GREEN - ); - - // EndBattleWatcher has confirmed the completed exit transition. - // Ordinary trainers need no scene-wide dialogue-free gate. - if (!post_battle_dialogue_cleanup) { - context.wait_for_all_requests(); - return; - } - // Story NPCs and gym leaders opt into the existing cleanup. - OverworldWatcher overworld; - int cleanup_ret = run_until(stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, {{overworld}}); - if (cleanup_ret != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Post-battle cleanup timed out.", stream); - } - context.wait_for_all_requests(); - confirm_battle_overworld(stream, context, label + " cleanup"); - return; - } + default: + stream.log("[AutoStory] " + label + " battle timeout.", COLOR_RED); - case 2: - stream.log("["+label+"] Declining move learning; preserving scripted move positions."); - pbf_move_right_joystick(context,{0,-1},160ms,840ms); - pbf_press_button(context,BUTTON_ZL,160ms,840ms); - break; + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "[AutoStory] " + label + " battle timeout.", stream); + } + } +} - default: - stream.log( - "[AutoStory] " + label + " battle timeout.", - COLOR_RED - ); +// Preserve the existing early-game callers and their B text cleanup. +static void fight_using_move_slot(VideoStream& stream, ProControllerContext& context, BattleType battle_type, + size_t move_slot, const std::string& label){ + fight_using_move_list(stream, context, battle_type, {move_slot}, label, false, true); +} - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] " + label + " battle timeout.", stream); - } +void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, Button button, + Milliseconds quiet_period, const char* instruction_card){ + context.wait_for_all_requests(); + std::unique_ptr card; + if (instruction_card){ + card = std::make_unique(std::make_shared(RESOURCE_PATH() + instruction_card), + ImageFloatBox{.04, .32, .92, .34}, 30.0); + } + ShortDialogDetector dialogue(COLOR_YELLOW); + BlackScreenDetector black; + VideoOverlaySet overlays(stream.overlay()); + dialogue.make_overlays(overlays); + black.make_overlays(overlays); + const auto deadline = std::chrono::steady_clock::now() + 120s; + auto absent_since = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() < deadline){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Post-battle dialogue: No video frame.", stream); + } + const auto now = std::chrono::steady_clock::now(); + const bool dialogue_visible = dialogue.detect(*frame.frame); + const bool card_visible = card && card->detect(*frame.frame); + if (dialogue_visible || card_visible || black.detect(*frame.frame)){ + absent_since = now; + }else if (now - absent_since >= quiet_period){ + stream.log("[Post-battle dialogue] " + std::to_string(quiet_period.count()) + + "ms without dialogue confirmed."); + return; + } + // A can interact again once dialogue disappears. Only confirm + // visible prompts; B retains the existing post-battle behavior. + if (card_visible){ + pbf_press_button(context, BUTTON_B, 80ms, 100ms); + }else if (button == BUTTON_A && !dialogue_visible){ + pbf_wait(context, 100ms); + }else{ + pbf_mash_button(context, button, 100ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Post-battle dialogue: Three clear seconds not confirmed.", stream); +} + +void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, + bool allow_story_start, bool horizontal_first, bool search_down_first, + OverworldMatchResult (*entry_matcher)(const ImageRGB32&), bool* entry_detected, + double arrival_tolerance_x, double arrival_tolerance_y, + double moderate_entry_confidence, bool* rock_acquisition_timed_out){ + if (rock_acquisition_timed_out){ + *rock_acquisition_timed_out = false; + } + auto missing_rocks_since = std::chrono::steady_clock::time_point::max(); + if (entry_detected){ + *entry_detected = false; + } + context.wait_for_all_requests(); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + ShortDialogDetector dialogue(COLOR_YELLOW); + BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); + size_t confirmations = 0, missing = 0; + bool ace_bypass = false; + const bool route215_renewal = matcher == match_route215_bridge_renewal || matcher == match_route215_ace_renewal || + matcher == match_route215_derek_stairs_renewal || + matcher == match_route215_derek_stair_top_renewal; + Milliseconds correction_movement = 0ms; + for (size_t attempt = 0; attempt < 80; attempt++){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No video frame.", stream); + } + if (entry_matcher){ + BlackScreenDetector transition; + if (entry_matcher(*frame.frame).located || transition.detect(*frame.frame)){ + BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Building transition did not finish.", stream); } + const auto inside = stream.video().snapshot(); + if (!inside || !entry_matcher(*inside.frame).located){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Expected interior not confirmed after transition.", stream); + } + if (entry_detected){ + *entry_detected = true; + } + stream.log("[" + label + "] Expected interior confirmed; ending outdoor alignment.", COLOR_GREEN); + return; } - - // Preserve the existing early-game callers and their B text cleanup. - static void fight_using_move_slot(VideoStream& stream, ProControllerContext& context, - BattleType battle_type, size_t move_slot, const std::string& label) { - fight_using_move_list(stream,context,battle_type,{move_slot},label,false,true); + } + // The story can start before the exact reference point. Hand off + // to battle preparation instead of steering during dialogue. + if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)){ + if (allow_story_start){ + stream.log("[" + label + "] Story dialogue/battle started; handing off without further movement."); + return; } - - void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, - Button button, Milliseconds quiet_period, - const char* instruction_card) { + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Unexpected dialogue/battle during alignment.", stream); + } + const auto result = matcher(*frame.frame); + if (result.located){ + missing_rocks_since = std::chrono::steady_clock::time_point::max(); + } + VideoOverlaySet overlays(stream.overlay()); + for (const auto& landmark : result.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); + } + stream.log("[" + label + "] " + landmark.name + " score=" + std::to_string(landmark.score) + + ", margin=" + std::to_string(landmark.margin) + ", offset=(" + std::to_string(landmark.dx) + + "," + std::to_string(landmark.dy) + ")"); + } + stream.log("[" + label + "] located=" + std::to_string(result.located) + + ", coarse=" + std::to_string(result.coarse) + ", at_target=" + std::to_string(result.at_target) + + ", consensus offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); + // The blue-haired NPC patrols the narrow eastern row. Shift + // one tile north on the clear road before crossing west; + // return to the original row only after clearing his column. + if (matcher == match_route215_ace_renewal && result.located){ + if (result.dx < -1.0 / 12){ + ace_bypass = true; + }else if (result.dx >= -1.0 / 32){ + ace_bypass = false; + } + } + const bool ace_row_recovery = ace_bypass && std::abs(result.dy - 70.0 / 1080) > 1.0 / 108; + const bool ace_central_row = matcher == match_route215_ace_renewal && !ace_bypass && result.coarse && + std::abs(result.dx) <= 1.0 / 12 && std::abs(result.dy) > 1.0 / 54; + const double entry_confidence = moderate_entry_confidence > 0 ? overworld_entry_confidence(result) : 0; + const bool moderate_arrival = moderate_entry_confidence > 0 && entry_confidence >= moderate_entry_confidence; + if (moderate_entry_confidence > 0){ + stream.log("[" + label + "] Exterior entry confidence=" + std::to_string(entry_confidence) + + ", moderate threshold=" + std::to_string(moderate_entry_confidence) + + ", strict target=" + std::to_string(result.at_target)); + } + // Explicit loose bounds are for non-interacting route handoffs. + // Default callers still require the matcher's precise arrival. + const bool rough_arrival = arrival_tolerance_x > 0 && arrival_tolerance_y > 0 && result.located && + std::abs(result.dx) <= arrival_tolerance_x && + std::abs(result.dy) <= arrival_tolerance_y; + if (result.at_target || rough_arrival || moderate_arrival){ + if (++confirmations >= 3){ + stream.log("[" + label + "] Position confirmed."); + return; + } + pbf_wait(context, 250ms); + }else if (result.located){ + confirmations = 0; + missing = 0; + // Wait at an aligned pose while strict visual support + // recovers, rather than stepping away under a cloud. + if ((matcher == match_galactic_hq_jupiter || matcher == match_route208_hannah_departure || + matcher == match_route208_stair_top) && + result.coarse && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 54){ + pbf_wait(context, 500ms); context.wait_for_all_requests(); - std::unique_ptr card; - if (instruction_card) card = std::make_unique( - std::make_shared(RESOURCE_PATH()+instruction_card), - ImageFloatBox{.04,.32,.92,.34},30.0); - ShortDialogDetector dialogue(COLOR_YELLOW); - BlackScreenDetector black; - VideoOverlaySet overlays(stream.overlay()); - dialogue.make_overlays(overlays); - black.make_overlays(overlays); - const auto deadline = std::chrono::steady_clock::now() + 120s; - auto absent_since = std::chrono::steady_clock::now(); - while (std::chrono::steady_clock::now() < deadline) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Post-battle dialogue: No video frame.", stream); - const auto now = std::chrono::steady_clock::now(); - const bool dialogue_visible = dialogue.detect(*frame.frame); - const bool card_visible = card && card->detect(*frame.frame); - if (dialogue_visible || card_visible || black.detect(*frame.frame)) absent_since = now; - else if (now - absent_since >= quiet_period) { - stream.log("[Post-battle dialogue] " + std::to_string(quiet_period.count()) + - "ms without dialogue confirmed."); - return; + continue; + } + // A coarse match cannot confirm position, but two accepted + // anchors can establish a safe horizontal direction when both + // offsets are well outside the uncertainty in their spacing. + bool coarse_horizontal = false; + if (horizontal_first && result.coarse && result.landmarks.size() == 2){ + const auto& a = result.landmarks[0]; + const auto& b = result.landmarks[1]; + const double nearest = std::min(std::abs(a.dx), std::abs(b.dx)); + coarse_horizontal = a.accepted && b.accepted && a.dx * b.dx > 0 && nearest > 1.0 / 64 && + nearest > 2 * std::abs(a.dx - b.dx); + } + const bool plaza_anchor_horizontal = matcher == match_jubilife_plaza_approach && result.coarse && + std::abs(result.dx) > 1.0 / 50 && std::abs(result.dy) <= 1.0 / 54; + // A uniquely matched Eterna ball may correct the door + // column while the sign/bush enter the frame. Arrival + // still requires the matcher's full three-anchor check. + const bool eterna_anchor_horizontal = + (matcher == match_eterna_center_entrance || matcher == match_eterna_underground_house || + matcher == match_eterna_underground_man) && + result.coarse && std::abs(result.dx) > 1.0 / 128; + // Recover the gym's doorway row before moving right if + // the south approach overshot toward the fence/cliff. + const bool gym_anchor_horizontal = matcher == match_eterna_gym_entrance && result.coarse && + std::abs(result.dx) > 1.0 / 128 && std::abs(result.dy) <= .06; + const bool gym_row_recovery = + matcher == match_eterna_gym_entrance && result.coarse && std::abs(result.dy) > .06; + // Reach the row north of Jupiter before entering her + // column. From above that row, align sideways before + // moving south so the NPC cannot block the correction. + const bool jupiter_row_recovery = matcher == match_galactic_hq_jupiter && result.dy < -1.0 / 54; + const bool jupiter_anchor_horizontal = matcher == match_galactic_hq_jupiter && !jupiter_row_recovery && + result.coarse && std::abs(result.dx) > 1.0 / 128; + // Stay south of the Contest Hall wall/sign before crossing to Fantina. + const bool contest_row_recovery = matcher == match_hearthome_contest_fantina && result.dy > 1.0 / 54; + // Keep the stair approach horizontal-first until close to + // its column. Recorded fine poses otherwise spent repeated + // 16ms sideways turns while a larger vertical error waited. + // Fine correction changes axis priority, never arrival gates. + const bool stair_fine_correction = + matcher == match_route208_stair_top && !result.coarse && std::abs(result.dx) <= 1.0 / 64; + const bool horizontal = + !ace_row_recovery && !ace_central_row && + (ace_bypass || (!contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && + (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || + eterna_anchor_horizontal || gym_anchor_horizontal || + (!result.coarse && + ((horizontal_first && !stair_fine_correction && std::abs(result.dx) > 1.0 / 128) || + std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16))))); + if (stair_fine_correction && horizontal_first && !horizontal && std::abs(result.dx) > 1.0 / 128){ + stream.log("[" + label + "] Near stair column; correcting the larger vertical error first."); + } + if (ace_row_recovery){ + stream.log("[" + label + "] Clearing the NPC row before crossing west."); + }else if (ace_central_row){ + stream.log("[" + label + "] Both road bends support restoring the arrival row."); + }else if (coarse_horizontal && horizontal){ + stream.log("[" + label + + "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); + } + const double error = horizontal ? result.dx : (result.dy - (ace_bypass ? 70.0 / 1080 : 0.0)) * 9.0 / 16; + auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 1.0 / 64 ? 40ms : 16ms; + // A 16ms turn may not complete a step. Use a bounded + // full correction while reacquiring the second plaza anchor. + if (matcher == match_jubilife_plaza_approach && result.coarse){ + duration = std::max(duration, 60ms); + } + if (matcher == match_galactic_hq_jupiter){ + for (const auto& landmark : result.landmarks){ + // The player stands around y=.74. Do not steer + // sideways/north when the stair opening is close + // to that row; a southward correction moves away. + if (landmark.name == "StairBoundary" && landmark.accepted && + landmark.found.y + landmark.found.height > .60 && (horizontal || result.dy < 0)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Correction would approach the stair boundary.", stream); } - // A can interact again once dialogue disappears. Only confirm - // visible prompts; B retains the existing post-battle behavior. - if (card_visible) pbf_press_button(context, BUTTON_B, 80ms, 100ms); - else if (button == BUTTON_A && !dialogue_visible) pbf_wait(context, 100ms); - else pbf_mash_button(context, button, 100ms); - context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Post-battle dialogue: Three clear seconds not confirmed.", stream); } - - void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, bool allow_story_start, bool horizontal_first, - bool search_down_first, - OverworldMatchResult (*entry_matcher)(const ImageRGB32&), - bool* entry_detected, - double arrival_tolerance_x, double arrival_tolerance_y, - double moderate_entry_confidence, bool* rock_acquisition_timed_out) { - if (rock_acquisition_timed_out) *rock_acquisition_timed_out=false; - auto missing_rocks_since=std::chrono::steady_clock::time_point::max(); - if (entry_detected) *entry_detected=false; - context.wait_for_all_requests(); + if (route215_renewal){ + if (correction_movement + duration > 1500ms){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, label + ": Renewal alignment movement budget exhausted.", + stream); + } + correction_movement += duration; + } + pbf_move_left_joystick( + context, {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (error < 0 ? 1.0 : -1.0)}, + duration, 0ms); + pbf_wait(context, 500ms); + }else{ + confirmations = 0; + if (matcher == match_hearthome_center_entrance || matcher == match_hearthome_contest_fantina || + matcher == match_veilstone_city_passage || matcher == match_veilstone_dawn_stairs || route215_renewal){ + // Do not sweep into a different doorway or around the Hall's + // side, or onto a Veilstone terrace, while required anchors are hidden. + if (++missing > 20){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Scene anchors missing; stationary reacquisition exhausted.", stream); + } + // Recorded expiry endpoints can be north of the passage. + // Acquire its buildings along the clear south-only strip; + // cap total search motion, then remain stationary. + if (matcher == match_veilstone_city_passage && search_down_first && correction_movement < 1800ms){ + pbf_move_left_joystick(context, {0.0, -1.0}, 100ms, 0ms); + correction_movement += 100ms; + } pbf_wait(context, 500ms); context.wait_for_all_requests(); - ShortDialogDetector dialogue(COLOR_YELLOW); - BattleMenuDetector battle(BattleType::TRAINER, COLOR_GREEN); - size_t confirmations = 0, missing = 0; - bool ace_bypass = false; - const bool route215_renewal=matcher==match_route215_bridge_renewal || - matcher==match_route215_ace_renewal || matcher==match_route215_derek_stairs_renewal || - matcher==match_route215_derek_stair_top_renewal; - Milliseconds correction_movement=0ms; - for (size_t attempt = 0; attempt < 80; attempt++) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); - if (entry_matcher){ - BlackScreenDetector transition; - if (entry_matcher(*frame.frame).located || transition.detect(*frame.frame)){ - BlackScreenWatcher visible(COLOR_RED,{0.1,0.1,0.8,0.8}, - 100,10,BlackScreenWatcher::FinderType::GONE,500ms); - if (wait_until(stream,context,30s,{{visible}})!=0) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Building transition did not finish.",stream); - const auto inside=stream.video().snapshot(); - if (!inside || !entry_matcher(*inside.frame).located) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Expected interior not confirmed after transition.",stream); - if(entry_detected)*entry_detected=true; - stream.log("["+label+"] Expected interior confirmed; ending outdoor alignment.",COLOR_GREEN); - return; - } - } - // The story can start before the exact reference point. Hand off - // to battle preparation instead of steering during dialogue. - if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)) { - if (allow_story_start) { - stream.log("[" + label + "] Story dialogue/battle started; handing off without further movement."); - return; - } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Unexpected dialogue/battle during alignment.", stream); - } - const auto result = matcher(*frame.frame); - if (result.located) - missing_rocks_since=std::chrono::steady_clock::time_point::max(); - VideoOverlaySet overlays(stream.overlay()); - for (const auto& landmark : result.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[" + label + "] " + landmark.name + " score=" + - std::to_string(landmark.score) + ", margin=" + std::to_string(landmark.margin) + - ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); - } - stream.log("[" + label + "] located=" + std::to_string(result.located) + - ", coarse=" + std::to_string(result.coarse) + ", at_target=" + std::to_string(result.at_target) + - ", consensus offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); - // The blue-haired NPC patrols the narrow eastern row. Shift - // one tile north on the clear road before crossing west; - // return to the original row only after clearing his column. - if (matcher==match_route215_ace_renewal && result.located){ - if (result.dx< -1.0/12) ace_bypass=true; - else if (result.dx>=-1.0/32) ace_bypass=false; + continue; + } + if (matcher == match_galactic_hq_jupiter || matcher == match_route208_stair_top){ + // Keep generic missing-anchor behavior unchanged. Route208's + // explicit caller can instead request its recorded recovery. + const bool stair_rocks = matcher == match_route208_stair_top; + if (stair_rocks && rock_acquisition_timed_out){ + const auto now = std::chrono::steady_clock::now(); + if (missing_rocks_since == std::chrono::steady_clock::time_point::max()){ + missing_rocks_since = now; } - const bool ace_row_recovery=ace_bypass && - std::abs(result.dy-70.0/1080)>1.0/108; - const bool ace_central_row=matcher==match_route215_ace_renewal && - !ace_bypass && result.coarse && std::abs(result.dx)<=1.0/12 && - std::abs(result.dy)>1.0/54; - const double entry_confidence=moderate_entry_confidence>0 ? overworld_entry_confidence(result) : 0; - const bool moderate_arrival=moderate_entry_confidence>0 && entry_confidence>=moderate_entry_confidence; - if(moderate_entry_confidence>0) stream.log("["+label+"] Exterior entry confidence="+ - std::to_string(entry_confidence)+", moderate threshold="+std::to_string(moderate_entry_confidence)+ - ", strict target="+std::to_string(result.at_target)); - // Explicit loose bounds are for non-interacting route handoffs. - // Default callers still require the matcher's precise arrival. - const bool rough_arrival=arrival_tolerance_x>0 && arrival_tolerance_y>0 && - result.located && std::abs(result.dx)<=arrival_tolerance_x && - std::abs(result.dy)<=arrival_tolerance_y; - if (result.at_target || rough_arrival || moderate_arrival) { - if (++confirmations >= 3) { stream.log("[" + label + "] Position confirmed."); return; } - pbf_wait(context, 250ms); - } else if (result.located) { - confirmations = 0; - missing = 0; - // Wait at an aligned pose while strict visual support - // recovers, rather than stepping away under a cloud. - if ((matcher==match_galactic_hq_jupiter || - matcher==match_route208_hannah_departure || matcher==match_route208_stair_top) && result.coarse && - std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/54){ - pbf_wait(context,500ms); - context.wait_for_all_requests(); - continue; - } - // A coarse match cannot confirm position, but two accepted - // anchors can establish a safe horizontal direction when both - // offsets are well outside the uncertainty in their spacing. - bool coarse_horizontal = false; - if (horizontal_first && result.coarse && result.landmarks.size() == 2) { - const auto& a = result.landmarks[0]; - const auto& b = result.landmarks[1]; - const double nearest = std::min(std::abs(a.dx), std::abs(b.dx)); - coarse_horizontal = a.accepted && b.accepted && a.dx*b.dx > 0 && - nearest > 1.0/64 && nearest > 2*std::abs(a.dx-b.dx); - } - const bool plaza_anchor_horizontal = matcher == match_jubilife_plaza_approach && - result.coarse && std::abs(result.dx)>1.0/50 && std::abs(result.dy)<=1.0/54; - // A uniquely matched Eterna ball may correct the door - // column while the sign/bush enter the frame. Arrival - // still requires the matcher's full three-anchor check. - const bool eterna_anchor_horizontal = (matcher == match_eterna_center_entrance || - matcher == match_eterna_underground_house || matcher == match_eterna_underground_man) && - result.coarse && std::abs(result.dx)>1.0/128; - // Recover the gym's doorway row before moving right if - // the south approach overshot toward the fence/cliff. - const bool gym_anchor_horizontal=matcher==match_eterna_gym_entrance && - result.coarse && std::abs(result.dx)>1.0/128 && std::abs(result.dy)<=.06; - const bool gym_row_recovery=matcher==match_eterna_gym_entrance && - result.coarse && std::abs(result.dy)>.06; - // Reach the row north of Jupiter before entering her - // column. From above that row, align sideways before - // moving south so the NPC cannot block the correction. - const bool jupiter_row_recovery=matcher==match_galactic_hq_jupiter && - result.dy < -1.0/54; - const bool jupiter_anchor_horizontal=matcher==match_galactic_hq_jupiter && - !jupiter_row_recovery && result.coarse && std::abs(result.dx)>1.0/128; - // Stay south of the Contest Hall wall/sign before crossing to Fantina. - const bool contest_row_recovery=matcher==match_hearthome_contest_fantina && result.dy>1.0/54; - // Keep the stair approach horizontal-first until close to - // its column. Recorded fine poses otherwise spent repeated - // 16ms sideways turns while a larger vertical error waited. - // Fine correction changes axis priority, never arrival gates. - const bool stair_fine_correction=matcher==match_route208_stair_top && - !result.coarse && std::abs(result.dx)<=1.0/64; - const bool horizontal = !ace_row_recovery && !ace_central_row && (ace_bypass || - (!contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || eterna_anchor_horizontal || gym_anchor_horizontal || (!result.coarse && - ((horizontal_first && !stair_fine_correction && std::abs(result.dx)>1.0/128) || - std::abs(result.dx)>=std::abs(result.dy)*9.0/16))))); - if (stair_fine_correction && horizontal_first && !horizontal && std::abs(result.dx)>1.0/128) - stream.log("["+label+"] Near stair column; correcting the larger vertical error first."); - if (ace_row_recovery) stream.log("["+label+"] Clearing the NPC row before crossing west."); - else if (ace_central_row) stream.log("["+label+"] Both road bends support restoring the arrival row."); - else if (coarse_horizontal && horizontal) stream.log("[" + label + - "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); - const double error = horizontal ? result.dx : - (result.dy-(ace_bypass ? 70.0/1080 : 0.0))*9.0/16; - auto duration = std::abs(error) > 1.0/24 ? 80ms : std::abs(error) > 1.0/64 ? 40ms : 16ms; - // A 16ms turn may not complete a step. Use a bounded - // full correction while reacquiring the second plaza anchor. - if (matcher == match_jubilife_plaza_approach && result.coarse) - duration = std::max(duration, 60ms); - if (matcher==match_galactic_hq_jupiter){ - for (const auto& landmark:result.landmarks){ - // The player stands around y=.74. Do not steer - // sideways/north when the stair opening is close - // to that row; a southward correction moves away. - if (landmark.name=="StairBoundary" && landmark.accepted && - landmark.found.y+landmark.found.height>.60 && - (horizontal || result.dy<0)) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Correction would approach the stair boundary.",stream); - } - } - if (route215_renewal){ - if (correction_movement+duration>1500ms) - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Renewal alignment movement budget exhausted.",stream); - correction_movement+=duration; - } - pbf_move_left_joystick(context, - {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, - horizontal ? 0.0 : (error < 0 ? 1.0 : -1.0)}, duration, 0ms); - pbf_wait(context, 500ms); - } else { - confirmations = 0; - if (matcher==match_hearthome_center_entrance || matcher==match_hearthome_contest_fantina || - matcher==match_veilstone_city_passage || matcher==match_veilstone_dawn_stairs || - route215_renewal){ - // Do not sweep into a different doorway or around the Hall's - // side, or onto a Veilstone terrace, while required anchors are hidden. - if (++missing>20) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": Scene anchors missing; stationary reacquisition exhausted.",stream); - // Recorded expiry endpoints can be north of the passage. - // Acquire its buildings along the clear south-only strip; - // cap total search motion, then remain stationary. - if (matcher==match_veilstone_city_passage && search_down_first && - correction_movement<1800ms){ - pbf_move_left_joystick(context,{0.0,-1.0},100ms,0ms); - correction_movement+=100ms; - } - pbf_wait(context,500ms); - context.wait_for_all_requests(); - continue; - } - if (matcher==match_galactic_hq_jupiter || matcher==match_route208_stair_top){ - // Keep generic missing-anchor behavior unchanged. Route208's - // explicit caller can instead request its recorded recovery. - const bool stair_rocks=matcher==match_route208_stair_top; - if (stair_rocks && rock_acquisition_timed_out) { - const auto now=std::chrono::steady_clock::now(); - if (missing_rocks_since==std::chrono::steady_clock::time_point::max()) - missing_rocks_since=now; - // Matcher processing can take seconds per observation; - // measure real elapsed time rather than counting waits. - if (now-missing_rocks_since>=10s) { - context.wait_for_all_requests(); - *rock_acquisition_timed_out=true; - stream.log("["+label+"] Rock acquisition timed out after 10s; handing off to the requested cliff recovery."); - return; - } - } else if (++missing>(stair_rocks ? 20 : 6)) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+(stair_rocks ? ": Rock anchors missing; no blind search near stairs." : - ": Chair/stair anchors missing; no blind search near stairs."),stream); - } - pbf_wait(context,500ms); - context.wait_for_all_requests(); - continue; - } - // Search right first, then across the starting point to the - // left and back. Each complete sweep returns to its origin. - // Try that sweep at the initial height, above it, and below it; - // no missing/weak landmark ever confirms the destination. - if (++missing > (search_down_first ? 78 : 74)) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Landmarks missing during bounded bidirectional search.", stream); - if (search_down_first && missing <= 4) { - stream.log("[" + label + "] Post-battle acquisition: moving down before lateral search."); - pbf_move_left_joystick(context, {0.0, -1.0}, 100ms, 0ms); - } else { - const size_t search_step = missing - 1 - (search_down_first ? 4 : 0); - const size_t level = search_step / 25; - const size_t slot = search_step % 25; - const bool lateral = slot < 24; - const double direction = slot < 6 || slot >= 18 ? 1.0 : -1.0; - const auto search_duration = lateral ? 100ms : level == 0 ? 600ms : 1200ms; - stream.log("[" + label + "] Acquisition search step=" + std::to_string(missing) + - ", direction=" + (lateral ? (direction>0 ? "right" : "left") : (level==0 ? "up" : "down"))); - // On the street, a northward search from the east - // edge can stop at the building/NPC collision row. - // Step west around it before changing height, then - // return to the same lane for the next bounded sweep. - const bool street_height_search=!lateral && matcher==match_jubilife_street_approach; - if (street_height_search) pbf_move_left_joystick(context,{-1.0,0.0},150ms,0ms); - pbf_move_left_joystick(context, - {lateral ? direction : 0.0, lateral ? 0.0 : (level == 0 ? 1.0 : -1.0)}, search_duration, 0ms); - if (street_height_search) pbf_move_left_joystick(context,{1.0,0.0},150ms,0ms); - } - pbf_wait(context, 500ms); + // Matcher processing can take seconds per observation; + // measure real elapsed time rather than counting waits. + if (now - missing_rocks_since >= 10s){ + context.wait_for_all_requests(); + *rock_acquisition_timed_out = true; + stream.log( + "[" + label + + "] Rock acquisition timed out after 10s; handing off to the requested cliff recovery."); + return; } - context.wait_for_all_requests(); + }else if (++missing > (stair_rocks ? 20 : 6)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + (stair_rocks ? ": Rock anchors missing; no blind search near stairs." + : ": Chair/stair anchors missing; no blind search near stairs."), + stream); } - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Could not confirm the reference position.", stream); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + continue; } - - void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, - const std::vector& moves, const std::string& label, bool post_battle_dialogue_cleanup) { - fight_using_move_list(stream,context,BattleType::TRAINER,moves,label,true,post_battle_dialogue_cleanup); + // Search right first, then across the starting point to the + // left and back. Each complete sweep returns to its origin. + // Try that sweep at the initial height, above it, and below it; + // no missing/weak landmark ever confirms the destination. + if (++missing > (search_down_first ? 78 : 74)){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + label + ": Landmarks missing during bounded bidirectional search.", stream); } - - void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, - size_t move, const std::string& label, bool post_battle_dialogue_cleanup) { - fight_trainer_using_moves(stream,context,std::vector{move},label,post_battle_dialogue_cleanup); + if (search_down_first && missing <= 4){ + stream.log("[" + label + "] Post-battle acquisition: moving down before lateral search."); + pbf_move_left_joystick(context, {0.0, -1.0}, 100ms, 0ms); + }else{ + const size_t search_step = missing - 1 - (search_down_first ? 4 : 0); + const size_t level = search_step / 25; + const size_t slot = search_step % 25; + const bool lateral = slot < 24; + const double direction = slot < 6 || slot >= 18 ? 1.0 : -1.0; + const auto search_duration = lateral ? 100ms : level == 0 ? 600ms : 1200ms; + stream.log("[" + label + "] Acquisition search step=" + std::to_string(missing) + ", direction=" + + (lateral ? (direction > 0 ? "right" : "left") : (level == 0 ? "up" : "down"))); + // On the street, a northward search from the east + // edge can stop at the building/NPC collision row. + // Step west around it before changing height, then + // return to the same lane for the next bounded sweep. + const bool street_height_search = !lateral && matcher == match_jubilife_street_approach; + if (street_height_search){ + pbf_move_left_joystick(context, {-1.0, 0.0}, 150ms, 0ms); + } + pbf_move_left_joystick(context, {lateral ? direction : 0.0, lateral ? 0.0 : (level == 0 ? 1.0 : -1.0)}, + search_duration, 0ms); + if (street_height_search){ + pbf_move_left_joystick(context, {1.0, 0.0}, 150ms, 0ms); + } } + pbf_wait(context, 500ms); + } + context.wait_for_all_requests(); + } + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Could not confirm the reference position.", stream); +} - void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, - size_t move_slot, const std::string& label) { - fight_using_move_slot(stream,context,BattleType::TRAINER,move_slot,label); - } +void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, const std::vector& moves, + const std::string& label, bool post_battle_dialogue_cleanup){ + fight_using_move_list(stream, context, BattleType::TRAINER, moves, label, true, post_battle_dialogue_cleanup); +} - void fight_tristan(VideoStream& stream, ProControllerContext& context) { - // All starters use move slot 3. Confirm its cursor position - // visually without reading the move name; fail if slot 3 is absent. - fight_trainer_using_move_slot(stream, context, 3, "Tristan (move slot 3)"); - } +void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, size_t move, + const std::string& label, bool post_battle_dialogue_cleanup){ + fight_trainer_using_moves(stream, context, std::vector{move}, label, post_battle_dialogue_cleanup); +} - void battle_1_pokemon(VideoStream& stream, ProControllerContext& context) { - stream.log("[Training] Finding one wild Pokemon using the catch encounter wiggle.", COLOR_BLUE); - encounter_one_wild_pokemon(stream, context); - // Use the same battle loop as Tristan with move slot 1. - fight_using_move_slot(stream, context, BattleType::STANDARD, 1, "Training (move slot 1)"); - stream.log("[Training] Fight and level-up cleanup complete; verifying Potion healing."); - use_potion_first_pokemon(stream, context); - context.wait_for_all_requests(); - stream.log("[Training] One wild battle completed; slot 1 healing verified.", COLOR_GREEN); - } +void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, size_t move_slot, + const std::string& label){ + fight_using_move_slot(stream, context, BattleType::TRAINER, move_slot, label); +} - // --------------------------------------------------------------------------- - // Catch 1 Pokemon - // --------------------------------------------------------------------------- - - void catch_1_pokemon( - VideoStream& stream, - ProControllerContext& context - ) { - stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); - - encounter_one_wild_pokemon(stream, context); - - // Catch the pokemon. - stream.log("[AutoStory] Throwing Poke Ball (manual bag open)...", COLOR_BLUE); - ReceivePokemonDetector received_pokemon; - // Preserve exit-transition state from the ball outcome through - // receipt/experience cleanup; do not recreate it between phases. - EndBattleWatcher battle_ended; - bool caught = false; - bool exit_seen = false; - for (int throw_attempt = 0; throw_attempt < 10; throw_attempt++) { - // Open bag from battle menu (X), confirm first slot (ZL). - pbf_press_button(context, BUTTON_X, 160ms, 840ms); - context.wait_for_all_requests(); - pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); - context.wait_for_all_requests(); +void fight_tristan(VideoStream& stream, ProControllerContext& context){ + // All starters use move slot 3. Confirm its cursor position + // visually without reading the move name; fail if slot 3 is absent. + fight_trainer_using_move_slot(stream, context, 3, "Tristan (move slot 3)"); +} - // Wait to see if the battle menu comes back (ball failed) - // or if the battle ends (caught or fainted). - BattleMenuWatcher menu_back(BattleType::STANDARD, COLOR_YELLOW); - int throw_ret = run_until( - stream, context, - [](ProControllerContext& ctx) { - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{menu_back}, {received_pokemon}, {battle_ended}} - ); - - if (throw_ret == 1 || (throw_ret == 2 && received_pokemon.received())) { - caught = true; - exit_seen = throw_ret == 2; - stream.log("[AutoStory] Pokemon receipt verified.", COLOR_GREEN); - break; - } - if (throw_ret < 0 || throw_ret == 2) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Catch failed: No verified Pokemon receipt before battle ended or timed out.", stream); - } - stream.log( - "[AutoStory] Ball failed, retrying (attempt " + std::to_string(throw_attempt + 2) + ")...", - COLOR_ORANGE - ); - } +void battle_1_pokemon(VideoStream& stream, ProControllerContext& context){ + stream.log("[Training] Finding one wild Pokemon using the catch encounter wiggle.", COLOR_BLUE); + encounter_one_wild_pokemon(stream, context); + // Use the same battle loop as Tristan with move slot 1. + fight_using_move_slot(stream, context, BattleType::STANDARD, 1, "Training (move slot 1)"); + stream.log("[Training] Fight and level-up cleanup complete; verifying Potion healing."); + use_potion_first_pokemon(stream, context); + context.wait_for_all_requests(); + stream.log("[Training] One wild battle completed; slot 1 healing verified.", COLOR_GREEN); +} - context.wait_for_all_requests(); +// --------------------------------------------------------------------------- +// Catch 1 Pokemon +// --------------------------------------------------------------------------- + +void catch_1_pokemon(VideoStream& stream, ProControllerContext& context){ + stream.log("[AutoStory] Starting catch_1_pokemon helper...", COLOR_BLUE); + + encounter_one_wild_pokemon(stream, context); + + // Catch the pokemon. + stream.log("[AutoStory] Throwing Poke Ball (manual bag open)...", COLOR_BLUE); + ReceivePokemonDetector received_pokemon; + // Preserve exit-transition state from the ball outcome through + // receipt/experience cleanup; do not recreate it between phases. + EndBattleWatcher battle_ended; + bool caught = false; + bool exit_seen = false; + for (int throw_attempt = 0; throw_attempt < 10; throw_attempt++){ + // Open bag from battle menu (X), confirm first slot (ZL). + pbf_press_button(context, BUTTON_X, 160ms, 840ms); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + context.wait_for_all_requests(); + + // Wait to see if the battle menu comes back (ball failed) + // or if the battle ends (caught or fainted). + BattleMenuWatcher menu_back(BattleType::STANDARD, COLOR_YELLOW); + int throw_ret = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{menu_back}, {received_pokemon}, {battle_ended}}); + + if (throw_ret == 1 || (throw_ret == 2 && received_pokemon.received())){ + caught = true; + exit_seen = throw_ret == 2; + stream.log("[AutoStory] Pokemon receipt verified.", COLOR_GREEN); + break; + } + if (throw_ret < 0 || throw_ret == 2){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Catch failed: No verified Pokemon receipt before battle ended or timed out.", stream); + } + stream.log("[AutoStory] Ball failed, retrying (attempt " + std::to_string(throw_attempt + 2) + ")...", + COLOR_ORANGE); + } - if (!caught) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Catch failed: Exhausted ten ball attempts.", stream); - } - // Receipt alone is not an overworld return: clear experience, - // automatic move learning, dex registration and nickname screens - // until the same completed-black-transition watcher used in battles fires. - if (!exit_seen) { - stream.log("[Catch cleanup] Receipt confirmed; waiting for battle exit transition."); - const int cleanup_ret = run_until(stream, context, - [](ProControllerContext& ctx) { pbf_mash_button(ctx, BUTTON_B, 120000ms); }, - {{battle_ended}}); - if (cleanup_ret != 0) { - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Catch cleanup: Battle exit transition did not finish.", stream); - } - } - context.wait_for_all_requests(); - stream.log("[Catch cleanup] Battle exit transition confirmed; checking stable overworld."); - confirm_battle_overworld(stream, context, "Catch cleanup"); - stream.log("[Catch cleanup] Verified receipt, completed battle exit, and four seconds of clear overworld.", COLOR_GREEN); - } + context.wait_for_all_requests(); - // --------------------------------------------------------------------------- - // catch_1_pokemon_v2 - burst wiggle variant for testing. - // Uses WIGGLES_PER_BURST consecutive left/right moves before checking for a - // battle, reducing total wait time compared to checking after every move. - // Commented out - enable for testing when ready. - // --------------------------------------------------------------------------- - // void catch_1_pokemon_v2( - // SingleSwitchProgramEnvironment& env, - // VideoStream& stream, - // ProControllerContext& context - // ) { - // stream.log("[AutoStory] Starting catch_1_pokemon_v2 helper...", COLOR_BLUE); - // - // // Save once before attempting to catch. On failure the game - // // resets back to this save, so the save lives outside the loop. - // stream.log("[AutoStory] Saving game before catch attempt...", COLOR_BLUE); - // { - // static constexpr int SAVE_MAX_RETRIES = 3; - // bool save_succeeded = false; - // for (int save_attempt = 0; save_attempt < SAVE_MAX_RETRIES; save_attempt++) { - // try { - // if (save_attempt > 0) { - // stream.log( - // "[AutoStory] Retrying save (attempt " + std::to_string(save_attempt + 1) + ")...", - // COLOR_ORANGE - // ); - // pbf_mash_button(context, BUTTON_B, 1000ms); - // context.wait_for_all_requests(); - // } - // save_game(stream, context); - // save_succeeded = true; - // break; - // } - // catch (OperationFailedException& e) { - // stream.log( - // "[AutoStory] Save attempt " + std::to_string(save_attempt + 1) + " failed: " + e.message(), - // COLOR_RED - // ); - // context.wait_for(1000ms); - // } - // } - // if (!save_succeeded) { - // stream.log("[AutoStory] All save attempts failed. Using blind fallback save.", COLOR_ORANGE); - // pbf_mash_button(context, BUTTON_B, 1000ms); - // save_game(context); - // pbf_mash_button(context, BUTTON_B, 2000ms); - // } - // } - // context.wait_for_all_requests(); - // pbf_wait(context, 1000ms); - // - // // ------------------------------------------------------- - // // Mini-checkpoint loop: retry from the save above if we - // // run out of balls or our Pokemon faints. - // // ------------------------------------------------------- - // while (true) { - // - // BlackScreenWatcher battle_start; - // BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); - // BattleDialogWatcher battle_dialog(COLOR_YELLOW); - // - // bool encounter_found = false; - // - // // Wiggle left/right in bursts, checking for a battle after each burst. - // // Doing multiple moves before checking avoids waiting after every step. - // static constexpr int WIGGLES_PER_BURST = 3; - // while (!encounter_found) { - // for (int i = 0; i < WIGGLES_PER_BURST; i++) { - // pbf_move_left_joystick(context, {-1, 0}, 120ms, 80ms); - // pbf_move_left_joystick(context, { 1, 0}, 120ms, 80ms); - // } - // context.wait_for_all_requests(); - // - // int ret = wait_until( - // stream, - // context, - // 1500ms, - // { - // {battle_start}, - // {battle_menu}, - // {battle_dialog}, - // } - // ); - // - // if (ret >= 0) { - // encounter_found = true; - // } - // } - // - // stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); - // - // // Wait until the actual battle menu appears. - // BattleMenuWatcher confirm_menu(BattleType::STANDARD); - // - // int menu_ret = wait_until( - // stream, - // context, - // 15s, - // { - // {confirm_menu}, - // } - // ); - // - // if (menu_ret < 0) { - // stream.log( - // "[AutoStory] Failed to detect battle menu. Resetting to checkpoint...", - // COLOR_RED - // ); - // reset_game_from_home(env, env.console, context, true); - // continue; - // } - // - // stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); - // - // context.wait_for_all_requests(); - // pbf_wait(context, 500ms); - // - // // --- Throw balls --- - // CatchResults result = basic_catcher( - // stream, - // context, - // Language::English, - // "poke-ball", - // 10 - // ); - // - // switch (result.result) { - // case CatchResult::POKEMON_CAUGHT: - // stream.log( - // "[AutoStory] Pokemon successfully caught after " + - // std::to_string(result.balls_used) + " ball(s).", - // COLOR_GREEN - // ); - // { - // OverworldWatcher overworld; - // run_until( - // stream, context, - // [](ProControllerContext& ctx) { - // pbf_mash_button(ctx, BUTTON_B, 120000ms); - // }, - // {{overworld}} - // ); - // } - // return; - // - // case CatchResult::POKEMON_FAINTED: - // stream.log("[AutoStory] Wild Pokemon fainted. Exiting battle.", COLOR_ORANGE); - // { - // OverworldWatcher overworld; - // run_until( - // stream, context, - // [](ProControllerContext& ctx) { - // pbf_mash_button(ctx, BUTTON_B, 120000ms); - // }, - // {{overworld}} - // ); - // } - // return; - // - // case CatchResult::OUT_OF_BALLS: - // stream.log( - // "[AutoStory] Ran out of Poke Balls after " + - // std::to_string(result.balls_used) + " throw(s). Resetting to checkpoint...", - // COLOR_RED - // ); - // reset_game_from_home(env, env.console, context, true); - // continue; - // - // case CatchResult::OWN_FAINTED: - // stream.log( - // "[AutoStory] Own Pokemon fainted. Resetting to checkpoint...", - // COLOR_RED - // ); - // reset_game_from_home(env, env.console, context, true); - // continue; - // - // case CatchResult::BALL_LIMIT_REACHED: - // stream.log( - // "[AutoStory] Ball limit reached (" + - // std::to_string(result.balls_used) + " throw(s)). Resetting to checkpoint...", - // COLOR_RED - // ); - // reset_game_from_home(env, env.console, context, true); - // continue; - // - // case CatchResult::CANNOT_THROW_BALL: - // stream.log( - // "[AutoStory] Cannot throw ball (menu timing issue). Resetting to checkpoint...", - // COLOR_RED - // ); - // reset_game_from_home(env, env.console, context, true); - // continue; - // - // default: - // stream.log( - // "[AutoStory] Catch timed out after " + - // std::to_string(result.balls_used) + " ball(s). Resetting to checkpoint...", - // COLOR_RED - // ); - // reset_game_from_home(env, env.console, context, true); - // continue; - // } - // - // } // end while(true) - // } - - // --------------------------------------------------------------------------- - // Movement Helpers - // --------------------------------------------------------------------------- - - bool detect_black_screen_after_movement( - VideoStream& stream, - ProControllerContext& context, - Milliseconds timeout - ) { - BlackScreenWatcher black_screen; - return wait_until(stream, context, timeout, {{black_screen}}) == 0; - } + if (!caught){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Catch failed: Exhausted ten ball attempts.", stream); + } + // Receipt alone is not an overworld return: clear experience, + // automatic move learning, dex registration and nickname screens + // until the same completed-black-transition watcher used in battles fires. + if (!exit_seen){ + stream.log("[Catch cleanup] Receipt confirmed; waiting for battle exit transition."); + const int cleanup_ret = run_until(stream, context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{battle_ended}}); + if (cleanup_ret != 0){ + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Catch cleanup: Battle exit transition did not finish.", stream); + } + } + context.wait_for_all_requests(); + stream.log("[Catch cleanup] Battle exit transition confirmed; checking stable overworld."); + confirm_battle_overworld(stream, context, "Catch cleanup"); + stream.log("[Catch cleanup] Verified receipt, completed battle exit, and four seconds of clear overworld.", + COLOR_GREEN); +} - bool try_right_down_exit_search( - VideoStream& stream, - ProControllerContext& context, - size_t iterations - ) { - for (size_t i = 0; i < iterations; i++) { - pbf_move_left_joystick(context, { 1.0, 0.0 }, 80ms, 0ms); - pbf_move_left_joystick(context, { 0.0, -1.0 }, 220ms, 0ms); - - if (detect_black_screen_after_movement(stream, context, 1000ms)) { - return true; - } - } - return false; - } +// --------------------------------------------------------------------------- +// catch_1_pokemon_v2 - burst wiggle variant for testing. +// Uses WIGGLES_PER_BURST consecutive left/right moves before checking for a +// battle, reducing total wait time compared to checking after every move. +// Commented out - enable for testing when ready. +// --------------------------------------------------------------------------- +// void catch_1_pokemon_v2( +// SingleSwitchProgramEnvironment& env, +// VideoStream& stream, +// ProControllerContext& context +// ) { +// stream.log("[AutoStory] Starting catch_1_pokemon_v2 helper...", COLOR_BLUE); +// +// // Save once before attempting to catch. On failure the game +// // resets back to this save, so the save lives outside the loop. +// stream.log("[AutoStory] Saving game before catch attempt...", COLOR_BLUE); +// { +// static constexpr int SAVE_MAX_RETRIES = 3; +// bool save_succeeded = false; +// for (int save_attempt = 0; save_attempt < SAVE_MAX_RETRIES; save_attempt++) { +// try { +// if (save_attempt > 0) { +// stream.log( +// "[AutoStory] Retrying save (attempt " + std::to_string(save_attempt + 1) + ")...", +// COLOR_ORANGE +// ); +// pbf_mash_button(context, BUTTON_B, 1000ms); +// context.wait_for_all_requests(); +// } +// save_game(stream, context); +// save_succeeded = true; +// break; +// } +// catch (OperationFailedException& e) { +// stream.log( +// "[AutoStory] Save attempt " + std::to_string(save_attempt + 1) + " failed: " + e.message(), +// COLOR_RED +// ); +// context.wait_for(1000ms); +// } +// } +// if (!save_succeeded) { +// stream.log("[AutoStory] All save attempts failed. Using blind fallback save.", COLOR_ORANGE); +// pbf_mash_button(context, BUTTON_B, 1000ms); +// save_game(context); +// pbf_mash_button(context, BUTTON_B, 2000ms); +// } +// } +// context.wait_for_all_requests(); +// pbf_wait(context, 1000ms); +// +// // ------------------------------------------------------- +// // Mini-checkpoint loop: retry from the save above if we +// // run out of balls or our Pokemon faints. +// // ------------------------------------------------------- +// while (true) { +// +// BlackScreenWatcher battle_start; +// BattleMenuWatcher battle_menu(BattleType::STANDARD, COLOR_YELLOW); +// BattleDialogWatcher battle_dialog(COLOR_YELLOW); +// +// bool encounter_found = false; +// +// // Wiggle left/right in bursts, checking for a battle after each burst. +// // Doing multiple moves before checking avoids waiting after every step. +// static constexpr int WIGGLES_PER_BURST = 3; +// while (!encounter_found) { +// for (int i = 0; i < WIGGLES_PER_BURST; i++) { +// pbf_move_left_joystick(context, {-1, 0}, 120ms, 80ms); +// pbf_move_left_joystick(context, { 1, 0}, 120ms, 80ms); +// } +// context.wait_for_all_requests(); +// +// int ret = wait_until( +// stream, +// context, +// 1500ms, +// { +// {battle_start}, +// {battle_menu}, +// {battle_dialog}, +// } +// ); +// +// if (ret >= 0) { +// encounter_found = true; +// } +// } +// +// stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); +// +// // Wait until the actual battle menu appears. +// BattleMenuWatcher confirm_menu(BattleType::STANDARD); +// +// int menu_ret = wait_until( +// stream, +// context, +// 15s, +// { +// {confirm_menu}, +// } +// ); +// +// if (menu_ret < 0) { +// stream.log( +// "[AutoStory] Failed to detect battle menu. Resetting to checkpoint...", +// COLOR_RED +// ); +// reset_game_from_home(env, env.console, context, true); +// continue; +// } +// +// stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); +// +// context.wait_for_all_requests(); +// pbf_wait(context, 500ms); +// +// // --- Throw balls --- +// CatchResults result = basic_catcher( +// stream, +// context, +// Language::English, +// "poke-ball", +// 10 +// ); +// +// switch (result.result) { +// case CatchResult::POKEMON_CAUGHT: +// stream.log( +// "[AutoStory] Pokemon successfully caught after " + +// std::to_string(result.balls_used) + " ball(s).", +// COLOR_GREEN +// ); +// { +// OverworldWatcher overworld; +// run_until( +// stream, context, +// [](ProControllerContext& ctx) { +// pbf_mash_button(ctx, BUTTON_B, 120000ms); +// }, +// {{overworld}} +// ); +// } +// return; +// +// case CatchResult::POKEMON_FAINTED: +// stream.log("[AutoStory] Wild Pokemon fainted. Exiting battle.", COLOR_ORANGE); +// { +// OverworldWatcher overworld; +// run_until( +// stream, context, +// [](ProControllerContext& ctx) { +// pbf_mash_button(ctx, BUTTON_B, 120000ms); +// }, +// {{overworld}} +// ); +// } +// return; +// +// case CatchResult::OUT_OF_BALLS: +// stream.log( +// "[AutoStory] Ran out of Poke Balls after " + +// std::to_string(result.balls_used) + " throw(s). Resetting to checkpoint...", +// COLOR_RED +// ); +// reset_game_from_home(env, env.console, context, true); +// continue; +// +// case CatchResult::OWN_FAINTED: +// stream.log( +// "[AutoStory] Own Pokemon fainted. Resetting to checkpoint...", +// COLOR_RED +// ); +// reset_game_from_home(env, env.console, context, true); +// continue; +// +// case CatchResult::BALL_LIMIT_REACHED: +// stream.log( +// "[AutoStory] Ball limit reached (" + +// std::to_string(result.balls_used) + " throw(s)). Resetting to checkpoint...", +// COLOR_RED +// ); +// reset_game_from_home(env, env.console, context, true); +// continue; +// +// case CatchResult::CANNOT_THROW_BALL: +// stream.log( +// "[AutoStory] Cannot throw ball (menu timing issue). Resetting to checkpoint...", +// COLOR_RED +// ); +// reset_game_from_home(env, env.console, context, true); +// continue; +// +// default: +// stream.log( +// "[AutoStory] Catch timed out after " + +// std::to_string(result.balls_used) + " ball(s). Resetting to checkpoint...", +// COLOR_RED +// ); +// reset_game_from_home(env, env.console, context, true); +// continue; +// } +// +// } // end while(true) +// } + +// --------------------------------------------------------------------------- +// Movement Helpers +// --------------------------------------------------------------------------- + +bool detect_black_screen_after_movement(VideoStream& stream, ProControllerContext& context, Milliseconds timeout){ + BlackScreenWatcher black_screen; + return wait_until(stream, context, timeout, {{black_screen}}) == 0; +} - void leave_pokemon_center_before_barry_1( - VideoStream& stream, - ProControllerContext& context - ) { - // Original down movement to leave Pokemon Center. - pbf_move_left_joystick(context, { 0.0, -1.0 }, 814ms, 0ms); +bool try_right_down_exit_search(VideoStream& stream, ProControllerContext& context, size_t iterations){ + for (size_t i = 0; i < iterations; i++){ + pbf_move_left_joystick(context, {1.0, 0.0}, 80ms, 0ms); + pbf_move_left_joystick(context, {0.0, -1.0}, 220ms, 0ms); - if (detect_black_screen_after_movement(stream, context, 1200ms)) { - pbf_wait(context, 2340ms); - return; - } + if (detect_black_screen_after_movement(stream, context, 1000ms)){ + return true; + } + } + return false; +} - stream.log("[AutoStory] Pokemon Center exit black screen not detected. Searching with right/down movement.", COLOR_ORANGE); +void leave_pokemon_center_before_barry_1(VideoStream& stream, ProControllerContext& context){ + // Original down movement to leave Pokemon Center. + pbf_move_left_joystick(context, {0.0, -1.0}, 814ms, 0ms); - if (try_right_down_exit_search(stream, context, 20)) { - pbf_wait(context, 2340ms); - return; - } + if (detect_black_screen_after_movement(stream, context, 1200ms)){ + pbf_wait(context, 2340ms); + return; + } - stream.log("[AutoStory] First exit search failed. Repositioning left and retrying.", COLOR_ORANGE); - pbf_move_left_joystick(context, { -1.0, 0.0 }, 5000ms, 0ms); + stream.log("[AutoStory] Pokemon Center exit black screen not detected. Searching with right/down movement.", + COLOR_ORANGE); + + if (try_right_down_exit_search(stream, context, 20)){ + pbf_wait(context, 2340ms); + return; + } + + stream.log("[AutoStory] First exit search failed. Repositioning left and retrying.", COLOR_ORANGE); + pbf_move_left_joystick(context, {-1.0, 0.0}, 5000ms, 0ms); + + if (try_right_down_exit_search(stream, context, 30)){ + pbf_wait(context, 2340ms); + return; + } - if (try_right_down_exit_search(stream, context, 30)) { - pbf_wait(context, 2340ms); + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "Failed to find Pokemon Center exit black screen before Barry 1. Triggering checkpoint retry.", stream); +} + +// --------------------------------------------------------------------------- +// Generic Movement Helper - Walk through grass in specified direction for +// specified steps, attempting to run from any battles that are encountered. +// --------------------------------------------------------------------------- +void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8_t x, int8_t y, size_t steps, + const std::string& direction_name, BattleType battle_type, Milliseconds movement_duration, + OverworldMatchResult (*destination)(const ImageRGB32&)){ + size_t movements = 0, confirmations = 0; + while (true){ + context.wait_for_all_requests(); + // Destination checks happen before input and after each step/escape. + // With no matcher, retain the original fixed-step behavior. + VideoOverlaySet overlays(stream.overlay()); + OverworldMatchResult location; + if (destination){ + const auto frame = stream.video().snapshot(); + if (!frame){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Grass destination: No video frame.", stream); + } + location = destination(*frame.frame); + for (const auto& landmark : location.landmarks){ + overlays.add(COLOR_CYAN, landmark.search); + if (landmark.accepted){ + overlays.add(COLOR_GREEN, landmark.found); + } + stream.log("[Grass destination] " + direction_name + " " + landmark.name + + " score=" + std::to_string(landmark.score) + " margin=" + std::to_string(landmark.margin) + + " accepted=" + std::to_string(landmark.accepted)); + } + stream.log("[Grass destination] located=" + std::to_string(location.located) + + " at_target=" + std::to_string(location.at_target) + " screen-relative offset=(" + + std::to_string(location.dx) + "," + std::to_string(location.dy) + ")"); + if (location.at_target){ + if (++confirmations >= 3){ + stream.log("[Grass] Destination confirmed; stopped " + direction_name + " after " + + std::to_string(movements) + "/" + std::to_string(steps) + " movements.", + COLOR_GREEN); return; } - + pbf_wait(context, 250ms); + continue; + } + confirmations = 0; + } + if (movements >= steps){ + if (destination){ OperationFailedExceptionWithScreenshot::fire( ErrorReportMode::SEND_ERROR_REPORT, - "Failed to find Pokemon Center exit black screen before Barry 1. Triggering checkpoint retry.", - stream - ); + "Grass destination not confirmed within movement limit: " + direction_name, stream); } - - // --------------------------------------------------------------------------- - // Generic Movement Helper - Walk through grass in specified direction for - // specified steps, attempting to run from any battles that are encountered. - // --------------------------------------------------------------------------- - void walk_through_grass( - VideoStream& stream, ProControllerContext& context, - int8_t x, int8_t y, size_t steps, const std::string& direction_name, - BattleType battle_type, Milliseconds movement_duration, - OverworldMatchResult (*destination)(const ImageRGB32&) - ) { - size_t movements = 0, confirmations = 0; - while (true) { - context.wait_for_all_requests(); - // Destination checks happen before input and after each step/escape. - // With no matcher, retain the original fixed-step behavior. - VideoOverlaySet overlays(stream.overlay()); - OverworldMatchResult location; - if (destination) { - const auto frame = stream.video().snapshot(); - if (!frame) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Grass destination: No video frame.", stream); - location = destination(*frame.frame); - for (const auto& landmark : location.landmarks) { - overlays.add(COLOR_CYAN, landmark.search); - if (landmark.accepted) overlays.add(COLOR_GREEN, landmark.found); - stream.log("[Grass destination] " + direction_name + " " + landmark.name + - " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + - " accepted=" + std::to_string(landmark.accepted)); - } - stream.log("[Grass destination] located=" + std::to_string(location.located) + - " at_target=" + std::to_string(location.at_target) + - " screen-relative offset=(" + std::to_string(location.dx) + - "," + std::to_string(location.dy) + ")"); - if (location.at_target) { - if (++confirmations >= 3) { - stream.log("[Grass] Destination confirmed; stopped " + direction_name + - " after " + std::to_string(movements) + "/" + std::to_string(steps) + " movements.", COLOR_GREEN); - return; - } - pbf_wait(context, 250ms); - continue; - } - confirmations = 0; - } - if (movements >= steps) { - if (destination) OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Grass destination not confirmed within movement limit: " + direction_name, stream); - stream.log("[Grass] Completed " + direction_name + " (" + std::to_string(steps) + " steps).", COLOR_GREEN); - return; - } - int8_t move_x=x, move_y=y; - Milliseconds duration=movement_duration; - if (destination && location.located && !location.coarse && - std::abs(location.dx)<=0.10 && std::abs(location.dy)<=0.12) { - // Small corrections prevent stepping past a narrow stop target. - const bool horizontal=std::abs(location.dx)>=std::abs(location.dy)*9.0/16; - const double error=horizontal ? location.dx : location.dy*9.0/16; - duration=std::min(duration, std::abs(error)>0.03125 ? 80ms : - std::abs(error)>0.015625 ? 40ms : 16ms); - move_x=horizontal ? (location.dx>0 ? 1 : -1) : 0; - move_y=horizontal ? 0 : (location.dy<0 ? 1 : -1); - stream.log("[Grass destination] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + - " axis for " + std::to_string(duration.count()) + "ms."); - } - // Each movement gets fresh watcher state. A completed white - // transition from a prior encounter must not trigger again. - BlackScreenWatcher battle_start; - WhiteScreenOverWatcher white_screen(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}); - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - BattleDialogWatcher battle_dialog(COLOR_YELLOW); - pbf_move_left_joystick(context, {(double)move_x, (double)move_y}, duration, 0ms); - movements++; - context.wait_for_all_requests(); - int ret = wait_until(stream, context, 2500ms, - {{white_screen}, {battle_start}, {battle_dialog}, {battle_menu}}); - if (ret >= 0) { - confirmations = 0; - stream.log("[Grass] " + direction_name + " step " + std::to_string(movements) + - "/" + std::to_string(steps) + ": encounter trigger=" + std::to_string(ret) + - "; using shared confirmed-Run escape."); - escape_wild_battle(stream, context, battle_type); - context.wait_for_all_requests(); - stream.log("[Grass] Escape confirmed; resuming " + direction_name + "."); - } - } - } - + stream.log("[Grass] Completed " + direction_name + " (" + std::to_string(steps) + " steps).", COLOR_GREEN); + return; + } + int8_t move_x = x, move_y = y; + Milliseconds duration = movement_duration; + if (destination && location.located && !location.coarse && std::abs(location.dx) <= 0.10 && + std::abs(location.dy) <= 0.12){ + // Small corrections prevent stepping past a narrow stop target. + const bool horizontal = std::abs(location.dx) >= std::abs(location.dy) * 9.0 / 16; + const double error = horizontal ? location.dx : location.dy * 9.0 / 16; + duration = std::min(duration, std::abs(error) > 0.03125 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms); + move_x = horizontal ? (location.dx > 0 ? 1 : -1) : 0; + move_y = horizontal ? 0 : (location.dy < 0 ? 1 : -1); + stream.log("[Grass destination] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + + " axis for " + std::to_string(duration.count()) + "ms."); + } + // Each movement gets fresh watcher state. A completed white + // transition from a prior encounter must not trigger again. + BlackScreenWatcher battle_start; + WhiteScreenOverWatcher white_screen(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}); + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + BattleDialogWatcher battle_dialog(COLOR_YELLOW); + pbf_move_left_joystick(context, {(double)move_x, (double)move_y}, duration, 0ms); + movements++; + context.wait_for_all_requests(); + int ret = wait_until(stream, context, 2500ms, {{white_screen}, {battle_start}, {battle_dialog}, {battle_menu}}); + if (ret >= 0){ + confirmations = 0; + stream.log("[Grass] " + direction_name + " step " + std::to_string(movements) + "/" + + std::to_string(steps) + ": encounter trigger=" + std::to_string(ret) + + "; using shared confirmed-Run escape."); + escape_wild_battle(stream, context, battle_type); + context.wait_for_all_requests(); + stream.log("[Grass] Escape confirmed; resuming " + direction_name + "."); } } } + +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index b7c1f7e5ca..f71ec492e1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -1,163 +1,122 @@ #pragma once -#include "PokemonBDSP_OverworldMatchers.h" - -#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" -#include "../StorySegments/PokemonBDSP_AutoStory_Segment_00.h" #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Programs/DateManip/NintendoSwitch_DateManip.h" +#include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h" + +#include "PokemonBDSP_OverworldMatchers.h" namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { - - // --------------------------------------------------------------------------- - // Utility Watchers - // --------------------------------------------------------------------------- - - class BattleDialogWatcher : public DetectorToFinder { - public: - BattleDialogWatcher(Color color = COLOR_RED); - }; - - // --------------------------------------------------------------------------- - // Time / setup helpers - // --------------------------------------------------------------------------- - - // Shared visual alignment and dialogue cleanup used by story routes. - void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, - Button button = BUTTON_B, Milliseconds quiet_period = Milliseconds(3000), - const char* instruction_card = nullptr); - void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, bool allow_story_start = false, bool horizontal_first = false, - bool search_down_first = false, - OverworldMatchResult (*entry_matcher)(const ImageRGB32&) = nullptr, - bool* entry_detected = nullptr, - double arrival_tolerance_x = 0, double arrival_tolerance_y = 0, - double moderate_entry_confidence = 0, - bool* rock_acquisition_timed_out = nullptr); - - void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context); - - void set_time_fast( - SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - const DateTime& target, - bool preserve_date = false // Keep current year/month/day; set only target time. - ); - - // --------------------------------------------------------------------------- - // Battles - // --------------------------------------------------------------------------- - - void fight_starly( - VideoStream& stream, - ProControllerContext& context - ); - - // Select the requested 1-based move slot using visual cursor confirmation. - void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, - size_t move_slot, const std::string& label); - - void fight_tristan( - VideoStream& stream, - ProControllerContext& context - ); - - void fight_Natalie( - VideoStream& stream, - ProControllerContext& context - ); - - // --------------------------------------------------------------------------- - // Catching / healing - // --------------------------------------------------------------------------- - - void catch_1_pokemon( - VideoStream& stream, - ProControllerContext& context - ); - - // One wild encounter using move slot 1, followed by verified Potion healing. - void battle_1_pokemon(VideoStream& stream, ProControllerContext& context); - - // Burst wiggle variant of catch_1_pokemon for testing. - // Commented out in the .cpp - uncomment both to enable. - // void catch_1_pokemon_v2( - // SingleSwitchProgramEnvironment& env, - // VideoStream& stream, - // ProControllerContext& context - // ); - - void use_potion_first_pokemon( - VideoStream& stream, - ProControllerContext& context - ); - - // --------------------------------------------------------------------------- - // Grass movement helpers - // --------------------------------------------------------------------------- - - void walk_through_grass( - VideoStream& stream, - ProControllerContext& context, - int8_t x, - int8_t y, - size_t steps, - const std::string& direction_name, - BattleType battle_type = BattleType::STANDARD, - Milliseconds movement_duration = Milliseconds(200), - OverworldMatchResult (*destination)(const ImageRGB32&) = nullptr - ); - - bool detect_black_screen_after_movement( - VideoStream& stream, - ProControllerContext& context, - Milliseconds timeout - ); - - bool try_right_down_exit_search( - VideoStream& stream, - ProControllerContext& context, - size_t iterations - ); - - void leave_pokemon_center_before_barry_1( - VideoStream& stream, - ProControllerContext& context - ); - // --------------------------------------------------------------------------- - // Helper function to navigate the poketech challenge clown dialogue. - // Example usage - // Move up and talk to clown. - // clown_dialog_helper( - // stream, - // context, - // DPAD_UP - // ); - - // Move left and talk to clown. - // clown_dialog_helper( - // stream, - // context, - // DPAD_LEFT - // ); - - // Move right and talk to clown. - // clown_dialog_helper( - // stream, - // context, - // DPAD_RIGHT - // ); - // ---------------------------------------------------------------------------- - void clown_dialog_helper( - VideoStream& stream, - ProControllerContext& context, - DpadPosition direction - ); - } - } +namespace NintendoSwitch { +namespace PokemonBDSP { + +// --------------------------------------------------------------------------- +// Utility Watchers +// --------------------------------------------------------------------------- + +class BattleDialogWatcher : public DetectorToFinder { +public: + BattleDialogWatcher(Color color = COLOR_RED); +}; + +// --------------------------------------------------------------------------- +// Time / setup helpers +// --------------------------------------------------------------------------- + +// Shared visual alignment and dialogue cleanup used by story routes. +void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, Button button = BUTTON_B, + Milliseconds quiet_period = Milliseconds(3000), const char* instruction_card = nullptr); +void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, + bool allow_story_start = false, bool horizontal_first = false, + bool search_down_first = false, + OverworldMatchResult (*entry_matcher)(const ImageRGB32&) = nullptr, + bool* entry_detected = nullptr, double arrival_tolerance_x = 0, + double arrival_tolerance_y = 0, double moderate_entry_confidence = 0, + bool* rock_acquisition_timed_out = nullptr); + +void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context); + +void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const DateTime& target, + bool preserve_date = false // Keep current year/month/day; set only target time. +); + +// --------------------------------------------------------------------------- +// Battles +// --------------------------------------------------------------------------- + +void fight_starly(VideoStream& stream, ProControllerContext& context); + +// Select the requested 1-based move slot using visual cursor confirmation. +void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, size_t move_slot, + const std::string& label); + +void fight_tristan(VideoStream& stream, ProControllerContext& context); + +void fight_Natalie(VideoStream& stream, ProControllerContext& context); + +// --------------------------------------------------------------------------- +// Catching / healing +// --------------------------------------------------------------------------- + +void catch_1_pokemon(VideoStream& stream, ProControllerContext& context); + +// One wild encounter using move slot 1, followed by verified Potion healing. +void battle_1_pokemon(VideoStream& stream, ProControllerContext& context); + +// Burst wiggle variant of catch_1_pokemon for testing. +// Commented out in the .cpp - uncomment both to enable. +// void catch_1_pokemon_v2( +// SingleSwitchProgramEnvironment& env, +// VideoStream& stream, +// ProControllerContext& context +// ); + +void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context); + +// --------------------------------------------------------------------------- +// Grass movement helpers +// --------------------------------------------------------------------------- + +void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8_t x, int8_t y, size_t steps, + const std::string& direction_name, BattleType battle_type = BattleType::STANDARD, + Milliseconds movement_duration = Milliseconds(200), + OverworldMatchResult (*destination)(const ImageRGB32&) = nullptr); + +bool detect_black_screen_after_movement(VideoStream& stream, ProControllerContext& context, Milliseconds timeout); + +bool try_right_down_exit_search(VideoStream& stream, ProControllerContext& context, size_t iterations); + +void leave_pokemon_center_before_barry_1(VideoStream& stream, ProControllerContext& context); +// --------------------------------------------------------------------------- +// Helper function to navigate the poketech challenge clown dialogue. +// Example usage +// Move up and talk to clown. +// clown_dialog_helper( +// stream, +// context, +// DPAD_UP +// ); + +// Move left and talk to clown. +// clown_dialog_helper( +// stream, +// context, +// DPAD_LEFT +// ); + +// Move right and talk to clown. +// clown_dialog_helper( +// stream, +// context, +// DPAD_RIGHT +// ); +// ---------------------------------------------------------------------------- +void clown_dialog_helper(VideoStream& stream, ProControllerContext& context, DpadPosition direction); +} +} } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index 797fe2d530..a14cd54b8a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -5,270 +5,248 @@ * */ -#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" -#include "CommonFramework/GlobalAutoPaths.h" #include #include #include + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/ImageTools/ImageDiff.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" + #include "CommonTools/Async/InferenceRoutines.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" #include "CommonTools/VisualDetectors/ImageMatchDetector.h" + #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" + #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_EndBattleDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_MenuDetector.h" +#include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" -#include "../PokemonBDSP_AutoStoryTools.h" -#include "PokemonBDSP_Autostory_Segment_01_tools.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" + #include "PokemonBDSP_AutoStory_Segment_00_tools.h" +#include "PokemonBDSP_Autostory_Segment_01_tools.h" + using namespace std::chrono_literals; namespace PokemonAutomation { - namespace NintendoSwitch { - namespace PokemonBDSP { +namespace NintendoSwitch { +namespace PokemonBDSP { // Extracted from the Segment 01 Oreburgh Gate approach. Both callers use // the same bounded down/right search and cancel queued input at the black fade. // Oreburgh preserves its original north sweep; Coronet uses only down/right. -void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, - const std::string& label, Milliseconds north_fallback) { +void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, const std::string& label, + Milliseconds north_fallback){ context.wait_for_all_requests(); - for (size_t attempt=0;attempt<3;attempt++) { + for (size_t attempt = 0; attempt < 3; attempt++){ BlackScreenWatcher black_screen; - const int result=run_until(stream,context, - [north_fallback](ProControllerContext& ctx) { - for (size_t i=0;i<10;i++) { - pbf_press_button(ctx,BUTTON_DOWN,180ms,0ms); - pbf_press_button(ctx,BUTTON_RIGHT,180ms,0ms); - pbf_press_button(ctx,BUTTON_RIGHT,180ms,0ms); - } - if (north_fallback>0ms) - pbf_move_left_joystick(ctx,{0,1},north_fallback,0ms); - },{{black_screen}}); + const int result = + run_until(stream, context, + [north_fallback](ProControllerContext& ctx){ + for (size_t i = 0; i < 10; i++){ + pbf_press_button(ctx, BUTTON_DOWN, 180ms, 0ms); + pbf_press_button(ctx, BUTTON_RIGHT, 180ms, 0ms); + pbf_press_button(ctx, BUTTON_RIGHT, 180ms, 0ms); + } + if (north_fallback > 0ms){ + pbf_move_left_joystick(ctx, {0, 1}, north_fallback, 0ms); + } + }, + {{black_screen}}); context.wait_for_all_requests(); - if (result==0) { - stream.log(label+": Black screen detected."); + if (result == 0){ + stream.log(label + ": Black screen detected."); return; } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label+": No black screen after 3 attempts.",stream); + label + ": No black screen after 3 attempts.", stream); } - static bool mine_confirm_escape_via_menu( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type - ) { - MenuWatcher game_menu; - OverworldWatcher overworld; - BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); - for (size_t attempt = 1; attempt <= 3; attempt++) { - // Do not accept the absence-based overworld detector before menu proof. - const int state = wait_until(stream, context, 300ms, {{game_menu}, {battle_menu}}); - if (state == 1) { - stream.log("[Mine escape] Battle menu still present; retrying Run."); - return false; - } - int detected = state; - if (state != 0) { - stream.log("[Mine escape] Main menu fallback attempt " + std::to_string(attempt) + "/3."); - detected = run_until( - stream, context, - [](ProControllerContext& controller) { - pbf_press_button(controller, BUTTON_X, 160ms, 840ms); - pbf_wait(controller, 3s); - controller.wait_for_all_requests(); - }, - {game_menu, battle_menu} - ); - if (detected == 1) { - return false; - } - } - if (detected != 0) { - continue; - } - stream.log("[Mine escape] Main game menu confirmed; battle has ended.", COLOR_GREEN); - // Close the confirmed menu before returning control to navigation. - MenuDetector menu_visible; - for (size_t close = 0; close < 3; close++) { - pbf_press_button(context, BUTTON_B, 80ms, 500ms); - context.wait_for_all_requests(); - if (wait_until(stream, context, 1s, {{overworld}}) == 0) { - return true; - } - BlackScreenWatcher visible( - COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms - ); - if (wait_until(stream, context, 2s, {{visible}}) == 0) { - const VideoSnapshot frame = stream.video().snapshot(); - if (frame && !menu_visible.detect(frame)) { - stream.log("[Mine escape] Confirmed game menu closed; returning to navigation.", COLOR_GREEN); - return true; - } - } - } - stream.log("[Mine escape] Main menu detected but could not confirm it closed.", COLOR_ORANGE); - return false; - } +static bool mine_confirm_escape_via_menu(VideoStream& stream, ProControllerContext& context, BattleType battle_type){ + MenuWatcher game_menu; + OverworldWatcher overworld; + BattleMenuWatcher battle_menu(battle_type, COLOR_YELLOW); + for (size_t attempt = 1; attempt <= 3; attempt++){ + // Do not accept the absence-based overworld detector before menu proof. + const int state = wait_until(stream, context, 300ms, {{game_menu}, {battle_menu}}); + if (state == 1){ + stream.log("[Mine escape] Battle menu still present; retrying Run."); + return false; + } + int detected = state; + if (state != 0){ + stream.log("[Mine escape] Main menu fallback attempt " + std::to_string(attempt) + "/3."); + detected = run_until(stream, context, + [](ProControllerContext& controller){ + pbf_press_button(controller, BUTTON_X, 160ms, 840ms); + pbf_wait(controller, 3s); + controller.wait_for_all_requests(); + }, + {game_menu, battle_menu}); + if (detected == 1){ return false; } - - static bool mine_select_run( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type - ) { - BattleMenuDetector cursor(battle_type, COLOR_YELLOW); - size_t matching_frames = 0; - for (size_t check = 0; check < 16; check++) { - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - if (!frame) { - return false; - } - const BattleMenuOption selected = cursor.selected_option(frame); - stream.log("[Mine escape] Battle cursor row=" + - std::to_string(static_cast(selected) + 1) + "; target Run=4."); - if (selected == BattleMenuOption::RUN) { - if (++matching_frames >= 3) { - stream.log("[Mine escape] Run cursor confirmed.", COLOR_GREEN); - return true; - } - pbf_wait(context, 100ms); - } - else { - matching_frames = 0; - if (selected == BattleMenuOption::UNKNOWN) { - // Do not send a direction or A without a known cursor. - pbf_wait(context, 150ms); - } - else { - const DpadPosition direction = selected == BattleMenuOption::BATTLE - ? DPAD_UP : DPAD_DOWN; - pbf_press_dpad(context, direction, 80ms, 250ms); - } - } - } - stream.log("[Mine escape] Could not confirm Run selection; A was not pressed.", COLOR_ORANGE); - return false; + } + if (detected != 0){ + continue; + } + stream.log("[Mine escape] Main game menu confirmed; battle has ended.", COLOR_GREEN); + // Close the confirmed menu before returning control to navigation. + MenuDetector menu_visible; + for (size_t close = 0; close < 3; close++){ + pbf_press_button(context, BUTTON_B, 80ms, 500ms); + context.wait_for_all_requests(); + if (wait_until(stream, context, 1s, {{overworld}}) == 0){ + return true; } - - void escape_wild_battle( - VideoStream& stream, - ProControllerContext& context, - BattleType battle_type, - std::chrono::seconds timeout - ) { - EndBattleWatcher battle_end; - bool exit_transition = false; - const auto deadline = std::chrono::steady_clock::now() + timeout; - while (std::chrono::steady_clock::now() < deadline) { - pbf_press_button(context, BUTTON_B, 40ms, 200ms); - context.wait_for_all_requests(); - BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); - const int ready = wait_until(stream, context, 3s, {{battle_end}, {menu_ready}}); - if (ready == 0) { - exit_transition = true; - break; - } - if (ready != 1) { - continue; - } - if (!mine_select_run(stream, context, battle_type)) { - continue; - } - stream.log("[Mine escape] Run input sent; watching for black-screen exit."); - const int result = run_until( - stream, context, - [](ProControllerContext& controller) { - pbf_press_button(controller, BUTTON_A, 60ms, 0ms); - pbf_wait(controller, 4s); - controller.wait_for_all_requests(); - }, - {battle_end} - ); - if (result == 0) { - exit_transition = true; - break; - } - } - if (!exit_transition && wait_until(stream, context, 10s, {{battle_end}}) == 0) { - exit_transition = true; - } - if (exit_transition) { - // EndBattleWatcher requires black followed by a visible screen. - stream.log("[Mine escape] Black-screen exit transition confirmed.", COLOR_GREEN); - pbf_wait(context, 800ms); - context.wait_for_all_requests(); - return; + BlackScreenWatcher visible(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}, 100, 10, + BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 2s, {{visible}}) == 0){ + const VideoSnapshot frame = stream.video().snapshot(); + if (frame && !menu_visible.detect(frame)){ + stream.log("[Mine escape] Confirmed game menu closed; returning to navigation.", COLOR_GREEN); + return true; } - // Only probe X after normal escape attempts and the final transition wait. - // Absence of battle UI by itself is not evidence of an overworld return. - stream.log("[Mine escape] Exit transition missed; trying late main-menu fallback."); - if (mine_confirm_escape_via_menu(stream, context, battle_type)) { - return; - } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", - stream - ); } + } + stream.log("[Mine escape] Main menu detected but could not confirm it closed.", COLOR_ORANGE); + return false; + } + return false; +} - void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context) { - stream.log("Starting Barry 1 battle..."); +static bool mine_select_run(VideoStream& stream, ProControllerContext& context, BattleType battle_type){ + BattleMenuDetector cursor(battle_type, COLOR_YELLOW); + size_t matching_frames = 0; + for (size_t check = 0; check < 16; check++){ + context.wait_for_all_requests(); + const VideoSnapshot frame = stream.video().snapshot(); + if (!frame){ + return false; + } + const BattleMenuOption selected = cursor.selected_option(frame); + stream.log("[Mine escape] Battle cursor row=" + std::to_string(static_cast(selected) + 1) + + "; target Run=4."); + if (selected == BattleMenuOption::RUN){ + if (++matching_frames >= 3){ + stream.log("[Mine escape] Run cursor confirmed.", COLOR_GREEN); + return true; + } + pbf_wait(context, 100ms); + }else{ + matching_frames = 0; + if (selected == BattleMenuOption::UNKNOWN){ + // Do not send a direction or A without a known cursor. + pbf_wait(context, 150ms); + }else{ + const DpadPosition direction = selected == BattleMenuOption::BATTLE ? DPAD_UP : DPAD_DOWN; + pbf_press_dpad(context, direction, 80ms, 250ms); + } + } + } + stream.log("[Mine escape] Could not confirm Run selection; A was not pressed.", COLOR_ORANGE); + return false; +} - // State machine: loop until EndBattleWatcher fires - for (size_t c = 0; c < 20; c++) { - context.wait_for_all_requests(); +void escape_wild_battle(VideoStream& stream, ProControllerContext& context, BattleType battle_type, + std::chrono::seconds timeout){ + EndBattleWatcher battle_end; + bool exit_transition = false; + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline){ + pbf_press_button(context, BUTTON_B, 40ms, 200ms); + context.wait_for_all_requests(); + BattleMenuWatcher menu_ready(battle_type, COLOR_YELLOW); + const int ready = wait_until(stream, context, 3s, {{battle_end}, {menu_ready}}); + if (ready == 0){ + exit_transition = true; + break; + } + if (ready != 1){ + continue; + } + if (!mine_select_run(stream, context, battle_type)){ + continue; + } + stream.log("[Mine escape] Run input sent; watching for black-screen exit."); + const int result = run_until(stream, context, + [](ProControllerContext& controller){ + pbf_press_button(controller, BUTTON_A, 60ms, 0ms); + pbf_wait(controller, 4s); + controller.wait_for_all_requests(); + }, + {battle_end}); + if (result == 0){ + exit_transition = true; + break; + } + } + if (!exit_transition && wait_until(stream, context, 10s, {{battle_end}}) == 0){ + exit_transition = true; + } + if (exit_transition){ + // EndBattleWatcher requires black followed by a visible screen. + stream.log("[Mine escape] Black-screen exit transition confirmed.", COLOR_GREEN); + pbf_wait(context, 800ms); + context.wait_for_all_requests(); + return; + } + // Only probe X after normal escape attempts and the final transition wait. + // Absence of battle UI by itself is not evidence of an overworld return. + stream.log("[Mine escape] Exit transition missed; trying late main-menu fallback."); + if (mine_confirm_escape_via_menu(stream, context, battle_type)){ + return; + } + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, + "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", stream); +} - BattleMenuWatcher battle_menu(BattleType::STANDARD); - EndBattleWatcher end_battle; - int ret = run_until( - stream, context, - [](ProControllerContext& context) { - pbf_mash_button(context, BUTTON_ZL, 120000ms); - }, - { - battle_menu, - end_battle, - } - ); - switch (ret) { - case 0: - stream.log("Battle menu detected - using first move."); - pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); - break; - case 1: - stream.log("Barry 1 battle complete."); - pbf_mash_button(context, BUTTON_B, 5000ms); - context.wait_for_all_requests(); - return; - default: - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Barry_1_fight: Timed out waiting for battle menu or end.", - stream - ); - } - } +void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ + stream.log("Starting Barry 1 battle..."); - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Barry_1_fight: Battle did not end after 20 turns.", - stream - ); - } + // State machine: loop until EndBattleWatcher fires + for (size_t c = 0; c < 20; c++){ + context.wait_for_all_requests(); + BattleMenuWatcher battle_menu(BattleType::STANDARD); + EndBattleWatcher end_battle; + int ret = run_until(stream, context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_ZL, 120000ms); + }, + { + battle_menu, + end_battle, + }); + switch (ret){ + case 0: + stream.log("Battle menu detected - using first move."); + pbf_press_button(context, BUTTON_ZL, 160ms, 1000ms); + break; + case 1: + stream.log("Barry 1 battle complete."); + pbf_mash_button(context, BUTTON_B, 5000ms); + context.wait_for_all_requests(); + return; + default: + OperationFailedExceptionWithScreenshot::fire( + ErrorReportMode::SEND_ERROR_REPORT, "Barry_1_fight: Timed out waiting for battle menu or end.", stream); } } + + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Barry_1_fight: Battle did not end after 20 turns.", stream); +} + +} +} } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h index 0ec7f5663f..282eee3885 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h @@ -1,22 +1,25 @@ // Shared battle helpers used by AutoStory routes. #pragma once -#include "../PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" +#include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" -namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { // Reuse the Oreburgh Gate down/right entry search. Returns at fade start; // caller owns destination-specific fade completion and interior confirmation. // north_fallback preserves Oreburgh's final north sweep when explicitly set. -void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, - const std::string& label, Milliseconds north_fallback = Milliseconds(0)); +void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, const std::string& label, + Milliseconds north_fallback = Milliseconds(0)); // Shared confirmed-Run escape: verifies exit transition or main menu. void escape_wild_battle(VideoStream& stream, ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD, - std::chrono::seconds timeout = std::chrono::seconds(30)); + BattleType battle_type = BattleType::STANDARD, + std::chrono::seconds timeout = std::chrono::seconds(30)); // Shared legacy first-move battle routine; consolidation remains preliminary. -void Barry_1_fight(SingleSwitchProgramEnvironment& env, - VideoStream& stream, ProControllerContext& context); -}}} +void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index bb9219551d..fee13da09b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -1,161 +1,254 @@ -#include "CommonFramework/GlobalAutoPaths.h" + + #include #include #include + #include + +#include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/Globals.h" + #include "PokemonBDSP_OverworldMatchers.h" -namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ -enum class LandmarkFeatures { GRAYSCALE, LOCAL_CONTRAST, BLUE_FRACTION }; -struct LandmarkConfig { const char* name; const char* path; ImageFloatBox target,search; double threshold,margin; LandmarkFeatures features=LandmarkFeatures::GRAYSCALE; }; + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { +enum class LandmarkFeatures { + GRAYSCALE, + LOCAL_CONTRAST, + BLUE_FRACTION +}; +struct LandmarkConfig { + const char* name; + const char* path; + ImageFloatBox target, search; + double threshold, margin; + LandmarkFeatures features = LandmarkFeatures::GRAYSCALE; +}; // Screen-relative half-widths of the accepted position box, and how closely // two landmarks (or reference variants) must agree to be trusted together. -static constexpr double POSITION_TOLERANCE_X = 1.0/50; -static constexpr double POSITION_TOLERANCE_Y = 1.0/54; -static constexpr double AGREEMENT_X = 1.0/64; -static constexpr double AGREEMENT_Y = 1.0/54; +static constexpr double POSITION_TOLERANCE_X = 1.0 / 50; +static constexpr double POSITION_TOLERANCE_Y = 1.0 / 54; +static constexpr double AGREEMENT_X = 1.0 / 64; +static constexpr double AGREEMENT_Y = 1.0 / 54; static cv::Mat gray_image(const ImageRGB32& image){ - cv::Mat pixels((int)image.height(),(int)image.width(),CV_8UC4, - const_cast(image.data()),image.bytes_per_row()); - cv::Mat gray; cv::cvtColor(pixels,gray,cv::COLOR_BGRA2GRAY); return gray; + cv::Mat pixels((int)image.height(), (int)image.width(), CV_8UC4, const_cast(image.data()), + image.bytes_per_row()); + cv::Mat gray; + cv::cvtColor(pixels, gray, cv::COLOR_BGRA2GRAY); + return gray; } // Opt-in for selected outdoor structural anchors. Divide out slow local lighting // changes so a moving cloud does not alter the printed sign/emblem structure. static cv::Mat normalize_landmark_illumination(const cv::Mat& gray){ - cv::Mat pixels,background,normalized; - gray.convertTo(pixels,CV_32F); - cv::GaussianBlur(pixels,background,cv::Size(0,0),3.0); - cv::divide(pixels,background+1.0,normalized); + cv::Mat pixels, background, normalized; + gray.convertTo(pixels, CV_32F); + cv::GaussianBlur(pixels, background, cv::Size(0, 0), 3.0); + cv::divide(pixels, background + 1.0, normalized); cv::Mat structure; - normalized.convertTo(structure,CV_8U,128.0); + normalized.convertTo(structure, CV_8U, 128.0); return structure; } // White Pokeball versus yellow fascia is a color-ratio boundary. A cast // shadow changes their brightness, so compare blue fraction rather than luma. static cv::Mat landmark_blue_fraction(const ImageRGB32& image){ - cv::Mat pixels((int)image.height(),(int)image.width(),CV_8UC4, - const_cast(image.data()),image.bytes_per_row()); - cv::Mat small,floating; - cv::resize(pixels,small,cv::Size(960,540),0,0,cv::INTER_AREA); - small.convertTo(floating,CV_32F); - std::vector channels;cv::split(floating,channels); - cv::Mat ratio,structure; - cv::divide(channels[0],channels[0]+channels[1]+channels[2]+3.0,ratio); - ratio.convertTo(structure,CV_8U,768.0); + cv::Mat pixels((int)image.height(), (int)image.width(), CV_8UC4, const_cast(image.data()), + image.bytes_per_row()); + cv::Mat small, floating; + cv::resize(pixels, small, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + small.convertTo(floating, CV_32F); + std::vector channels; + cv::split(floating, channels); + cv::Mat ratio, structure; + cv::divide(channels[0], channels[0] + channels[1] + channels[2] + 3.0, ratio); + ratio.convertTo(structure, CV_8U, 768.0); return structure; } -static OverworldMatchResult match_landmarks(const ImageRGB32& frame, - const std::vector& configs, size_t required, double tolerance_x, double tolerance_y, bool smooth_resize=false){ +static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std::vector& configs, + size_t required, double tolerance_x, double tolerance_y, + bool smooth_resize = false){ OverworldMatchResult result; - if (!frame) return result; + if (!frame){ + return result; + } cv::Mat gray; if (smooth_resize){ // Center matching uses explicit area averaging for stable emblem edges. // Other matchers retain their existing ImageRGB32 resize behavior. - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); + cv::resize(gray_image(frame), gray, cv::Size(960, 540), 0, 0, cv::INTER_AREA); }else{ - ImageRGB32 scaled=frame.scale_to(960,540); - gray=gray_image(scaled); + ImageRGB32 scaled = frame.scale_to(960, 540); + gray = gray_image(scaled); } - cv::Mat normalized_gray,chromaticity; - for(const auto& config: configs){ - OverworldLandmarkResult landmark;landmark.name=config.name;landmark.search=config.search; - ImageRGB32 reference(RESOURCE_PATH()+config.path); + cv::Mat normalized_gray, chromaticity; + for (const auto& config : configs){ + OverworldLandmarkResult landmark; + landmark.name = config.name; + landmark.search = config.search; + ImageRGB32 reference(RESOURCE_PATH() + config.path); // Convert normalized geometry to pixels only at the image-processing boundary. - const int template_width = std::max(1, (int)std::lround(config.target.width*gray.cols)); - const int template_height = std::max(1, (int)std::lround(config.target.height*gray.rows)); + const int template_width = std::max(1, (int)std::lround(config.target.width * gray.cols)); + const int template_height = std::max(1, (int)std::lround(config.target.height * gray.rows)); cv::Mat templ; if (smooth_resize){ - cv::resize(gray_image(reference),templ,cv::Size(template_width,template_height),0,0,cv::INTER_AREA); + cv::resize(gray_image(reference), templ, cv::Size(template_width, template_height), 0, 0, cv::INTER_AREA); }else{ - ImageRGB32 small=reference.scale_to(template_width,template_height); - templ=gray_image(small); + ImageRGB32 small = reference.scale_to(template_width, template_height); + templ = gray_image(small); + } + cv::Rect search((int)(config.search.x * 960), (int)(config.search.y * 540), (int)(config.search.width * 960), + (int)(config.search.height * 540)); + search &= cv::Rect(0, 0, 960, 540); + if (search.width < templ.cols || search.height < templ.rows){ + result.landmarks.push_back(landmark); + continue; } - cv::Rect search((int)(config.search.x*960),(int)(config.search.y*540), - (int)(config.search.width*960),(int)(config.search.height*540)); - search &= cv::Rect(0,0,960,540); - if(search.width=config.threshold && landmark.margin>=config.margin; - landmark.found={(double)location.x/960,(double)location.y/540,(double)templ.cols/960,(double)templ.rows/540}; + double runner_up; + cv::minMaxLoc(competitors, nullptr, &runner_up); + landmark.margin = landmark.score - runner_up; + location += search.tl(); + landmark.dx = (double)location.x / gray.cols - config.target.x; + landmark.dy = (double)location.y / gray.rows - config.target.y; + landmark.accepted = landmark.score >= config.threshold && landmark.margin >= config.margin; + landmark.found = {(double)location.x / 960, (double)location.y / 540, (double)templ.cols / 960, + (double)templ.rows / 540}; result.landmarks.push_back(landmark); } - if(required==1 && result.landmarks.size()==1){ - result.located=result.landmarks[0].accepted;result.dx=result.landmarks[0].dx;result.dy=result.landmarks[0].dy; + if (required == 1 && result.landmarks.size() == 1){ + result.located = result.landmarks[0].accepted; + result.dx = result.landmarks[0].dx; + result.dy = result.landmarks[0].dy; }else{ - size_t best_support=0; bool ambiguous=false; - for(const auto& candidate:result.landmarks){ - if(!candidate.accepted)continue; - size_t support=0;double dx=0,dy=0; - for(const auto& other:result.landmarks){ - if(other.accepted && std::abs(candidate.dx-other.dx)<=AGREEMENT_X && std::abs(candidate.dy-other.dy)<=AGREEMENT_Y){ - support++;dx+=other.dx;dy+=other.dy; + size_t best_support = 0; + bool ambiguous = false; + for (const auto& candidate : result.landmarks){ + if (!candidate.accepted){ + continue; + } + size_t support = 0; + double dx = 0, dy = 0; + for (const auto& other : result.landmarks){ + if (other.accepted && std::abs(candidate.dx - other.dx) <= AGREEMENT_X && + std::abs(candidate.dy - other.dy) <= AGREEMENT_Y){ + support++; + dx += other.dx; + dy += other.dy; } } - if(supportbest_support){best_support=support;result.dx=dx;result.dy=dy;ambiguous=false;} - else if(support==best_support && (std::abs(dx-result.dx)>AGREEMENT_X || std::abs(dy-result.dy)>AGREEMENT_Y)) ambiguous=true; + if (support < required){ + continue; + } + dx /= support; + dy /= support; + if (support > best_support){ + best_support = support; + result.dx = dx; + result.dy = dy; + ambiguous = false; + }else if (support == best_support && + (std::abs(dx - result.dx) > AGREEMENT_X || std::abs(dy - result.dy) > AGREEMENT_Y)){ + ambiguous = true; + } } - result.located=best_support>=required && !ambiguous; + result.located = best_support >= required && !ambiguous; } - result.at_target=result.located && std::abs(result.dx)<=tolerance_x && std::abs(result.dy)<=tolerance_y; + result.at_target = result.located && std::abs(result.dx) <= tolerance_x && std::abs(result.dy) <= tolerance_y; return result; } OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ // Same cropped reference emblems and local search windows as production. - OverworldMatchResult fine = match_landmarks(frame,{ - {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{598.0/960,0,356.0/960,212.0/540},.90,0}, - {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{724.0/960,264.0/540,236.0/960,276.0/540},.90,0}, - {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,268.0/540,240.0/960,272.0/540},.90,0} - },2,7.0/960,7.0/540); - if (fine.located) return fine; + OverworldMatchResult fine = match_landmarks(frame, + {{"Mart", + "PokemonBDSP/Autostory/PoketchInteraction/Mart.png", + {0.768750000000, 0.044444444444, 0.079166666667, 0.088888888889}, + {598.0 / 960, 0, 356.0 / 960, 212.0 / 540}, + .90, + 0}, + {"Center", + "PokemonBDSP/Autostory/PoketchInteraction/Center.png", + {0.900000000000, 0.748148148148, 0.075000000000, 0.137037037037}, + {724.0 / 960, 264.0 / 540, 236.0 / 960, 276.0 / 540}, + .90, + 0}, + {"School", + "PokemonBDSP/Autostory/PoketchInteraction/School.png", + {0.006250000000, 0.755555555556, 0.097916666667, 0.133333333333}, + {0, 268.0 / 540, 240.0 / 960, 272.0 / 540}, + .90, + 0}}, + 2, 7.0 / 960, 7.0 / 540); + if (fine.located){ + return fine; + } // Coarse acquisition searches the whole frame. Agreement and a peak margin // guard against confusing the Mart/Center emblems with another building. - OverworldMatchResult coarse = match_landmarks(frame,{ - {"Mart","PokemonBDSP/Autostory/PoketchInteraction/Mart.png",{0.768750000000,0.044444444444,0.079166666667,0.088888888889},{0,0,1,1},.93,.12}, - {"Center","PokemonBDSP/Autostory/PoketchInteraction/Center.png",{0.900000000000,0.748148148148,0.075000000000,0.137037037037},{0,0,1,1},.93,.12}, - {"School","PokemonBDSP/Autostory/PoketchInteraction/School.png",{0.006250000000,0.755555555556,0.097916666667,0.133333333333},{0,0,1,1},.92,.40} - },2,7.0/960,7.0/540); - if (!coarse.located) { + OverworldMatchResult coarse = match_landmarks(frame, + {{"Mart", + "PokemonBDSP/Autostory/PoketchInteraction/Mart.png", + {0.768750000000, 0.044444444444, 0.079166666667, 0.088888888889}, + {0, 0, 1, 1}, + .93, + .12}, + {"Center", + "PokemonBDSP/Autostory/PoketchInteraction/Center.png", + {0.900000000000, 0.748148148148, 0.075000000000, 0.137037037037}, + {0, 0, 1, 1}, + .93, + .12}, + {"School", + "PokemonBDSP/Autostory/PoketchInteraction/School.png", + {0.006250000000, 0.755555555556, 0.097916666667, 0.133333333333}, + {0, 0, 1, 1}, + .92, + .40}}, + 2, 7.0 / 960, 7.0 / 540); + if (!coarse.located){ // Acquisition may expose just one building. School keeps its strong // spatial margin; Mart/Center require a higher score than pair matching. // A single anchor guides coarse movement only, never interaction. const OverworldLandmarkResult* anchor = nullptr; - for (const auto& landmark : coarse.landmarks) { - const bool strong = landmark.accepted && (landmark.name == "School" || - (landmark.score >= .95 && landmark.margin >= .12)); - if (strong && (!anchor || landmark.score > anchor->score)) anchor = &landmark; + for (const auto& landmark : coarse.landmarks){ + const bool strong = + landmark.accepted && (landmark.name == "School" || (landmark.score >= .95 && landmark.margin >= .12)); + if (strong && (!anchor || landmark.score > anchor->score)){ + anchor = &landmark; + } } - if (anchor) { + if (anchor){ bool contradictory = false; - for (const auto& other : coarse.landmarks) - if (other.accepted && (std::abs(other.dx - anchor->dx) > 1.0/192 || std::abs(other.dy - anchor->dy) > 1.0/108)) + for (const auto& other : coarse.landmarks){ + if (other.accepted && + (std::abs(other.dx - anchor->dx) > 1.0 / 192 || std::abs(other.dy - anchor->dy) > 1.0 / 108)){ contradictory = true; - if (!contradictory) { + } + } + if (!contradictory){ coarse.located = true; coarse.dx = anchor->dx; coarse.dy = anchor->dy; @@ -169,106 +262,147 @@ OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ return coarse; } OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame){ - const ImageFloatBox target{0.502083333333,0.235185185185,0.056250000000,0.079629629630}; - auto original=match_landmarks(frame,{{"Mart emblem","PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", - target,{0,0,1,360.0/540},.93,.10}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y); + const ImageFloatBox target{0.502083333333, 0.235185185185, 0.056250000000, 0.079629629630}; + auto original = match_landmarks(frame, + {{"Mart emblem", + "PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", + target, + {0, 0, 1, 360.0 / 540}, + .93, + .10}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y); // The recorded daytime roof appearance narrowly missed the old template. // Preserve both confidence gates and reject disagreement between variants. - auto recorded=match_landmarks(frame,{{"Mart emblem","PokemonBDSP/Autostory/OreburghMart/MartEmblemRecorded.png", - target,{0,0,1,360.0/540},.93,.10}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y); + auto recorded = match_landmarks(frame, + {{"Mart emblem", + "PokemonBDSP/Autostory/OreburghMart/MartEmblemRecorded.png", + target, + {0, 0, 1, 360.0 / 540}, + .93, + .10}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y); if (original.located && recorded.located && - (std::abs(original.dx-recorded.dx)>AGREEMENT_X || std::abs(original.dy-recorded.dy)>AGREEMENT_Y)){ - original.located=false;original.at_target=false;return original; + (std::abs(original.dx - recorded.dx) > AGREEMENT_X || std::abs(original.dy - recorded.dy) > AGREEMENT_Y)){ + original.located = false; + original.at_target = false; + return original; + } + if ((recorded.located && !original.located) || + (recorded.located == original.located && recorded.landmarks[0].score > original.landmarks[0].score)){ + return recorded; } - if ((recorded.located && !original.located) || (recorded.located==original.located && - recorded.landmarks[0].score>original.landmarks[0].score)) return recorded; return original; } OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame){ // Native lighting-test captures avoid resizing artifacts in the reference. // The emblem is about 5.73% of screen width left of the player at approach: // shift its target right to center the door, preserving the approach height. - OverworldMatchResult clear = match_landmarks(frame,{{"Center entrance", - "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", - {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); - OverworldMatchResult shadow = match_landmarks(frame,{{"Center entrance", - "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", - {0.462500000000,0.109259259259,0.072916666667,0.111111111111},{0,0,1,1},.90,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + OverworldMatchResult clear = match_landmarks(frame, + {{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", + {0.462500000000, 0.109259259259, 0.072916666667, 0.111111111111}, + {0, 0, 1, 1}, + .90, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + OverworldMatchResult shadow = match_landmarks(frame, + {{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", + {0.462500000000, 0.109259259259, 0.072916666667, 0.111111111111}, + {0, 0, 1, 1}, + .90, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); // Lighting alternatives must identify the same position when both pass. if (clear.located && shadow.located && - (std::abs(clear.dx-shadow.dx)>AGREEMENT_X || std::abs(clear.dy-shadow.dy)>AGREEMENT_Y)){ + (std::abs(clear.dx - shadow.dx) > AGREEMENT_X || std::abs(clear.dy - shadow.dy) > AGREEMENT_Y)){ clear.located = false; clear.at_target = false; return clear; } - if (shadow.located && (!clear.located || shadow.landmarks[0].score>clear.landmarks[0].score)) + if (shadow.located && (!clear.located || shadow.landmarks[0].score > clear.landmarks[0].score)){ return shadow; + } return clear; } OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame){ // The sign face is stable across lighting; its post and surrounding grass are not. // Path shadows change shape, so validate the corner against four lighting states. // Each alternative still requires the sign and path to agree on position. - const char* corners[]={ - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour0.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png" - }; + const char* corners[] = {"PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour0.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png"}; OverworldMatchResult best; - double best_score=-1; + double best_score = -1; for (const char* corner : corners){ - auto candidate=match_landmarks(frame,{ - {"Route sign face","PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", - {0.292708333333,0.470370370370,0.109375,0.072222222222},{0,0,1,1},.94,.20}, - {"Path corner",corner, - {0.202083333333,0.648148148148,0.133333333333,0.174074074074},{0,0,1,1},.90,.10} - },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + auto candidate = match_landmarks(frame, + {{"Route sign face", + "PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", + {0.292708333333, 0.470370370370, 0.109375, 0.072222222222}, + {0, 0, 1, 1}, + .94, + .20}, + {"Path corner", + corner, + {0.202083333333, 0.648148148148, 0.133333333333, 0.174074074074}, + {0, 0, 1, 1}, + .90, + .10}}, + 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); if (best.located && candidate.located && - (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)){ - best.located=false; - best.at_target=false; + (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ + best.located = false; + best.at_target = false; return best; } - if (candidate.landmarks.size()!=2) continue; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate); - best_score=score; + if (candidate.landmarks.size() != 2){ + continue; + } + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; } } return best; } OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame){ // Building emblems contain no localized words. Ignore NPCs and door lights. - auto result = match_landmarks(frame,{ - {"Center emblem","PokemonBDSP/Autostory/SandgemJunction/Center.png", - {0.171875,0.2,0.058333333333,0.092592592593},{0,0,1,1},.90,.08}, - {"Mart emblem","PokemonBDSP/Autostory/SandgemJunction/Mart.png", - {0.726041666667,0.222222222222,0.0625,0.088888888889},{0,0,1,1},.90,.08} - },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); - if (result.located || result.landmarks.size()!=2) return result; - const auto& center=result.landmarks[0]; - const auto& mart=result.landmarks[1]; - const double dy=(center.dy+mart.dy)/2; - const double horizontal_disagreement=center.dx-mart.dx; + auto result = match_landmarks(frame, + {{"Center emblem", + "PokemonBDSP/Autostory/SandgemJunction/Center.png", + {0.171875, 0.2, 0.058333333333, 0.092592592593}, + {0, 0, 1, 1}, + .90, + .08}, + {"Mart emblem", + "PokemonBDSP/Autostory/SandgemJunction/Mart.png", + {0.726041666667, 0.222222222222, 0.0625, 0.088888888889}, + {0, 0, 1, 1}, + .90, + .08}}, + 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + if (result.located || result.landmarks.size() != 2){ + return result; + } + const auto& center = result.landmarks[0]; + const auto& mart = result.landmarks[1]; + const double dy = (center.dy + mart.dy) / 2; + const double horizontal_disagreement = center.dx - mart.dx; // At a different approach height, perspective changes the buildings' spacing. // Trust matching height offsets for vertical acquisition only. Bound the // horizontal discrepancy and require its direction to agree with perspective: // scenery above target contracts, scenery below target expands. - if (center.accepted && mart.accepted && - std::abs(center.dy-mart.dy)<=AGREEMENT_Y && - std::abs(dy)>1.0/72 && std::abs(dy)<=0.20 && - std::abs(horizontal_disagreement)<=0.04 && - std::abs(horizontal_disagreement)<=0.35*std::abs(dy)+1.0/192 && - horizontal_disagreement*dy<=0){ - result.located=true; - result.coarse=true; - result.dx=(center.dx+mart.dx)/2; - result.dy=dy; + if (center.accepted && mart.accepted && std::abs(center.dy - mart.dy) <= AGREEMENT_Y && std::abs(dy) > 1.0 / 72 && + std::abs(dy) <= 0.20 && std::abs(horizontal_disagreement) <= 0.04 && + std::abs(horizontal_disagreement) <= 0.35 * std::abs(dy) + 1.0 / 192 && horizontal_disagreement * dy <= 0){ + result.located = true; + result.coarse = true; + result.dx = (center.dx + mart.dx) / 2; + result.dy = dy; // Acquisition may move vertically; it never confirms the route endpoint. - result.at_target=false; + result.at_target = false; } return result; } @@ -276,77 +410,92 @@ OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ // Route marker and upper path bend avoid the player, NPCs, and localized text. // Morning tree shadows need a second corner reference. Each alternative // must still agree with the sign; neither can confirm the destination alone. - const char* corners[]={ - "PokemonBDSP/Autostory/Route202GrassExit/PathCorner.png", - "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png" - }; + const char* corners[] = {"PokemonBDSP/Autostory/Route202GrassExit/PathCorner.png", + "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png"}; OverworldMatchResult best; - double best_score=-1; + double best_score = -1; for (const char* corner : corners){ - auto candidate=match_landmarks(frame,{ - {"Route 202 sign","PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", - {0.59375,0.194444444444,0.091666666667,0.059259259259},{0,0,1,1},.94,.20}, - {"Path corner",corner, - {0.405208333333,0.331481481481,0.1375,0.062962962963},{0,0,1,1},.90,.08} - },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + auto candidate = match_landmarks( + frame, + {{"Route 202 sign", + "PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", + {0.59375, 0.194444444444, 0.091666666667, 0.059259259259}, + {0, 0, 1, 1}, + .94, + .20}, + {"Path corner", corner, {0.405208333333, 0.331481481481, 0.1375, 0.062962962963}, {0, 0, 1, 1}, .90, .08}}, + 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); if (best.located && candidate.located && - (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)){ - best.located=false; - best.at_target=false; + (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ + best.located = false; + best.at_target = false; return best; } - if (candidate.landmarks.size()!=2) continue; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate); - best_score=score; + if (candidate.landmarks.size() != 2){ + continue; + } + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; } } return best; } // Lighting alternatives describe one physical landmark, not extra votes. // Select one reference per landmark before requiring the two positions to agree. -static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, - const std::vector& variants, const char* first, const char* second, - double tolerance_x=POSITION_TOLERANCE_X, bool allow_vertical_acquisition=false, double agreement_x=AGREEMENT_X){ - auto candidates=match_landmarks(frame,variants,variants.size()+1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); +static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, const std::vector& variants, + const char* first, const char* second, + double tolerance_x = POSITION_TOLERANCE_X, + bool allow_vertical_acquisition = false, + double agreement_x = AGREEMENT_X){ + auto candidates = + match_landmarks(frame, variants, variants.size() + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); OverworldMatchResult result; - for (const char* name : {first,second}){ - const OverworldLandmarkResult* best=nullptr; + for (const char* name : {first, second}){ + const OverworldLandmarkResult* best = nullptr; for (const auto& candidate : candidates.landmarks){ - if (candidate.name!=name) continue; + if (candidate.name != name){ + continue; + } if (!best || (candidate.accepted && !best->accepted) || - (candidate.accepted==best->accepted && candidate.score>best->score)) best=&candidate; + (candidate.accepted == best->accepted && candidate.score > best->score)){ + best = &candidate; + } + } + if (!best){ + return result; } - if (!best) return result; result.landmarks.push_back(*best); - auto& chosen=result.landmarks.back(); + auto& chosen = result.landmarks.back(); // Similar strong references must not disagree about location. for (const auto& candidate : candidates.landmarks){ - if (candidate.name==name && candidate.accepted && - candidate.score>=chosen.score-0.02 && - (std::abs(candidate.dx-chosen.dx)>AGREEMENT_X || - std::abs(candidate.dy-chosen.dy)>AGREEMENT_Y)) chosen.accepted=false; - } - } - const auto& a=result.landmarks[0];const auto& b=result.landmarks[1]; - result.located=a.accepted && b.accepted && - std::abs(a.dx-b.dx)<=agreement_x && std::abs(a.dy-b.dy)<=AGREEMENT_Y; - result.dx=(a.dx+b.dx)/2;result.dy=(a.dy+b.dy)/2; - result.at_target=result.located && - std::abs(a.dx-b.dx)<=AGREEMENT_X && std::abs(result.dx)<=tolerance_x && std::abs(result.dy)<=POSITION_TOLERANCE_Y; - const double spacing_difference=a.dx-b.dx; + if (candidate.name == name && candidate.accepted && candidate.score >= chosen.score - 0.02 && + (std::abs(candidate.dx - chosen.dx) > AGREEMENT_X || + std::abs(candidate.dy - chosen.dy) > AGREEMENT_Y)){ + chosen.accepted = false; + } + } + } + const auto& a = result.landmarks[0]; + const auto& b = result.landmarks[1]; + result.located = + a.accepted && b.accepted && std::abs(a.dx - b.dx) <= agreement_x && std::abs(a.dy - b.dy) <= AGREEMENT_Y; + result.dx = (a.dx + b.dx) / 2; + result.dy = (a.dy + b.dy) / 2; + result.at_target = result.located && std::abs(a.dx - b.dx) <= AGREEMENT_X && std::abs(result.dx) <= tolerance_x && + std::abs(result.dy) <= POSITION_TOLERANCE_Y; + const double spacing_difference = a.dx - b.dx; // At the street approach height, perspective can change column spacing. // Agreeing vertical offsets may guide an upward correction, but cannot // confirm the destination or authorize a horizontal correction yet. if (!result.located && allow_vertical_acquisition && a.accepted && b.accepted && - std::abs(a.dy-b.dy)<=AGREEMENT_Y && std::abs(result.dy)>POSITION_TOLERANCE_Y && std::abs(result.dy)<=0.20 && - std::abs(spacing_difference)<=0.04 && - std::abs(spacing_difference)<=0.35*std::abs(result.dy)+1.0/192 && spacing_difference*result.dy<=0){ - result.located=true; - result.coarse=true; - result.at_target=false; + std::abs(a.dy - b.dy) <= AGREEMENT_Y && std::abs(result.dy) > POSITION_TOLERANCE_Y && + std::abs(result.dy) <= 0.20 && std::abs(spacing_difference) <= 0.04 && + std::abs(spacing_difference) <= 0.35 * std::abs(result.dy) + 1.0 / 192 && spacing_difference * result.dy <= 0){ + result.located = true; + result.coarse = true; + result.at_target = false; } return result; } @@ -355,58 +504,96 @@ OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame){ // The Pokeball is primary. Sign and bush assist when visible, but cloud // shadows must not prevent arrival from a strong, unique emblem match. OverworldMatchResult best; - double best_score=-1; - bool best_primary_only=false; + double best_score = -1; + bool best_primary_only = false; // Recorded evening/daytime appearances retain the same destination and gates. - for (int appearance=0;appearance<5;appearance++){ - for (double scale : {.90,.95,1.0,1.05,1.10}){ - std::vector configs={ - {"Center Pokeball","PokemonBDSP/Autostory/EternaCenter/BallTarget.png", - {904.0/1920,144.0/1080,112.0/1920,104.0/1080},{0,0,1,.65},.93,.20}, - {"Right sign","PokemonBDSP/Autostory/EternaCenter/RightSignTarget.png", - {1762.0/1920,304.0/1080,128.0/1920,138.0/1080},{0,0,1,.85},.93,.20}, - {"Lower left bush","PokemonBDSP/Autostory/EternaCenter/BushTarget.png", - {248.0/1920,854.0/1080,180.0/1920,146.0/1080},{0,.35,1,.65},.93,.20} - }; - if (appearance==1 || appearance==2){ - configs={ - {"Center Pokeball",appearance==1 ? "PokemonBDSP/Autostory/EternaCenter/BallEvening1.png" : "PokemonBDSP/Autostory/EternaCenter/BallEvening2.png", - {920.0/1920,154.0/1080,82.0/1920,84.0/1080},{0,0,1,.65},.93,.20}, - {"Right sign",appearance==1 ? "PokemonBDSP/Autostory/EternaCenter/RightSignEvening1.png" : "PokemonBDSP/Autostory/EternaCenter/RightSignEvening2.png", - {1770.0/1920,310.0/1080,116.0/1920,63.0/1080},{0,0,1,.85},.93,.20}, - {"Lower left bush",appearance==1 ? "PokemonBDSP/Autostory/EternaCenter/BushEvening1.png" : "PokemonBDSP/Autostory/EternaCenter/BushEvening2.png", - {248.0/1920,932.0/1080,180.0/1920,68.0/1080},{0,.35,1,.65},.93,.20} - }; - } - if (appearance>=3){ + for (int appearance = 0; appearance < 5; appearance++){ + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs = {{"Center Pokeball", + "PokemonBDSP/Autostory/EternaCenter/BallTarget.png", + {904.0 / 1920, 144.0 / 1080, 112.0 / 1920, 104.0 / 1080}, + {0, 0, 1, .65}, + .93, + .20}, + {"Right sign", + "PokemonBDSP/Autostory/EternaCenter/RightSignTarget.png", + {1762.0 / 1920, 304.0 / 1080, 128.0 / 1920, 138.0 / 1080}, + {0, 0, 1, .85}, + .93, + .20}, + {"Lower left bush", + "PokemonBDSP/Autostory/EternaCenter/BushTarget.png", + {248.0 / 1920, 854.0 / 1080, 180.0 / 1920, 146.0 / 1080}, + {0, .35, 1, .65}, + .93, + .20}}; + if (appearance == 1 || appearance == 2){ + configs = {{"Center Pokeball", + appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/BallEvening1.png" + : "PokemonBDSP/Autostory/EternaCenter/BallEvening2.png", + {920.0 / 1920, 154.0 / 1080, 82.0 / 1920, 84.0 / 1080}, + {0, 0, 1, .65}, + .93, + .20}, + {"Right sign", + appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/RightSignEvening1.png" + : "PokemonBDSP/Autostory/EternaCenter/RightSignEvening2.png", + {1770.0 / 1920, 310.0 / 1080, 116.0 / 1920, 63.0 / 1080}, + {0, 0, 1, .85}, + .93, + .20}, + {"Lower left bush", + appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/BushEvening1.png" + : "PokemonBDSP/Autostory/EternaCenter/BushEvening2.png", + {248.0 / 1920, 932.0 / 1080, 180.0 / 1920, 68.0 / 1080}, + {0, .35, 1, .65}, + .93, + .20}}; + } + if (appearance >= 3){ // Recorded daylight changes the bush/planter contrast. These // crops retain the original doorway geometry and all three votes. - configs[0].path=appearance==3 ? "PokemonBDSP/Autostory/EternaCenter/BallDaytime1.png" : "PokemonBDSP/Autostory/EternaCenter/BallDaytime2.png"; - configs[1].path=appearance==3 ? "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime1.png" : "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime2.png"; - configs[2].path=appearance==3 ? "PokemonBDSP/Autostory/EternaCenter/BushDaytime1.png" : "PokemonBDSP/Autostory/EternaCenter/BushDaytime2.png"; + configs[0].path = appearance == 3 ? "PokemonBDSP/Autostory/EternaCenter/BallDaytime1.png" + : "PokemonBDSP/Autostory/EternaCenter/BallDaytime2.png"; + configs[1].path = appearance == 3 ? "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime1.png" + : "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime2.png"; + configs[2].path = appearance == 3 ? "PokemonBDSP/Autostory/EternaCenter/BushDaytime1.png" + : "PokemonBDSP/Autostory/EternaCenter/BushDaytime2.png"; } for (auto& config : configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); - if (candidate.located) { - double score=1; - for (const auto& landmark:candidate.landmarks) score=std::min(score,landmark.score); - if (!best.located || best_primary_only || score>best_score){best=candidate;best_score=score;best_primary_only=false;} - } else if (!best.located || best_primary_only) { + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 3, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (candidate.located){ + double score = 1; + for (const auto& landmark : candidate.landmarks){ + score = std::min(score, landmark.score); + } + if (!best.located || best_primary_only || score > best_score){ + best = candidate; + best_score = score; + best_primary_only = false; + } + }else if (!best.located || best_primary_only){ // Optional sign/bush can disappear under clouds or at frame edges. // Retain the existing stricter primary-emblem confidence gate. - const auto& ball=candidate.landmarks[0]; - if (ball.accepted && ball.score>=.95 && - (!best.located || ball.score>best_score)){ - best=candidate;best.located=true;best.coarse=false; - best.dx=ball.dx;best.dy=ball.dy; - best.at_target=std::abs(best.dx)<=1.0/128 && std::abs(best.dy)<=POSITION_TOLERANCE_Y; - best_score=ball.score;best_primary_only=true; - } else if (best.landmarks.empty()) best=candidate; + const auto& ball = candidate.landmarks[0]; + if (ball.accepted && ball.score >= .95 && (!best.located || ball.score > best_score)){ + best = candidate; + best.located = true; + best.coarse = false; + best.dx = ball.dx; + best.dy = ball.dy; + best.at_target = std::abs(best.dx) <= 1.0 / 128 && std::abs(best.dy) <= POSITION_TOLERANCE_Y; + best_score = ball.score; + best_primary_only = true; + }else if (best.landmarks.empty()){ + best = candidate; + } } } } @@ -416,80 +603,119 @@ OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame){ // Native screenshot 20261022-213928168268: centered above the east stairs. // The isolated upper rock and lower rock pair must both agree before descent. OverworldMatchResult best; - double best_score=-1; + double best_score = -1; // Stationary hourly captures supply lighting appearances, not new goals. // Both physical rock groups still need the same confidence and agreement. - const char* references[][2]={ - {"PokemonBDSP/Autostory/Route208Stairs/UpperRock.png","PokemonBDSP/Autostory/Route208Stairs/LowerRocks.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Upper.png","PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Lower.png"}, + const char* references[][2] = { + {"PokemonBDSP/Autostory/Route208Stairs/UpperRock.png", "PokemonBDSP/Autostory/Route208Stairs/LowerRocks.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Lower.png"}, }; - for (const auto& reference:references){ - for (double scale : {.90,.95,1.0,1.05,1.10}){ - std::vector configs={ - {"Stair-top upper rock",reference[0], - {170.0/1920,350.0/1080,120.0/1920,120.0/1080},{0,0,1,1},.93,.20}, - {"Stair-top lower rock pair",reference[1], - {240.0/1920,425.0/1080,130.0/1920,180.0/1080},{0,0,1,1},.93,.20} - }; - for (auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - double score=1; - for (const auto& landmark:candidate.landmarks) score=std::min(score,landmark.score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate);best_score=score; + for (const auto& reference : references){ + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs = {{"Stair-top upper rock", + reference[0], + {170.0 / 1920, 350.0 / 1080, 120.0 / 1920, 120.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}, + {"Stair-top lower rock pair", + reference[1], + {240.0 / 1920, 425.0 / 1080, 130.0 / 1920, 180.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}; + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + double score = 1; + for (const auto& landmark : candidate.landmarks){ + score = std::min(score, landmark.score); + } + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; + } } } + if (best.located){ + return best; } - if (best.located) return best; - const char* approaches[][2]={ - {"PokemonBDSP/Autostory/Route208Stairs/Approach0507Upper.png","PokemonBDSP/Autostory/Route208Stairs/Approach0507Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Approach0538Upper.png","PokemonBDSP/Autostory/Route208Stairs/Approach0538Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/Approach0549Upper.png","PokemonBDSP/Autostory/Route208Stairs/Approach0549Lower.png"}, + const char* approaches[][2] = { + {"PokemonBDSP/Autostory/Route208Stairs/Approach0507Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Approach0507Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Approach0538Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Approach0538Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/Approach0549Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/Approach0549Lower.png"}, // The eastern cliff pose changes rock perspective and the local shadow // pattern. These real-run appearances guide acquisition only: retain // .92 acquisition NCC/.20 uniqueness and two-rock offset agreement. // Arrival still requires the original .93 strict reference bank. - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Lower.png"}, - {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Upper.png","PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Lower.png"}, + {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Upper.png", + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Lower.png"}, }; - for (const auto& reference:approaches){ - for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ - std::vector configs={ - {"Stair-top approach upper rock",reference[0], - {170.0/1920,350.0/1080,120.0/1920,120.0/1080},{0,0,.50,.85},.92,.20}, - {"Stair-top approach lower rock pair",reference[1], - {240.0/1920,425.0/1080,130.0/1920,180.0/1080},{0,0,.50,.85},.92,.20} - }; - for (auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - candidate.coarse=true;candidate.at_target=false; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if (candidate.located && (!best.located || score>best_score)){ - best=std::move(candidate);best_score=score; + for (const auto& reference : approaches){ + for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ + std::vector configs = {{"Stair-top approach upper rock", + reference[0], + {170.0 / 1920, 350.0 / 1080, 120.0 / 1920, 120.0 / 1080}, + {0, 0, .50, .85}, + .92, + .20}, + {"Stair-top approach lower rock pair", + reference[1], + {240.0 / 1920, 425.0 / 1080, 130.0 / 1920, 180.0 / 1080}, + {0, 0, .50, .85}, + .92, + .20}}; + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + candidate.coarse = true; + candidate.at_target = false; + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if (candidate.located && (!best.located || score > best_score)){ + best = std::move(candidate); + best_score = score; } } } @@ -499,40 +725,54 @@ OverworldMatchResult match_route208_lower_stair_lane(const ImageRGB32& frame){ // Successful 1825ms descent endpoint at video 230s (2026-10-24 15-38-06). // Independent cliff/tree anchors must agree before movement is authorized. OverworldMatchResult best; - double best_score=-1; - for (double scale : {.90,.95,1.0,1.05,1.10}){ - std::vector configs={ - {"Lower stair cliff corner","PokemonBDSP/Autostory/Route208LowerLane/CliffCorner.png", - {690.0/1920,540.0/1080,110.0/1920,150.0/1080},{0,0,1,1},.93,.20}, - {"Honey tree","PokemonBDSP/Autostory/Route208LowerLane/HoneyTree.png", - {1510.0/1920,600.0/1080,100.0/1920,170.0/1080},{0,0,1,1},.93,.20} - }; - for (auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - if (candidate.landmarks.size()!=2) continue; - const auto& cliff=candidate.landmarks[0]; - const auto& tree=candidate.landmarks[1]; + double best_score = -1; + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs = {{"Lower stair cliff corner", + "PokemonBDSP/Autostory/Route208LowerLane/CliffCorner.png", + {690.0 / 1920, 540.0 / 1080, 110.0 / 1920, 150.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}, + {"Honey tree", + "PokemonBDSP/Autostory/Route208LowerLane/HoneyTree.png", + {1510.0 / 1920, 600.0 / 1080, 100.0 / 1920, 170.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}; + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (candidate.landmarks.size() != 2){ + continue; + } + const auto& cliff = candidate.landmarks[0]; + const auto& tree = candidate.landmarks[1]; // Perspective changes anchor spacing farther down the field. Two strong // anchors can authorize north-only recovery while bracketing the player. // This cue never confirms arrival or authorizes east travel. - if (!candidate.located && cliff.accepted && tree.accepted && - cliff.dy < -1.0/24 && tree.dy < -1.0/24 && - std::abs(cliff.dy-tree.dy)<=AGREEMENT_Y && - cliff.found.x+cliff.found.width<.5 && tree.found.x>.65){ - candidate.located=true;candidate.coarse=true;candidate.at_target=false; - candidate.dx=0;candidate.dy=(cliff.dy+tree.dy)/2; - } - double score=1; - for (const auto& landmark:candidate.landmarks) score=std::min(score,landmark.score); + if (!candidate.located && cliff.accepted && tree.accepted && cliff.dy < -1.0 / 24 && tree.dy < -1.0 / 24 && + std::abs(cliff.dy - tree.dy) <= AGREEMENT_Y && cliff.found.x + cliff.found.width < .5 && + tree.found.x > .65){ + candidate.located = true; + candidate.coarse = true; + candidate.at_target = false; + candidate.dx = 0; + candidate.dy = (cliff.dy + tree.dy) / 2; + } + double score = 1; + for (const auto& landmark : candidate.landmarks){ + score = std::min(score, landmark.score); + } if ((candidate.located && !best.located) || (candidate.located && best.located && best.coarse && !candidate.coarse) || - (candidate.located==best.located && candidate.coarse==best.coarse && score>best_score)){ - best=std::move(candidate);best_score=score; + (candidate.located == best.located && candidate.coarse == best.coarse && score > best_score)){ + best = std::move(candidate); + best_score = score; } } return best; @@ -541,110 +781,123 @@ OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame){ // butters' marked sign frame and rock pair; the cloud-sensitive pillar is excluded. // Only the sign's left frame/post is used; lettering is excluded. OverworldMatchResult best; - double best_score=-1; + double best_score = -1; // Lighting samples are translated back to the original marked target. // Appearance 1 remains acquisition-only; all others describe that target. - static const std::array appearances[]={ - {"PokemonBDSP/Autostory/Route209Repel/SignFrame.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair.png"}, + static const std::array appearances[] = { + {"PokemonBDSP/Autostory/Route209Repel/SignFrame.png", "PokemonBDSP/Autostory/Route209Repel/RockPair.png"}, {"PokemonBDSP/Autostory/Route209Repel/EastSignFrame.png", - "PokemonBDSP/Autostory/Route209Repel/EastRockPair.png"}, + "PokemonBDSP/Autostory/Route209Repel/EastRockPair.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H04F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H04F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H04F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H18F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H18F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H18F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F3.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F3.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F3.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F2.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F2.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F2.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F3.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F3.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F3.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F2.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F2.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F2.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F3.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F3.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F3.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H03F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H03F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H03F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H13F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H13F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H13F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H14F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H14F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H14F5.png"}, }; - for (double scale : {1.0,.95,1.05,.90,1.10}){ + for (double scale : {1.0, .95, 1.05, .90, 1.10}){ std::vector lighting_landmarks(2); - for (size_t appearance=0;appearance configs={ - {"Sign frame","PokemonBDSP/Autostory/Route209Repel/SignFrame.png", - {1242.0/1920,580.0/1080,28.0/1920,80.0/1080},{0,0,1,1},.93,.15}, - {"Rock pair","PokemonBDSP/Autostory/Route209Repel/RockPair.png", - {580.0/1920,690.0/1080,155.0/1920,165.0/1080},{0,0,1,1},.93,.15} - }; - for (size_t i=0;i configs = {{"Sign frame", + "PokemonBDSP/Autostory/Route209Repel/SignFrame.png", + {1242.0 / 1920, 580.0 / 1080, 28.0 / 1920, 80.0 / 1080}, + {0, 0, 1, 1}, + .93, + .15}, + {"Rock pair", + "PokemonBDSP/Autostory/Route209Repel/RockPair.png", + {580.0 / 1920, 690.0 / 1080, 155.0 / 1920, 165.0 / 1080}, + {0, 0, 1, 1}, + .93, + .15}}; + for (size_t i = 0; i < configs.size(); i++){ + configs[i].path = appearances[appearance][i]; + } for (auto& config : configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - if (appearance!=1){ - for(size_t i=0;ilighting_landmarks[i].score))lighting_landmarks[i]=landmark; + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (appearance != 1){ + for (size_t i = 0; i < candidate.landmarks.size(); i++){ + const auto& landmark = candidate.landmarks[i]; + if (landmark.accepted && + (!lighting_landmarks[i].accepted || landmark.score > lighting_landmarks[i].score)){ + lighting_landmarks[i] = landmark; + } } } - double score=1; + double score = 1; // Both remaining anchors must pass confidence and agree spatially. - for (const auto& landmark : candidate.landmarks) - score=std::min(score,landmark.score); - candidate.at_target=appearance!=1 && candidate.located && std::abs(candidate.dx)<=1.0/128 && - std::abs(candidate.dy)<=POSITION_TOLERANCE_Y; + for (const auto& landmark : candidate.landmarks){ + score = std::min(score, landmark.score); + } + candidate.at_target = appearance != 1 && candidate.located && std::abs(candidate.dx) <= 1.0 / 128 && + std::abs(candidate.dy) <= POSITION_TOLERANCE_Y; // East-side appearance is acquisition-only; the marked pose owns arrival. - if (appearance==1 && std::abs(candidate.dx)<=1.0/128 && - std::abs(candidate.dy)<=POSITION_TOLERANCE_Y) candidate.located=false; - if (candidate.located)return candidate; - if ((candidate.at_target && !best.at_target) || - (candidate.located && !best.located) || - (candidate.at_target==best.at_target && candidate.located==best.located && score>best_score)){ - best=std::move(candidate);best_score=score; + if (appearance == 1 && std::abs(candidate.dx) <= 1.0 / 128 && + std::abs(candidate.dy) <= POSITION_TOLERANCE_Y){ + candidate.located = false; + } + if (candidate.located){ + return candidate; + } + if ((candidate.at_target && !best.at_target) || (candidate.located && !best.located) || + (candidate.at_target == best.at_target && candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; } } // Combine lighting references only when the sign and rock agree. OverworldMatchResult combined; - combined.landmarks=std::move(lighting_landmarks); - const auto& sign=combined.landmarks[0]; - const auto& rock=combined.landmarks[1]; - combined.located=sign.accepted && rock.accepted && - std::abs(sign.dx-rock.dx)<=AGREEMENT_X && - std::abs(sign.dy-rock.dy)<=AGREEMENT_Y; - if(combined.located){ - combined.dx=(sign.dx+rock.dx)/2; - combined.dy=(sign.dy+rock.dy)/2; - combined.at_target=std::abs(combined.dx)<=1.0/128 && - std::abs(combined.dy)<=POSITION_TOLERANCE_Y; + combined.landmarks = std::move(lighting_landmarks); + const auto& sign = combined.landmarks[0]; + const auto& rock = combined.landmarks[1]; + combined.located = sign.accepted && rock.accepted && std::abs(sign.dx - rock.dx) <= AGREEMENT_X && + std::abs(sign.dy - rock.dy) <= AGREEMENT_Y; + if (combined.located){ + combined.dx = (sign.dx + rock.dx) / 2; + combined.dy = (sign.dy + rock.dy) / 2; + combined.at_target = std::abs(combined.dx) <= 1.0 / 128 && std::abs(combined.dy) <= POSITION_TOLERANCE_Y; return combined; } } @@ -652,43 +905,70 @@ OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame){ } OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ OverworldMatchResult best; - double best_score=-1; - for (int appearance : {0,865,930,965,978}){ - for (double scale : {.90,.95,1.0,1.05,1.10}){ - std::vector configs={ - {"Field stair foot","PokemonBDSP/Autostory/Route208Hannah/StairFoot.png", - {225.0/1920,275.0/1080,250.0/1920,110.0/1080},{0,0,1,1},.93,.20,LandmarkFeatures::LOCAL_CONTRAST}, - {"Field sign frame","PokemonBDSP/Autostory/Route208Hannah/TreeSign.png", - {1000.0/1920,125.0/1080,100.0/1920,80.0/1080},{0,0,1,1},.93,.20}, - {"Field house sign frame","PokemonBDSP/Autostory/Route208Hannah/HouseSign.png", - {1440.0/1920,110.0/1080,110.0/1920,110.0/1080},{0,0,1,1},.93,.20} - }; + double best_score = -1; + for (int appearance : {0, 865, 930, 965, 978}){ + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs = {{"Field stair foot", + "PokemonBDSP/Autostory/Route208Hannah/StairFoot.png", + {225.0 / 1920, 275.0 / 1080, 250.0 / 1920, 110.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Field sign frame", + "PokemonBDSP/Autostory/Route208Hannah/TreeSign.png", + {1000.0 / 1920, 125.0 / 1080, 100.0 / 1920, 80.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}, + {"Field house sign frame", + "PokemonBDSP/Autostory/Route208Hannah/HouseSign.png", + {1440.0 / 1920, 110.0 / 1080, 110.0 / 1920, 110.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}; if (appearance){ // Recorded dodges stand farther east, with the stairs clipped. // These agreeing signs guide acquisition only; the successful // departure reference above alone may confirm arrival. configs.erase(configs.begin()); - configs[0].path=appearance==865 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign865.png" : - appearance==930 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign930.png" : appearance==965 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign965.png" : "PokemonBDSP/Autostory/Route208Hannah/FieldSign978.png"; - configs[1].path=appearance==865 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign865.png" : - appearance==930 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign930.png" : appearance==965 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign965.png" : "PokemonBDSP/Autostory/Route208Hannah/HouseSign978.png"; - } - for (auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - if (appearance){candidate.coarse=true;candidate.at_target=false;} - double score=1; - size_t accepted=0; - for (const auto& landmark:candidate.landmarks) if (landmark.accepted) {score=std::min(score,landmark.score);accepted++;} - if (!accepted) score=-1; + configs[0].path = appearance == 865 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign865.png" + : appearance == 930 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign930.png" + : appearance == 965 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign965.png" + : "PokemonBDSP/Autostory/Route208Hannah/FieldSign978.png"; + configs[1].path = appearance == 865 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign865.png" + : appearance == 930 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign930.png" + : appearance == 965 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign965.png" + : "PokemonBDSP/Autostory/Route208Hannah/HouseSign978.png"; + } + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (appearance){ + candidate.coarse = true; + candidate.at_target = false; + } + double score = 1; + size_t accepted = 0; + for (const auto& landmark : candidate.landmarks){ + if (landmark.accepted){ + score = std::min(score, landmark.score); + accepted++; + } + } + if (!accepted){ + score = -1; + } if ((candidate.located && !best.located) || (candidate.located && best.located && !candidate.coarse && best.coarse) || - (candidate.located==best.located && candidate.coarse==best.coarse && score>best_score)){ - best=std::move(candidate);best_score=score; + (candidate.located == best.located && candidate.coarse == best.coarse && score > best_score)){ + best = std::move(candidate); + best_score = score; } } } @@ -697,58 +977,90 @@ OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame){ // Real stationary hourly views cover daylight, night, dusk and cloud edges. // The target is butters' street pose below the door; entry remains watched. - struct Reference { const char* ball; const char* awning; double ball_width,ball_height; }; - const Reference references[]={ - {"PokemonBDSP/Autostory/HearthomeCenter/Lighting00Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting00Awning.png",144.0/1920,128.0/1080}, - {"PokemonBDSP/Autostory/HearthomeCenter/Lighting01Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting01Awning.png",144.0/1920,128.0/1080}, - {"PokemonBDSP/Autostory/HearthomeCenter/Lighting02Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting02Awning.png",144.0/1920,128.0/1080}, - {"PokemonBDSP/Autostory/HearthomeCenter/Lighting03Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting03Awning.png",144.0/1920,128.0/1080}, - {"PokemonBDSP/Autostory/HearthomeCenter/Lighting04Ball.png","PokemonBDSP/Autostory/HearthomeCenter/Lighting04Awning.png",112.0/1920,108.0/1080} + struct Reference { + const char* ball; + const char* awning; + double ball_width, ball_height; }; + const Reference references[] = { + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting00Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/Lighting00Awning.png", 144.0 / 1920, 128.0 / 1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting01Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/Lighting01Awning.png", 144.0 / 1920, 128.0 / 1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting02Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/Lighting02Awning.png", 144.0 / 1920, 128.0 / 1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting03Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/Lighting03Awning.png", 144.0 / 1920, 128.0 / 1080}, + {"PokemonBDSP/Autostory/HearthomeCenter/Lighting04Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/Lighting04Awning.png", 112.0 / 1920, 108.0 / 1080}}; std::vector variants; - for (const auto& reference:references){ - for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ - const double w=reference.ball_width*scale,h=reference.ball_height*scale; - const double aw=272.0/1920*scale,ah=80.0/1080*scale; - variants.push_back({"Hearthome Center roof ball",reference.ball, - {972.0/1920-w/2,208.0/1080-h/2,w,h},{0,0,1,.65},.95,.20}); - variants.push_back({"Hearthome Center orange awning",reference.awning, - {968.0/1920-aw/2,308.0/1080-ah/2,aw,ah},{0,0,1,.65},.95,.20}); + for (const auto& reference : references){ + for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ + const double w = reference.ball_width * scale, h = reference.ball_height * scale; + const double aw = 272.0 / 1920 * scale, ah = 80.0 / 1080 * scale; + variants.push_back({"Hearthome Center roof ball", + reference.ball, + {972.0 / 1920 - w / 2, 208.0 / 1080 - h / 2, w, h}, + {0, 0, 1, .65}, + .95, + .20}); + variants.push_back({"Hearthome Center orange awning", + reference.awning, + {968.0 / 1920 - aw / 2, 308.0 / 1080 - ah / 2, aw, ah}, + {0, 0, 1, .65}, + .95, + .20}); } } - return match_lighting_pair(frame,variants,"Hearthome Center roof ball", - "Hearthome Center orange awning",1.0/128); + return match_lighting_pair(frame, variants, "Hearthome Center roof ball", "Hearthome Center orange awning", + 1.0 / 128); } OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ // Three observed entry-camera angles. Perspective changes the machine // and register independently, so require a pair from the same real view. - struct View { const char* machine; const char* desk; ImageFloatBox a,b; }; - const View views[]={ - {"InteriorMachine.png","InteriorRegister.png",{660.0/1920,160.0/1080,220.0/1920,185.0/1080}, - {1150.0/1920,155.0/1080,140.0/1920,175.0/1080}}, - {"EntryRightMachine.png","EntryRightRegister.png",{390.0/1920,160.0/1080,240.0/1920,185.0/1080}, - {905.0/1920,155.0/1080,130.0/1920,175.0/1080}}, - {"EntryLeftMachine.png","EntryLeftRegister.png",{1045.0/1920,160.0/1080,240.0/1920,185.0/1080}, - {1550.0/1920,220.0/1080,155.0/1920,110.0/1080}} + struct View { + const char* machine; + const char* desk; + ImageFloatBox a, b; }; + const View views[] = {{"InteriorMachine.png", + "InteriorRegister.png", + {660.0 / 1920, 160.0 / 1080, 220.0 / 1920, 185.0 / 1080}, + {1150.0 / 1920, 155.0 / 1080, 140.0 / 1920, 175.0 / 1080}}, + {"EntryRightMachine.png", + "EntryRightRegister.png", + {390.0 / 1920, 160.0 / 1080, 240.0 / 1920, 185.0 / 1080}, + {905.0 / 1920, 155.0 / 1080, 130.0 / 1920, 175.0 / 1080}}, + {"EntryLeftMachine.png", + "EntryLeftRegister.png", + {1045.0 / 1920, 160.0 / 1080, 240.0 / 1920, 185.0 / 1080}, + {1550.0 / 1920, 220.0 / 1080, 155.0 / 1920, 110.0 / 1080}}}; OverworldMatchResult best; - double best_score=-1; - for (const auto& view:views){ - const std::string machine="PokemonBDSP/Autostory/HearthomeCenter/"+std::string(view.machine); - const std::string desk="PokemonBDSP/Autostory/HearthomeCenter/"+std::string(view.desk); - for (double scale : {.90,.95,1.0,1.05,1.10}){ - auto a=view.a,b=view.b; - a.x=.5+(a.x-.5)*scale;a.y=.5+(a.y-.5)*scale;a.width*=scale;a.height*=scale; - b.x=.5+(b.x-.5)*scale;b.y=.5+(b.y-.5)*scale;b.width*=scale;b.height*=scale; - auto candidate=match_landmarks(frame,{ - {"Center healing machine",machine.c_str(),a,{0,0,1,.8},.93,.20}, - {"Center register",desk.c_str(),b,{0,0,1,.8},.93,.20} - },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); - double score=1; - for (const auto& anchor:candidate.landmarks) score=std::min(score,anchor.score); - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)){ - best=std::move(candidate);best_score=score; + double best_score = -1; + for (const auto& view : views){ + const std::string machine = "PokemonBDSP/Autostory/HearthomeCenter/" + std::string(view.machine); + const std::string desk = "PokemonBDSP/Autostory/HearthomeCenter/" + std::string(view.desk); + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + auto a = view.a, b = view.b; + a.x = .5 + (a.x - .5) * scale; + a.y = .5 + (a.y - .5) * scale; + a.width *= scale; + a.height *= scale; + b.x = .5 + (b.x - .5) * scale; + b.y = .5 + (b.y - .5) * scale; + b.width *= scale; + b.height *= scale; + auto candidate = match_landmarks(frame, + {{"Center healing machine", machine.c_str(), a, {0, 0, 1, .8}, .93, .20}, + {"Center register", desk.c_str(), b, {0, 0, 1, .8}, .93, .20}}, + 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + double score = 1; + for (const auto& anchor : candidate.landmarks){ + score = std::min(score, anchor.score); + } + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; } } } @@ -757,232 +1069,346 @@ OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame){ // Static sign rim above localized text and roof emblem; Fantina can move. // Hourly captures vary the sign illumination. The roof remains reliable. - const char* signs[] = { - "PokemonBDSP/Autostory/HearthomeContest/SignRim.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting00Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting01Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting02Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting03Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting04Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting05Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting06Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting07Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting08Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting09Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting10Sign.png" - }; + const char* signs[] = {"PokemonBDSP/Autostory/HearthomeContest/SignRim.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting00Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting01Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting02Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting03Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting04Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting05Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting06Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting07Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting08Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting09Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting10Sign.png"}; std::vector variants; - for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ - const double sw=162.0/1920*scale,sh=62.0/1080*scale; - const double rw=136.0/1920*scale,rh=98.0/1080*scale; + for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ + const double sw = 162.0 / 1920 * scale, sh = 62.0 / 1080 * scale; + const double rw = 136.0 / 1920 * scale, rh = 98.0 / 1080 * scale; for (const char* sign : signs){ - variants.push_back({"Contest Hall sign frame",sign, - {595.0/1920-sw/2,467.0/1080-sh/2,sw,sh},{0,.15,.75,.65},.93,.15}); + variants.push_back({"Contest Hall sign frame", + sign, + {595.0 / 1920 - sw / 2, 467.0 / 1080 - sh / 2, sw, sh}, + {0, .15, .75, .65}, + .93, + .15}); } - variants.push_back({"Contest Hall roof emblem","PokemonBDSP/Autostory/HearthomeContest/RoofEmblem.png", - {940.0/1920-rw/2,175.0/1080-rh/2,rw,rh},{.1,0,.8,.65},.93,.15}); + variants.push_back({"Contest Hall roof emblem", + "PokemonBDSP/Autostory/HearthomeContest/RoofEmblem.png", + {940.0 / 1920 - rw / 2, 175.0 / 1080 - rh / 2, rw, rh}, + {.1, 0, .8, .65}, + .93, + .15}); } - return match_lighting_pair(frame,variants,"Contest Hall sign frame", - "Contest Hall roof emblem",1.0/128); + return match_lighting_pair(frame, variants, "Contest Hall sign frame", "Contest Hall roof emblem", 1.0 / 128); } // Fixed city landmarks preserve the recorded passage and western stair lane. OverworldMatchResult match_veilstone_city_passage(const ImageRGB32& frame){ - auto fine=match_landmarks(frame,{ - {"Passage house","PokemonBDSP/Autostory/VeilstoneDawn/PassageHouse.png", - {42.0/960,222.0/540,210.0/960,110.0/540},{0,0,.55,1},.93,.12}, - {"Game Corner facade","PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", - {592.0/960,227.0/540,230.0/960,110.0/540},{.45,0,.55,1},.93,.12} - },2,1.0/128,1.0/108,true); - if (fine.located) return fine; + auto fine = match_landmarks(frame, + {{"Passage house", + "PokemonBDSP/Autostory/VeilstoneDawn/PassageHouse.png", + {42.0 / 960, 222.0 / 540, 210.0 / 960, 110.0 / 540}, + {0, 0, .55, 1}, + .93, + .12}, + {"Game Corner facade", + "PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", + {592.0 / 960, 227.0 / 540, 230.0 / 960, 110.0 / 540}, + {.45, 0, .55, 1}, + .93, + .12}}, + 2, 1.0 / 128, 1.0 / 108, true); + if (fine.located){ + return fine; + } // The former reference column lies just inside the left cliff in the // live corridor. Recenter eight pixels horizontally and two vertically in the // matcher image; keep both arrival tolerances unchanged. // A farther-east expiry may expose only the house's right window. std::vector variants; - for (double scale : {.85,.90,.95,1.0,1.05,1.10}){ - const double hw=95.0/960*scale,hh=80.0/540*scale; - const double gw=230.0/960*scale,gh=110.0/540*scale; - variants.push_back({"House window","PokemonBDSP/Autostory/VeilstoneDawn/PassageHouseWindow.png", - {(165.0+95.0/2-8)/960-hw/2,(250.0+80.0/2+2)/540-hh/2,hw,hh},{0,0,.55,1},.94,.15}); - variants.push_back({"Game Corner facade","PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", - {(600.0+230.0/2-8)/960-gw/2,(225.0+110.0/2+2)/540-gh/2,gw,gh},{.25,0,.75,1},.93,.12}); + for (double scale : {.85, .90, .95, 1.0, 1.05, 1.10}){ + const double hw = 95.0 / 960 * scale, hh = 80.0 / 540 * scale; + const double gw = 230.0 / 960 * scale, gh = 110.0 / 540 * scale; + variants.push_back({"House window", + "PokemonBDSP/Autostory/VeilstoneDawn/PassageHouseWindow.png", + {(165.0 + 95.0 / 2 - 8) / 960 - hw / 2, (250.0 + 80.0 / 2 + 2) / 540 - hh / 2, hw, hh}, + {0, 0, .55, 1}, + .94, + .15}); + variants.push_back({"Game Corner facade", + "PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", + {(600.0 + 230.0 / 2 - 8) / 960 - gw / 2, (225.0 + 110.0 / 2 + 2) / 540 - gh / 2, gw, gh}, + {.25, 0, .75, 1}, + .93, + .12}); } // At the east-side roof obstruction, perspective changes the facade. // Calibrate this acquisition-only view to the simultaneously visible house // window. It guides west off that roof corner; fine arrival stays mandatory. - variants.push_back({"Game Corner facade","PokemonBDSP/Autostory/VeilstoneDawn/GameCornerEastApproach.png", - {644.0/960,228.0/540,240.0/960,100.0/540},{.25,0,.75,1},.93,.12}); - auto approach=match_lighting_pair(frame,variants,"House window","Game Corner facade",1.0/128); - approach.coarse=true;approach.at_target=false; + variants.push_back({"Game Corner facade", + "PokemonBDSP/Autostory/VeilstoneDawn/GameCornerEastApproach.png", + {644.0 / 960, 228.0 / 540, 240.0 / 960, 100.0 / 540}, + {.25, 0, .75, 1}, + .93, + .12}); + auto approach = match_lighting_pair(frame, variants, "House window", "Game Corner facade", 1.0 / 128); + approach.coarse = true; + approach.at_target = false; return approach; } OverworldMatchResult match_veilstone_dawn_stairs(const ImageRGB32& frame){ // The residential terrace to the east has similar stairs. Require its // distinct west corner together with the western staircase, not NPCs. - return match_landmarks(frame,{ - {"Western stairs","PokemonBDSP/Autostory/VeilstoneDawn/GymStairs.png", - {445.0/960,40.0/540,165.0/960,55.0/540},{.10,0,.65,.70},.93,.12}, - {"East terrace corner","PokemonBDSP/Autostory/VeilstoneDawn/EastTerraceCorner.png", - {620.0/960,155.0/540,200.0/960,110.0/540},{.35,0,.65,.85},.93,.12} - },2,1.0/128,1.0/108,true); + return match_landmarks(frame, + {{"Western stairs", + "PokemonBDSP/Autostory/VeilstoneDawn/GymStairs.png", + {445.0 / 960, 40.0 / 540, 165.0 / 960, 55.0 / 540}, + {.10, 0, .65, .70}, + .93, + .12}, + {"East terrace corner", + "PokemonBDSP/Autostory/VeilstoneDawn/EastTerraceCorner.png", + {620.0 / 960, 155.0 / 540, 200.0 / 960, 110.0 / 540}, + {.35, 0, .65, .85}, + .93, + .12}}, + 2, 1.0 / 128, 1.0 / 108, true); } OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame){ // Wider fixed facade distinguishes the window bays at night. Real hourly // references cover the illuminated windows; cursor, NPCs and text stay out. - const char* facades[] = { - "PokemonBDSP/Autostory/HearthomeGym/LeftFacade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting00Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting01Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting02Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting03Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting04Facade.png" - }; + const char* facades[] = {"PokemonBDSP/Autostory/HearthomeGym/LeftFacade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting00Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting01Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting02Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting03Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting04Facade.png"}; std::vector variants; - for (double scale : {.90,.925,.95,.975,1.0,1.025,1.05,1.075,1.10}){ - const double aw=160.0/1920*scale,ah=84.0/1080*scale; - const double fw=218.0/1920*scale,fh=222.0/1080*scale; - variants.push_back({"Hearthome Gym doorway arch","PokemonBDSP/Autostory/HearthomeGym/DoorArch.png", - {966.0/1920-aw/2,236.0/1080-ah/2,aw,ah},{.15,0,.75,.65},.93,.20}); + for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ + const double aw = 160.0 / 1920 * scale, ah = 84.0 / 1080 * scale; + const double fw = 218.0 / 1920 * scale, fh = 222.0 / 1080 * scale; + variants.push_back({"Hearthome Gym doorway arch", + "PokemonBDSP/Autostory/HearthomeGym/DoorArch.png", + {966.0 / 1920 - aw / 2, 236.0 / 1080 - ah / 2, aw, ah}, + {.15, 0, .75, .65}, + .93, + .20}); for (const char* facade : facades){ - variants.push_back({"Hearthome Gym left facade",facade, - {719.0/1920-fw/2,315.0/1080-fh/2,fw,fh},{0,0,.55,.65},.93,.20}); + variants.push_back({"Hearthome Gym left facade", + facade, + {719.0 / 1920 - fw / 2, 315.0 / 1080 - fh / 2, fw, fh}, + {0, 0, .55, .65}, + .93, + .20}); } } - return match_lighting_pair(frame,variants,"Hearthome Gym doorway arch", - "Hearthome Gym left facade",1.0/128); + return match_lighting_pair(frame, variants, "Hearthome Gym doorway arch", "Hearthome Gym left facade", 1.0 / 128); } OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ // Recorded centered doorway pose. All three stationary landmarks must agree // before entering; two can guide acquisition without confirming arrival. OverworldMatchResult best; - double best_score=-1; - for (int appearance : {19,16}){ - for (double scale : {.90,.95,1.0,1.05,1.10}){ - std::vector configs={ - {"Left gym sign",appearance==19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" : "PokemonBDSP/Autostory/EternaGym/LeftSign16.png", - {458.0/1920,358.0/1080,140.0/1920,150.0/1080},{0,0,.65,.85},.93,.20}, - {"Gym Pokeball",appearance==19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" : "PokemonBDSP/Autostory/EternaGym/Ball16.png", - {880.0/1920,264.0/1080,150.0/1920,80.0/1080},{.15,0,.7,.70},.93,.20}, - {"Right bike post",appearance==19 ? "PokemonBDSP/Autostory/EternaGym/BikePost19.png" : "PokemonBDSP/Autostory/EternaGym/BikePost16.png", - {1468.0/1920,460.0/1080,130.0/1920,178.0/1080},{.35,.15,.65,.80},.93,.20} - }; - for (auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); + double best_score = -1; + for (int appearance : {19, 16}){ + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs = {{"Left gym sign", + appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" + : "PokemonBDSP/Autostory/EternaGym/LeftSign16.png", + {458.0 / 1920, 358.0 / 1080, 140.0 / 1920, 150.0 / 1080}, + {0, 0, .65, .85}, + .93, + .20}, + {"Gym Pokeball", + appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" + : "PokemonBDSP/Autostory/EternaGym/Ball16.png", + {880.0 / 1920, 264.0 / 1080, 150.0 / 1920, 80.0 / 1080}, + {.15, 0, .7, .70}, + .93, + .20}, + {"Right bike post", + appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/BikePost19.png" + : "PokemonBDSP/Autostory/EternaGym/BikePost16.png", + {1468.0 / 1920, 460.0 / 1080, 130.0 / 1920, 178.0 / 1080}, + {.35, .15, .65, .80}, + .93, + .20}}; + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 3, 1.0 / 128, POSITION_TOLERANCE_Y, true); if (!candidate.located){ - candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - candidate.coarse=true;candidate.at_target=false; + candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + candidate.coarse = true; + candidate.at_target = false; + } + double score = 1; + for (const auto& landmark : candidate.landmarks){ + if (landmark.accepted){ + score = std::min(score, landmark.score); + } + } + if (candidate.located && (!best.located || (best.coarse && !candidate.coarse) || + (best.coarse == candidate.coarse && score > best_score))){ + best = candidate; + best_score = score; + }else if (best.landmarks.empty()){ + best = candidate; } - double score=1; - for(const auto& landmark:candidate.landmarks)if(landmark.accepted)score=std::min(score,landmark.score); - if(candidate.located && (!best.located || (best.coarse && !candidate.coarse) || - (best.coarse==candidate.coarse && score>best_score))){best=candidate;best_score=score;} - else if(best.landmarks.empty())best=candidate; } } - if (best.located) return best; + if (best.located){ + return best; + } // The gym first appears at the far right of the approach street. Search // the whole frame for its left sign and emblem; the bike post may be offscreen. // These broad matches guide travel only. Arrival still needs all three above. - double coarse_score=-1; - for (int appearance : {680,19,333,303,306,339}){ - for (double scale : {.85,.90,1.0,1.10,1.15}){ - std::vector configs={ - {"Left gym sign",appearance==680 ? "PokemonBDSP/Autostory/EternaGym/LeftSignApproach.png" : appearance==19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" : - appearance==333 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWest.png" : - appearance==306 ? "PokemonBDSP/Autostory/EternaGym/LeftSignSouthShadow.png" : - appearance==339 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWestShadow.png" : "PokemonBDSP/Autostory/EternaGym/LeftSignSouth.png", - {458.0/1920,358.0/1080,140.0/1920,150.0/1080},{0,0,1,.95},.93,.20}, - {"Gym Pokeball",appearance==680 ? "PokemonBDSP/Autostory/EternaGym/BallApproach.png" : appearance==19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" : "PokemonBDSP/Autostory/EternaGym/BallWest.png", - {880.0/1920,264.0/1080,150.0/1920,80.0/1080},{0,0,1,.90},.93,.20} - }; - if(appearance==680){ - configs[0].target.width=148.0/1920;configs[0].target.height=158.0/1080; - configs[1].target.width=190.0/1920; - } - for(auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2;config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - const auto& sign=candidate.landmarks[0];const auto& ball=candidate.landmarks[1]; - double score=std::min(sign.score,ball.score); + double coarse_score = -1; + for (int appearance : {680, 19, 333, 303, 306, 339}){ + for (double scale : {.85, .90, 1.0, 1.10, 1.15}){ + std::vector configs = { + {"Left gym sign", + appearance == 680 ? "PokemonBDSP/Autostory/EternaGym/LeftSignApproach.png" + : appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" + : appearance == 333 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWest.png" + : appearance == 306 ? "PokemonBDSP/Autostory/EternaGym/LeftSignSouthShadow.png" + : appearance == 339 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWestShadow.png" + : "PokemonBDSP/Autostory/EternaGym/LeftSignSouth.png", + {458.0 / 1920, 358.0 / 1080, 140.0 / 1920, 150.0 / 1080}, + {0, 0, 1, .95}, + .93, + .20}, + {"Gym Pokeball", + appearance == 680 ? "PokemonBDSP/Autostory/EternaGym/BallApproach.png" + : appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" + : "PokemonBDSP/Autostory/EternaGym/BallWest.png", + {880.0 / 1920, 264.0 / 1080, 150.0 / 1920, 80.0 / 1080}, + {0, 0, 1, .90}, + .93, + .20}}; + if (appearance == 680){ + configs[0].target.width = 148.0 / 1920; + configs[0].target.height = 158.0 / 1080; + configs[1].target.width = 190.0 / 1920; + } + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + const auto& sign = candidate.landmarks[0]; + const auto& ball = candidate.landmarks[1]; + double score = std::min(sign.score, ball.score); // Perspective changes their spacing while the gym is far right. // Both accepted anchors must still agree on the rightward direction // and vertical row before using that directional-only acquisition. - if(!candidate.located && sign.accepted && ball.accepted && - sign.dx>.08 && ball.dx>.08 && std::abs(sign.dy-ball.dy)<=.04 && - std::min(sign.dx,ball.dx)>2*std::abs(sign.dx-ball.dx)){ - candidate.located=true;candidate.dx=(sign.dx+ball.dx)/2;candidate.dy=(sign.dy+ball.dy)/2; + if (!candidate.located && sign.accepted && ball.accepted && sign.dx > .08 && ball.dx > .08 && + std::abs(sign.dy - ball.dy) <= .04 && std::min(sign.dx, ball.dx) > 2 * std::abs(sign.dx - ball.dx)){ + candidate.located = true; + candidate.dx = (sign.dx + ball.dx) / 2; + candidate.dy = (sign.dy + ball.dy) / 2; } - if(!candidate.located){ + if (!candidate.located){ // One fully visible unique landmark is enough to guide the // approach when the other is clipped at the right edge. - for(const auto& anchor:candidate.landmarks){ - if(anchor.accepted && anchor.score>=.97 && anchor.margin>=.30 && - anchor.dx>.08 && std::abs(anchor.dy)<=.22 && anchor.score>score){ - candidate.located=true;candidate.dx=anchor.dx;candidate.dy=anchor.dy;score=anchor.score; + for (const auto& anchor : candidate.landmarks){ + if (anchor.accepted && anchor.score >= .97 && anchor.margin >= .30 && anchor.dx > .08 && + std::abs(anchor.dy) <= .22 && anchor.score > score){ + candidate.located = true; + candidate.dx = anchor.dx; + candidate.dy = anchor.dy; + score = anchor.score; } } } - candidate.coarse=true;candidate.at_target=false; - if(candidate.located && score>coarse_score){best=candidate;coarse_score=score;} + candidate.coarse = true; + candidate.at_target = false; + if (candidate.located && score > coarse_score){ + best = candidate; + coarse_score = score; + } } } - if(best.located)return best; + if (best.located){ + return best; + } // The yellow-roofed house opposite the gym remains visible when the // gym itself is clipped offscreen. Reuse paired landmark agreement to // recover the doorway row and guide east until gym anchors take over. // Its virtual target is off the left edge: these approach anchors can // never satisfy near-door moderate confidence or confirm gym arrival. - double house_score=-1; - for(int perspective:{720,734,708}){ - for(double scale:{.90,.95,1.0,1.05,1.10}){ - std::vector configs={ - {"Gym approach house door",perspective==708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorFarWest.png" : perspective==734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorWest.png" : "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoor.png", - {548.0/1920-.55,448.0/1080,128.0/1920,156.0/1080},{0,.15,.75,.70},.93,.20}, - {"Gym approach house window",perspective==708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowFarWest.png" : perspective==734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowWest.png" : "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindow.png", - {718.0/1920-.55,462.0/1080,132.0/1920,112.0/1080},{0,.15,.85,.70},.93,.20} - }; - if(perspective==734){ - configs[0].target={338.0/1920-.55,450.0/1080,132.0/1920,150.0/1080}; - configs[1].target={496.0/1920-.55,464.0/1080,122.0/1920,102.0/1080}; - } - if(perspective==708){ - configs[0].target={240.0/1920-.55,464.0/1080,150.0/1920,150.0/1080}; - configs[1].target={416.0/1920-.55,478.0/1080,118.0/1920,90.0/1080}; - } - for(auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2;config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - candidate.coarse=true;candidate.at_target=false; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if(candidate.located && candidate.dx>.08 && score>house_score){best=candidate;house_score=score;} - } + double house_score = -1; + for (int perspective : {720, 734, 708}){ + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs = { + {"Gym approach house door", + perspective == 708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorFarWest.png" + : perspective == 734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorWest.png" + : "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoor.png", + {548.0 / 1920 - .55, 448.0 / 1080, 128.0 / 1920, 156.0 / 1080}, + {0, .15, .75, .70}, + .93, + .20}, + {"Gym approach house window", + perspective == 708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowFarWest.png" + : perspective == 734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowWest.png" + : "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindow.png", + {718.0 / 1920 - .55, 462.0 / 1080, 132.0 / 1920, 112.0 / 1080}, + {0, .15, .85, .70}, + .93, + .20}}; + if (perspective == 734){ + configs[0].target = {338.0 / 1920 - .55, 450.0 / 1080, 132.0 / 1920, 150.0 / 1080}; + configs[1].target = {496.0 / 1920 - .55, 464.0 / 1080, 122.0 / 1920, 102.0 / 1080}; + } + if (perspective == 708){ + configs[0].target = {240.0 / 1920 - .55, 464.0 / 1080, 150.0 / 1920, 150.0 / 1080}; + configs[1].target = {416.0 / 1920 - .55, 478.0 / 1080, 118.0 / 1920, 90.0 / 1080}; + } + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + candidate.coarse = true; + candidate.at_target = false; + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if (candidate.located && candidate.dx > .08 && score > house_score){ + best = candidate; + house_score = score; + } + } } return best; } -double overworld_entry_confidence(const OverworldMatchResult& result, - double tolerance_x, double tolerance_y, double minimum_margin){ - double confidence=0; - for(size_t i=0;itolerance_x || std::abs(a.dy)>tolerance_y)continue; - for(size_t j=i+1;jtolerance_x || std::abs(b.dy)>tolerance_y)continue; - if(std::abs(a.dx-b.dx)>AGREEMENT_X || std::abs(a.dy-b.dy)>AGREEMENT_Y)continue; - confidence=std::max(confidence,std::min(a.score,b.score)); +double overworld_entry_confidence(const OverworldMatchResult& result, double tolerance_x, double tolerance_y, + double minimum_margin){ + double confidence = 0; + for (size_t i = 0; i < result.landmarks.size(); i++){ + const auto& a = result.landmarks[i]; + if (a.margin < minimum_margin || std::abs(a.dx) > tolerance_x || std::abs(a.dy) > tolerance_y){ + continue; + } + for (size_t j = i + 1; j < result.landmarks.size(); j++){ + const auto& b = result.landmarks[j]; + if (b.margin < minimum_margin || std::abs(b.dx) > tolerance_x || std::abs(b.dy) > tolerance_y){ + continue; + } + if (std::abs(a.dx - b.dx) > AGREEMENT_X || std::abs(a.dy - b.dy) > AGREEMENT_Y){ + continue; + } + confidence = std::max(confidence, std::min(a.score, b.score)); } } return confidence; @@ -991,37 +1417,57 @@ OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame){ // The supplied reference is the interaction pose directly north of Jupiter. // Confirm the table-facing chair sides, adjacent table edges, and stairs. // Exclude the NPC-facing chair edges where Buneary can overlap the reference. - const std::vector configs{ - {"LeftChairs","PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairTableEdge.png", - {300.0/1920,780.0/1080,156.0/1920,206.0/1080},{0,.40,.45,.60},.93,.20}, - {"RightChairs","PokemonBDSP/Autostory/GalacticHQ/AboveRightChairTableEdge.png", - {1286.0/1920,780.0/1080,154.0/1920,206.0/1080},{.50,.40,.50,.60},.93,.20}, - {"StairBoundary","PokemonBDSP/Autostory/GalacticHQ/AboveStairs.png", - {424.0/1920,310.0/1080,214.0/1920,198.0/1080},{0,0,1,.85},.93,.20} - }; - auto result=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); + const std::vector configs{{"LeftChairs", + "PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairTableEdge.png", + {300.0 / 1920, 780.0 / 1080, 156.0 / 1920, 206.0 / 1080}, + {0, .40, .45, .60}, + .93, + .20}, + {"RightChairs", + "PokemonBDSP/Autostory/GalacticHQ/AboveRightChairTableEdge.png", + {1286.0 / 1920, 780.0 / 1080, 154.0 / 1920, 206.0 / 1080}, + {.50, .40, .50, .60}, + .93, + .20}, + {"StairBoundary", + "PokemonBDSP/Autostory/GalacticHQ/AboveStairs.png", + {424.0 / 1920, 310.0 / 1080, 214.0 / 1920, 198.0 / 1080}, + {0, 0, 1, .85}, + .93, + .20}}; + auto result = match_landmarks(frame, configs, 3, 1.0 / 128, POSITION_TOLERANCE_Y, true); if (!result.located){ // Two agreeing points guide the approach; all three still own arrival. - auto coarse=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - if (coarse.located){coarse.coarse=true;coarse.at_target=false;result=coarse;} + auto coarse = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (coarse.located){ + coarse.coarse = true; + coarse.at_target = false; + result = coarse; + } } if (!result.located){ // Existing recorded chair views can guide recovery from the old row. // Their coordinates still point to the new north-side destination; // only the three canonical above-Jupiter references confirm arrival. - auto approach=std::vector{configs[0],configs[1]}; - approach[0].path="PokemonBDSP/Autostory/GalacticHQ/LeftChairs.png"; - approach[1].path="PokemonBDSP/Autostory/GalacticHQ/RightChairs.png"; + auto approach = std::vector{configs[0], configs[1]}; + approach[0].path = "PokemonBDSP/Autostory/GalacticHQ/LeftChairs.png"; + approach[1].path = "PokemonBDSP/Autostory/GalacticHQ/RightChairs.png"; // Retain the original geometry for the old-row guide-only references. - approach[0].target={334.0/1920,756.0/1080,156.0/1920,228.0/1080}; - approach[1].target={1238.0/1920,756.0/1080,164.0/1920,230.0/1080}; - for (auto& chair:approach){chair.threshold=.95;chair.margin=.25;} - auto coarse=match_landmarks(frame,approach,2,1.0/128,POSITION_TOLERANCE_Y,true); + approach[0].target = {334.0 / 1920, 756.0 / 1080, 156.0 / 1920, 228.0 / 1080}; + approach[1].target = {1238.0 / 1920, 756.0 / 1080, 164.0 / 1920, 230.0 / 1080}; + for (auto& chair : approach){ + chair.threshold = .95; + chair.margin = .25; + } + auto coarse = match_landmarks(frame, approach, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); if (coarse.located){ - result.located=true;result.coarse=true;result.at_target=false; - result.dx=coarse.dx;result.dy=coarse.dy; - result.landmarks[0]=coarse.landmarks[0]; - result.landmarks[1]=coarse.landmarks[1]; + result.located = true; + result.coarse = true; + result.at_target = false; + result.dx = coarse.dx; + result.dy = coarse.dy; + result.landmarks[0] = coarse.landmarks[0]; + result.landmarks[1] = coarse.landmarks[1]; } } return result; @@ -1033,268 +1479,398 @@ OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame){ // Tight face/emblem crops avoid grass and moving cloud shadows on the roof. // The stronger emblem needs a corroborating sign, with a lower identity // gate for the smaller sign face. Both still must agree on the same pose. - auto result=match_lighting_pair(frame,{ - {"HQ sign","PokemonBDSP/Autostory/GalacticHQ/CutSignStructure.png", - {810.0/1920,500.0/1080,80.0/1920,58.0/1080},{0,.25,1,.45},.85,.10,LandmarkFeatures::LOCAL_CONTRAST}, - {"Center Pokeball","PokemonBDSP/Autostory/GalacticHQ/CutCenterBallStructure.png", - {1048.0/1920,1004.0/1080,136.0/1920,66.0/1080},{0,.65,1,.35},.93,.20,LandmarkFeatures::BLUE_FRACTION} - },"HQ sign","Center Pokeball",1.0/128); - if (!result.located && result.landmarks.size()==2){ - const auto& sign=result.landmarks[0];const auto& ball=result.landmarks[1]; + auto result = match_lighting_pair(frame, + {{"HQ sign", + "PokemonBDSP/Autostory/GalacticHQ/CutSignStructure.png", + {810.0 / 1920, 500.0 / 1080, 80.0 / 1920, 58.0 / 1080}, + {0, .25, 1, .45}, + .85, + .10, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Center Pokeball", + "PokemonBDSP/Autostory/GalacticHQ/CutCenterBallStructure.png", + {1048.0 / 1920, 1004.0 / 1080, 136.0 / 1920, 66.0 / 1080}, + {0, .65, 1, .35}, + .93, + .20, + LandmarkFeatures::BLUE_FRACTION}}, + "HQ sign", "Center Pokeball", 1.0 / 128); + if (!result.located && result.landmarks.size() == 2){ + const auto& sign = result.landmarks[0]; + const auto& ball = result.landmarks[1]; // In the recorded far-west misses, the low fascia emblem shifts 1.31 // screen pixels per sign pixel. Calibrate that acquisition observation // before applying the same position-agreement bound; never use it for arrival. - const double calibrated_ball_dx=ball.dx/1.31; - if (sign.accepted && ball.accepted && sign.dx*ball.dx>0 && - std::abs(sign.dx)>.03 && std::abs(sign.dx)<=.20 && - std::abs(sign.dx-calibrated_ball_dx)<=AGREEMENT_X && - std::abs(sign.dy-ball.dy)<=AGREEMENT_Y){ - result.located=true;result.coarse=true;result.at_target=false; - result.dx=(sign.dx+calibrated_ball_dx)/2; + const double calibrated_ball_dx = ball.dx / 1.31; + if (sign.accepted && ball.accepted && sign.dx * ball.dx > 0 && std::abs(sign.dx) > .03 && + std::abs(sign.dx) <= .20 && std::abs(sign.dx - calibrated_ball_dx) <= AGREEMENT_X && + std::abs(sign.dy - ball.dy) <= AGREEMENT_Y){ + result.located = true; + result.coarse = true; + result.at_target = false; + result.dx = (sign.dx + calibrated_ball_dx) / 2; } } // The recorded approach already clamps against the hedge. Correct only // its column; the watched Up/A loop owns the final north interaction. - result.dy=0; - result.at_target=result.located && !result.coarse && std::abs(result.dx)<=1.0/128; + result.dy = 0; + result.at_target = result.located && !result.coarse && std::abs(result.dx) <= 1.0 / 128; return result; } OverworldMatchResult match_eterna_cycle_shop_entrance(const ImageRGB32& frame){ - const std::vector configs{ - {"Shop sign","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_entrance-0.png",{555.0/1920,350.0/1080,100.0/1920,120.0/1080},{0,0.1,0.65,0.75},.93,.20}, - {"Shop roof braces","PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png",{680.0/1920,145.0/1080,470.0/1920,36.0/1080},{0,0,.95,.75},.93,.20} - }; - return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + const std::vector configs{{"Shop sign", + "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_entrance-0.png", + {555.0 / 1920, 350.0 / 1080, 100.0 / 1920, 120.0 / 1080}, + {0, 0.1, 0.65, 0.75}, + .93, + .20}, + {"Shop roof braces", + "PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png", + {680.0 / 1920, 145.0 / 1080, 470.0 / 1920, 36.0 / 1080}, + {0, 0, .95, .75}, + .93, + .20}}; + return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } OverworldMatchResult match_eterna_cycle_shop_interior(const ImageRGB32& frame){ // Entry mat view: fixed wall tools and register, clear of NPCs/Pokemon. // This identifies the room; it is not the counter interaction position. - const std::vector configs{ - {"Shop wall tools","PokemonBDSP/Autostory/EternaBikeShop/InteriorWallTools.png",{920.0/1920,20.0/1080,350.0/1920,150.0/1080},{.15,0,.60,.55},.93,.20}, - {"Shop register","PokemonBDSP/Autostory/EternaBikeShop/InteriorRegister.png",{760.0/1920,160.0/1080,120.0/1920,145.0/1080},{.20,0,.55,.65},.93,.20} - }; - auto result=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - if (result.located) return result; - const std::vector north_configs{ - {"Shop wall tools","PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthWallTools.png",{928.0/1920,84.0/1080,350.0/1920,150.0/1080},{.15,0,.60,.55},.93,.20}, - {"Shop register","PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthRegister.png",{754.0/1920,230.0/1080,120.0/1920,145.0/1080},{.20,0,.55,.65},.93,.20} - }; - return match_landmarks(frame,north_configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + const std::vector configs{{"Shop wall tools", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorWallTools.png", + {920.0 / 1920, 20.0 / 1080, 350.0 / 1920, 150.0 / 1080}, + {.15, 0, .60, .55}, + .93, + .20}, + {"Shop register", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorRegister.png", + {760.0 / 1920, 160.0 / 1080, 120.0 / 1920, 145.0 / 1080}, + {.20, 0, .55, .65}, + .93, + .20}}; + auto result = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (result.located){ + return result; + } + const std::vector north_configs{{"Shop wall tools", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthWallTools.png", + {928.0 / 1920, 84.0 / 1080, 350.0 / 1920, 150.0 / 1080}, + {.15, 0, .60, .55}, + .93, + .20}, + {"Shop register", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthRegister.png", + {754.0 / 1920, 230.0 / 1080, 120.0 / 1920, 145.0 / 1080}, + {.20, 0, .55, .65}, + .93, + .20}}; + return match_landmarks(frame, north_configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } OverworldMatchResult match_eterna_cycle_shop_reward(const ImageRGB32& frame){ // Reward returns the player outside on the bicycle, at a separate pose. // Reuse the stable roof braces; the nearby post changes appearance/shading. - const std::vector configs{ - {"Shop sign","PokemonBDSP/Autostory/EternaBikeShop/reward-sign.png",{580.0/1920,330.0/1080,100.0/1920,120.0/1080},{0,.1,.65,.75},.93,.20}, - {"Shop roof braces","PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png",{706.0/1920,128.0/1080,470.0/1920,36.0/1080},{0,0,.95,.75},.93,.20} - }; - return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + const std::vector configs{{"Shop sign", + "PokemonBDSP/Autostory/EternaBikeShop/reward-sign.png", + {580.0 / 1920, 330.0 / 1080, 100.0 / 1920, 120.0 / 1080}, + {0, .1, .65, .75}, + .93, + .20}, + {"Shop roof braces", + "PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png", + {706.0 / 1920, 128.0 / 1080, 470.0 / 1920, 36.0 / 1080}, + {0, 0, .95, .75}, + .93, + .20}}; + return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } OverworldMatchResult match_eterna_bike_riding_card(const ImageRGB32& frame){ // Fixed UI card: require both the blue illustration and B Close prompt. // Small search boxes tolerate capture offsets without matching other menus. - const std::vector configs{ - {"Blue Bike illustration","PokemonBDSP/Autostory/EternaBikeShop/BikeRidingBlueFeature.png", - {104.0/1920,314.0/1080,520.0/1920,450.0/1080},{.035,.26,.32,.48},.93,.20,LandmarkFeatures::BLUE_FRACTION}, - {"B Close prompt","PokemonBDSP/Autostory/EternaBikeShop/BikeRidingClosePrompt.png", - {1570.0/1920,730.0/1080,160.0/1920,60.0/1080},{.79,.65,.14,.10},.93,.20} - }; - return match_landmarks(frame,configs,2,1.0/64,1.0/54,true); + const std::vector configs{{"Blue Bike illustration", + "PokemonBDSP/Autostory/EternaBikeShop/BikeRidingBlueFeature.png", + {104.0 / 1920, 314.0 / 1080, 520.0 / 1920, 450.0 / 1080}, + {.035, .26, .32, .48}, + .93, + .20, + LandmarkFeatures::BLUE_FRACTION}, + {"B Close prompt", + "PokemonBDSP/Autostory/EternaBikeShop/BikeRidingClosePrompt.png", + {1570.0 / 1920, 730.0 / 1080, 160.0 / 1920, 60.0 / 1080}, + {.79, .65, .14, .10}, + .93, + .20}}; + return match_landmarks(frame, configs, 2, 1.0 / 64, 1.0 / 54, true); } OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame){ - const std::vector configs{ - {"Red frame","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-0.png",{620.0/1920,65.0/1080,390.0/1920,240.0/1080},{0.1,0,0.65,0.55},.93,.20}, - {"Counter detail","PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-1.png",{1110.0/1920,486.0/1080,86.0/1920,38.0/1080},{0.3,0.15,0.65,0.65},.93,.20} - }; - return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + const std::vector configs{{"Red frame", + "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-0.png", + {620.0 / 1920, 65.0 / 1080, 390.0 / 1920, 240.0 / 1080}, + {0.1, 0, 0.65, 0.55}, + .93, + .20}, + {"Counter detail", + "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-1.png", + {1110.0 / 1920, 486.0 / 1080, 86.0 / 1920, 38.0 / 1080}, + {0.3, 0.15, 0.65, 0.65}, + .93, + .20}}; + return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } // Two stationary architectural features confirm the intended gate interior. // The doorway and west window avoid the moving player, NPCs and localized text. OverworldMatchResult match_cycling_road_south_gate_interior(const ImageRGB32& frame){ - return match_landmarks(frame,{ - {"Gate north doorway","PokemonBDSP/Autostory/CyclingRoadSouthGate/NorthDoorway.png",{630.0/1920,118.0/1080,220.0/1920,270.0/1080},{.15,0,.70,.55},.93,.20}, - {"Gate west window","PokemonBDSP/Autostory/CyclingRoadSouthGate/WestWindow.png",{302.0/1920,140.0/1080,157.0/1920,145.0/1080},{.05,0,.35,.55},.93,.20} - },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y); + return match_landmarks(frame, + {{"Gate north doorway", + "PokemonBDSP/Autostory/CyclingRoadSouthGate/NorthDoorway.png", + {630.0 / 1920, 118.0 / 1080, 220.0 / 1920, 270.0 / 1080}, + {.15, 0, .70, .55}, + .93, + .20}, + {"Gate west window", + "PokemonBDSP/Autostory/CyclingRoadSouthGate/WestWindow.png", + {302.0 / 1920, 140.0 / 1080, 157.0 / 1920, 145.0 / 1080}, + {.05, 0, .35, .55}, + .93, + .20}}, + 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y); } OverworldMatchResult match_cycling_road_bridge_approach(const ImageRGB32& frame){ // Use the actual bridge entrance: cyan right rail with a broad metal deck // immediately to its left. Neither the Route 206 sign nor gate is involved. OverworldMatchResult best; - if (!frame) return best; - cv::Mat pixels((int)frame.height(),(int)frame.width(),CV_8UC4, - const_cast(frame.data()),frame.bytes_per_row()); - cv::Mat small,bgr,hsv,cyan; - cv::resize(pixels,small,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::cvtColor(small,bgr,cv::COLOR_BGRA2BGR); - cv::cvtColor(bgr,hsv,cv::COLOR_BGR2HSV); + if (!frame){ + return best; + } + cv::Mat pixels((int)frame.height(), (int)frame.width(), CV_8UC4, const_cast(frame.data()), + frame.bytes_per_row()); + cv::Mat small, bgr, hsv, cyan; + cv::resize(pixels, small, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::cvtColor(small, bgr, cv::COLOR_BGRA2BGR); + cv::cvtColor(bgr, hsv, cv::COLOR_BGR2HSV); // Hue/saturation retain the cyan rail under the recorded moving shadows. - cv::inRange(hsv,cv::Scalar(78,95,55),cv::Scalar(105,255,255),cyan); - cv::morphologyEx(cyan,cyan,cv::MORPH_CLOSE,cv::Mat::ones(3,3,CV_8U)); - const int w=hsv.cols,h=hsv.rows; + cv::inRange(hsv, cv::Scalar(78, 95, 55), cv::Scalar(105, 255, 255), cyan); + cv::morphologyEx(cyan, cyan, cv::MORPH_CLOSE, cv::Mat::ones(3, 3, CV_8U)); + const int w = hsv.cols, h = hsv.rows; std::vector> contours; - cv::findContours(cyan,contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE); - double best_support=0; - bool ambiguous=false; - for (const auto& contour:contours){ - const auto rail=cv::boundingRect(contour); - if (rail.xw*.85 || rail.height w * .85 || rail.height < h * .10){ + continue; + } // A left-hand rail has grass/cliff on this side and fails the deck test. - const int left=std::max(0,rail.x-(int)(w*.22)); - const int right=rail.x-(int)(w*.02); - if (right-left(row,x); + for (int half = 0; half < 2; half++){ + int neutral = 0; + const int x0 = left + (right - left) * half / 2; + const int x1 = left + (right - left) * (half + 1) / 2; + for (int row = y; row < y + sustained; row++){ + for (int x = x0; x < x1; x++){ + const auto p = hsv.at(row, x); // Shadowed metal remains low-saturation; the grass edge // and paired rail reject unrelated dark neutral panels. - neutral+=p[1]<=80 && p[2]>=60; + neutral += p[1] <= 80 && p[2] >= 60; } - const double fraction=(double)neutral/((x1-x0)*sustained); - if (fraction<.72) supported=false; - total+=fraction/2; + } + const double fraction = (double)neutral / ((x1 - x0) * sustained); + if (fraction < .72){ + supported = false; + } + total += fraction / 2; + } + if (supported){ + deck_y = y; + deck_support = total; + break; } - if (supported){deck_y=y;deck_support=total;break;} } - if (deck_y<0 || deck_y(row,x); - grass+=p[0]>=25 && p[0]<=85 && p[1]>=60 && p[2]>=30; - } - if ((double)grass/((right-left)*(above_end-above_begin))<.45) continue; + int grass = 0; + const int above_begin = std::max(0, deck_y - (int)(h * .05)); + const int above_end = deck_y - (int)(h * .02); + if (above_end <= above_begin){ + continue; + } + for (int row = above_begin; row < above_end; row++){ + for (int x = left; x < right; x++){ + const auto p = hsv.at(row, x); + grass += p[0] >= 25 && p[0] <= 85 && p[1] >= 60 && p[2] >= 30; + } + } + if ((double)grass / ((right - left) * (above_end - above_begin)) < .45){ + continue; + } // Measure the inner rail boundary at deck height, rather than using // the cliff wall or the rail cap's perspective-dependent bounding box. - double inner=0,ink=0;size_t rows=0; - const int measure_begin=std::max(deck_y+(int)(h*.015),(int)(h*.52)); - const int measure_end=std::min(h,measure_begin+(int)(h*.02)); - for (int y=measure_begin;y(y,x)) { - int last=x,count=0; - for (int edge=x;edge(y,edge)){last=edge;++count;} - const int width=last-x+1; - if (width>=w*.008 && width<=w*.055 && (double)count/width>=.80) { - inner+=x;ink+=(double)count/width;++rows; + double inner = 0, ink = 0; + size_t rows = 0; + const int measure_begin = std::max(deck_y + (int)(h * .015), (int)(h * .52)); + const int measure_end = std::min(h, measure_begin + (int)(h * .02)); + for (int y = measure_begin; y < measure_end; y++){ + for (int x = rail.x; x < std::min(w, rail.x + rail.width); x++){ + if (cyan.at(y, x)){ + int last = x, count = 0; + for (int edge = x; edge < std::min(w, rail.x + rail.width); edge++){ + if (cyan.at(y, edge)){ + last = edge; + ++count; + } + } + const int width = last - x + 1; + if (width >= w * .008 && width <= w * .055 && (double)count / width >= .80){ + inner += x; + ink += (double)count / width; + ++rows; } break; } } } - if (rows<3 || rows<(measure_end-measure_begin)*.75) continue; - inner/=rows; + if (rows < 3 || rows < (measure_end - measure_begin) * .75){ + continue; + } + inner /= rows; OverworldMatchResult candidate; - candidate.located=true; - candidate.dx=inner/w-.55; - candidate.dy=(double)deck_y/h-.36; + candidate.located = true; + candidate.dx = inner / w - .55; + candidate.dy = (double)deck_y / h - .36; // Park on the upper white platform beside the right rail. The user // demonstrated this stationary pose in the 12:33:35 recording; the // downhill section starts well beyond it. Measure the rail at wheel // height, not at its distant cap or the offset cliff wall. - candidate.at_target=std::abs(candidate.dx)<=.012 && - std::abs(candidate.dy)<=1.0/54; + candidate.at_target = std::abs(candidate.dx) <= .012 && std::abs(candidate.dy) <= 1.0 / 54; OverworldLandmarkResult rail_anchor; - rail_anchor.name="Bridge right rail inner edge"; - rail_anchor.accepted=true;rail_anchor.score=ink/rows; - rail_anchor.dx=candidate.dx;rail_anchor.dy=candidate.dy; - rail_anchor.search={.25,0,.68,1}; - rail_anchor.found={(double)inner/w,(double)rail.y/h,(double)rail.width/w,(double)rail.height/h}; + rail_anchor.name = "Bridge right rail inner edge"; + rail_anchor.accepted = true; + rail_anchor.score = ink / rows; + rail_anchor.dx = candidate.dx; + rail_anchor.dy = candidate.dy; + rail_anchor.search = {.25, 0, .68, 1}; + rail_anchor.found = {(double)inner / w, (double)rail.y / h, (double)rail.width / w, (double)rail.height / h}; OverworldLandmarkResult deck_anchor; - deck_anchor.name="Bridge deck leading edge"; - deck_anchor.accepted=true;deck_anchor.score=deck_support; - deck_anchor.dx=candidate.dx;deck_anchor.dy=candidate.dy; - deck_anchor.search={0,(double)begin/h,(double)right/w,(double)(end-begin)/h}; - deck_anchor.found={(double)left/w,(double)deck_y/h,(double)(right-left)/w,(double)sustained/h}; - candidate.landmarks={rail_anchor,deck_anchor}; - const double support=ink/rows+deck_support; - if (best.located && (std::abs(best.dx-candidate.dx)>AGREEMENT_X || - std::abs(best.dy-candidate.dy)>AGREEMENT_Y)) ambiguous=true; - if (support>best_support){best_support=support;best=std::move(candidate);} - } - if (ambiguous){best.located=false;best.at_target=false;} + deck_anchor.name = "Bridge deck leading edge"; + deck_anchor.accepted = true; + deck_anchor.score = deck_support; + deck_anchor.dx = candidate.dx; + deck_anchor.dy = candidate.dy; + deck_anchor.search = {0, (double)begin / h, (double)right / w, (double)(end - begin) / h}; + deck_anchor.found = {(double)left / w, (double)deck_y / h, (double)(right - left) / w, (double)sustained / h}; + candidate.landmarks = {rail_anchor, deck_anchor}; + const double support = ink / rows + deck_support; + if (best.located && + (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ + ambiguous = true; + } + if (support > best_support){ + best_support = support; + best = std::move(candidate); + } + } + if (ambiguous){ + best.located = false; + best.at_target = false; + } return best; } OverworldMatchResult match_galactic_hq_exterior(const ImageRGB32& frame){ - const std::vector configs{ - {"HQ sign","PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-0.png",{660.0/1920,615.0/1080,118.0/1920,124.0/1080},{0,0.2,0.65,0.8},.93,.20}, - {"HQ doorway lintel","PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-1.png",{874.0/1920,350.0/1080,172.0/1920,78.0/1080},{0.2,0,0.6,0.7},.93,.20} - }; - return match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); + const std::vector configs{{"HQ sign", + "PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-0.png", + {660.0 / 1920, 615.0 / 1080, 118.0 / 1920, 124.0 / 1080}, + {0, 0.2, 0.65, 0.8}, + .93, + .20}, + {"HQ doorway lintel", + "PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-1.png", + {874.0 / 1920, 350.0 / 1080, 172.0 / 1920, 78.0 / 1080}, + {0.2, 0, 0.6, 0.7}, + .93, + .20}}; + return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame){ // Center-exit pose in the recorded Center->Mart and bicycle-shop route. // Reuse the existing shadow/appearance-aware Center identification. - auto result=match_eterna_center_entrance(frame); - result.dy-=.070463; - result.at_target=result.located && !result.coarse && - std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=POSITION_TOLERANCE_Y; + auto result = match_eterna_center_entrance(frame); + result.dy -= .070463; + result.at_target = result.located && !result.coarse && std::abs(result.dx) <= 1.0 / 128 && + std::abs(result.dy) <= POSITION_TOLERANCE_Y; return result; } OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame){ // Actual crops from screenshot-20261018-171535781091. Separate left/right // search regions prevent one identical statue from satisfying both votes. - OverworldMatchResult best;double best_score=-1; - if(!frame)return best; + OverworldMatchResult best; + double best_score = -1; + if (!frame){ + return best; + } // Normalized correlation also matches almost-black fade frames. Require // visible statue regions before allowing those shapes to verify entry. - const cv::Mat gray=gray_image(frame); - const auto visible=[&](double x,double width){ - const cv::Rect box((int)(x*gray.cols),(int)(458.0/1080*gray.rows), - (int)(width*gray.cols),(int)(350.0/1080*gray.rows)); - return cv::mean(gray(box))[0]>=60; + const cv::Mat gray = gray_image(frame); + const auto visible = [&](double x, double width){ + const cv::Rect box((int)(x * gray.cols), (int)(458.0 / 1080 * gray.rows), (int)(width * gray.cols), + (int)(350.0 / 1080 * gray.rows)); + return cv::mean(gray(box))[0] >= 60; }; - const bool statues_visible=visible(448.0/1920,146.0/1920) - &&visible(1322.0/1920,154.0/1920); - for(double scale:{.95,1.0,1.05}){ - std::vector configs={ - {"Left gym statue","PokemonBDSP/Autostory/EternaGym/LeftStatue.png", - {448.0/1920,458.0/1080,146.0/1920,350.0/1080},{.12,.30,.27,.53},.93,.20}, - {"Right gym statue","PokemonBDSP/Autostory/EternaGym/RightStatue.png", - {1322.0/1920,458.0/1080,154.0/1920,350.0/1080},{.60,.30,.28,.53},.93,.20} - }; - for(auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2;config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,.04,.06,true); + const bool statues_visible = visible(448.0 / 1920, 146.0 / 1920) && visible(1322.0 / 1920, 154.0 / 1920); + for (double scale : {.95, 1.0, 1.05}){ + std::vector configs = {{"Left gym statue", + "PokemonBDSP/Autostory/EternaGym/LeftStatue.png", + {448.0 / 1920, 458.0 / 1080, 146.0 / 1920, 350.0 / 1080}, + {.12, .30, .27, .53}, + .93, + .20}, + {"Right gym statue", + "PokemonBDSP/Autostory/EternaGym/RightStatue.png", + {1322.0 / 1920, 458.0 / 1080, 154.0 / 1920, 350.0 / 1080}, + {.60, .30, .28, .53}, + .93, + .20}}; + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, .04, .06, true); // Interior verification requires both statues near the expected entry // positions, not merely anywhere in the room or during a fade. - candidate.at_target=candidate.at_target&&statues_visible; - candidate.located=candidate.at_target; - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if((candidate.located&&!best.located) || (candidate.located==best.located&&score>best_score)){ - best=candidate;best_score=score; + candidate.at_target = candidate.at_target && statues_visible; + candidate.located = candidate.at_target; + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = candidate; + best_score = score; } } return best; @@ -1304,59 +1880,94 @@ OverworldMatchResult match_eterna_underground_house(const ImageRGB32& frame){ // from the east. The sign is optional when a strong unique ball confirms // the doorway pose; cloud shadow on the sign must not block entry. OverworldMatchResult best; - double best_score=-1; - bool best_primary_only=false; - for (double scale : {.90,.95,1.0,1.05,1.10}){ - std::vector configs{ - {"Center Pokeball","PokemonBDSP/Autostory/EternaUndergroundMan/HouseBall.png", - {332.0/1920,224.0/1080,112.0/1920,104.0/1080},{0,0,.65,.7},.93,.20}, - {"House sign","PokemonBDSP/Autostory/EternaUndergroundMan/HouseSign.png", - {1240.0/1920,396.0/1080,110.0/1920,106.0/1080},{.35,0,.65,.85},.93,.20} - }; - for (auto& config:configs){ - const double w=config.target.width,h=config.target.height; - config.target.width*=scale;config.target.height*=scale; - config.target.x+=(w-config.target.width)/2; - config.target.y+=(h-config.target.height)/2; - } - auto candidate=match_landmarks(frame,configs,2,1.0/128,POSITION_TOLERANCE_Y,true); - double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if(candidate.located){ - if(!best.located || best_primary_only || score>best_score){best=candidate;best_score=score;best_primary_only=false;} - }else if(!best.located || best_primary_only){ - // The optional door-side sign can change perspective or disappear in - // cloud shadow. Preserve the high uniqueness margin on the primary ball. - const auto& ball=candidate.landmarks[0]; - if(ball.accepted && ball.score>=.95 && ball.margin>=.30 && std::abs(ball.dx)<.20 && std::abs(ball.dy)<.20 && - (!best.located || ball.score>best_score)){ - best=candidate;best.located=true;best.coarse=false; - best.dx=ball.dx;best.dy=ball.dy; - best.at_target=std::abs(best.dx)<=1.0/128 && std::abs(best.dy)<=POSITION_TOLERANCE_Y; - best_score=ball.score;best_primary_only=true; - }else if(best.landmarks.empty())best=candidate; - } + double best_score = -1; + bool best_primary_only = false; + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + std::vector configs{{"Center Pokeball", + "PokemonBDSP/Autostory/EternaUndergroundMan/HouseBall.png", + {332.0 / 1920, 224.0 / 1080, 112.0 / 1920, 104.0 / 1080}, + {0, 0, .65, .7}, + .93, + .20}, + {"House sign", + "PokemonBDSP/Autostory/EternaUndergroundMan/HouseSign.png", + {1240.0 / 1920, 396.0 / 1080, 110.0 / 1920, 106.0 / 1080}, + {.35, 0, .65, .85}, + .93, + .20}}; + for (auto& config : configs){ + const double w = config.target.width, h = config.target.height; + config.target.width *= scale; + config.target.height *= scale; + config.target.x += (w - config.target.width) / 2; + config.target.y += (h - config.target.height) / 2; + } + auto candidate = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); + double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if (candidate.located){ + if (!best.located || best_primary_only || score > best_score){ + best = candidate; + best_score = score; + best_primary_only = false; + } + }else if (!best.located || best_primary_only){ + // The optional door-side sign can change perspective or disappear in + // cloud shadow. Preserve the high uniqueness margin on the primary ball. + const auto& ball = candidate.landmarks[0]; + if (ball.accepted && ball.score >= .95 && ball.margin >= .30 && std::abs(ball.dx) < .20 && + std::abs(ball.dy) < .20 && (!best.located || ball.score > best_score)){ + best = candidate; + best.located = true; + best.coarse = false; + best.dx = ball.dx; + best.dy = ball.dy; + best.at_target = std::abs(best.dx) <= 1.0 / 128 && std::abs(best.dy) <= POSITION_TOLERANCE_Y; + best_score = ball.score; + best_primary_only = true; + }else if (best.landmarks.empty()){ + best = candidate; + } + } } return best; } OverworldMatchResult match_eterna_underground_man(const ImageRGB32& frame){ // Reuse fixed furniture, excluding people and the animated TV picture. // The right-window search excludes the identical window above the stool. - const std::vector configs{ - {"Stool","PokemonBDSP/Autostory/EternaUndergroundMan/Stool.png", - {380.0/1920,610.0/1080,86.0/1920,70.0/1080},{0,.3,.65,.5},.93,.20}, - {"Right tree top","PokemonBDSP/Autostory/EternaUndergroundMan/TreeTop.png", - {1372.0/1920,320.0/1080,102.0/1920,96.0/1080},{.5,0,.4,.7},.93,.20}, - {"Wall sign","PokemonBDSP/Autostory/EternaUndergroundMan/WallSign.png", - {642.0/1920,354.0/1080,114.0/1920,70.0/1080},{.2,0,.5,.6},.93,.20}, - {"Window beside TV","PokemonBDSP/Autostory/EternaUndergroundMan/RightWindow.png", - {906.0/1920,260.0/1080,162.0/1920,160.0/1080},{.35,0,.55,.55},.93,.20} - }; - auto result=match_landmarks(frame,configs,4,1.0/128,POSITION_TOLERANCE_Y,true); - if(!result.located){ + const std::vector configs{{"Stool", + "PokemonBDSP/Autostory/EternaUndergroundMan/Stool.png", + {380.0 / 1920, 610.0 / 1080, 86.0 / 1920, 70.0 / 1080}, + {0, .3, .65, .5}, + .93, + .20}, + {"Right tree top", + "PokemonBDSP/Autostory/EternaUndergroundMan/TreeTop.png", + {1372.0 / 1920, 320.0 / 1080, 102.0 / 1920, 96.0 / 1080}, + {.5, 0, .4, .7}, + .93, + .20}, + {"Wall sign", + "PokemonBDSP/Autostory/EternaUndergroundMan/WallSign.png", + {642.0 / 1920, 354.0 / 1080, 114.0 / 1920, 70.0 / 1080}, + {.2, 0, .5, .6}, + .93, + .20}, + {"Window beside TV", + "PokemonBDSP/Autostory/EternaUndergroundMan/RightWindow.png", + {906.0 / 1920, 260.0 / 1080, 162.0 / 1920, 160.0 / 1080}, + {.35, 0, .55, .55}, + .93, + .20}}; + auto result = match_landmarks(frame, configs, 4, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (!result.located){ // Three agreeing furniture anchors identify the room during entry and // guide acquisition when a fourth is clipped. Never authorize talking. - auto coarse=match_landmarks(frame,configs,3,1.0/128,POSITION_TOLERANCE_Y,true); - if(coarse.located){coarse.coarse=true;coarse.at_target=false;return coarse;} + auto coarse = match_landmarks(frame, configs, 3, 1.0 / 128, POSITION_TOLERANCE_Y, true); + if (coarse.located){ + coarse.coarse = true; + coarse.at_target = false; + return coarse; + } } return result; } @@ -1364,63 +1975,137 @@ OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame){ // The lighting-run pose was 1/108 screen height south of the original // screenshot. Targets preserve the original destination height. // Capture-derived references cover lamp, daylight, and moving tree shadows. - return match_lighting_pair(frame,{ - {"BuildingCorner","PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour3Frame4.png", - {0.588541666667,0.351851851852,0.088541666667,0.259259259259},{0,0,1,1},.93,.08}, - {"BuildingCorner","PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour14Frame2.png", - {0.588541666667,0.351851851852,0.088541666667,0.259259259259},{0,0,1,1},.93,.08}, - {"BuildingCorner","PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour17Frame4.png", - {0.588541666667,0.351851851852,0.088541666667,0.259259259259},{0,0,1,1},.93,.08}, - {"BuildingBase","PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour15Frame2.png", - {0.812500000000,0.388888888889,0.078125000000,0.203703703704},{0,0,1,1},.93,.08}, - {"BuildingBase","PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour17Frame5.png", - {0.812500000000,0.388888888889,0.078125000000,0.203703703704},{0,0,1,1},.93,.08}, - {"BuildingBase","PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour20Frame5.png", - {0.812500000000,0.388888888889,0.078125000000,0.203703703704},{0,0,1,1},.93,.08} - },"BuildingCorner","BuildingBase",POSITION_TOLERANCE_X,true); + return match_lighting_pair(frame, + {{"BuildingCorner", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour3Frame4.png", + {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingCorner", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour14Frame2.png", + {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingCorner", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour17Frame4.png", + {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingBase", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour15Frame2.png", + {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingBase", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour17Frame5.png", + {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingBase", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour20Frame5.png", + {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, + {0, 0, 1, 1}, + .93, + .08}}, + "BuildingCorner", "BuildingBase", POSITION_TOLERANCE_X, true); } OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame){ // Capture-derived references cover lamp, daylight, and moving tree shadows. - auto result=match_lighting_pair(frame,{ - {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour2Frame2.png", - {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, - {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame1.png", - {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, - {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame2.png", - {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, - {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame5.png", - {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, - {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour16Frame5.png", - {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, - {"RoofEdge","PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour23Frame2.png", - {0.614583333333,0.203703703704,0.299479166667,0.111111111111},{0,0,1,1},.93,.08}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour3Frame1.png", - {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour5Frame4.png", - {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour11Frame3.png", - {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour14Frame1.png", - {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour19Frame1.png", - {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08}, - {"Fence","PokemonBDSP/Autostory/JubilifePlaza/FenceHour21Frame4.png", - {0.044270833333,0.328703703704,0.169270833333,0.106481481481},{0,0,1,1},.93,.08} - // Small screen-relative quantization allowance avoids bouncing across the - // plaza target when one movement step spans both sides of the tolerance. - },"RoofEdge","Fence",POSITION_TOLERANCE_X,false,1.0/32); - if (!result.located && result.landmarks.size()==2){ - const auto& fence=result.landmarks[1]; + auto result = + match_lighting_pair(frame, + { + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour2Frame2.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame1.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame2.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame5.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour16Frame5.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour23Frame2.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour3Frame1.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour5Frame4.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour11Frame3.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour14Frame1.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour19Frame1.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour21Frame4.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08} // Small screen-relative quantization allowance avoids bouncing across the + // plaza target when one movement step spans both sides of the tolerance. + }, + "RoofEdge", "Fence", POSITION_TOLERANCE_X, false, 1.0 / 32); + if (!result.located && result.landmarks.size() == 2){ + const auto& fence = result.landmarks[1]; // A moving roof shadow must not trigger a blind lateral sweep when the // distinctive fence still gives a strong, nearby position estimate. // Fence-only guidance can reacquire the roof; it cannot confirm arrival. - if (fence.accepted && fence.score>=.95 && fence.margin>=.30 && - std::abs(fence.dx)<=.20 && std::abs(fence.dy)<=.20){ - result.located=true; - result.coarse=true; - result.at_target=false; - result.dx=fence.dx; - result.dy=fence.dy; + if (fence.accepted && fence.score >= .95 && fence.margin >= .30 && std::abs(fence.dx) <= .20 && + std::abs(fence.dy) <= .20){ + result.located = true; + result.coarse = true; + result.at_target = false; + result.dx = fence.dx; + result.dy = fence.dy; } } return result; @@ -1428,36 +2113,71 @@ OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame){ OverworldMatchResult match_route204_sign(const ImageRGB32& frame){ // One vote per physical landmark. Lighting variants avoid treating the // moving tree shadow over the fence as a missing route destination. - const ImageFloatBox sign_target{0.468750000000,0.376851851852,0.072916666667,0.052777777778}; - const ImageFloatBox fence_target{0.598958333333,0.421296296296,0.041666666667,0.152777777778}; + const ImageFloatBox sign_target{0.468750000000, 0.376851851852, 0.072916666667, 0.052777777778}; + const ImageFloatBox fence_target{0.598958333333, 0.421296296296, 0.041666666667, 0.152777777778}; std::vector variants{ - {"Route sign","PokemonBDSP/Autostory/Route204Sign/Sign.png",sign_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCorner.png",fence_target,{0,0,1,1},.93,.10}, - {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour0Frame3.png",sign_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour0Frame3.png",fence_target,{0,0,1,1},.93,.10}, - {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour8Frame3.png",sign_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour8Frame3.png",fence_target,{0,0,1,1},.93,.10}, - {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png",sign_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour13Frame3.png",fence_target,{0,0,1,1},.93,.10}, - {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour17Frame3.png",sign_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour17Frame3.png",fence_target,{0,0,1,1},.93,.10}, - {"Route sign","PokemonBDSP/Autostory/Route204Sign/SignHour2Frame1.png",sign_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour2Frame1.png",fence_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerHour10Frame3.png",fence_target,{0,0,1,1},.93,.10}, - {"Fence corner","PokemonBDSP/Autostory/Route204Sign/FenceCornerRecordedCloud.png",fence_target,{0,0,1,1},.93,.10}, + {"Route sign", "PokemonBDSP/Autostory/Route204Sign/Sign.png", sign_target, {0, 0, 1, 1}, .93, .10}, + {"Fence corner", "PokemonBDSP/Autostory/Route204Sign/FenceCorner.png", fence_target, {0, 0, 1, 1}, .93, .10}, + {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour0Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour0Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, + {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour8Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour8Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, + {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour13Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, + {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour17Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour17Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, + {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour2Frame1.png", sign_target, {0, 0, 1, 1}, .93, .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour2Frame1.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour10Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, + {"Fence corner", + "PokemonBDSP/Autostory/Route204Sign/FenceCornerRecordedCloud.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, }; - auto result=match_lighting_pair(frame,variants,"Route sign","Fence corner"); - if (!result.located && result.landmarks.size()==2){ - const auto& sign=result.landmarks[0]; + auto result = match_lighting_pair(frame, variants, "Route sign", "Fence corner"); + if (!result.located && result.landmarks.size() == 2){ + const auto& sign = result.landmarks[0]; // A distinctive high-confidence sign can guide bounded acquisition. // It cannot confirm the endpoint without the fence agreeing as well. - if (sign.accepted && sign.score>=.95 && sign.margin>=.30 && - std::abs(sign.dx)<=.20 && std::abs(sign.dy)<=.25){ - result.located=true; - result.coarse=true; - result.at_target=false; - result.dx=sign.dx; - result.dy=sign.dy; + if (sign.accepted && sign.score >= .95 && sign.margin >= .30 && std::abs(sign.dx) <= .20 && + std::abs(sign.dy) <= .25){ + result.located = true; + result.coarse = true; + result.at_target = false; + result.dx = sign.dx; + result.dy = sign.dy; } } return result; @@ -1466,51 +2186,80 @@ static OverworldMatchResult match_route205_marker(const ImageRGB32& frame, doubl OverworldMatchResult best; // Native sign faces at the two route positions. The post, moving NPCs, // grass and water are excluded from the acquisition references. - for (int variant : {0,1,2,3,4,5,6}) { - for (double scale : {.9,1.0,1.1,1.2}) { - const double w=(variant==6?188.0:variant==5?184.0:variant==4?220.0:variant==3?211.0:variant==2?208.0:variant==1?206.0:205.0)/1920*scale; - const double h=(variant==6?74.0:variant>=3?72.0:variant?80.0:82.0)/1080*scale; - auto candidate=match_landmarks(frame,{{"Route 205 marker", - variant==6?"PokemonBDSP/Autostory/Route205/BridgeSignMidCloud.png": - variant==5?"PokemonBDSP/Autostory/Route205/BridgeSignCloud.png": - variant==4?"PokemonBDSP/Autostory/Route205/BridgeSignLow.png": - variant==3?"PokemonBDSP/Autostory/Route205/BridgeSignLive.png": - variant==2?"PokemonBDSP/Autostory/Route205/NorthSignRecorded.png": - variant==1?"PokemonBDSP/Autostory/Route205/NorthSign.png": - "PokemonBDSP/Autostory/Route205/BridgeSign.png", - {cx-w/2,cy-h/2,w,h},{0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + for (int variant : {0, 1, 2, 3, 4, 5, 6}){ + for (double scale : {.9, 1.0, 1.1, 1.2}){ + const double w = (variant == 6 ? 188.0 + : variant == 5 ? 184.0 + : variant == 4 ? 220.0 + : variant == 3 ? 211.0 + : variant == 2 ? 208.0 + : variant == 1 ? 206.0 + : 205.0) / + 1920 * scale; + const double h = (variant == 6 ? 74.0 : variant >= 3 ? 72.0 : variant ? 80.0 : 82.0) / 1080 * scale; + auto candidate = match_landmarks(frame, + {{"Route 205 marker", + variant == 6 ? "PokemonBDSP/Autostory/Route205/BridgeSignMidCloud.png" + : variant == 5 ? "PokemonBDSP/Autostory/Route205/BridgeSignCloud.png" + : variant == 4 ? "PokemonBDSP/Autostory/Route205/BridgeSignLow.png" + : variant == 3 ? "PokemonBDSP/Autostory/Route205/BridgeSignLive.png" + : variant == 2 ? "PokemonBDSP/Autostory/Route205/NorthSignRecorded.png" + : variant == 1 ? "PokemonBDSP/Autostory/Route205/NorthSign.png" + : "PokemonBDSP/Autostory/Route205/BridgeSign.png", + {cx - w / 2, cy - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); if (best.landmarks.empty() || (candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } } } return best; } OverworldMatchResult match_route205_zackary(const ImageRGB32& frame){ OverworldMatchResult best; - double best_score=-1; - for (int view : {0,1,2,3}) { - for (double scale : {.9,1.0,1.1}) { - const double sw=91.0/1920*scale,sh=65.0/1080*scale; - // Include the fixed cliff edge beside both pipes: the repeated blue caps - // alone produce a second strong peak in cloud shadow. - const double cw=300.0/1920*scale,ch=300.0/1080*scale; - auto candidate=match_landmarks(frame,{ - {"Zackary bridge sign",view==2?"PokemonBDSP/Autostory/Route205/PatrolSignCloud.png": - view>=1?"PokemonBDSP/Autostory/Route205/PatrolSignLive.png":"PokemonBDSP/Autostory/Route205/PatrolSign.png", - {959.5/1920-sw/2,67.5/1080-sh/2,sw,sh},{0,0,1,1},.93,.20}, - {"Zackary twin chimneys",view==3?"PokemonBDSP/Autostory/Route205/PatrolChimneysPartialCloud.png": - view==2?"PokemonBDSP/Autostory/Route205/PatrolChimneysCloud.png": - view==1?"PokemonBDSP/Autostory/Route205/PatrolChimneysLive.png":"PokemonBDSP/Autostory/Route205/PatrolChimneys.png", - {1210.0/1920-cw/2,290.0/1080-ch/2,cw,ch},{0,0,1,1},.93,.20} - },2,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if (best.located && candidate.located && - (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)) { - best.located=false;best.at_target=false;return best; + double best_score = -1; + for (int view : {0, 1, 2, 3}){ + for (double scale : {.9, 1.0, 1.1}){ + const double sw = 91.0 / 1920 * scale, sh = 65.0 / 1080 * scale; + // Include the fixed cliff edge beside both pipes: the repeated blue caps + // alone produce a second strong peak in cloud shadow. + const double cw = 300.0 / 1920 * scale, ch = 300.0 / 1080 * scale; + auto candidate = + match_landmarks(frame, + {{"Zackary bridge sign", + view == 2 ? "PokemonBDSP/Autostory/Route205/PatrolSignCloud.png" + : view >= 1 ? "PokemonBDSP/Autostory/Route205/PatrolSignLive.png" + : "PokemonBDSP/Autostory/Route205/PatrolSign.png", + {959.5 / 1920 - sw / 2, 67.5 / 1080 - sh / 2, sw, sh}, + {0, 0, 1, 1}, + .93, + .20}, + {"Zackary twin chimneys", + view == 3 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysPartialCloud.png" + : view == 2 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysCloud.png" + : view == 1 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysLive.png" + : "PokemonBDSP/Autostory/Route205/PatrolChimneys.png", + {1210.0 / 1920 - cw / 2, 290.0 / 1080 - ch / 2, cw, ch}, + {0, 0, 1, 1}, + .93, + .20}}, + 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if (best.located && candidate.located && + (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ + best.located = false; + best.at_target = false; + return best; + } + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; + } } - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)) {best=std::move(candidate);best_score=score;} - } } return best; } @@ -1518,14 +2267,20 @@ OverworldMatchResult match_route205_kelsey(const ImageRGB32& frame){ OverworldMatchResult best; // This item is retained on the route. Restrict acquisition to the left of // the player, excluding the red cap and the northern bridge's item ball. - for (double scale : {.9,1.0,1.1}) { - const double w=56.0/1920*scale,h=53.0/1080*scale; - auto candidate=match_landmarks(frame,{{"Kelsey west item ball", - "PokemonBDSP/Autostory/Route205/KelseyItem.png", - {280.0/1920-w/2,574.5/1080-h/2,w,h},{0,.2,.48,.65},.93,.20}}, - 1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + for (double scale : {.9, 1.0, 1.1}){ + const double w = 56.0 / 1920 * scale, h = 53.0 / 1080 * scale; + auto candidate = match_landmarks(frame, + {{"Kelsey west item ball", + "PokemonBDSP/Autostory/Route205/KelseyItem.png", + {280.0 / 1920 - w / 2, 574.5 / 1080 - h / 2, w, h}, + {0, .2, .48, .65}, + .93, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); if (best.landmarks.empty() || (candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=std::move(candidate); + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = std::move(candidate); + } } return best; } @@ -1534,93 +2289,115 @@ OverworldMatchResult match_route205_bank_tree(const ImageRGB32& frame){ // Recorded cloud-edge views retain both anchor gates and agreement. // Trunk crops can include the player's cap when he stands under the tree. OverworldMatchResult best; - double best_score=-1; - for (int view : {0,1,2,3,4,5,6,7,8}) { - for (double scale : {.9,1.0,1.1}) { - const double tw=140.0/1920*scale,th=130.0/1080*scale; - const double nw=120.0/1920*scale,nh=170.0/1080*scale; - auto candidate=match_landmarks(frame,{ - {"Bank tree crown",view==8?"PokemonBDSP/Autostory/Route205/BankTreeCrownWest.png": - view==7?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud333.png": - view==6?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud76.png": - view==5?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud336.png": - view==4?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud328.png": - view==3?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud80.png": - view==2?"PokemonBDSP/Autostory/Route205/BankTreeCrownCloud72.png": - view==1?"PokemonBDSP/Autostory/Route205/BankTreeCrownLive.png": - "PokemonBDSP/Autostory/Route205/BankTreeCrown.png", - {770.0/1920-tw/2,235.0/1080-th/2,tw,th},{0,0,1,1},.93,.20}, - {"Bank blue-hat child",view==8?"PokemonBDSP/Autostory/Route205/BankBlueHatWest.png": - view==7?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud333.png": - view==6?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud76.png": - view==5?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud336.png": - view==4?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud328.png": - view==3?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud80.png": - view==2?"PokemonBDSP/Autostory/Route205/BankBlueHatCloud72.png": - view==1?"PokemonBDSP/Autostory/Route205/BankBlueHatLive.png": - "PokemonBDSP/Autostory/Route205/BankBlueHat.png", - {700.0/1920-nw/2,525.0/1080-nh/2,nw,nh},{0,0,1,1},.93,.20} - },2,1.0/96,POSITION_TOLERANCE_Y,true); - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if (best.located && candidate.located && - (std::abs(best.dx-candidate.dx)>AGREEMENT_X || std::abs(best.dy-candidate.dy)>AGREEMENT_Y)) { - best.located=false;best.at_target=false;return best; - } - if ((candidate.located && !best.located) || - (candidate.located==best.located && score>best_score)) { - best=std::move(candidate);best_score=score; + double best_score = -1; + for (int view : {0, 1, 2, 3, 4, 5, 6, 7, 8}){ + for (double scale : {.9, 1.0, 1.1}){ + const double tw = 140.0 / 1920 * scale, th = 130.0 / 1080 * scale; + const double nw = 120.0 / 1920 * scale, nh = 170.0 / 1080 * scale; + auto candidate = match_landmarks(frame, + {{"Bank tree crown", + view == 8 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownWest.png" + : view == 7 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud333.png" + : view == 6 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud76.png" + : view == 5 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud336.png" + : view == 4 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud328.png" + : view == 3 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud80.png" + : view == 2 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud72.png" + : view == 1 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownLive.png" + : "PokemonBDSP/Autostory/Route205/BankTreeCrown.png", + {770.0 / 1920 - tw / 2, 235.0 / 1080 - th / 2, tw, th}, + {0, 0, 1, 1}, + .93, + .20}, + {"Bank blue-hat child", + view == 8 ? "PokemonBDSP/Autostory/Route205/BankBlueHatWest.png" + : view == 7 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud333.png" + : view == 6 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud76.png" + : view == 5 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud336.png" + : view == 4 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud328.png" + : view == 3 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud80.png" + : view == 2 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud72.png" + : view == 1 ? "PokemonBDSP/Autostory/Route205/BankBlueHatLive.png" + : "PokemonBDSP/Autostory/Route205/BankBlueHat.png", + {700.0 / 1920 - nw / 2, 525.0 / 1080 - nh / 2, nw, nh}, + {0, 0, 1, 1}, + .93, + .20}}, + 2, 1.0 / 96, POSITION_TOLERANCE_Y, true); + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if (best.located && candidate.located && + (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ + best.located = false; + best.at_target = false; + return best; + } + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; + } } - } } return best; } OverworldMatchResult match_route205_bridge(const ImageRGB32& frame){ - return match_route205_marker(frame,722.5/1920,465.0/1080); + return match_route205_marker(frame, 722.5 / 1920, 465.0 / 1080); } OverworldMatchResult match_route205_forest_entrance(const ImageRGB32& frame){ - return match_route205_marker(frame,1245.0/1920,508.0/1080); + return match_route205_marker(frame, 1245.0 / 1920, 508.0 / 1080); } OverworldMatchResult match_windworks_interior_grunt(const ImageRGB32& frame){ OverworldMatchResult result; - cv::Mat pixels((int)frame.height(),(int)frame.width(),CV_8UC4, - (void*)frame.data(),frame.bytes_per_row()); - cv::Mat small,bgr,hsv; - cv::resize(pixels,small,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::cvtColor(small,bgr,cv::COLOR_BGRA2BGR); - cv::cvtColor(bgr,hsv,cv::COLOR_BGR2HSV); + cv::Mat pixels((int)frame.height(), (int)frame.width(), CV_8UC4, (void*)frame.data(), frame.bytes_per_row()); + cv::Mat small, bgr, hsv; + cv::resize(pixels, small, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::cvtColor(small, bgr, cv::COLOR_BGRA2BGR); + cv::cvtColor(bgr, hsv, cv::COLOR_BGR2HSV); // Grey indoor tiles distinguish this scene from the outdoor grunt, battle // screens and fades. Pale turquoise partitions have too little saturation // to qualify as the grunt's hair. cv::Mat floor; - cv::inRange(hsv,cv::Scalar(0,0,100),cv::Scalar(179,65,255),floor); - const cv::Rect room(288,81,624,405); - if ((double)cv::countNonZero(floor(room))/room.area()<.30) return result; - auto head=[&](cv::Scalar low,cv::Scalar high,bool player,cv::Point2d& center) { - cv::Mat mask,labels,stats,centroids; - cv::inRange(hsv,low,high,mask); - const int count=cv::connectedComponentsWithStats(mask,labels,stats,centroids); - int best=-1,best_area=0,runner_area=0; - for (int i=1;i(i,cv::CC_STAT_AREA); - int w=stats.at(i,cv::CC_STAT_WIDTH),h=stats.at(i,cv::CC_STAT_HEIGHT); - double x=centroids.at(i,0),y=centroids.at(i,1); - if (area<80 || area>4000 || w<12 || w>110 || h<8 || h>90 || - (double)area/(w*h)<.35 || x<288 || x>(player?672:864) || - y<(player?135:15) || y>459) continue; - if (area>best_area) {runner_area=best_area;best_area=area;best=i;} - else runner_area=std::max(runner_area,area); - } - if (best<0 || runner_area>best_area*.70) return false; - center={centroids.at(best,0),centroids.at(best,1)}; + cv::inRange(hsv, cv::Scalar(0, 0, 100), cv::Scalar(179, 65, 255), floor); + const cv::Rect room(288, 81, 624, 405); + if ((double)cv::countNonZero(floor(room)) / room.area() < .30){ + return result; + } + auto head = [&](cv::Scalar low, cv::Scalar high, bool player, cv::Point2d& center){ + cv::Mat mask, labels, stats, centroids; + cv::inRange(hsv, low, high, mask); + const int count = cv::connectedComponentsWithStats(mask, labels, stats, centroids); + int best = -1, best_area = 0, runner_area = 0; + for (int i = 1; i < count; i++){ + int area = stats.at(i, cv::CC_STAT_AREA); + int w = stats.at(i, cv::CC_STAT_WIDTH), h = stats.at(i, cv::CC_STAT_HEIGHT); + double x = centroids.at(i, 0), y = centroids.at(i, 1); + if (area < 80 || area > 4000 || w < 12 || w > 110 || h < 8 || h > 90 || (double)area / (w * h) < .35 || + x < 288 || x > (player ? 672 : 864) || y < (player ? 135 : 15) || y > 459){ + continue; + } + if (area > best_area){ + runner_area = best_area; + best_area = area; + best = i; + }else{ + runner_area = std::max(runner_area, area); + } + } + if (best < 0 || runner_area > best_area * .70){ + return false; + } + center = {centroids.at(best, 0), centroids.at(best, 1)}; return true; }; - cv::Point2d player,grunt; - if (!head({0,120,120},{22,255,255},true,player) || - !head({75,100,120},{105,255,255},false,grunt)) return result; - if (std::abs(player.x-grunt.x)>288 || std::abs(player.y-grunt.y)>350) return result; - result.located=true; - result.dx=(grunt.x-player.x)/960; - result.dy=(grunt.y-player.y)/540; + cv::Point2d player, grunt; + if (!head({0, 120, 120}, {22, 255, 255}, true, player) || !head({75, 100, 120}, {105, 255, 255}, false, grunt)){ + return result; + } + if (std::abs(player.x - grunt.x) > 288 || std::abs(player.y - grunt.y) > 350){ + return result; + } + result.located = true; + result.dx = (grunt.x - player.x) / 960; + result.dy = (grunt.y - player.y) / 540; return result; } OverworldMatchResult match_windworks_sign_approach(const ImageRGB32& frame){ @@ -1629,33 +2406,45 @@ OverworldMatchResult match_windworks_sign_approach(const ImageRGB32& frame){ OverworldMatchResult best; // The sign grows as it approaches the bottom of the camera view. Scale // acquisition templates around the same target center, keeping all gates. - for (int view : {0,1,2}) { - for (double scale : {1.0,1.1,1.2,1.3}) { - const double w=(view==2?124.0:view==1?144.0:121.0)/1920*scale; - const double h=(view==1?140.0:122.0)/1080*scale; - auto candidate=match_landmarks(frame,{{"Windworks approach sign", - view==2?"PokemonBDSP/Autostory/WindworksApproach/SignNight.png": - view==1?"PokemonBDSP/Autostory/WindworksApproach/SignLow.png": - "PokemonBDSP/Autostory/WindworksApproach/Sign.png", - {836.5/1920-w/2,515.0/1080-h/2,w,h}, - {0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); - if (best.landmarks.empty() || (candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; - } + for (int view : {0, 1, 2}){ + for (double scale : {1.0, 1.1, 1.2, 1.3}){ + const double w = (view == 2 ? 124.0 : view == 1 ? 144.0 : 121.0) / 1920 * scale; + const double h = (view == 1 ? 140.0 : 122.0) / 1080 * scale; + auto candidate = match_landmarks(frame, + {{"Windworks approach sign", + view == 2 ? "PokemonBDSP/Autostory/WindworksApproach/SignNight.png" + : view == 1 ? "PokemonBDSP/Autostory/WindworksApproach/SignLow.png" + : "PokemonBDSP/Autostory/WindworksApproach/Sign.png", + {836.5 / 1920 - w / 2, 515.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } + } } // Face-only references cover the high/mid camera perspectives without // relying on the moving grass that can cover the sign's lower border. - for (bool high_view : {false,true}) { - for (double scale : {.9,1.0,1.1}) { - const double w=(high_view?86.0:106.0)/1920*scale; - const double h=(high_view?72.0:94.0)/1080*scale; - auto candidate=match_landmarks(frame,{{"Windworks approach sign face", - high_view?"PokemonBDSP/Autostory/WindworksApproach/SignHighFace.png": - "PokemonBDSP/Autostory/WindworksApproach/SignMidFace.png", - {838.0/1920-w/2,516.0/1080-h/2,w,h}, - {0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + for (bool high_view : {false, true}){ + for (double scale : {.9, 1.0, 1.1}){ + const double w = (high_view ? 86.0 : 106.0) / 1920 * scale; + const double h = (high_view ? 72.0 : 94.0) / 1080 * scale; + auto candidate = match_landmarks(frame, + {{"Windworks approach sign face", + high_view ? "PokemonBDSP/Autostory/WindworksApproach/SignHighFace.png" + : "PokemonBDSP/Autostory/WindworksApproach/SignMidFace.png", + {838.0 / 1920 - w / 2, 516.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); if ((candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } } } return best; @@ -1666,163 +2455,269 @@ OverworldMatchResult match_route204_floaroma_turn(const ImageRGB32& frame){ // placing it above the player so the westward turn clears its post and // the flower-bed fence. Align horizontally with the sign before the // subsequent westward route, rather than staying in the eastern lane. - for (double scale_x : {1.0,1.05,1.10,1.15,1.20}) { - for (double scale_y : {.90,1.0,1.10}) { - const double w=140.0/1920*scale_x, h=57.0/1080*scale_y; - auto candidate=match_landmarks(frame,{{"Northern Route 204 sign", - "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", - {.5-w/2,420.0/1080-h/2,w,h},{0,0,1,1},.93,.20}},1,POSITION_TOLERANCE_X,POSITION_TOLERANCE_Y,true); + for (double scale_x : {1.0, 1.05, 1.10, 1.15, 1.20}){ + for (double scale_y : {.90, 1.0, 1.10}){ + const double w = 140.0 / 1920 * scale_x, h = 57.0 / 1080 * scale_y; + auto candidate = match_landmarks(frame, + {{"Northern Route 204 sign", + "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", + {.5 - w / 2, 420.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); if (best.landmarks.empty() || (candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } } } return best; } OverworldMatchResult match_route204_north_approach(const ImageRGB32& frame){ - auto result=match_route204_sign(frame); + auto result = match_route204_sign(frame); // The pre-battle reference stands below the sign. After Tyler, place // the player one body-width west of it before starting north. - constexpr double west_offset=.04; - for (auto& anchor:result.landmarks){anchor.dx-=west_offset;} - result.dx-=west_offset; + constexpr double west_offset = .04; + for (auto& anchor : result.landmarks){ + anchor.dx -= west_offset; + } + result.dx -= west_offset; if (!result.located && !result.landmarks.empty()){ - const auto& sign=result.landmarks[0]; - if (sign.accepted && sign.score>=.93 && sign.margin>=.30 && - std::abs(sign.dx)<=.45 && std::abs(sign.dy)<=.25){ - result.located=true;result.coarse=true;result.dx=sign.dx;result.dy=sign.dy; + const auto& sign = result.landmarks[0]; + if (sign.accepted && sign.score >= .93 && sign.margin >= .30 && std::abs(sign.dx) <= .45 && + std::abs(sign.dy) <= .25){ + result.located = true; + result.coarse = true; + result.dx = sign.dx; + result.dy = sign.dy; + } + } + result.at_target = result.located && !result.coarse; + for (const auto& anchor : result.landmarks){ + if (!anchor.accepted || std::abs(anchor.dx) > 1.0 / 128 || std::abs(anchor.dy) > POSITION_TOLERANCE_Y){ + result.at_target = false; } } - result.at_target=result.located && !result.coarse; - for (const auto& anchor:result.landmarks) - if (!anchor.accepted || std::abs(anchor.dx)>1.0/128 || - std::abs(anchor.dy)>POSITION_TOLERANCE_Y) result.at_target=false; return result; } OverworldMatchResult match_floaroma_house_entrance(const ImageRGB32& frame){ // Fixed facade details above/aside the player avoid door/player occlusion. // Both must agree on the pose recorded just outside the central house. - return match_landmarks(frame,{ - {"House window","PokemonBDSP/Autostory/FloaromaHouse/Window.png", - {1060.0/1920,370.0/1080,128.0/1920,106.0/1080},{0,0,1,1},.93,.10}, - {"House gable","PokemonBDSP/Autostory/FloaromaHouse/Gable.png", - {796.0/1920,196.0/1080,460.0/1920,154.0/1080},{0,0,1,1},.93,.10} - },2,1.0/64,1.0/54,true); + return match_landmarks(frame, + {{"House window", + "PokemonBDSP/Autostory/FloaromaHouse/Window.png", + {1060.0 / 1920, 370.0 / 1080, 128.0 / 1920, 106.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"House gable", + "PokemonBDSP/Autostory/FloaromaHouse/Gable.png", + {796.0 / 1920, 196.0 / 1080, 460.0 / 1920, 154.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}}, + 2, 1.0 / 64, 1.0 / 54, true); } OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame){ OverworldMatchResult best; // Perspective changes the emblem size as we approach the doorway gap. - for (double scale : {.90,.95,1.0,1.05,1.10}){ - const double w=105.0/1920*scale, h=86.0/1080*scale; - auto candidate=match_landmarks(frame,{{"Center emblem", - "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", - {.5-w/2,135.0/1080-h/2,w,h},{0,0,1,1},.93,.20}},1,1.0/64,1.0/36,true); + for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ + const double w = 105.0 / 1920 * scale, h = 86.0 / 1080 * scale; + auto candidate = match_landmarks(frame, + {{"Center emblem", + "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", + {.5 - w / 2, 135.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, 1.0 / 64, 1.0 / 36, true); if (best.landmarks.empty() || (candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } } // At the west wall the camera makes the emblem nearly circular, unlike // the vertically compressed doorway reference. Search that recorded // perspective without lowering the score or uniqueness requirements. - if (!best.located) { - for (double scale_x : {1.0,1.05,1.10,1.15,1.20}) { - for (double scale_y : {1.15,1.25,1.35,1.45}) { - const double w=105.0/1920*scale_x, h=86.0/1080*scale_y; - auto candidate=match_landmarks(frame,{{"Center emblem west perspective", - "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", - {.5-w/2,135.0/1080-h/2,w,h},{0,0,1,1},.93,.20}},1,1.0/64,1.0/36,true); - candidate.coarse=true; - candidate.at_target=false; + if (!best.located){ + for (double scale_x : {1.0, 1.05, 1.10, 1.15, 1.20}){ + for (double scale_y : {1.15, 1.25, 1.35, 1.45}){ + const double w = 105.0 / 1920 * scale_x, h = 86.0 / 1080 * scale_y; + auto candidate = match_landmarks(frame, + {{"Center emblem west perspective", + "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", + {.5 - w / 2, 135.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, 1.0 / 64, 1.0 / 36, true); + candidate.coarse = true; + candidate.at_target = false; if ((candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } } } } // A lower-face reference excludes the tester's burned-in overlay line // and covers the doorway-facing perspective, retaining score/margin gates. - auto lower=match_landmarks(frame,{{"Center emblem lower face", - "PokemonBDSP/Autostory/FloaromaCenter/EmblemLower.png", - {.5-52.0/1920,114.0/1080,104.0/1920,65.0/1080},{0,0,1,1},.93,.20}}, - 1,1.0/64,1.0/36,true); - if ((lower.located && (!best.located || best.coarse)) || (lower.located==best.located && - lower.landmarks[0].score>best.landmarks[0].score)) best=lower; + auto lower = match_landmarks(frame, + {{"Center emblem lower face", + "PokemonBDSP/Autostory/FloaromaCenter/EmblemLower.png", + {.5 - 52.0 / 1920, 114.0 / 1080, 104.0 / 1920, 65.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, 1.0 / 64, 1.0 / 36, true); + if ((lower.located && (!best.located || best.coarse)) || + (lower.located == best.located && lower.landmarks[0].score > best.landmarks[0].score)){ + best = lower; + } return best; } OverworldMatchResult match_route204_north_stairs(const ImageRGB32& frame){ OverworldMatchResult best; // Fixed rails/steps, with sunny and cloud-covered perspectives. - for (const char* path:{"PokemonBDSP/Autostory/Route204NorthStairs/Stairs.png", - "PokemonBDSP/Autostory/Route204NorthStairs/StairsCloud.png"}){ + for (const char* path : {"PokemonBDSP/Autostory/Route204NorthStairs/Stairs.png", + "PokemonBDSP/Autostory/Route204NorthStairs/StairsCloud.png"}){ // Camera pitch changes height and width by different amounts. - for (double scale_x:{.90,1.0,1.10}) for (double scale_y:{.80,.90,1.0,1.10}){ - const double w=298.0/1920*scale_x,h=282.0/1080*scale_y; - auto candidate=match_landmarks(frame,{{"North route stairs",path, - {.5-w/2,650.0/1080,w,h},{0,0,1,1},.93,.10}},1,1.0/64,1.0/54,true); - if (best.landmarks.empty() || (candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + for (double scale_x : {.90, 1.0, 1.10}){ + for (double scale_y : {.80, .90, 1.0, 1.10}){ + const double w = 298.0 / 1920 * scale_x, h = 282.0 / 1080 * scale_y; + auto candidate = match_landmarks( + frame, {{"North route stairs", path, {.5 - w / 2, 650.0 / 1080, w, h}, {0, 0, 1, 1}, .93, .10}}, 1, + 1.0 / 64, 1.0 / 54, true); + if (best.landmarks.empty() || (candidate.located && !best.located) || + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } + } } } if (!best.located){ // The lower steps distort as the camera moves north. The rail tops, // grass edge and first steps retain a stable physical upper edge. - for (const auto& config:std::vector{ - {"North route stairs top","PokemonBDSP/Autostory/Route204NorthStairs/Top.png", - {.5-149.0/1920,640.0/1080,298.0/1920,70.0/1080},{0,0,1,1},.93,.10}, - {"North route stairs top","PokemonBDSP/Autostory/Route204NorthStairs/TopRaised.png", - {.5-157.0/1920,640.0/1080,314.0/1920,70.0/1080},{0,0,1,1},.93,.10} - }) for (double scale_x:{.95,1.0,1.05}) for (double scale_y:{.90,1.0,1.10}){ - auto variant=config; - variant.target.width*=scale_x;variant.target.height*=scale_y; - variant.target.x=.5-variant.target.width/2; - auto candidate=match_landmarks(frame,{variant},1,1.0/64,1.0/54,true); - if ((candidate.located && !best.located) || - (candidate.located==best.located && candidate.landmarks[0].score>best.landmarks[0].score)) best=candidate; + for (const auto& config : + std::vector{{"North route stairs top", + "PokemonBDSP/Autostory/Route204NorthStairs/Top.png", + {.5 - 149.0 / 1920, 640.0 / 1080, 298.0 / 1920, 70.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"North route stairs top", + "PokemonBDSP/Autostory/Route204NorthStairs/TopRaised.png", + {.5 - 157.0 / 1920, 640.0 / 1080, 314.0 / 1920, 70.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}}){ + for (double scale_x : {.95, 1.0, 1.05}){ + for (double scale_y : {.90, 1.0, 1.10}){ + auto variant = config; + variant.target.width *= scale_x; + variant.target.height *= scale_y; + variant.target.x = .5 - variant.target.width / 2; + auto candidate = match_landmarks(frame, {variant}, 1, 1.0 / 64, 1.0 / 54, true); + if ((candidate.located && !best.located) || + (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ + best = candidate; + } + } + } } } return best; } OverworldMatchResult match_teighler_alcove_corner(const ImageRGB32& frame){ - auto result=match_route204_north_stairs(frame); + auto result = match_route204_north_stairs(frame); // Reuse the fixed stairs only as an anchor. The destination is the // bottom-left of Teighler's alcove, east of the stairs, not their center. // Geometry comes from the recorded 1920x1080 corner reference. - constexpr double shift_x=393.0/1920, shift_y=182.0/1080; - result.dx+=shift_x;result.dy+=shift_y; - for (auto& anchor:result.landmarks){anchor.dx+=shift_x;anchor.dy+=shift_y;} - result.at_target=result.located && std::abs(result.dx)<=1.0/64 && - std::abs(result.dy)<=1.0/54; + constexpr double shift_x = 393.0 / 1920, shift_y = 182.0 / 1080; + result.dx += shift_x; + result.dy += shift_y; + for (auto& anchor : result.landmarks){ + anchor.dx += shift_x; + anchor.dy += shift_y; + } + result.at_target = result.located && std::abs(result.dx) <= 1.0 / 64 && std::abs(result.dy) <= 1.0 / 54; return result; } OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame){ - const ImageFloatBox mouth{860.0/1920,220.0/1080,200.0/1920,155.0/1080}; - const ImageFloatBox sign{810.0/1920,380.0/1080,100.0/1920,110.0/1080}; + const ImageFloatBox mouth{860.0 / 1920, 220.0 / 1080, 200.0 / 1920, 155.0 / 1080}; + const ImageFloatBox sign{810.0 / 1920, 380.0 / 1080, 100.0 / 1920, 110.0 / 1080}; std::vector variants; // A different approach height changes perspective. Compare a small range // of sizes, keeping each physical anchor's reference center fixed. - for (double scale : {.90,1.0,1.10}){ - for (const auto& config : std::vector{ - {"Cave mouth","PokemonBDSP/Autostory/RavagedPathSouthEntrance/Mouth.png",mouth,{0,0,1,1},.93,.10}, - {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/Sign.png",sign,{0,0,1,1},.93,.10}, - {"Cave mouth","PokemonBDSP/Autostory/RavagedPathSouthEntrance/MouthShadow.png",mouth,{0,0,1,1},.93,.10}, - {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignShadow.png",sign,{0,0,1,1},.93,.10}, - {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignPartialShadow.png",sign,{0,0,1,1},.93,.10}, - {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFace.png",{828.0/1920,400.0/1080,64.0/1920,62.0/1080},{0,0,1,1},.93,.10}, - {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceLowApproach.png",{828.0/1920,400.0/1080,64.0/1920,62.0/1080},{0,0,1,1},.93,.10}, - {"Entrance sign","PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceCloud.png",{828.0/1920,400.0/1080,64.0/1920,62.0/1080},{0,0,1,1},.93,.10} - }){ - auto variant=config; - variant.target.width*=scale; - variant.target.height*=scale; - variant.target.x+=(config.target.width-variant.target.width)/2; - variant.target.y+=(config.target.height-variant.target.height)/2; + for (double scale : {.90, 1.0, 1.10}){ + for (const auto& config : + std::vector{{"Cave mouth", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Mouth.png", + mouth, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Sign.png", + sign, + {0, 0, 1, 1}, + .93, + .10}, + {"Cave mouth", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/MouthShadow.png", + mouth, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignShadow.png", + sign, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignPartialShadow.png", + sign, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFace.png", + {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceLowApproach.png", + {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceCloud.png", + {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}}){ + auto variant = config; + variant.target.width *= scale; + variant.target.height *= scale; + variant.target.x += (config.target.width - variant.target.width) / 2; + variant.target.y += (config.target.height - variant.target.height) / 2; variants.push_back(variant); } } - auto result=match_lighting_pair(frame,variants,"Cave mouth","Entrance sign",1.0/64,true); - if (!result.located && result.landmarks.size()==2){ + auto result = match_lighting_pair(frame, variants, "Cave mouth", "Entrance sign", 1.0 / 64, true); + if (!result.located && result.landmarks.size() == 2){ for (const auto& landmark : result.landmarks){ - if (landmark.accepted && landmark.score>=.95 && landmark.margin>=.20 && - std::abs(landmark.dx)<=.35 && std::abs(landmark.dy)<=.40){ - result.located=true;result.coarse=true;result.at_target=false; - result.dx=landmark.dx;result.dy=landmark.dy; + if (landmark.accepted && landmark.score >= .95 && landmark.margin >= .20 && std::abs(landmark.dx) <= .35 && + std::abs(landmark.dy) <= .40){ + result.located = true; + result.coarse = true; + result.at_target = false; + result.dx = landmark.dx; + result.dy = landmark.dy; break; } } @@ -1830,447 +2725,830 @@ OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame){ // The moving blue-hatted boy is deliberately absent from the references. // The lass is optional evidence for the southern movement boundary, never // a replacement for the fixed mouth/sign arrival anchors. - auto boundary=match_landmarks(frame,{{"Lass south boundary", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Lass.png", - {910.0/1920,788.0/1080,100.0/1920,142.0/1080},{0,0,1,1},.88,.08 - }},1,1.0/64,1.0/36,true); - if (!boundary.landmarks.empty()) result.landmarks.push_back(boundary.landmarks[0]); + auto boundary = match_landmarks(frame, + {{"Lass south boundary", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Lass.png", + {910.0 / 1920, 788.0 / 1080, 100.0 / 1920, 142.0 / 1080}, + {0, 0, 1, 1}, + .88, + .08}}, + 1, 1.0 / 64, 1.0 / 36, true); + if (!boundary.landmarks.empty()){ + result.landmarks.push_back(boundary.landmarks[0]); + } return result; } OverworldMatchResult match_route215_bridge_renewal(const ImageRGB32& frame){ // Restore the successful west bridge approach after variable expiry movement. - if (!frame) return {}; - cv::Mat gray,smoothed; - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); - ImageRGB32 filtered(960,540); - cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); - cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); - auto result=match_lighting_pair(filtered,{ - {"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeEdge.png", - {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, - {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeCliff.png", - {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} - ,{"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeFarEdge.png", - {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, - {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeFarCliff.png", - {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} - ,{"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeNightEdge.png", - {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, - {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeNightCliff.png", - {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} - ,{"Bridge west edge","PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarEdge.png", - {538.0/960,302.0/540,50.0/960,100.0/540},{.15,.25,.85,.75},.90,.10}, - {"Landing cliff corner","PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarCliff.png", - {333.0/960,410.0/540,64.0/960,88.0/540},{.10,.25,.85,.75},.90,.10} - },"Bridge west edge","Landing cliff corner",1.0/128); - result.at_target=result.located && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/108; + if (!frame){ + return {}; + } + cv::Mat gray, smoothed; + cv::resize(gray_image(frame), gray, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::GaussianBlur(gray, smoothed, cv::Size(0, 0), .8); + ImageRGB32 filtered(960, 540); + cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); + cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); + auto result = match_lighting_pair(filtered, + {{"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}, + {"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeFarEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeFarCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}, + {"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}, + {"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}}, + "Bridge west edge", "Landing cliff corner", 1.0 / 128); + result.at_target = result.located && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 108; return result; } OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame){ // The road bend and fence corner exclude the patrolling trainers. - if (!frame) return {}; - cv::Mat gray,smoothed; - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); - ImageRGB32 filtered(960,540); - cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); - cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); - auto result=match_landmarks(filtered,{ - {"Road bend","PokemonBDSP/Autostory/Route215Renewal/AceRoad.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{.10,.15,.85,.80},.92,.08}, - {"Fence corner","PokemonBDSP/Autostory/Route215Renewal/AceFence.png", - {165.0/960,389.0/540,110.0/960,78.0/540},{.05,.25,.90,.75},.90,.10,LandmarkFeatures::LOCAL_CONTRAST} - },2,1.0/128,1.0/108,true); - if (result.located) return result; + if (!frame){ + return {}; + } + cv::Mat gray, smoothed; + cv::resize(gray_image(frame), gray, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::GaussianBlur(gray, smoothed, cv::Size(0, 0), .8); + ImageRGB32 filtered(960, 540); + cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); + cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); + auto result = match_landmarks(filtered, + {{"Road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceRoad.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {.10, .15, .85, .80}, + .92, + .08}, + {"Fence corner", + "PokemonBDSP/Autostory/Route215Renewal/AceFence.png", + {165.0 / 960, 389.0 / 540, 110.0 / 960, 78.0 / 540}, + {.05, .25, .90, .75}, + .90, + .10, + LandmarkFeatures::LOCAL_CONTRAST}}, + 2, 1.0 / 128, 1.0 / 108, true); + if (result.located){ + return result; + } // Expiry may finish east of the fence's visible range. Two separate road // bends establish a horizontal recovery direction along this clear row. // Perspective changes their spacing, so this pair cannot confirm arrival. // Suppress thin rain streaks and puddle rings in the broader road shapes; // retain the original input and confidence gates for the fine fence pair. - cv::medianBlur(gray,smoothed,5); - cv::GaussianBlur(smoothed,smoothed,cv::Size(0,0),.8); - cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); - auto approach=match_lighting_pair(filtered,{ - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadGoal.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadNear.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFar.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestFar.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNear.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWest.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNearRain.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Upper road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFarRain.png", - {352.0/960,180.0/540,60.0/960,80.0/540},{0,.25,1,.30},.92,.08}, - {"Lower road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoadEast.png", - {500.0/960,420.0/540,80.0/960,70.0/540},{0,.70,1,.30},.93,.10}, - {"Lower road bend","PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoad.png", - {500.0/960,420.0/540,80.0/960,70.0/540},{0,.70,1,.30},.93,.10} - },"Upper road bend","Lower road bend",1.0/128); + cv::medianBlur(gray, smoothed, 5); + cv::GaussianBlur(smoothed, smoothed, cv::Size(0, 0), .8); + cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); + auto approach = match_lighting_pair(filtered, + {{"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadGoal.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadNear.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFar.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestFar.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNear.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWest.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNearRain.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFarRain.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Lower road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoadEast.png", + {500.0 / 960, 420.0 / 540, 80.0 / 960, 70.0 / 540}, + {0, .70, 1, .30}, + .93, + .10}, + {"Lower road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoad.png", + {500.0 / 960, 420.0 / 540, 80.0 / 960, 70.0 / 540}, + {0, .70, 1, .30}, + .93, + .10}}, + "Upper road bend", "Lower road bend", 1.0 / 128); // At the eastern extreme, the original bends leave the screen. The two // corners of the same grass bed remain visible above the clear road. - auto east=match_lighting_pair(filtered,{ - {"Grass west corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassWest.png", - {665.0/960,145.0/540,110.0/960,75.0/540},{0,.20,1,.40},.92,.10}, - {"Grass east corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassEast.png", - {890.0/960,175.0/540,65.0/960,62.0/540},{0,.20,1,.40},.92,.10}, - {"Grass west corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", - {665.0/960,145.0/540,110.0/960,75.0/540},{0,.20,1,.40},.92,.10}, - {"Grass east corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEast.png", - {890.0/960,175.0/540,65.0/960,62.0/540},{0,.20,1,.40},.92,.10}, - {"Grass east corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEastShoulder.png", - {850.0/960,145.0/540,105.0/960,80.0/540},{0,.20,1,.40},.92,.10} - },"Grass west corner","Grass east corner",1.0/128); - const auto& west=east.landmarks[0];const auto& right=east.landmarks[1]; - const double east_nearest=std::min(std::abs(west.dx),std::abs(right.dx)); - east.located=west.accepted && right.accepted && - std::abs(west.dy-right.dy)<=AGREEMENT_Y && - west.dx< -1.0/12 && right.dx< -1.0/12 && - east_nearest>2*std::abs(west.dx-right.dx) && - east.dy>=-1.0/27 && east.dy<=1.0/9; - if (east.located) { east.coarse=true;east.at_target=false;return east; } + auto east = match_lighting_pair(filtered, + {{"Grass west corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassWest.png", + {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass east corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassEast.png", + {890.0 / 960, 175.0 / 540, 65.0 / 960, 62.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass west corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", + {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass east corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEast.png", + {890.0 / 960, 175.0 / 540, 65.0 / 960, 62.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass east corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEastShoulder.png", + {850.0 / 960, 145.0 / 540, 105.0 / 960, 80.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}}, + "Grass west corner", "Grass east corner", 1.0 / 128); + const auto& west = east.landmarks[0]; + const auto& right = east.landmarks[1]; + const double east_nearest = std::min(std::abs(west.dx), std::abs(right.dx)); + east.located = west.accepted && right.accepted && std::abs(west.dy - right.dy) <= AGREEMENT_Y && + west.dx < -1.0 / 12 && right.dx < -1.0 / 12 && east_nearest > 2 * std::abs(west.dx - right.dx) && + east.dy >= -1.0 / 27 && east.dy <= 1.0 / 9; + if (east.located){ + east.coarse = true; + east.at_target = false; + return east; + } // From the eastern end, the fixed route sign remains unique while the // road bends are off-screen. Its virtual goal lies beyond the right edge; // this pair guides acquisition only and can never confirm arrival. - auto far_east=match_lighting_pair(filtered,{ - {"Grass west corner","PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", - {665.0/960,145.0/540,110.0/960,75.0/540},{0,.20,1,.40},.92,.10}, - {"Route 215 sign","PokemonBDSP/Autostory/Route215Renewal/AceRoute215Sign.png", - {1128.0/960,359.0/540,90.0/960,45.0/540},{.50,.40,.50,.50},.94,.20}, - {"Route 215 sign","PokemonBDSP/Autostory/Route215Renewal/AceRoute215SignRain.png", - {1128.0/960,359.0/540,90.0/960,45.0/540},{.50,.40,.50,.50},.94,.20} - },"Grass west corner","Route 215 sign",1.0/128); - const auto& corner=far_east.landmarks[0];const auto& sign=far_east.landmarks[1]; - const double far_nearest=std::min(std::abs(corner.dx),std::abs(sign.dx)); - far_east.located=corner.accepted && sign.accepted && - std::abs(corner.dy-sign.dy)<=AGREEMENT_Y && - corner.dx< -1.0/4 && sign.dx< -1.0/4 && - far_nearest>2*std::abs(corner.dx-sign.dx) && - far_east.dy>=-1.0/27 && far_east.dy<=1.0/9; - if (far_east.located) {far_east.coarse=true;far_east.at_target=false;return far_east;} - const auto& upper=approach.landmarks[0];const auto& lower=approach.landmarks[1]; + auto far_east = match_lighting_pair(filtered, + {{"Grass west corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", + {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Route 215 sign", + "PokemonBDSP/Autostory/Route215Renewal/AceRoute215Sign.png", + {1128.0 / 960, 359.0 / 540, 90.0 / 960, 45.0 / 540}, + {.50, .40, .50, .50}, + .94, + .20}, + {"Route 215 sign", + "PokemonBDSP/Autostory/Route215Renewal/AceRoute215SignRain.png", + {1128.0 / 960, 359.0 / 540, 90.0 / 960, 45.0 / 540}, + {.50, .40, .50, .50}, + .94, + .20}}, + "Grass west corner", "Route 215 sign", 1.0 / 128); + const auto& corner = far_east.landmarks[0]; + const auto& sign = far_east.landmarks[1]; + const double far_nearest = std::min(std::abs(corner.dx), std::abs(sign.dx)); + far_east.located = corner.accepted && sign.accepted && std::abs(corner.dy - sign.dy) <= AGREEMENT_Y && + corner.dx < -1.0 / 4 && sign.dx < -1.0 / 4 && far_nearest > 2 * std::abs(corner.dx - sign.dx) && + far_east.dy >= -1.0 / 27 && far_east.dy <= 1.0 / 9; + if (far_east.located){ + far_east.coarse = true; + far_east.at_target = false; + return far_east; + } + const auto& upper = approach.landmarks[0]; + const auto& lower = approach.landmarks[1]; // Keep the shared row when both bends already agree. In the recorded // perspective-shifted rows the nearer bend moves 1.35 times as far; // compare those acquisition errors in the farther bend's plane. // Neither model changes the strict fine-arrival checks above. - const double lower_plane_dy=std::abs(upper.dy-lower.dy)<=AGREEMENT_Y ? - lower.dy : lower.dy/1.35; - approach.dy=(upper.dy+lower_plane_dy)/2; - const double nearest=std::min(std::abs(upper.dx),std::abs(lower.dx)); - const bool horizontal_support=upper.dx*lower.dx>0 && nearest>1.0/64 && - nearest>2*std::abs(upper.dx-lower.dx); + const double lower_plane_dy = std::abs(upper.dy - lower.dy) <= AGREEMENT_Y ? lower.dy : lower.dy / 1.35; + approach.dy = (upper.dy + lower_plane_dy) / 2; + const double nearest = std::min(std::abs(upper.dx), std::abs(lower.dx)); + const bool horizontal_support = + upper.dx * lower.dx > 0 && nearest > 1.0 / 64 && nearest > 2 * std::abs(upper.dx - lower.dx); // Expiry can also finish one row south, already near the goal column. // Both accepted bends must agree before correcting that row vertically. - const bool vertical_support=std::abs(upper.dx-lower.dx)<=AGREEMENT_X && - std::max(std::abs(upper.dx),std::abs(lower.dx))<=1.0/12 && - std::abs(upper.dy-lower_plane_dy)<=1.0/108 && - upper.dy*lower_plane_dy>0 && std::abs(approach.dy)>1.0/54; - approach.located=upper.accepted && lower.accepted && - std::abs(upper.dy-lower_plane_dy)<=AGREEMENT_Y && - std::abs(approach.dy)<=1.0/9 && (horizontal_support || vertical_support); - approach.coarse=true;approach.at_target=false; + const bool vertical_support = std::abs(upper.dx - lower.dx) <= AGREEMENT_X && + std::max(std::abs(upper.dx), std::abs(lower.dx)) <= 1.0 / 12 && + std::abs(upper.dy - lower_plane_dy) <= 1.0 / 108 && upper.dy * lower_plane_dy > 0 && + std::abs(approach.dy) > 1.0 / 54; + approach.located = upper.accepted && lower.accepted && std::abs(upper.dy - lower_plane_dy) <= AGREEMENT_Y && + std::abs(approach.dy) <= 1.0 / 9 && (horizontal_support || vertical_support); + approach.coarse = true; + approach.at_target = false; return approach; } OverworldMatchResult match_route215_derek_stairs_renewal(const ImageRGB32& frame){ - if (!frame) return {}; - cv::Mat gray,smoothed; - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); - ImageRGB32 filtered(960,540); - cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); - cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + if (!frame){ + return {}; + } + cv::Mat gray, smoothed; + cv::resize(gray_image(frame), gray, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::GaussianBlur(gray, smoothed, cv::Size(0, 0), .8); + ImageRGB32 filtered(960, 540); + cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); + cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); // Clear landing below the short stairs; neither tree nor player is an anchor. - auto result=match_lighting_pair(filtered,{ - {"White stair railing","PokemonBDSP/Autostory/Route215Renewal/DerekStairsRailing.png", - {500.0/960,78.0/540,90.0/960,110.0/540},{.10,.05,.85,.85},.93,.12}, - {"Lower stair corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairsCorner.png", - {589.0/960,315.0/540,95.0/960,105.0/540},{.10,.20,.85,.80},.93,.12} - ,{"White stair railing","PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightRailing.png", - {500.0/960,78.0/540,90.0/960,110.0/540},{.10,.05,.85,.85},.93,.12}, - {"Lower stair corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightCorner.png", - {589.0/960,315.0/540,95.0/960,105.0/540},{.10,.20,.85,.80},.93,.12} - },"White stair railing","Lower stair corner",1.0/128); - result.at_target=result.located && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/108; - if (result.located) return result; + auto result = match_lighting_pair(filtered, + {{"White stair railing", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsRailing.png", + {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, + {.10, .05, .85, .85}, + .93, + .12}, + {"Lower stair corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsCorner.png", + {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, + {.10, .20, .85, .80}, + .93, + .12}, + {"White stair railing", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightRailing.png", + {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, + {.10, .05, .85, .85}, + .93, + .12}, + {"Lower stair corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightCorner.png", + {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, + {.10, .20, .85, .80}, + .93, + .12}}, + "White stair railing", "Lower stair corner", 1.0 / 128); + result.at_target = result.located && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 108; + if (result.located){ + return result; + } // Far west of this landing, perspective shifts the two structures by // different amounts. Use that view only to acquire the horizontal column; // the original close pair above remains the sole arrival check. - auto approach=match_landmarks(filtered,{ - {"White stair railing","PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestRailing.png", - {500.0/960,78.0/540,90.0/960,110.0/540},{.10,.05,.85,.85},.93,.12}, - {"Lower stair corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestCorner.png", - {589.0/960,315.0/540,95.0/960,105.0/540},{.10,.20,.85,.80},.93,.12} - },2,1.0/128,1.0/108,true); - const auto& rail=approach.landmarks[0];const auto& stair=approach.landmarks[1]; - const double nearest=std::min(std::abs(rail.dx),std::abs(stair.dx)); - approach.located=rail.accepted && stair.accepted && - std::abs(rail.dy-stair.dy)<=AGREEMENT_Y && - std::abs((rail.dy+stair.dy)/2)<=1.0/108 && - rail.dx*stair.dx>0 && nearest>1.0/64 && - nearest>2*std::abs(rail.dx-stair.dx); - approach.dx=(rail.dx+stair.dx)/2;approach.dy=(rail.dy+stair.dy)/2; - approach.coarse=true;approach.at_target=false; + auto approach = match_landmarks(filtered, + {{"White stair railing", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestRailing.png", + {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, + {.10, .05, .85, .85}, + .93, + .12}, + {"Lower stair corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestCorner.png", + {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, + {.10, .20, .85, .80}, + .93, + .12}}, + 2, 1.0 / 128, 1.0 / 108, true); + const auto& rail = approach.landmarks[0]; + const auto& stair = approach.landmarks[1]; + const double nearest = std::min(std::abs(rail.dx), std::abs(stair.dx)); + approach.located = rail.accepted && stair.accepted && std::abs(rail.dy - stair.dy) <= AGREEMENT_Y && + std::abs((rail.dy + stair.dy) / 2) <= 1.0 / 108 && rail.dx * stair.dx > 0 && + nearest > 1.0 / 64 && nearest > 2 * std::abs(rail.dx - stair.dx); + approach.dx = (rail.dx + stair.dx) / 2; + approach.dy = (rail.dy + stair.dy) / 2; + approach.coarse = true; + approach.at_target = false; return approach; } OverworldMatchResult match_route215_derek_stair_top_renewal(const ImageRGB32& frame){ - if (!frame) return {}; - cv::Mat gray,smoothed; - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); - ImageRGB32 filtered(960,540); - cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); - cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + if (!frame){ + return {}; + } + cv::Mat gray, smoothed; + cv::resize(gray_image(frame), gray, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::GaussianBlur(gray, smoothed, cv::Size(0, 0), .8); + ImageRGB32 filtered(960, 540); + cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); + cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); // The road row above the stairs is clear west/east, below the tall grass. - auto result=match_lighting_pair(filtered,{ - {"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrier.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, - {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrierEast.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, - {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFenceEast.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightBarrier.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, - {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastBarrier.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, - {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestBarrier.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12}, - {"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNorthFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthBarrier.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12} - ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - ,{"Stair-top barrier","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastBarrier.png", - {384.0/960,401.0/540,127.0/960,70.0/540},{.05,.15,.90,.85},.93,.12} - ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - // Raw-luminance alternatives preserve fence structure when local - // contrast normalization amplifies a passing rain streak. - ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthGrayFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12} - ,{"West fence corner","PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastGrayFence.png", - {274.0/960,275.0/540,69.0/960,125.0/540},{.05,.15,.90,.85},.90,.12} - },"Stair-top barrier","West fence corner",1.0/128); - result.at_target=result.located && std::abs(result.dx)<=1.0/128 && std::abs(result.dy)<=1.0/108; - if (!result.located && result.landmarks.size()==2){ - const auto& barrier=result.landmarks[0];const auto& fence=result.landmarks[1]; - const double nearest=std::min(std::abs(barrier.dx),std::abs(fence.dx)); + auto result = match_lighting_pair(filtered, + {{"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrierEast.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFenceEast.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNorthFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST} + // Raw-luminance alternatives preserve fence structure when local + // contrast normalization amplifies a passing rain streak. + , + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthGrayFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastGrayFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12}}, + "Stair-top barrier", "West fence corner", 1.0 / 128); + result.at_target = result.located && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 108; + if (!result.located && result.landmarks.size() == 2){ + const auto& barrier = result.landmarks[0]; + const auto& fence = result.landmarks[1]; + const double nearest = std::min(std::abs(barrier.dx), std::abs(fence.dx)); // Far-east perspective changes spacing: acquire the column first, // then use the original strict two-anchor arrival check. - if (barrier.accepted && fence.accepted && - std::abs(barrier.dy-fence.dy)<=AGREEMENT_Y && std::abs(result.dy)<=1.0/20 && - barrier.dx*fence.dx>0 && nearest>1.0/64 && - nearest>2*std::abs(barrier.dx-fence.dx)){ - result.located=true;result.coarse=true;result.at_target=false; + if (barrier.accepted && fence.accepted && std::abs(barrier.dy - fence.dy) <= AGREEMENT_Y && + std::abs(result.dy) <= 1.0 / 20 && barrier.dx * fence.dx > 0 && nearest > 1.0 / 64 && + nearest > 2 * std::abs(barrier.dx - fence.dx)){ + result.located = true; + result.coarse = true; + result.at_target = false; } } return result; } OverworldMatchResult match_route215_jogger_corner(const ImageRGB32& frame){ - if (!frame) return {}; + if (!frame){ + return {}; + } // These references describe the game capture, without the recording canvas. // Smooth only this locator before contrast normalization to suppress rain // streaks and capture resampling noise; other detectors retain their inputs. - cv::Mat gray,smoothed; - cv::resize(gray_image(frame),gray,cv::Size(960,540),0,0,cv::INTER_AREA); - cv::GaussianBlur(gray,smoothed,cv::Size(0,0),.8); - ImageRGB32 filtered(960,540); - cv::Mat pixels(540,960,CV_8UC4,filtered.data(),filtered.bytes_per_row()); - cv::cvtColor(smoothed,pixels,cv::COLOR_GRAY2BGRA); + cv::Mat gray, smoothed; + cv::resize(gray_image(frame), gray, cv::Size(960, 540), 0, 0, cv::INTER_AREA); + cv::GaussianBlur(gray, smoothed, cv::Size(0, 0), .8); + ImageRGB32 filtered(960, 540); + cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); + cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); // Fixed fence cap and road bend; neither the moving jogger nor the item // supplies position evidence. Each view maps back to the same lane-4 corner. // Search only the cap/bend height of lanes 4 and 3. This excludes similar // lower fence sections; a missing cap never authorizes a vertical search. // Low-frequency cap/cliff alternatives require higher correlation (.95/.97) // as well as a unique peak and an independently agreeing structural anchor. - const std::vector appearances={ - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceGoal.png", - {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceRainWest.png", - {0.535416666667,0.311111111111,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceCloudWest.png", - {0.535416666667,0.311111111111,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceLane3.png", - {0.535416666667,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceMid.png", - {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceNear.png", - {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachFar.png", - {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachMiddle.png", - {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachNear.png", - {0.534375,0.312962962963,32.0/960,42.0/540},{0,.24,1,.26},.93,.08,LandmarkFeatures::LOCAL_CONTRAST}, - {"Fence top","PokemonBDSP/Autostory/Route215JoggerV2/FenceGrayLane3.png", - {0.535416666667,0.312962962963,32.0/960,24.0/540},{0,.24,1,.26},.95,.05}, - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperDayGoal.png", - {422.0/960,164.0/540,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, + const std::vector appearances = { + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceGoal.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceRainWest.png", + {0.535416666667, 0.311111111111, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceCloudWest.png", + {0.535416666667, 0.311111111111, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceLane3.png", + {0.535416666667, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceMid.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceNear.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachFar.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachMiddle.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachNear.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Fence top", + "PokemonBDSP/Autostory/Route215JoggerV2/FenceGrayLane3.png", + {0.535416666667, 0.312962962963, 32.0 / 960, 24.0 / 540}, + {0, .24, 1, .26}, + .95, + .05}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperDayGoal.png", + {422.0 / 960, 164.0 / 540, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, // In lane 3 the cap overlaps the lower part of the full road patch. // Upper-bend alternatives keep position evidence clear of the player. - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperGoal.png", - {0.439583333333,0.303703703704,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperRainWest.png", - {0.439583333333,0.303703703704,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperCloudWest.png", - {0.447916666667,0.307407407407,80.0/960,25.0/540},{0,.24,1,.26},.90,.15}, - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadGoal.png", - {0.439583333333,0.303703703704,80.0/960,41.0/540},{0,.24,1,.26},.90,.15}, - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadRainWest.png", - {0.439583333333,0.303703703704,80.0/960,41.0/540},{0,.24,1,.26},.90,.15}, - {"Road bend","PokemonBDSP/Autostory/Route215JoggerV2/RoadCloudWest.png", - {0.447916666667,0.307407407407,80.0/960,41.0/540},{0,.24,1,.26},.90,.15}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperGoal.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperRainWest.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperCloudWest.png", + {0.447916666667, 0.307407407407, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadGoal.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 41.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadRainWest.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 41.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, + {"Road bend", + "PokemonBDSP/Autostory/Route215JoggerV2/RoadCloudWest.png", + {0.447916666667, 0.307407407407, 80.0 / 960, 41.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, }; std::vector variants; for (const auto& appearance : appearances){ - if (std::string(appearance.name)=="Road bend"){ + if (std::string(appearance.name) == "Road bend"){ variants.push_back(appearance); continue; } // Horizontal perspective changes as the fence crosses the screen. - for (double scale : {.96,1.0,1.04}){ - auto variant=appearance; - variant.target.width*=scale; - variant.target.x+=(appearance.target.width-variant.target.width)/2; + for (double scale : {.96, 1.0, 1.04}){ + auto variant = appearance; + variant.target.width *= scale; + variant.target.x += (appearance.target.width - variant.target.width) / 2; variants.push_back(variant); } } // A road bend can disambiguate repeated fence sections. Choose complete // agreeing pairs, rather than rejecting one anchor before considering the other. - const auto candidates=match_landmarks(filtered,variants,variants.size()+1, - 1.0/128,POSITION_TOLERANCE_Y,true); + const auto candidates = + match_landmarks(filtered, variants, variants.size() + 1, 1.0 / 128, POSITION_TOLERANCE_Y, true); OverworldMatchResult best; - double best_score=-1; - bool supported=false,ambiguous=false; - double first_x=0,first_y=0; + double best_score = -1; + bool supported = false, ambiguous = false; + double first_x = 0, first_y = 0; for (const auto& fence : candidates.landmarks){ - if (fence.name!="Fence top") continue; + if (fence.name != "Fence top"){ + continue; + } for (const auto& road : candidates.landmarks){ - if (road.name!="Road bend") continue; - const double score=std::min(fence.score,road.score); - const bool agrees=std::abs(fence.dx-road.dx)<=AGREEMENT_X && - std::abs(fence.dy-road.dy)<=AGREEMENT_Y; - const bool valid=fence.accepted && road.accepted && agrees; - const double dx=(fence.dx+road.dx)/2,dy=(fence.dy+road.dy)/2; + if (road.name != "Road bend"){ + continue; + } + const double score = std::min(fence.score, road.score); + const bool agrees = + std::abs(fence.dx - road.dx) <= AGREEMENT_X && std::abs(fence.dy - road.dy) <= AGREEMENT_Y; + const bool valid = fence.accepted && road.accepted && agrees; + const double dx = (fence.dx + road.dx) / 2, dy = (fence.dy + road.dy) / 2; if (valid){ - if (supported && (std::abs(dx-first_x)>AGREEMENT_X || - std::abs(dy-first_y)>AGREEMENT_Y)) ambiguous=true; - if (!supported){first_x=dx;first_y=dy;} + if (supported && (std::abs(dx - first_x) > AGREEMENT_X || std::abs(dy - first_y) > AGREEMENT_Y)){ + ambiguous = true; + } + if (!supported){ + first_x = dx; + first_y = dy; + } } - if ((valid && !supported) || (valid==supported && score>best_score)){ - best.landmarks={fence,road};best.dx=dx;best.dy=dy;best_score=score; + if ((valid && !supported) || (valid == supported && score > best_score)){ + best.landmarks = {fence, road}; + best.dx = dx; + best.dy = dy; + best_score = score; } - supported|=valid; + supported |= valid; } } - best.located=supported && !ambiguous; - best.at_target=best.located && std::abs(best.dx)<=1.0/128 && - std::abs(best.dy)<=POSITION_TOLERANCE_Y; - if (best.located) return best; + best.located = supported && !ambiguous; + best.at_target = best.located && std::abs(best.dx) <= 1.0 / 128 && std::abs(best.dy) <= POSITION_TOLERANCE_Y; + if (best.located){ + return best; + } // West of the visible fence, bridge/cliff pairs establish the walking row. // Their x coordinates are acquisition-only and can never confirm arrival. - const std::vector> approaches={ + const std::vector> approaches = { + {// The observed lane-4 to lane-3 step changes the bridge-foot view. + // Both corners must agree; this pair never confirms arrival. + {"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestNightLane3.png", + {106.0 / 960 - .435625, 297.0 / 540 - 70.0 / 1080, 60.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastNightLane3.png", + {260.0 / 960 - .435625, 295.0 / 540 - 70.0 / 1080, 65.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}}, + {// When the east fence is off-screen, these two full bridge-foot + // corners establish lane 4. They never authorize arrival. + {"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestCurrent.png", + {106.0 / 960 - .435, 257.0 / 540, 60.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastCurrent.png", + {260.0 / 960 - .435, 255.0 / 540, 65.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}}, { - // The observed lane-4 to lane-3 step changes the bridge-foot view. - // Both corners must agree; this pair never confirms arrival. - {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestNightLane3.png", - {106.0/960-.435625,297.0/540-70.0/1080,60.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, - {"Bridge east corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastNightLane3.png", - {260.0/960-.435625,295.0/540-70.0/1080,65.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - },{ - // When the east fence is off-screen, these two full bridge-foot - // corners establish lane 4. They never authorize arrival. - {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestCurrent.png", - {106.0/960-.435,257.0/540,60.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, - {"Bridge east corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastCurrent.png", - {260.0/960-.435,255.0/540,65.0/960,75.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - },{ - {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeFar.png", - {240.0/960-.61,246.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, - {"Bridge east corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastBridgeFar.png", - {388.0/960-.61,246.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST} - },{ - {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeMid.png", - {108.0/960-.445,245.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, - {"South cliff corner","PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeMid.png", - {77.0/960-.445,295.0/540,90.0/960,80.0/540},{0,.2,1,.6},.97,.05} - },{ - {"Bridge west corner","PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeNear.png", - {68.0/960-.405,245.0/540,64.0/960,60.0/540},{0,.2,1,.6},.90,.12,LandmarkFeatures::LOCAL_CONTRAST}, - {"South cliff corner","PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeNear.png", - {37.0/960-.405,295.0/540,90.0/960,80.0/540},{0,.2,1,.6},.97,.05} - } - }; + {"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeFar.png", + {240.0 / 960 - .61, 246.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastBridgeFar.png", + {388.0 / 960 - .61, 246.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}}, + {{"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeMid.png", + {108.0 / 960 - .445, 245.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"South cliff corner", + "PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeMid.png", + {77.0 / 960 - .445, 295.0 / 540, 90.0 / 960, 80.0 / 540}, + {0, .2, 1, .6}, + .97, + .05}}, + {{"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeNear.png", + {68.0 / 960 - .405, 245.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"South cliff corner", + "PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeNear.png", + {37.0 / 960 - .405, 295.0 / 540, 90.0 / 960, 80.0 / 540}, + {0, .2, 1, .6}, + .97, + .05}}}; for (const auto& approach : approaches){ - auto candidate=match_landmarks(filtered,approach,2,1.0/128,1.0/54,true); - const double score=std::min(candidate.landmarks[0].score,candidate.landmarks[1].score); - if (best.located && candidate.located && std::abs(best.dy-candidate.dy)>AGREEMENT_Y){ - best.located=false;best.at_target=false;best.coarse=true;return best; + auto candidate = match_landmarks(filtered, approach, 2, 1.0 / 128, 1.0 / 54, true); + const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); + if (best.located && candidate.located && std::abs(best.dy - candidate.dy) > AGREEMENT_Y){ + best.located = false; + best.at_target = false; + best.coarse = true; + return best; } - candidate.coarse=true;candidate.at_target=false; - if ((candidate.located && !best.located) || (candidate.located==best.located && score>best_score)){ - best=std::move(candidate);best_score=score; + candidate.coarse = true; + candidate.at_target = false; + if ((candidate.located && !best.located) || (candidate.located == best.located && score > best_score)){ + best = std::move(candidate); + best_score = score; } } return best; } -const std::vector& overworld_matchers(){ static const std::vector matchers={ - {"route204-north-approach","Route 204 left of sign after Tyler",match_route204_north_approach}, - {"teighler-alcove-corner","Teighler alcove bottom-left corner",match_teighler_alcove_corner}, - {"route204-north-stairs","Route 204 north stairs junction",match_route204_north_stairs}, - {"floaroma-center-entrance","Floaroma Pokemon Center entrance",match_floaroma_center_entrance}, - {"ravaged-path-south-entrance","Ravaged Path south entrance",match_ravaged_path_south_entrance}, - {"route204-sign","Route 204 sign before the next trainer",match_route204_sign}, - {"windworks-sign","Valley Windworks west of the door grunt",match_windworks_sign_approach}, - {"route205-bridge","Route 205 south of the bridge",match_route205_bridge}, - {"route205-bank-tree","Route 205 north bank beside the golden tree",match_route205_bank_tree}, - {"route205-zackary","Route 205 Zackary patrol intercept",match_route205_zackary}, - {"route205-kelsey","Route 205 Kelsey patrol intercept",match_route205_kelsey}, - {"route205-forest","Route 205 outside Eterna Forest",match_route205_forest_entrance}, - {"jubilife-street-approach","Jubilife street before Galactic plaza",match_jubilife_street_approach}, - {"jubilife-plaza-approach","Jubilife Galactic plaza approach",match_jubilife_plaza_approach}, - {"poketch-landmarks","Jubilife Poketch landmarks",match_poketch_landmarks}, - {"oreburgh-mart","Oreburgh Mart emblem",match_oreburgh_mart}, - {"jubilife-center","Jubilife Pokemon Center entrance",match_jubilife_center_entrance}, - {"eterna-center","Eterna Pokemon Center doorway",match_eterna_center_entrance}, - {"hearthome-center","Hearthome Pokemon Center doorway",match_hearthome_center_entrance}, - {"hearthome-center-interior","Hearthome Pokemon Center interior",match_hearthome_center_interior}, - {"hearthome-fantina","Hearthome Contest Hall sign + roof emblem (Fantina)",match_hearthome_contest_fantina}, - {"veilstone-city-passage","Veilstone city passage before the west turn",match_veilstone_city_passage}, - {"veilstone-dawn-stairs","Veilstone western stairs toward Dawn",match_veilstone_dawn_stairs}, - {"hearthome-gym","Hearthome Gym doorway (arch + facade)",match_hearthome_gym_entrance}, - {"route208-stair-top","Route 208 stair-top rocks",match_route208_stair_top}, - {"route209-repel-point","Route 209 Repel renewal point",match_route209_repel_point}, - {"route215-jogger-corner","Route 215 east corner beyond the jogger",match_route215_jogger_corner}, - {"route215-bridge-renewal","Route 215 west bridge renewal",match_route215_bridge_renewal}, - {"route215-ace-renewal","Route 215 renewal before Ace Trainers",match_route215_ace_renewal}, - {"route215-derek-stairs-renewal","Route 215 renewal below Derek stairs",match_route215_derek_stairs_renewal}, - {"route215-derek-stair-top-renewal","Route 215 renewal above Derek stairs",match_route215_derek_stair_top_renewal}, - {"route208-hannah-departure","Route 208 field after Hannah",match_route208_hannah_departure}, - {"eterna-gym","Eterna Gym doorway",match_eterna_gym_entrance}, - {"galactic-hq-jupiter","Galactic HQ Jupiter interaction",match_galactic_hq_jupiter}, - {"eterna-gym-statues","Eterna Gym interior statues",match_eterna_gym_statues}, - {"eterna-underground-house","Eterna Underground Man house doorway",match_eterna_underground_house}, - {"eterna-underground-man","Eterna Underground Man interaction",match_eterna_underground_man}, - {"route201-potion-exit","Route 201 after Potion pickup",match_route201_potion_exit}, - {"sandgem-junction","Sandgem Center/Mart junction",match_sandgem_junction}, - {"route202-grass-exit","Route 202 after catching: grass exit",match_route202_grass_exit}, +const std::vector& overworld_matchers(){ + static const std::vector matchers = { + {"route204-north-approach", "Route 204 left of sign after Tyler", match_route204_north_approach}, + {"teighler-alcove-corner", "Teighler alcove bottom-left corner", match_teighler_alcove_corner}, + {"route204-north-stairs", "Route 204 north stairs junction", match_route204_north_stairs}, + {"floaroma-center-entrance", "Floaroma Pokemon Center entrance", match_floaroma_center_entrance}, + {"ravaged-path-south-entrance", "Ravaged Path south entrance", match_ravaged_path_south_entrance}, + {"route204-sign", "Route 204 sign before the next trainer", match_route204_sign}, + {"windworks-sign", "Valley Windworks west of the door grunt", match_windworks_sign_approach}, + {"route205-bridge", "Route 205 south of the bridge", match_route205_bridge}, + {"route205-bank-tree", "Route 205 north bank beside the golden tree", match_route205_bank_tree}, + {"route205-zackary", "Route 205 Zackary patrol intercept", match_route205_zackary}, + {"route205-kelsey", "Route 205 Kelsey patrol intercept", match_route205_kelsey}, + {"route205-forest", "Route 205 outside Eterna Forest", match_route205_forest_entrance}, + {"jubilife-street-approach", "Jubilife street before Galactic plaza", match_jubilife_street_approach}, + {"jubilife-plaza-approach", "Jubilife Galactic plaza approach", match_jubilife_plaza_approach}, + {"poketch-landmarks", "Jubilife Poketch landmarks", match_poketch_landmarks}, + {"oreburgh-mart", "Oreburgh Mart emblem", match_oreburgh_mart}, + {"jubilife-center", "Jubilife Pokemon Center entrance", match_jubilife_center_entrance}, + {"eterna-center", "Eterna Pokemon Center doorway", match_eterna_center_entrance}, + {"hearthome-center", "Hearthome Pokemon Center doorway", match_hearthome_center_entrance}, + {"hearthome-center-interior", "Hearthome Pokemon Center interior", match_hearthome_center_interior}, + {"hearthome-fantina", "Hearthome Contest Hall sign + roof emblem (Fantina)", match_hearthome_contest_fantina}, + {"veilstone-city-passage", "Veilstone city passage before the west turn", match_veilstone_city_passage}, + {"veilstone-dawn-stairs", "Veilstone western stairs toward Dawn", match_veilstone_dawn_stairs}, + {"hearthome-gym", "Hearthome Gym doorway (arch + facade)", match_hearthome_gym_entrance}, + {"route208-stair-top", "Route 208 stair-top rocks", match_route208_stair_top}, + {"route209-repel-point", "Route 209 Repel renewal point", match_route209_repel_point}, + {"route215-jogger-corner", "Route 215 east corner beyond the jogger", match_route215_jogger_corner}, + {"route215-bridge-renewal", "Route 215 west bridge renewal", match_route215_bridge_renewal}, + {"route215-ace-renewal", "Route 215 renewal before Ace Trainers", match_route215_ace_renewal}, + {"route215-derek-stairs-renewal", "Route 215 renewal below Derek stairs", match_route215_derek_stairs_renewal}, + {"route215-derek-stair-top-renewal", "Route 215 renewal above Derek stairs", + match_route215_derek_stair_top_renewal}, + {"route208-hannah-departure", "Route 208 field after Hannah", match_route208_hannah_departure}, + {"eterna-gym", "Eterna Gym doorway", match_eterna_gym_entrance}, + {"galactic-hq-jupiter", "Galactic HQ Jupiter interaction", match_galactic_hq_jupiter}, + {"eterna-gym-statues", "Eterna Gym interior statues", match_eterna_gym_statues}, + {"eterna-underground-house", "Eterna Underground Man house doorway", match_eterna_underground_house}, + {"eterna-underground-man", "Eterna Underground Man interaction", match_eterna_underground_man}, + {"route201-potion-exit", "Route 201 after Potion pickup", match_route201_potion_exit}, + {"sandgem-junction", "Sandgem Center/Mart junction", match_sandgem_junction}, + {"route202-grass-exit", "Route 202 after catching: grass exit", match_route202_grass_exit}, }; return matchers; } -}}} +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 8dfb889fe7..36aa0aaf8f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -1,10 +1,15 @@ #pragma once + #include #include #include + #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" -namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonBDSP { struct OverworldLandmarkResult { std::string name; double score = 0, margin = 0; @@ -82,11 +87,13 @@ OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame); // Two unique, agreeing landmarks close to the doorway; this confidence only // authorizes a watched entry attempt, never proves the interior location. -double overworld_entry_confidence(const OverworldMatchResult& result, - double tolerance_x=1.0/50, double tolerance_y=1.0/27, double minimum_margin=.15); +double overworld_entry_confidence(const OverworldMatchResult& result, double tolerance_x = 1.0 / 50, + double tolerance_y = 1.0 / 27, double minimum_margin = .15); OverworldMatchResult match_eterna_underground_house(const ImageRGB32& frame); OverworldMatchResult match_eterna_underground_man(const ImageRGB32& frame); OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame); OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame); OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame); -}}} +} +} +} diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp index d6bec8469d..7a745f12cf 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/TestPrograms/PokemonBDSP_OverworldLightingTest.cpp @@ -62,6 +62,7 @@ OverworldLightingTest_Descriptor::OverworldLightingTest_Descriptor() : SingleSwitchProgramDescriptor("PokemonBDSP:OverworldLightingTest","Pokemon BDSP","Overworld Matcher Lighting Test","", "Place the player at the selected matcher's reference location. Tests 24 hourly lighting settings without moving the player. " "Changes console date/time; saves screenshots and CSV results under Screenshots/OverworldLightingTest.", + PanelQualityStanding::DEVELOPER, ProgramControllerClass::StandardController_NoRestrictions,FeedbackType::REQUIRED,AllowCommandsWhenRunning::DISABLE_COMMANDS) {} OverworldLightingTest::~OverworldLightingTest(){ From de9252a0b5df0e484fb4d17be15698bdb333749d Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 29 Oct 2026 16:54:39 -0500 Subject: [PATCH 37/39] Fixxing additional formatting Fixed formmatting to comply with contributor.md/agents.md --- .../Data/PokemonBDSP_AutoStory_BDSPData.cpp | 24 +- ...kemonBDSP_AutoStory_MenuCursorDetector.cpp | 145 +- ...okemonBDSP_AutoStory_OverworldDetector.cpp | 16 +- .../AutoStory/PokemonBDSP_AutoStory.cpp | 402 ++- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 406 ++- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 112 +- .../PokemonBDSP_AutoStory_Segment_00.cpp | 385 +- .../PokemonBDSP_AutoStory_Segment_00.h | 78 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 494 +-- .../PokemonBDSP_AutoStory_Segment_01.h | 47 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 1013 +++--- .../PokemonBDSP_AutoStory_Segment_02.h | 79 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 1528 +++++--- .../PokemonBDSP_AutoStory_Segment_03.h | 90 +- .../PokemonBDSP_AutoStory_Segment_04.cpp | 842 +++-- .../PokemonBDSP_AutoStory_Segment_04.h | 216 +- .../PokemonBDSP_AutoStory_Segment_05.cpp | 973 +++--- .../PokemonBDSP_AutoStory_Segment_05.h | 210 +- .../PokemonBDSP_AutoStory_Segment_06.cpp | 408 ++- .../PokemonBDSP_AutoStory_Segment_06.h | 138 +- .../PokemonBDSP_AutoStory_Segment_07.cpp | 1023 +++--- .../PokemonBDSP_AutoStory_Segment_07.h | 234 +- .../PokemonBDSP_AutoStory_Segment_08.cpp | 1511 ++++---- .../PokemonBDSP_AutoStory_Segment_08.h | 336 +- .../PokemonBDSP_AutoStory_Segment_09.cpp | 892 +++-- .../PokemonBDSP_AutoStory_Segment_09.h | 174 +- .../Utils/PokemonBDSP_AutoStory_Battle.cpp | 201 +- .../Utils/PokemonBDSP_AutoStory_Battle.h | 21 +- .../PokemonBDSP_AutoStory_Navigation.cpp | 6 +- .../Utils/PokemonBDSP_AutoStory_Navigation.h | 18 +- ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 575 +-- .../PokemonBDSP_AutoStory_Segment_00_tools.h | 53 +- ...PokemonBDSP_Autostory_Segment_01_tools.cpp | 114 +- .../PokemonBDSP_Autostory_Segment_01_tools.h | 13 +- .../Utils/PokemonBDSP_OverworldMatchers.cpp | 3093 +++++++++-------- .../Utils/PokemonBDSP_OverworldMatchers.h | 6 +- 36 files changed, 9408 insertions(+), 6468 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp index d6509f1345..f72ee6f780 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_BDSPData.cpp @@ -30,12 +30,24 @@ static const std::string RESOURCE_DIR = "PokemonBDSP/AutoStory/"; static PokemonType parse_type(const std::string& slug, const std::string& path){ static const std::map TABLE{ - {"normal", PokemonType::NORMAL}, {"fire", PokemonType::FIRE}, {"fighting", PokemonType::FIGHTING}, - {"water", PokemonType::WATER}, {"flying", PokemonType::FLYING}, {"grass", PokemonType::GRASS}, - {"poison", PokemonType::POISON}, {"electric", PokemonType::ELECTRIC}, {"ground", PokemonType::GROUND}, - {"psychic", PokemonType::PSYCHIC}, {"rock", PokemonType::ROCK}, {"ice", PokemonType::ICE}, - {"bug", PokemonType::BUG}, {"dragon", PokemonType::DRAGON}, {"ghost", PokemonType::GHOST}, - {"dark", PokemonType::DARK}, {"steel", PokemonType::STEEL}, {"fairy", PokemonType::FAIRY}, + {"normal", PokemonType::NORMAL}, + {"fire", PokemonType::FIRE}, + {"fighting", PokemonType::FIGHTING}, + {"water", PokemonType::WATER}, + {"flying", PokemonType::FLYING}, + {"grass", PokemonType::GRASS}, + {"poison", PokemonType::POISON}, + {"electric", PokemonType::ELECTRIC}, + {"ground", PokemonType::GROUND}, + {"psychic", PokemonType::PSYCHIC}, + {"rock", PokemonType::ROCK}, + {"ice", PokemonType::ICE}, + {"bug", PokemonType::BUG}, + {"dragon", PokemonType::DRAGON}, + {"ghost", PokemonType::GHOST}, + {"dark", PokemonType::DARK}, + {"steel", PokemonType::STEEL}, + {"fairy", PokemonType::FAIRY}, }; auto iter = TABLE.find(slug); if (iter == TABLE.end()){ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp index 0759014d39..9cf2086703 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_MenuCursorDetector.cpp @@ -42,75 +42,72 @@ std::string menu_cursor_to_string(MenuCursorPosition pos){ } -MenuCursorDetector::MenuCursorDetector(Color color) - : m_color(color) - // ------------------------------------------------------------------------- - // Top row — left to right - // ------------------------------------------------------------------------- - // TODO: replace each ImageFloatBox(0,0,0,0) with the real normalized - // (x, y, width, height) for a small region that gains dark pixels when - // that icon is hovered. Use the test program overlays to tune coordinates. - // ------------------------------------------------------------------------- - , - m_pokedex_box{ - ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), - }, - m_pokemon_box{ - ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), - }, - m_bag_box{ - ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), - }, - m_card_box{ - ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), - } // ------------------------------------------------------------------------- - // Bottom row — left to right - // 5-icon layout: OPTIONS is the only bottom icon (far-left position). - // 8-icon layout: MAP, CAPSULES, OPTIONS, MYSTERYGIFT from left to right. - // OPTIONS sits at a different x-position in each layout — use a separate - // MenuCursorDetector instance if you need to detect OPTIONS in both. - // ------------------------------------------------------------------------- - , - m_options_box{ - ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), - }, - m_map_box{ - ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), - }, - m_capsules_box{ - ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), - }, - m_mysterygift_box{ - ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), - } // ------------------------------------------------------------------------- - // Approach 2 search boxes — one per icon, same area as the dark-pixel box. - // The template PNG is slid across this area at its native size. - // ------------------------------------------------------------------------- - , - m_icon_match_box{ - ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), // 0 POKEDEX - ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), // 1 POKEMON - ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), // 2 BAG - ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), // 3 CARD - ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), // 4 OPTIONS (8-icon layout) - ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), // 5 MAP - ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), // 6 CAPSULES - ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), // 7 MYSTERYGIFT - } { +MenuCursorDetector::MenuCursorDetector(Color color) : + m_color(color) + // ------------------------------------------------------------------------- + // Top row — left to right + // ------------------------------------------------------------------------- + // TODO: replace each ImageFloatBox(0,0,0,0) with the real normalized + // (x, y, width, height) for a small region that gains dark pixels when + // that icon is hovered. Use the test program overlays to tune coordinates. + // ------------------------------------------------------------------------- + , m_pokedex_box{ + ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), + }, + m_pokemon_box{ + ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), + }, + m_bag_box{ + ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), + }, + m_card_box{ + ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), + } // ------------------------------------------------------------------------- + // Bottom row — left to right + // 5-icon layout: OPTIONS is the only bottom icon (far-left position). + // 8-icon layout: MAP, CAPSULES, OPTIONS, MYSTERYGIFT from left to right. + // OPTIONS sits at a different x-position in each layout — use a separate + // MenuCursorDetector instance if you need to detect OPTIONS in both. + // ------------------------------------------------------------------------- + , m_options_box{ + ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), + }, + m_map_box{ + ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), + }, + m_capsules_box{ + ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), + }, + m_mysterygift_box{ + ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), + } // ------------------------------------------------------------------------- + // Approach 2 search boxes — one per icon, same area as the dark-pixel box. + // The template PNG is slid across this area at its native size. + // ------------------------------------------------------------------------- + , m_icon_match_box{ + ImageFloatBox(0.180000, 0.115000, 0.135000, 0.195000), // 0 POKEDEX + ImageFloatBox(0.347500, 0.115000, 0.135000, 0.195000), // 1 POKEMON + ImageFloatBox(0.515000, 0.115000, 0.135000, 0.195000), // 2 BAG + ImageFloatBox(0.682500, 0.115000, 0.135000, 0.195000), // 3 CARD + ImageFloatBox(0.515000, 0.364000, 0.135000, 0.195000), // 4 OPTIONS (8-icon layout) + ImageFloatBox(0.180000, 0.364000, 0.135000, 0.195000), // 5 MAP + ImageFloatBox(0.347500, 0.364000, 0.135000, 0.195000), // 6 CAPSULES + ImageFloatBox(0.682500, 0.364000, 0.135000, 0.195000), // 7 MYSTERYGIFT + } { } void MenuCursorDetector::make_overlays(VideoOverlaySet& items) const { for (const ImageFloatBox* icon_boxes : { - m_pokedex_box, - m_pokemon_box, - m_bag_box, - m_card_box, - m_options_box, - m_map_box, - m_capsules_box, - m_mysterygift_box, - }){ + m_pokedex_box, + m_pokemon_box, + m_bag_box, + m_card_box, + m_options_box, + m_map_box, + m_capsules_box, + m_mysterygift_box, + }){ for (int i = 0; i < BOXES_PER_ICON; i++){ if (icon_boxes[i].width > 0.0 && icon_boxes[i].height > 0.0){ items.add(m_color, icon_boxes[i]); @@ -139,8 +136,12 @@ int count_dark_boxes(const ImageViewRGB32& screen, const ImageFloatBox* boxes, i continue; } size_t dark_pixels = 0; - filter_rgb32_range(dark_pixels, extract_box_reference(screen, boxes[i]), 0xff000000u, maxs, Color(0xffffffff), - false); + filter_rgb32_range(dark_pixels, + extract_box_reference(screen, boxes[i]), + 0xff000000u, + maxs, + Color(0xffffffff), + false); if (dark_pixels >= MenuCursorDetector::MIN_DARK_PIXEL_COUNT){ count++; } @@ -199,10 +200,14 @@ MenuCursorPosition MenuCursorDetector::detect_dark_pixels(const ImageViewRGB32& const ImageFloatBox* boxes; }; const IconEntry icons[] = { - {MenuCursorPosition::POKEDEX, m_pokedex_box}, {MenuCursorPosition::POKEMON, m_pokemon_box}, - {MenuCursorPosition::BAG, m_bag_box}, {MenuCursorPosition::CARD, m_card_box}, - {MenuCursorPosition::OPTIONS, m_options_box}, {MenuCursorPosition::MAP, m_map_box}, - {MenuCursorPosition::CAPSULES, m_capsules_box}, {MenuCursorPosition::MYSTERYGIFT, m_mysterygift_box}, + {MenuCursorPosition::POKEDEX, m_pokedex_box}, + {MenuCursorPosition::POKEMON, m_pokemon_box}, + {MenuCursorPosition::BAG, m_bag_box}, + {MenuCursorPosition::CARD, m_card_box}, + {MenuCursorPosition::OPTIONS, m_options_box}, + {MenuCursorPosition::MAP, m_map_box}, + {MenuCursorPosition::CAPSULES, m_capsules_box}, + {MenuCursorPosition::MYSTERYGIFT, m_mysterygift_box}, }; MenuCursorPosition best_pos = MenuCursorPosition::UNKNOWN; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp index aacb67bfeb..3e4e0f41ca 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.cpp @@ -23,7 +23,9 @@ namespace PokemonBDSP { // OverworldDetector // --------------------------------------------------------------------------- -OverworldDetector::OverworldDetector(Color color) : m_color(color), m_battle_menu(BattleType::STANDARD, color){ +OverworldDetector::OverworldDetector(Color color) + : m_color(color) + , m_battle_menu(BattleType::STANDARD, color){ } void OverworldDetector::make_overlays(VideoOverlaySet& items) const { @@ -52,7 +54,8 @@ bool OverworldDetector::detect(const ImageViewRGB32& screen){ // --------------------------------------------------------------------------- OverworldWatcher::OverworldWatcher(Color color) - : OverworldDetector(color), VisualInferenceCallback("OverworldWatcher"){ + : OverworldDetector(color) + , VisualInferenceCallback("OverworldWatcher"){ } void OverworldWatcher::make_overlays(VideoOverlaySet& items) const { @@ -68,7 +71,9 @@ bool OverworldWatcher::process_frame(const ImageViewRGB32& frame, WallClock /*ti // PoketchButtonDetector // --------------------------------------------------------------------------- -PoketchButtonDetector::PoketchButtonDetector(Color color) : m_color(color), m_button(0.968, 0.170, 0.022, 0.120){ +PoketchButtonDetector::PoketchButtonDetector(Color color) + : m_color(color) + , m_button(0.968, 0.170, 0.022, 0.120){ } void PoketchButtonDetector::make_overlays(VideoOverlaySet& items) const { @@ -82,7 +87,8 @@ bool PoketchButtonDetector::detect(const ImageViewRGB32& screen){ } PoketchButtonWatcher::PoketchButtonWatcher(Color color) - : PoketchButtonDetector(color), VisualInferenceCallback("PoketchButtonWatcher"){ + : PoketchButtonDetector(color) + , VisualInferenceCallback("PoketchButtonWatcher"){ } void PoketchButtonWatcher::make_overlays(VideoOverlaySet& items) const { @@ -106,7 +112,7 @@ bool ensure_poketch_visible(VideoStream& stream, ProControllerContext& context){ return true; } stream.log("[AutoStory] Poketch button not found. Attempt " + std::to_string(attempt + 1) + " of 3.", - COLOR_ORANGE); + COLOR_ORANGE); if (attempt == 0){ // Most likely hidden: one tap shows it. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index 0d27724388..f4b9f1676b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -285,7 +285,7 @@ static const StringSelectDatabase& section_database(bool checkpoints, bool tutor return db; }(); return checkpoints ? (tutorial ? tutorial_checkpoints : main_checkpoints) - : (tutorial ? tutorial_segments : main_segments); + : (tutorial ? tutorial_segments : main_segments); } // --------------------------------------------------------------------------- @@ -293,11 +293,16 @@ static const StringSelectDatabase& section_database(bool checkpoints, bool tutor // --------------------------------------------------------------------------- BDSPAutoStory_Descriptor::BDSPAutoStory_Descriptor() - : SingleSwitchProgramDescriptor( - "PokemonBDSP:AutoStory", "Pokemon BDSP", "Auto Story", "Programs/PokemonBDSP/AutoStory.html", - "Automatically completes BDSP story segments.", PanelQualityStanding::THIRD_PARTY_UNVETTED, - ProgramControllerClass::StandardController_NoRestrictions, FeedbackType::REQUIRED, - AllowCommandsWhenRunning::DISABLE_COMMANDS, {"Tesseract"}){ + : SingleSwitchProgramDescriptor("PokemonBDSP:AutoStory", + "Pokemon BDSP", + "Auto Story", + "Programs/PokemonBDSP/AutoStory.html", + "Automatically completes BDSP story segments.", + PanelQualityStanding::THIRD_PARTY_UNVETTED, + ProgramControllerClass::StandardController_NoRestrictions, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS, + {"Tesseract"}){ } @@ -332,179 +337,186 @@ BDSPAutoStory::~BDSPAutoStory(){ BDSPAutoStory::BDSPAutoStory() : SingleSwitchProgramInstance() - // DIAGNOSTIC ONLY: Remove before pushing to production. - , - DIAGNOSTIC_POST_CATCH_ONLY( - "Diagnostic only: Run 00.04 after catch through 00.05
" - "Start at the saved position immediately after catching the second Pokemon. " - "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " - "Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, false) - // DIAGNOSTIC ONLY: Remove before pushing to production. - , - DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( - "Diagnostic only: Fake checkpoint saves
" - "Use the fake save helper instead of normal checkpoint saving. " - "Use an existing in-game save at the selected starting checkpoint or segment. " - "Any failure reloads that save and reruns the entire selected range from its start. " - "Checkpoint saves are not verified in this mode. Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, false), - STORY_SECTION( - "Story Section:", - {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, {StorySection::MAIN_STORY, "main-story", "Main Story"}}, - LockMode::LOCK_WHILE_RUNNING, StorySection::TUTORIAL), - MAINSTORY_NOTE("", "Return your maxed starter from Pokemon HOME before starting Main Story. " - "Use the required moveset and the starting position shown below."), - ENABLE_ADVANCED_MODE("Advanced mode:
" - "Use checkpoints instead of segments.", - LockMode::UNLOCK_WHILE_RUNNING, false), - START_CHECKPOINT_TUTORIAL("Start Checkpoint:", section_database(true, true), LockMode::LOCK_WHILE_RUNNING, - "0"), - END_CHECKPOINT_TUTORIAL("End Checkpoint:", section_database(true, true), LockMode::UNLOCK_WHILE_RUNNING, - "5") - - , - START_DESCRIPTION_SEGMENT("Start:
", ""), END_DESCRIPTION_SEGMENT("End:
", "") - - , - START_DESCRIPTION_CHECKPOINT("Start:
", ""), END_DESCRIPTION_CHECKPOINT("End:
", "") - - , - START_CHECKPOINT_MAINSTORY("Start Checkpoint (Main):", section_database(true, false), - LockMode::LOCK_WHILE_RUNNING, "6") - - , - END_CHECKPOINT_MAINSTORY("End Checkpoint (Main):", section_database(true, false), - LockMode::UNLOCK_WHILE_RUNNING, std::to_string(ALL_CHECKPOINTS().size() - 1)) - - , - START_SEGMENT("Start Segment:", section_database(false, true), LockMode::LOCK_WHILE_RUNNING, "0") - - , - END_SEGMENT("End Segment:", section_database(false, true), LockMode::UNLOCK_WHILE_RUNNING, "0") - - , - START_SEGMENT_MAINSTORY("Start Segment:", section_database(false, false), LockMode::LOCK_WHILE_RUNNING, - "1"), - END_SEGMENT_MAINSTORY("End Segment:", section_database(false, false), LockMode::LOCK_WHILE_RUNNING, - std::to_string(ALL_SEGMENTS().size() - 1)), - STARTERCHOICE("Starter Pokemon:", - { - {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, - {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, - {StarterChoice::PIPLUP, "piplup", "Piplup"}, - }, - LockMode::LOCK_WHILE_RUNNING, StarterChoice::CHIMCHAR) - - , - STARTER_SHINY("Shiny Starter:", LockMode::LOCK_WHILE_RUNNING, false) - - , - CATCH_LEGENDARY("Catch Box Legendary:", LockMode::LOCK_WHILE_RUNNING, false) - - , - HELP_MODE("Help Mode:", - { - {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, - {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, - }, - LockMode::LOCK_WHILE_RUNNING, HelpMode::NO_OUTSIDE_HELP) - - , - OUTSIDE_HELP_POKEMON("Pokemon:", - { - {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, - }, - LockMode::LOCK_WHILE_RUNNING, OutsideHelpPokemon::TORTERRA) - - , - OUTSIDE_HELP_ATTACK_1("Attack 1:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, TorterraMove::EARTHQUAKE) - - , - OUTSIDE_HELP_ATTACK_2("Attack 2:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, TorterraMove::ROCK_SLIDE) - - , - OUTSIDE_HELP_ATTACK_3("Attack 3:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, TorterraMove::WOOD_HAMMER) - - , - OUTSIDE_HELP_ATTACK_4("Attack 4:", - { - {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, - {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, - {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, - {TorterraMove::CRUNCH, "crunch", "Crunch"}, - {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, - {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, - {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, - {TorterraMove::FACADE, "facade", "Facade"}, - {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, - {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, - {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, - {TorterraMove::STRENGTH, "strength", "Strength"}, - {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, - {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, - }, - LockMode::LOCK_WHILE_RUNNING, TorterraMove::CRUNCH) - - , - NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)), - NOTIFICATIONS({ - &NOTIFICATION_STATUS_UPDATE, - &NOTIFICATION_PROGRAM_FINISH, - &NOTIFICATION_ERROR_RECOVERABLE, - &NOTIFICATION_ERROR_FATAL, - }) + // DIAGNOSTIC ONLY: Remove before pushing to production. + , DIAGNOSTIC_POST_CATCH_ONLY( + "Diagnostic only: Run 00.04 after catch through 00.05
" + "Start at the saved position immediately after catching the second Pokemon. " + "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " + "Remove before pushing to production.", + LockMode::LOCK_WHILE_RUNNING, + false) + // DIAGNOSTIC ONLY: Remove before pushing to production. + , DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( + "Diagnostic only: Fake checkpoint saves
" + "Use the fake save helper instead of normal checkpoint saving. " + "Use an existing in-game save at the selected starting checkpoint or segment. " + "Any failure reloads that save and reruns the entire selected range from its start. " + "Checkpoint saves are not verified in this mode. Remove before pushing to production.", + LockMode::LOCK_WHILE_RUNNING, + false) + , STORY_SECTION("Story Section:", + {{StorySection::TUTORIAL, "tutorial", "Tutorial"}, {StorySection::MAIN_STORY, "main-story", "Main Story"}}, + LockMode::LOCK_WHILE_RUNNING, + StorySection::TUTORIAL) + , MAINSTORY_NOTE("", + "Return your maxed starter from Pokemon HOME before starting Main Story. " + "Use the required moveset and the starting position shown below.") + , ENABLE_ADVANCED_MODE("Advanced mode:
" + "Use checkpoints instead of segments.", + LockMode::UNLOCK_WHILE_RUNNING, + false) + , START_CHECKPOINT_TUTORIAL("Start Checkpoint:", + section_database(true, true), + LockMode::LOCK_WHILE_RUNNING, + "0") + , END_CHECKPOINT_TUTORIAL("End Checkpoint:", + section_database(true, true), + LockMode::UNLOCK_WHILE_RUNNING, + "5") + + , START_DESCRIPTION_SEGMENT("Start:
", "") + , END_DESCRIPTION_SEGMENT("End:
", "") + + , START_DESCRIPTION_CHECKPOINT("Start:
", "") + , END_DESCRIPTION_CHECKPOINT("End:
", "") + + , START_CHECKPOINT_MAINSTORY("Start Checkpoint (Main):", + section_database(true, false), + LockMode::LOCK_WHILE_RUNNING, + "6") + + , END_CHECKPOINT_MAINSTORY("End Checkpoint (Main):", + section_database(true, false), + LockMode::UNLOCK_WHILE_RUNNING, + std::to_string(ALL_CHECKPOINTS().size() - 1)) + + , START_SEGMENT("Start Segment:", section_database(false, true), LockMode::LOCK_WHILE_RUNNING, "0") + + , END_SEGMENT("End Segment:", section_database(false, true), LockMode::UNLOCK_WHILE_RUNNING, "0") + + , START_SEGMENT_MAINSTORY("Start Segment:", + section_database(false, false), + LockMode::LOCK_WHILE_RUNNING, + "1") + , END_SEGMENT_MAINSTORY("End Segment:", + section_database(false, false), + LockMode::LOCK_WHILE_RUNNING, + std::to_string(ALL_SEGMENTS().size() - 1)) + , STARTERCHOICE("Starter Pokemon:", + { + {StarterChoice::TURTWIG, "turtwig", "Turtwig"}, + {StarterChoice::CHIMCHAR, "chimchar", "Chimchar"}, + {StarterChoice::PIPLUP, "piplup", "Piplup"}, + }, + LockMode::LOCK_WHILE_RUNNING, + StarterChoice::CHIMCHAR) + + , STARTER_SHINY("Shiny Starter:", LockMode::LOCK_WHILE_RUNNING, false) + + , CATCH_LEGENDARY("Catch Box Legendary:", LockMode::LOCK_WHILE_RUNNING, false) + + , HELP_MODE("Help Mode:", + { + {HelpMode::NO_OUTSIDE_HELP, "no_outside_help", "No Outside Help"}, + {HelpMode::OUTSIDE_HELP, "outside_help", "Outside Help"}, + }, + LockMode::LOCK_WHILE_RUNNING, + HelpMode::NO_OUTSIDE_HELP) + + , OUTSIDE_HELP_POKEMON("Pokemon:", + { + {OutsideHelpPokemon::TORTERRA, "torterra", "Torterra"}, + }, + LockMode::LOCK_WHILE_RUNNING, + OutsideHelpPokemon::TORTERRA) + + , OUTSIDE_HELP_ATTACK_1("Attack 1:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::EARTHQUAKE) + + , OUTSIDE_HELP_ATTACK_2("Attack 2:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::ROCK_SLIDE) + + , OUTSIDE_HELP_ATTACK_3("Attack 3:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::WOOD_HAMMER) + + , OUTSIDE_HELP_ATTACK_4("Attack 4:", + { + {TorterraMove::EARTHQUAKE, "earthquake", "Earthquake"}, + {TorterraMove::WOOD_HAMMER, "wood_hammer", "Wood Hammer"}, + {TorterraMove::RAZOR_LEAF, "razor_leaf", "Razor Leaf"}, + {TorterraMove::CRUNCH, "crunch", "Crunch"}, + {TorterraMove::GIGA_DRAIN, "giga_drain", "Giga Drain"}, + {TorterraMove::IRON_TAIL, "iron_tail", "Iron Tail"}, + {TorterraMove::ROCK_TOMB, "rock_tomb", "Rock Tomb"}, + {TorterraMove::FACADE, "facade", "Facade"}, + {TorterraMove::STONE_EDGE, "stone_edge", "Stone Edge"}, + {TorterraMove::ROCK_SLIDE, "rock_slide", "Rock Slide"}, + {TorterraMove::BULLDOZE, "bulldoze", "Bulldoze"}, + {TorterraMove::STRENGTH, "strength", "Strength"}, + {TorterraMove::ROCK_SMASH, "rock_smash", "Rock Smash"}, + {TorterraMove::ROCK_CLIMB, "rock_climb", "Rock Climb"}, + }, + LockMode::LOCK_WHILE_RUNNING, + TorterraMove::CRUNCH) + + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(30)) + , NOTIFICATIONS({ + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + &NOTIFICATION_ERROR_RECOVERABLE, + &NOTIFICATION_ERROR_FATAL, + }) { // ----------------------------------------------------------------------- @@ -632,17 +644,17 @@ void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerCo AutoStoryStats& stats = env.current_stats(); AutoStoryOptions options{Language::English, - STARTERCHOICE, - NOTIFICATION_STATUS_UPDATE, - (HelpMode)HELP_MODE, - OutsideHelpConfig{(OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, - { - (TorterraMove)OUTSIDE_HELP_ATTACK_1, - (TorterraMove)OUTSIDE_HELP_ATTACK_2, - (TorterraMove)OUTSIDE_HELP_ATTACK_3, - (TorterraMove)OUTSIDE_HELP_ATTACK_4, - }}, - DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL}; + STARTERCHOICE, + NOTIFICATION_STATUS_UPDATE, + (HelpMode)HELP_MODE, + OutsideHelpConfig{(OutsideHelpPokemon)OUTSIDE_HELP_POKEMON, + { + (TorterraMove)OUTSIDE_HELP_ATTACK_1, + (TorterraMove)OUTSIDE_HELP_ATTACK_2, + (TorterraMove)OUTSIDE_HELP_ATTACK_3, + (TorterraMove)OUTSIDE_HELP_ATTACK_4, + }}, + DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL}; const bool fake_saves = options.checkpoint_save_mode == CheckpointSaveMode::FAKE; if (fake_saves){ @@ -689,8 +701,14 @@ void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerCo } }; if (fake_saves){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, std::move(run_selected_range), true, - CheckpointSaveMode::FAKE, CheckpointRetryScope::SELECTED_RANGE); + checkpoint_reattempt_loop(env, + context, + options.notif_status_update, + stats, + std::move(run_selected_range), + true, + CheckpointSaveMode::FAKE, + CheckpointRetryScope::SELECTED_RANGE); }else{ run_selected_range(0); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 229eba3076..62de9bebf9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -136,7 +136,8 @@ bool save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& contex int ret = wait_until(env.console, context, std::chrono::seconds(15), {overworld}); if (ret < 0){ throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, - "Failed to detect overworld after save.", env.console); + "Failed to detect overworld after save.", + env.console); } env.console.log("Checkpoint save verified."); @@ -160,21 +161,24 @@ bool save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& contex } -void checkpoint_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats, - CheckpointSaveMode save_mode){ +void checkpoint_save(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + CheckpointSaveMode save_mode){ const bool fake = save_mode == CheckpointSaveMode::FAKE; - env.console.log(fake ? "[Checkpoint save] DIAGNOSTIC FAKE mode: save is not verified." - : "[Checkpoint save] REAL mode."); + env.console.log( + fake ? "[Checkpoint save] DIAGNOSTIC FAKE mode: save is not verified." : "[Checkpoint save] REAL mode."); bool success = fake ? fake_save_game(env, context) : save_game(env, context); stats.m_checkpoint++; env.update_stats(); - send_program_status_notification(env, notif_status_update, - fake ? "Diagnostic fake checkpoint save (not verified)." - : success ? "Saved at checkpoint (verified)." - : "Saved at checkpoint (blind fallback)."); + send_program_status_notification(env, + notif_status_update, + fake ? "Diagnostic fake checkpoint save (not verified)." + : success ? "Saved at checkpoint (verified)." + : "Saved at checkpoint (blind fallback)."); } @@ -187,10 +191,14 @@ void reload_backup_save(SingleSwitchProgramEnvironment& env, ProControllerContex } -void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats, - std::function&& action, bool save, - CheckpointSaveMode save_mode, CheckpointRetryScope retry_scope){ +void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + std::function&& action, + bool save, + CheckpointSaveMode save_mode, + CheckpointRetryScope retry_scope){ static constexpr size_t MAX_ATTEMPTS = 20; for (size_t i = 0;; i++){ @@ -210,7 +218,7 @@ void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControlle throw; } env.console.log("checkpoint_reattempt_loop: attempt " + std::to_string(i) + " failed: " + e.message(), - COLOR_RED); + COLOR_RED); stats.m_reset++; env.update_stats(); @@ -222,8 +230,8 @@ void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControlle env.console.log("Resetting game from HOME before retry...", COLOR_ORANGE); bool ok = reset_game_from_home(env, env.console, context, true, 1000ms); if (!ok){ - throw OperationFailedExceptionWithScreenshot( - ErrorReportMode::SEND_ERROR_REPORT, "Failed to reset game from HOME during checkpoint retry.", + throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, + "Failed to reset game from HOME during checkpoint retry.", env.console); } }else{ @@ -244,8 +252,10 @@ void repeat(std::initializer_list> actions, size_t times){ } -void wait_for_dialogue(VideoStream& stream, ProControllerContext& context, const std::string& label, - Milliseconds timeout){ +void wait_for_dialogue(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + Milliseconds timeout){ auto log_detected = [&] { if (!label.empty()){ stream.log(label + " dialogue detected."); @@ -253,50 +263,58 @@ void wait_for_dialogue(VideoStream& stream, ProControllerContext& context, const }; ShortDialogWatcher watcher_cyan(COLOR_CYAN); - int ret = run_until(stream, context, - [timeout](ProControllerContext& context){ - context.wait_for(timeout); - }, - {{watcher_cyan}}); + int ret = run_until(stream, + context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_cyan}}); if (ret == 0){ log_detected(); return; } ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until(stream, context, - [timeout](ProControllerContext& context){ - context.wait_for(timeout); - }, - {{watcher_red}}); + ret = run_until(stream, + context, + [timeout](ProControllerContext& context){ + context.wait_for(timeout); + }, + {{watcher_red}}); if (ret == 0){ log_detected(); return; } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + (label.empty() ? "wait_for_dialogue" : label) + ": dialogue not detected within timeout.", + stream); } void mash_until_dialogue_ends(VideoStream& stream, ProControllerContext& context, Button button, Milliseconds timeout){ OverworldWatcher overworld; - int ret = run_until(stream, context, - [button, timeout](ProControllerContext& context){ - pbf_mash_button(context, button, timeout); - }, - {{overworld}}); + int ret = run_until(stream, + context, + [button, timeout](ProControllerContext& context){ + pbf_mash_button(context, button, timeout); + }, + {{overworld}}); if (ret < 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "mash_until_dialogue_ends: overworld not reached within timeout.", - stream); + "mash_until_dialogue_ends: overworld not reached within timeout.", + stream); } } -void repeat_dpad(ProControllerContext& context, DpadState& state, DpadPosition dir, Milliseconds press, - Milliseconds hold, size_t times, bool include_last_dir){ +void repeat_dpad(ProControllerContext& context, + DpadState& state, + DpadPosition dir, + Milliseconds press, + Milliseconds hold, + size_t times, + bool include_last_dir){ if (include_last_dir && state.last_dir != DPAD_NONE && state.last_dir != dir){ times += 1; } @@ -386,8 +404,10 @@ static std::string make_buy_label(const std::vector& items){ return label; } -bool buy_items(VideoStream& stream, ProControllerContext& context, const std::vector& items, - Language language){ +bool buy_items(VideoStream& stream, + ProControllerContext& context, + const std::vector& items, + Language language){ if (items.empty()){ return true; } @@ -423,13 +443,14 @@ bool buy_items(VideoStream& stream, ProControllerContext& context, const std::ve // case 0: no detectors fired — press A in a for-loop (5 presses) while watching. stream.log("Starting to loop over items"); - int state = run_until(stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 5; i++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); - } - }, - {{arrow_vendor}, {buying_window}, {dialog}}); + int state = run_until(stream, + context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 5; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + } + }, + {{arrow_vendor}, {buying_window}, {dialog}}); context.wait_for_all_requests(); pbf_wait(context, 500ms); @@ -454,8 +475,11 @@ bool buy_items(VideoStream& stream, ProControllerContext& context, const std::ve // Detector fires only when the red outline is found AND both // name and price OCR match the expected item slug. - ShopItemHoverDetector hover(stream.logger(), ITEM_SCAN_AREA, shop_item_slug(item.item), language, - COLOR_CYAN); + ShopItemHoverDetector hover(stream.logger(), + ITEM_SCAN_AREA, + shop_item_slug(item.item), + language, + COLOR_CYAN); ShopQuantityWindowWatcher qty_window; SelectionArrowFinder confirm_arrow(stream.overlay(), CONFIRM_ARROW_BOX, COLOR_GREEN); @@ -464,11 +488,12 @@ bool buy_items(VideoStream& stream, ProControllerContext& context, const std::ve for (size_t inner = 0; inner < MAX_ITEM_SEARCH && !item_bought; inner++){ context.wait_for_all_requests(); - int sub = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(1000ms); - }, - {{confirm_arrow}, {qty_window}, {hover}}); + int sub = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(1000ms); + }, + {{confirm_arrow}, {qty_window}, {hover}}); context.wait_for_all_requests(); @@ -481,13 +506,14 @@ bool buy_items(VideoStream& stream, ProControllerContext& context, const std::ve { context.wait_for_all_requests(); ShopBuyingWindowWatcher back_in_shop; - run_until(stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 10; i++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); - } - }, - {{back_in_shop}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 10; i++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 800ms); + } + }, + {{back_in_shop}}); item_bought = true; break; } @@ -532,8 +558,8 @@ bool buy_items(VideoStream& stream, ProControllerContext& context, const std::ve if (!item_bought){ stream.log(label + ": buy_items: could not locate " + shop_item_name(item.item) + - " in shop list.", - COLOR_RED); + " in shop list.", + COLOR_RED); buying_failed = true; } } @@ -549,11 +575,12 @@ bool buy_items(VideoStream& stream, ProControllerContext& context, const std::ve case 2: // case 3: unexpected ShortDialog → press B until back in buying window { ShopBuyingWindowWatcher back_in_shop; - run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 5000ms); - }, - {{back_in_shop}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 5000ms); + }, + {{back_in_shop}}); break; } @@ -585,11 +612,12 @@ bool heal_pokemon(VideoStream& stream, ProControllerContext& context, const std: // Exiting the Pokemon Center BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 15000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_pokemon(" + label + "): black screen not detected", COLOR_RED); return false; @@ -656,7 +684,9 @@ bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ ImageFloatBox button_a_box{0.540000, 0.960000, 0.070000, 0.035000}; struct FlyAButtonDetector : VisualInferenceCallback { ImageFloatBox m_box; - FlyAButtonDetector(const ImageFloatBox& box) : VisualInferenceCallback("FlyAButtonDetector"), m_box(box){ + FlyAButtonDetector(const ImageFloatBox& box) + : VisualInferenceCallback("FlyAButtonDetector") + , m_box(box){ } void make_overlays(VideoOverlaySet&) const override { } @@ -672,11 +702,12 @@ bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ return false; } BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int fly_ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{flying_black_screen}}); + int fly_ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{flying_black_screen}}); if (fly_ret < 0){ stream.log("CurrentCity: black screen not detected!", COLOR_RED); return false; @@ -717,7 +748,9 @@ bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ struct FlyAButtonDetector : VisualInferenceCallback { ImageFloatBox m_box; - FlyAButtonDetector(const ImageFloatBox& box) : VisualInferenceCallback("FlyAButtonDetector"), m_box(box){ + FlyAButtonDetector(const ImageFloatBox& box) + : VisualInferenceCallback("FlyAButtonDetector") + , m_box(box){ } void make_overlays(VideoOverlaySet&) const override { } @@ -796,7 +829,9 @@ bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ struct CityIconDetector : VisualInferenceCallback { ImageFloatBox m_box; - CityIconDetector(const ImageFloatBox& box) : VisualInferenceCallback("CityIconDetector"), m_box(box){ + CityIconDetector(const ImageFloatBox& box) + : VisualInferenceCallback("CityIconDetector") + , m_box(box){ } void make_overlays(VideoOverlaySet&) const override { } @@ -821,11 +856,12 @@ bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ stream.log("Found " + name + ", flying towards it.", COLOR_GREEN); WhiteScreenOverWatcher flying_white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); BlackScreenOverWatcher flying_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{flying_white_screen}, {flying_black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{flying_white_screen}, {flying_black_screen}}); if (ret < 0){ stream.log("Flying to " + name + ": white / black screen not detected!", COLOR_RED); return false; @@ -896,7 +932,8 @@ static double confirmed_potion_hp(VideoStream& stream, ProControllerContext& con pbf_wait(context, 150ms); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 HP bar could not be confirmed.", stream); + "Potion: Slot 1 HP bar could not be confirmed.", + stream); } void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context){ @@ -909,8 +946,9 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context for (size_t dose = 0; dose < 3; dose++){ stream.log("[Potion] Finding Bag/Medicine/Potion; dose " + std::to_string(dose + 1) + "/3."); if (!find_bag_item(stream, context, potion, 1, 5, 64, std::chrono::seconds(45))){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Potion: Could not verify Potion in the Medicine pocket.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Could not verify Potion in the Medicine pocket.", + stream); } const double before = confirmed_potion_hp(stream, context); stream.log("[Potion] Slot 1 HP bar before use=" + std::to_string(before * 100) + "%."); @@ -920,8 +958,8 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context } if (before <= 0.005){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 appears fainted; a Potion cannot revive it.", - stream); + "Potion: Slot 1 appears fainted; a Potion cannot revive it.", + stream); } pbf_press_button(context, BUTTON_A, 80ms, 500ms); pbf_press_button(context, BUTTON_A, 80ms, 500ms); @@ -931,7 +969,8 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context OverworldWatcher overworld; if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Did not return to the overworld after use.", stream); + "Potion: Did not return to the overworld after use.", + stream); } // Reopen Bag to verify the result, including when the last Potion was used. open_menu(stream, context, MenuCursorPosition::BAG, 5); @@ -940,9 +979,9 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context const double after = confirmed_potion_hp(stream, context); stream.log("[Potion] Slot 1 HP bar after use=" + std::to_string(after * 100) + "%."); if (after <= before + 0.005){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Slot 1 HP did not increase; refusing to continue movement.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Potion: Slot 1 HP did not increase; refusing to continue movement.", + stream); } if (after >= 0.98){ healed = true; @@ -953,14 +992,16 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context } if (!healed){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Full HP not confirmed after three uses.", stream); + "Potion: Full HP not confirmed after three uses.", + stream); } pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); OverworldWatcher overworld; if (wait_until(stream, context, std::chrono::seconds(10), {{overworld}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Potion: Final overworld return not confirmed.", stream); + "Potion: Final overworld return not confirmed.", + stream); } stream.log("[Potion] Slot 1 HP bar full and overworld confirmed."); } @@ -1011,7 +1052,14 @@ void open_menu(VideoStream& stream, ProControllerContext& context, MenuCursorPos MenuCursorDetector detector; { static const char* ICON_NAMES[MENU_CURSOR_NUM_ICONS] = { - "POKEDEX", "POKEMON", "BAG", "CARD", "OPTIONS", "MAP", "CAPSULES", "MYSTERYGIFT", + "POKEDEX", + "POKEMON", + "BAG", + "CARD", + "OPTIONS", + "MAP", + "CAPSULES", + "MYSTERYGIFT", }; // Previous optional template directory: PokemonBDSP/AutoStory. std::string ref_dir = RESOURCE_PATH() + "PokemonBDSP/Autostory"; @@ -1075,25 +1123,27 @@ void open_menu(VideoStream& stream, ProControllerContext& context, MenuCursorPos } if (cur != MenuCursorPosition::UNKNOWN){ stream.log("[Open menu] Menu and cursor detected on attempt " + std::to_string(attempt) + ".", - COLOR_GREEN); + COLOR_GREEN); break; } stream.log("[Open menu] Menu cursor not detected on attempt " + std::to_string(attempt) + ".", - COLOR_ORANGE); + COLOR_ORANGE); } catch (OperationFailedException& error){ stream.log("[Open menu] Attempt " + std::to_string(attempt) + " failed: " + error.message(), COLOR_ORANGE); } } if (cur == MenuCursorPosition::UNKNOWN){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Menu or cursor detection failed after 3 opening attempts.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Menu or cursor detection failed after 3 opening attempts.", + stream); } IconGrid cur_g = icon_to_grid(cur, num_icons); IconGrid tgt_g = icon_to_grid(target, num_icons); if (tgt_g.row == -1){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Unknown target menu icon.", - stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Unknown target menu icon.", + stream); } if (cur_g.row != tgt_g.row){ @@ -1298,9 +1348,18 @@ size_t bag_clear_tab_count(const ImageViewRGB32& frame){ } static const std::vector> references = [] { std::vector> refs; - for (const char* name : - {"Normal", "NormalMedium", "NormalSmall", "Alternate", "AlternateMedium", "AlternateSmall", "Selected", - "SelectedMedium", "SelectedSmall", "SelectedBlue", "SelectedBlueMedium", "SelectedBlueSmall"}){ + for (const char* name : {"Normal", + "NormalMedium", + "NormalSmall", + "Alternate", + "AlternateMedium", + "AlternateSmall", + "Selected", + "SelectedMedium", + "SelectedSmall", + "SelectedBlue", + "SelectedBlueMedium", + "SelectedBlueSmall"}){ refs.emplace_back( std::make_shared(RESOURCE_PATH() + "PokemonBDSP/Autostory/BagTabs/" + name + ".png")); } @@ -1372,8 +1431,8 @@ bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, in pbf_mash_button(context, BUTTON_B, 2000ms); context.wait_for_all_requests(); stream.log(cleared ? "[Bag NEW flags] All eight tabs clear; bag closed." - : "[Bag NEW flags] Full pocket pass did not clear all eight tabs; bag closed.", - cleared ? COLOR_GREEN : COLOR_ORANGE); + : "[Bag NEW flags] Full pocket pass did not clear all eight tabs; bag closed.", + cleared ? COLOR_GREEN : COLOR_ORANGE); return cleared; } @@ -1421,8 +1480,8 @@ bool bag_list_is_empty(const ImageViewRGB32& frame){ } -std::array match_bag_item_rows(const ImageViewRGB32& frame, const ImageViewRGB32& reference, - const BagItemTemplate& item){ +std::array +match_bag_item_rows(const ImageViewRGB32& frame, const ImageViewRGB32& reference, const BagItemTemplate& item){ std::array result; if (!frame || !reference || item.reference_row < 1 || item.reference_row > 7){ return result; @@ -1447,7 +1506,7 @@ BagMovePlan plan_bag_item_move(int cursor_row, int item_row){ return {BagMoveAction::CONFIRM, 0}; } return {item_row > cursor_row ? BagMoveAction::DOWN : BagMoveAction::UP, - static_cast(std::abs(item_row - cursor_row))}; + static_cast(std::abs(item_row - cursor_row))}; } if (cursor_row == 7){ return {BagMoveAction::REWIND, 1}; @@ -1503,7 +1562,7 @@ BagPocketMovePlan plan_bag_pocket_move(int current, int target){ return {BagPocketAction::CONFIRM, 0}; } return right <= left ? BagPocketMovePlan{BagPocketAction::RIGHT, static_cast(right)} - : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; + : BagPocketMovePlan{BagPocketAction::LEFT, static_cast(left)}; } static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& context, int target){ @@ -1518,15 +1577,15 @@ static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& co return false; } stream.log("[Bag search] Pocket " + std::to_string(current) + " to " + std::to_string(target) + ": " + - (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + std::to_string(plan.presses) + - " presses, attempt " + std::to_string(attempt + 1) + "/3."); + (plan.action == BagPocketAction::RIGHT ? "right " : "left ") + std::to_string(plan.presses) + + " presses, attempt " + std::to_string(attempt + 1) + "/3."); for (size_t press = 0; press < plan.presses; press++){ pbf_press_dpad(context, plan.action == BagPocketAction::RIGHT ? DPAD_RIGHT : DPAD_LEFT, 80ms, 150ms); } pbf_wait(context, 350ms); current = bag_confirm_pocket(stream, context); - stream.log("[Bag search] Final pocket=" + std::to_string(current) + ", expected=" + std::to_string(target) + - "."); + stream.log( + "[Bag search] Final pocket=" + std::to_string(current) + ", expected=" + std::to_string(target) + "."); if (current == target){ return true; } @@ -1536,8 +1595,13 @@ static bool bag_navigate_to_pocket(VideoStream& stream, ProControllerContext& co } -bool find_bag_item(VideoStream& stream, ProControllerContext& context, const BagItemTemplate& item, int target_pocket, - int num_icons, size_t max_scrolls_per_pocket, std::chrono::milliseconds search_timeout){ +bool find_bag_item(VideoStream& stream, + ProControllerContext& context, + const BagItemTemplate& item, + int target_pocket, + int num_icons, + size_t max_scrolls_per_pocket, + std::chrono::milliseconds search_timeout){ if (target_pocket < 1 || target_pocket > 8 || item.reference_row < 1 || item.reference_row > 7){ stream.log("[Bag search] Invalid template reference row.", COLOR_ORANGE); return false; @@ -1602,7 +1666,7 @@ bool find_bag_item(VideoStream& stream, ProControllerContext& context, const Bag size_t unchanged_pages = 0; ImageRGB32 previous_page; stream.log("[Bag search] Looking for " + item.label + ": pocket " + std::to_string(selected_pocket) + - ", pass " + std::to_string(pass + 1) + "/3."); + ", pass " + std::to_string(pass + 1) + "/3."); while (std::chrono::steady_clock::now() < deadline){ const VideoSnapshot frame = stream.video().snapshot(); if (!frame || !bag.detect(frame)){ @@ -1644,8 +1708,7 @@ bool find_bag_item(VideoStream& stream, ProControllerContext& context, const Bag } } stream.log("[Bag search] " + item.label + (target == 0 ? " is not detected" : " is detected") + - " on screen; item row=" + std::to_string(target) + ", cursor row=" + std::to_string(cursor) + - "."); + " on screen; item row=" + std::to_string(target) + ", cursor row=" + std::to_string(cursor) + "."); if (ambiguous){ stream.log("[Bag search] Multiple rows match; selection is uncertain.", COLOR_ORANGE); return false; @@ -1673,7 +1736,7 @@ bool find_bag_item(VideoStream& stream, ProControllerContext& context, const Bag if (target == cursor){ if (++matching_frames >= 3){ stream.log("[Bag search] Item and cursor confirmed in row " + std::to_string(cursor) + ".", - COLOR_GREEN); + COLOR_GREEN); return true; } pbf_wait(context, 150ms); @@ -1683,7 +1746,7 @@ bool find_bag_item(VideoStream& stream, ProControllerContext& context, const Bag matching_frames = 0; const size_t count = plan_bag_item_move(cursor, target).presses; stream.log("[Bag search] Moving " + std::string(target > cursor ? "down " : "up ") + - std::to_string(count) + " time(s), then checking again."); + std::to_string(count) + " time(s), then checking again."); if (!move(target > cursor ? DPAD_DOWN : DPAD_UP, count)){ return false; } @@ -1740,7 +1803,8 @@ bool find_bag_item(VideoStream& stream, ProControllerContext& context, const Bag bool use_escape_rope(VideoStream& stream, ProControllerContext& context, int num_icons){ const BagItemTemplate rope{// "PokemonBDSP/AutoStory/Bag_EscapeRope.png", "escape-rope" - "PokemonBDSP/Autostory/Bag_EscapeRope.png", "escape-rope"}; + "PokemonBDSP/Autostory/Bag_EscapeRope.png", + "escape-rope"}; if (!find_bag_item(stream, context, rope, 5, num_icons)){ return false; } @@ -1758,27 +1822,32 @@ bool use_escape_rope(VideoStream& stream, ProControllerContext& context, int num BlackScreenWatcher transition; // Escape Rope fades white in the supplied clips; support black transitions too. WhiteScreenWatcher white_transition; - const int result = run_until(stream, context, - [](ProControllerContext& ctx){ - for (size_t press = 0; press < 10; press++){ - pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); - } - ctx.wait_for_all_requests(); - pbf_wait(ctx, 10s); - ctx.wait_for_all_requests(); - }, - {transition, white_transition}); + const int result = run_until(stream, + context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 10; press++){ + pbf_press_button(ctx, BUTTON_A, 80ms, 500ms); + } + ctx.wait_for_all_requests(); + pbf_wait(ctx, 10s); + ctx.wait_for_all_requests(); + }, + {transition, white_transition}); if (result < 0){ stream.log("[Escape Rope] No exit transition detected.", COLOR_ORANGE); return false; } BlackScreenWatcher finished(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); - WhiteScreenWatcher white_finished(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, - 500ms); + WhiteScreenWatcher white_finished(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 500, + 10, + WhiteScreenWatcher::FinderType::GONE, + 500ms); stream.log(std::string("[Escape Rope] ") + (result == 1 ? "White" : "Black") + " exit transition detected."); // if (wait_until(stream, context, 15s, {{finished}}) != 0){ return false; } const int finish_result = result == 1 ? wait_until(stream, context, 15s, {{white_finished}}) - : wait_until(stream, context, 15s, {{finished}}); + : wait_until(stream, context, 15s, {{finished}}); if (finish_result != 0){ stream.log("[Escape Rope] Exit transition did not finish.", COLOR_ORANGE); return false; @@ -1835,14 +1904,14 @@ bool activate_repel(VideoStream& stream, ProControllerContext& context, int num_ wait_until(stream, context, 400ms, {{watcher}, {small_watcher}, {honey_watcher}, {honey_small_watcher}}); if (ret >= 0){ stream.log("[AutoStory] activate_repel: repel tab found after " + std::to_string(press) + " press(es).", - COLOR_GREEN); + COLOR_GREEN); found = true; break; } if (press < 8){ - stream.log("[AutoStory] activate_repel: tab not found, pressing right (" + std::to_string(press + 1) + - "/8)."); + stream.log( + "[AutoStory] activate_repel: tab not found, pressing right (" + std::to_string(press + 1) + "/8)."); pbf_press_dpad(context, DPAD_RIGHT, 80ms, 300ms); } } @@ -1932,15 +2001,16 @@ void use_HM(VideoStream& stream, ProControllerContext& context, const std::strin const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 30000ms); - }, - {{arrow}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 30000ms); + }, + {{arrow}}); if (ret != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Using " + label + ": HM confirmation arrow not found; stopping before further movement.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Using " + label + ": HM confirmation arrow not found; stopping before further movement.", + stream); } stream.log("Using " + label + ": selection arrow found, confirming.", COLOR_GREEN); @@ -1977,10 +2047,18 @@ bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLeve } size_t front_pixels = 0, back_pixels = 0; - filter_rgb32_range(front_pixels, extract_box_reference(screen, FRONT_BOX), SPARK_MIN, SPARK_MAX, Color(0), - false); - filter_rgb32_range(back_pixels, extract_box_reference(screen, BACK_BOX), SPARK_MIN, SPARK_MAX, Color(0), - false); + filter_rgb32_range(front_pixels, + extract_box_reference(screen, FRONT_BOX), + SPARK_MIN, + SPARK_MAX, + Color(0), + false); + filter_rgb32_range(back_pixels, + extract_box_reference(screen, BACK_BOX), + SPARK_MIN, + SPARK_MAX, + Color(0), + false); if (front_pixels > max_front){ max_front = front_pixels; @@ -1991,18 +2069,18 @@ bool get_on_bicycle(VideoStream& stream, ProControllerContext& context, GearLeve if (front_pixels >= SPARK_THRESHOLD || back_pixels >= SPARK_THRESHOLD){ stream.log("[AutoStory] get_on_bicycle: sparks detected " - "(front=" + - std::to_string(front_pixels) + " back=" + std::to_string(back_pixels) + - " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", - COLOR_GREEN); + "(front=" + + std::to_string(front_pixels) + " back=" + std::to_string(back_pixels) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_GREEN); return true; } } stream.log("[AutoStory] get_on_bicycle: no sparks detected " - "(peak front=" + - std::to_string(max_front) + " back=" + std::to_string(max_back) + - " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", - COLOR_ORANGE); + "(peak front=" + + std::to_string(max_front) + " back=" + std::to_string(max_back) + + " threshold=" + std::to_string(SPARK_THRESHOLD) + ").", + COLOR_ORANGE); return false; }; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 9d5021c6cb..7d511bea85 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -41,7 +41,10 @@ namespace PokemonBDSP { // --------------------------------------------------------------------------- struct AutoStoryStats : public StatsTracker { - AutoStoryStats() : m_checkpoint(m_stats["Checkpoint"]), m_segment(m_stats["Segment"]), m_reset(m_stats["Reset"]){ + AutoStoryStats() + : m_checkpoint(m_stats["Checkpoint"]) + , m_segment(m_stats["Segment"]) + , m_reset(m_stats["Reset"]){ m_display_order.emplace_back("Checkpoint"); m_display_order.emplace_back("Segment"); m_display_order.emplace_back("Reset"); @@ -138,8 +141,10 @@ class AutoStory_Segment { virtual std::string name() const = 0; virtual std::string start_text() const = 0; virtual std::string end_text() const = 0; - virtual void run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const = 0; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const = 0; }; class AutoStory_Checkpoint { @@ -148,8 +153,10 @@ class AutoStory_Checkpoint { virtual std::string name() const = 0; virtual std::string start_text() const = 0; virtual std::string end_text() const = 0; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const = 0; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const = 0; }; @@ -166,9 +173,11 @@ void checkpoint_save(SingleSwitchProgramEnvironment& env, ProControllerContext& // Save the game from the overworld, increment the checkpoint counter, // update stats display, and send a status notification. -void checkpoint_save(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats, - CheckpointSaveMode save_mode = CheckpointSaveMode::REAL); +void checkpoint_save(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL); // Reload the game from the most recent backup save (auto-save). // Called on retry when a checkpoint has no hard save (save=false). @@ -181,18 +190,30 @@ void reload_backup_save(SingleSwitchProgramEnvironment& env, ProControllerContex // SELECTED_RANGE skips the entry save and owns HOME resets for the range. // DEFER_TO_SELECTED_RANGE lets an inner failure reach that outer retry loop. // Throws a fatal error after too many consecutive failures. -void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats, - std::function&& action, bool save = true, - CheckpointSaveMode save_mode = CheckpointSaveMode::REAL, - CheckpointRetryScope retry_scope = CheckpointRetryScope::CHECKPOINT); +void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats, + std::function&& action, + bool save = true, + CheckpointSaveMode save_mode = CheckpointSaveMode::REAL, + CheckpointRetryScope retry_scope = CheckpointRetryScope::CHECKPOINT); // Forward the explicit checkpoint save mode selected by AutoStory's options. -inline void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats, - std::function&& action, bool save = true){ - checkpoint_reattempt_loop(env, context, options.notif_status_update, stats, std::move(action), save, - options.checkpoint_save_mode, options.checkpoint_retry_scope); +inline void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats, + std::function&& action, + bool save = true){ + checkpoint_reattempt_loop(env, + context, + options.notif_status_update, + stats, + std::move(action), + save, + options.checkpoint_save_mode, + options.checkpoint_retry_scope); } // Execute every action in `actions` in order, repeated `times` times. @@ -206,20 +227,29 @@ struct DpadState { // Press `dir` on the D-pad `times` times (plus one extra if the direction // changed from the previous call, since the first press turns the character). -void repeat_dpad(ProControllerContext& context, DpadState& state, DpadPosition dir, Milliseconds press, - Milliseconds hold, size_t times, bool include_last_dir = true); +void repeat_dpad(ProControllerContext& context, + DpadState& state, + DpadPosition dir, + Milliseconds press, + Milliseconds hold, + size_t times, + bool include_last_dir = true); // Wait (without pressing any buttons) until a short dialogue box appears. // Logs `label + " dialogue detected."` when found (omit label to skip logging). // Throws OperationFailedException if no dialogue appears within `timeout`. -void wait_for_dialogue(VideoStream& stream, ProControllerContext& context, const std::string& label = "", - Milliseconds timeout = std::chrono::seconds(60)); +void wait_for_dialogue(VideoStream& stream, + ProControllerContext& context, + const std::string& label = "", + Milliseconds timeout = std::chrono::seconds(60)); // Mash `button` (default B) until the game returns to the free-roaming overworld // (no dialogue, menu, or battle UI visible). // Throws OperationFailedException if the overworld is not reached within `timeout`. -void mash_until_dialogue_ends(VideoStream& stream, ProControllerContext& context, Button button = BUTTON_B, - Milliseconds timeout = std::chrono::seconds(60)); +void mash_until_dialogue_ends(VideoStream& stream, + ProControllerContext& context, + Button button = BUTTON_B, + Milliseconds timeout = std::chrono::seconds(60)); // Scripted trainer battle: choose a 1-based move position each turn, then // confirm the default target with A and clear narration with B. No target steering. @@ -231,10 +261,16 @@ void mash_until_dialogue_ends(VideoStream& stream, ProControllerContext& context // fight_trainer_using_moves(stream, context, 2, "Grunt", false); // const std::vector moves{2, 1, 3}; // fight_trainer_using_moves(stream, context, moves, "Gym leader", true); -void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, size_t move, - const std::string& label, bool post_battle_dialogue_cleanup); -void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, const std::vector& moves, - const std::string& label, bool post_battle_dialogue_cleanup); +void fight_trainer_using_moves(VideoStream& stream, + ProControllerContext& context, + size_t move, + const std::string& label, + bool post_battle_dialogue_cleanup); +void fight_trainer_using_moves(VideoStream& stream, + ProControllerContext& context, + const std::vector& moves, + const std::string& label, + bool post_battle_dialogue_cleanup); // A full Bag screenshot with the desired item highlighted. Paths are relative // to RESOURCE_PATH(); quantities and selection borders are excluded. @@ -270,8 +306,8 @@ size_t bag_clear_tab_count(const ImageViewRGB32& frame); bool clear_new_item_flags(VideoStream& stream, ProControllerContext& context, int num_icons = 8); bool bag_list_is_empty(const ImageViewRGB32& frame); int bag_cursor_position(const ImageViewRGB32& frame); -std::array match_bag_item_rows(const ImageViewRGB32& frame, const ImageViewRGB32& reference, - const BagItemTemplate& item); +std::array +match_bag_item_rows(const ImageViewRGB32& frame, const ImageViewRGB32& reference, const BagItemTemplate& item); enum class BagMoveAction { UNKNOWN, @@ -310,9 +346,13 @@ BagPocketMovePlan plan_bag_pocket_move(int current, int target); // item in the green header. Makes up to three passes per pocket. // Limits report an incomplete search, not absence. // Returns false on missing frames, timeout, or no match; does not use an item. -bool find_bag_item(VideoStream& stream, ProControllerContext& context, const BagItemTemplate& item, int target_pocket, - int num_icons = 8, size_t max_scrolls_per_pocket = 512, - std::chrono::milliseconds search_timeout = std::chrono::minutes(10)); +bool find_bag_item(VideoStream& stream, + ProControllerContext& context, + const BagItemTemplate& item, + int target_pocket, + int num_icons = 8, + size_t max_scrolls_per_pocket = 512, + std::chrono::milliseconds search_timeout = std::chrono::minutes(10)); // Find Escape Rope, choose Use, and verify a transition back to the overworld. bool use_escape_rope(VideoStream& stream, ProControllerContext& context, int num_icons = 8); @@ -404,8 +444,10 @@ struct ItemToBuy { // and purchase each item in `items` in order. The function presses DPAD_DOWN // until the matching item name is found via OCR, so items may appear in any // list order. Returns true on success. -bool buy_items(VideoStream& stream, ProControllerContext& context, const std::vector& items, - Language language = Language::English); +bool buy_items(VideoStream& stream, + ProControllerContext& context, + const std::vector& items, + Language language = Language::English); // Mount the bicycle (press +) and confirm it is in the requested gear. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 4f11f6ab34..6b68b60593 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -46,26 +46,33 @@ namespace PokemonBDSP { // Watch before movement starts, release on the fade, and hold the // following recorded wait until the destination is visible again. -static void recorded_move_until_transition(VideoStream& stream, ProControllerContext& context, double x, double y, - Milliseconds duration, const std::string& label){ +static void recorded_move_until_transition(VideoStream& stream, + ProControllerContext& context, + double x, + double y, + Milliseconds duration, + const std::string& label){ context.wait_for_all_requests(); stream.log("[Tutorial transition] " + label + ": approaching fade."); BlackScreenWatcher black; - int ret = run_until(stream, context, - [x, y, duration](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); - // Keep observing a delayed fade after the recorded hold. - pbf_wait(ctx, 5000ms); - }, - {{black}}); + int ret = run_until(stream, + context, + [x, y, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + // Keep observing a delayed fade after the recorded hold. + pbf_wait(ctx, 5000ms); + }, + {{black}}); if (ret != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Black transition not detected.", stream); + label + ": Black transition not detected.", + stream); } BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 15s, {{visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not finish.", stream); + label + ": Transition did not finish.", + stream); } stream.log("[Tutorial transition] " + label + ": destination visible."); } @@ -77,21 +84,23 @@ static void select_turtwig(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); ShortDialogWatcher watcher(COLOR_CYAN); - int ret = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(std::chrono::milliseconds(5000)); - }, - {watcher}); + int ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher}); if (ret != 0){ ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(std::chrono::milliseconds(5000)); - }, - {watcher_red}); + ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher_red}); } stream.log(ret == 0 ? "[DEBUG] Turtwig Selection: Confirmation dialogue detected" - : "[WARNING] Turtwig Selection: Dialogue detection failed, proceeding anyway"); + : "[WARNING] Turtwig Selection: Dialogue detection failed, proceeding anyway"); pbf_wait(context, 400ms); pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); @@ -108,21 +117,23 @@ static void select_chimchar(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); ShortDialogWatcher watcher(COLOR_CYAN); - int ret = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(std::chrono::milliseconds(5000)); - }, - {watcher}); + int ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher}); if (ret != 0){ ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(std::chrono::milliseconds(5000)); - }, - {watcher_red}); + ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher_red}); } stream.log(ret == 0 ? "[DEBUG] Chimchar Selection: Confirmation dialogue detected" - : "[WARNING] Chimchar Selection: Dialogue detection failed, proceeding anyway"); + : "[WARNING] Chimchar Selection: Dialogue detection failed, proceeding anyway"); pbf_wait(context, 400ms); pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); @@ -140,21 +151,23 @@ static void select_piplup(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); ShortDialogWatcher watcher(COLOR_CYAN); - int ret = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(std::chrono::milliseconds(5000)); - }, - {watcher}); + int ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher}); if (ret != 0){ ShortDialogWatcher watcher_red(COLOR_RED); - ret = run_until(stream, context, - [](ProControllerContext& ctx){ - ctx.wait_for(std::chrono::milliseconds(5000)); - }, - {watcher_red}); + ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + ctx.wait_for(std::chrono::milliseconds(5000)); + }, + {watcher_red}); } stream.log(ret == 0 ? "[DEBUG] Piplup Selection: Confirmation dialogue detected" - : "[WARNING] Piplup Selection: Dialogue detection failed, proceeding anyway"); + : "[WARNING] Piplup Selection: Dialogue detection failed, proceeding anyway"); pbf_wait(context, 400ms); pbf_press_dpad(context, DPAD_UP, 80ms, 400ms); @@ -172,8 +185,8 @@ static void select_piplup(VideoStream& stream, ProControllerContext& context){ // Navigates from player's room to Route 201 through Mom/Barry dialogues. // --------------------------------------------------------------------------- -static void tutorial_part_1_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +static void +tutorial_part_1_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ DateTime t{2026, 1, 1, 0, 0, 0}; set_time_fast(env, context, t); pbf_move_left_joystick(context, {0.000000, 1.000000}, 527ms, 0ms); @@ -224,22 +237,29 @@ static void tutorial_part_1_navigation(SingleSwitchProgramEnvironment& env, Vide context.wait_for_all_requests(); stream.log("[Barry house] Entering; waiting for entry fade."); BlackScreenWatcher entry_black; - int entry_ret = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, 1}, 5000ms, 0ms); - }, - {{entry_black}}); + int entry_ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 5000ms, 0ms); + }, + {{entry_black}}); if (entry_ret != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry house: Entry fade not detected.", stream); + "Barry house: Entry fade not detected.", + stream); } // Release the joystick during loading; resume indoor movement only // after the screen has stayed non-black for half a second. - BlackScreenWatcher entry_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher entry_visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 15s, {{entry_visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry house: Entry fade did not finish.", stream); + "Barry house: Entry fade did not finish.", + stream); } pbf_wait(context, 500ms); context.wait_for_all_requests(); @@ -328,18 +348,20 @@ static void starter_nav(VideoStream& stream, ProControllerContext& context){ // Mash B until we see the briefcase. ImageMatchWatcher detector(briefcase, {0.5, 0.1, 0.5, 0.7}, 100, true); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 120000ms); - }, - {{detector}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 120000ms); + }, + {{detector}}); if (ret == 0){ stream.log("Detected briefcase!"); }else{ stream.log("Timed out waiting for briefcase."); throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, - "Failed to detect briefcase in Segment 00.", stream); + "Failed to detect briefcase in Segment 00.", + stream); } // Wait for briefcase to fully open. @@ -358,8 +380,8 @@ static void post_starly_clenaup(VideoStream& stream, ProControllerContext& conte // Navigates from Exit of lake Verity to receipt of the Pokedex // --------------------------------------------------------------------------- -static void tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +static void +tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -416,8 +438,8 @@ static void tutorial_part_2_navigation(SingleSwitchProgramEnvironment& env, Vide // and send the starter to pokemon HOME. // --------------------------------------------------------------------------- -static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +static void +tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ // Arm exit detection before movement and finish the fade before // starting the dialogue watcher, which cancels queued commands. recorded_move_until_transition(stream, context, 0.0, -1.0, 2010ms, "Rowan lab exit"); @@ -512,8 +534,10 @@ static void tutorial_part_3_navigation(SingleSwitchProgramEnvironment& env, Vide // Align to a route reference before continuing recorded navigation. // Matcher offsets and thresholds are fractions of screen width/height. -static void align_to_tutorial_landmarks(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label){ +static void align_to_tutorial_landmarks(VideoStream& stream, + ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), + const std::string& label){ context.wait_for_all_requests(); pbf_wait(context, 700ms); context.wait_for_all_requests(); @@ -523,7 +547,8 @@ static void align_to_tutorial_landmarks(VideoStream& stream, ProControllerContex const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); + label + ": No video frame.", + stream); } const auto result = matcher(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -533,13 +558,12 @@ static void align_to_tutorial_landmarks(VideoStream& stream, ProControllerContex overlays.add(COLOR_GREEN, landmark.found); } stream.log("[" + label + "] " + landmark.name + " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + - " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + - ")"); + " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + + " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } - stream.log("[" + label + "] located=" + std::to_string(result.located) + " mode=" + - (result.coarse ? "vertical acquisition" : "fine alignment") + " reference screen-relative offset=(" + - std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); + stream.log("[" + label + "] located=" + std::to_string(result.located) + + " mode=" + (result.coarse ? "vertical acquisition" : "fine alignment") + + " reference screen-relative offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); if (result.coarse && result.located){ if (coarse_confirmations && std::abs(result.dx - previous_dx) <= 0.00625 && std::abs(result.dy - previous_dy) <= 1.0 / 90){ @@ -571,17 +595,23 @@ static void align_to_tutorial_landmarks(VideoStream& stream, ProControllerContex const double error = horizontal ? result.dx : result.dy * 9.0 / 16; const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; stream.log("[" + label + "] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + " axis " + - (horizontal ? (result.dx > 0 ? "right" : "left") : (result.dy < 0 ? "up" : "down")) + " for " + - std::to_string(duration.count()) + "ms."); + (horizontal ? (result.dx > 0 ? "right" : "left") : (result.dy < 0 ? "up" : "down")) + " for " + + std::to_string(duration.count()) + "ms."); // Reuse confirmed-Run encounter handling if correction enters grass. - walk_through_grass(stream, context, horizontal ? (result.dx > 0 ? 1 : -1) : 0, - horizontal ? 0 : (result.dy < 0 ? 1 : -1), 1, label, BattleType::STANDARD, duration); + walk_through_grass(stream, + context, + horizontal ? (result.dx > 0 ? 1 : -1) : 0, + horizontal ? 0 : (result.dy < 0 ? 1 : -1), + 1, + label, + BattleType::STANDARD, + duration); } context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Reference position not confirmed; route movement stopped.", - stream); + label + ": Reference position not confirmed; route movement stopped.", + stream); } static void align_to_sandgem_junction(VideoStream& stream, ProControllerContext& context){ @@ -589,14 +619,22 @@ static void align_to_sandgem_junction(VideoStream& stream, ProControllerContext& } // Resumes from the save made after catching the second Pokemon. -static void tutorial_part_3_after_catch_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +static void tutorial_part_3_after_catch_navigation(SingleSwitchProgramEnvironment& env, + VideoStream& stream, + ProControllerContext& context){ pbf_wait(context, 1500ms); //-------------------------------------------------------- // To get potions // ------------------------------------------------------- - walk_through_grass(stream, context, 1, 0, 10, "Route 202 grass exit", BattleType::STANDARD, 200ms, - match_route202_grass_exit); + walk_through_grass(stream, + context, + 1, + 0, + 10, + "Route 202 grass exit", + BattleType::STANDARD, + 200ms, + match_route202_grass_exit); // Recording: the exit reference was reached after two 200ms steps. // Eight further steps (1600ms) reached the eastern tree boundary. // Continue outside grass to preserve the next down movement's start. @@ -633,8 +671,15 @@ static void tutorial_part_3_after_catch_navigation(SingleSwitchProgramEnvironmen // Stop on the verified Route 201 reference instead of overshooting // with ten fixed steps. The limit also allows small final corrections. - walk_through_grass(stream, context, 1, 0, 20, "Route 201 Potion exit", BattleType::STANDARD, 200ms, - match_route201_potion_exit); + walk_through_grass(stream, + context, + 1, + 0, + 20, + "Route 201 Potion exit", + BattleType::STANDARD, + 200ms, + match_route201_potion_exit); context.wait_for_all_requests(); pbf_wait(context, 1500ms); @@ -726,7 +771,8 @@ static void approach_poketch_landmarks(VideoStream& stream, ProControllerContext auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Poketch approach: No video frame.", stream); + "Poketch approach: No video frame.", + stream); } const auto result = match_poketch_landmarks(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -736,11 +782,11 @@ static void approach_poketch_landmarks(VideoStream& stream, ProControllerContext overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Poketch approach] " + landmark.name + " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin)); + " margin=" + std::to_string(landmark.margin)); } if (result.located){ stream.log("[Poketch approach] Landmarks acquired after " + std::to_string(walked.count()) + - "ms; ending downward approach."); + "ms; ending downward approach."); return; } const Milliseconds step = std::min(200ms, 1900ms - walked); @@ -766,7 +812,8 @@ static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerCont auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Poketch interaction: No video frame.", stream); + "Poketch interaction: No video frame.", + stream); } const OverworldMatchResult result = match_poketch_landmarks(*frame.frame); stream.log(result.coarse ? "[Poketch landmarks] Coarse acquisition." : "[Poketch landmarks] Fine alignment."); @@ -777,9 +824,8 @@ static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerCont overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Poketch landmarks] " + landmark.name + " score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + - " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + - ")"); + " margin=" + std::to_string(landmark.margin) + " accepted=" + std::to_string(landmark.accepted) + + " screen-relative offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } if (result.located){ acquisition_misses = 0; @@ -802,7 +848,7 @@ static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerCont int probe = vertical_probes[vertical_probe++]; acquisition_misses = 0; stream.log("[Poketch landmarks] Bounded vertical visibility probe " + std::to_string(vertical_probe) + - "/10: " + (probe < 0 ? "down " : "up ") + std::to_string(std::abs(probe)) + "ms."); + "/10: " + (probe < 0 ? "down " : "up ") + std::to_string(std::abs(probe)) + "ms."); pbf_move_left_joystick(context, {0, probe < 0 ? -1.0 : 1.0}, Milliseconds(std::abs(probe)), 0ms); pbf_wait(context, 700ms); }else{ @@ -828,7 +874,7 @@ static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerCont confirmed = 0; const double dx = result.dx, dy = result.dy; stream.log("[Poketch landmarks] Reference screen-relative offset=(" + std::to_string(result.dx) + "," + - std::to_string(result.dy) + ")."); + std::to_string(result.dy) + ")."); const bool horizontal = std::abs(dx) > 7.0 / 960 && (std::abs(dy) <= 7.0 / 540 || std::abs(dx) >= std::abs(dy) * 9.0 / 16); double error = horizontal ? dx : dy * 9.0 / 16; @@ -840,17 +886,18 @@ static void align_and_talk_to_poketch_man(VideoStream& stream, ProControllerCont continue; } stream.log("[Poketch landmarks] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + - " axis for " + std::to_string(duration.count()) + "ms."); + " axis for " + std::to_string(duration.count()) + "ms."); pbf_move_left_joystick(context, - {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, - duration, 0ms); + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, + duration, + 0ms); pbf_wait(context, 700ms); } context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Poketch interaction: Building landmarks did not confirm the reference position; A not pressed.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Poketch interaction: Building landmarks did not confirm the reference position; A not pressed.", + stream); } void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& context){ @@ -862,7 +909,8 @@ void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Jubilife Center alignment: No video frame.", stream); + "Jubilife Center alignment: No video frame.", + stream); } const auto result = match_jubilife_center_entrance(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -872,8 +920,8 @@ void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Jubilife Center alignment] score=" + std::to_string(landmark.score) + - " margin=" + std::to_string(landmark.margin) + " screen-relative offset=(" + - std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + " margin=" + std::to_string(landmark.margin) + " screen-relative offset=(" + + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } if (!result.located){ confirmed = 0; @@ -892,21 +940,21 @@ void align_and_enter_jubilife_center(VideoStream& stream, ProControllerContext& bool horizontal = std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16; double error = horizontal ? result.dx : result.dy * 9.0 / 16; const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; - pbf_move_left_joystick( - context, + pbf_move_left_joystick(context, {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0)}, - duration, 0ms); + duration, + 0ms); pbf_wait(context, 700ms); } context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Jubilife Center alignment: Door reference not confirmed; entry not attempted.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Jubilife Center alignment: Door reference not confirmed; entry not attempted.", + stream); } -static void tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +static void +tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ pbf_move_left_joystick(context, {0.000000, 1.000000}, 2580ms, 0ms); pbf_wait(context, 504ms); pbf_move_left_joystick(context, {-1.000000, -0.000000}, 830ms, 0ms); @@ -1080,8 +1128,10 @@ std::string AutoStory_Segment_00::end_text() const { return "End: Starter Pokemon chosen, received, and Pokedex acquired, standing in front of the PC."; } -void AutoStory_Segment_00::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_00::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -1107,28 +1157,40 @@ std::string AutoStory_Checkpoint_0000::start_text() const { std::string AutoStory_Checkpoint_0000::end_text() const { return "Required game settings verified and saved; ready for 00.01."; } -void AutoStory_Checkpoint_0000::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0000::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { // Before any route movement or checkpoint save: fail visibly if // options cannot be read, rather than navigate with unknown settings. verify_tutorial_settings(env.console, context); context.wait_for_all_requests(); } -static void checkpoint_0001(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +static void checkpoint_0001(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Segment 00-00: Intro and starter selection tutorial_part_1_navigation(env, env.console, context); }, true); } -static void checkpoint_0002(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +static void checkpoint_0002(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Segment 00-01: Starter acquisition starter_nav(env.console, context); @@ -1149,20 +1211,30 @@ static void checkpoint_0002(SingleSwitchProgramEnvironment& env, ProControllerCo }, true); } -static void checkpoint_0003(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +static void checkpoint_0003(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Segment 00-002: Pokedex acquisition tutorial_part_2_navigation(env, env.console, context); }, true); } -static void checkpoint_0004(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +static void checkpoint_0004(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Segment 00-003: Poketech acquisition tutorial_part_3_navigation(env, env.console, context); @@ -1172,27 +1244,40 @@ static void checkpoint_0004(SingleSwitchProgramEnvironment& env, ProControllerCo // Save and advance progress at the catch boundary. Retries of this // section must start from the caught-Pokemon save, not the old route. checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ tutorial_part_3_after_catch_navigation(env, env.console, context); }, true); } // DIAGNOSTIC ONLY: Remove before pushing to production. -void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ tutorial_part_3_after_catch_navigation(env, env.console, context); }, true); } -static void checkpoint_0005(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +static void checkpoint_0005(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Segment 00-004: Poketech acquisition tutorial_part_4_navigation(env, env.console, context); @@ -1213,8 +1298,10 @@ std::string AutoStory_Checkpoint_0001::end_text() const { return "Standing outside Lake Verity before picking starter."; } -void AutoStory_Checkpoint_0001::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0001::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0001(env, context, options, stats); } @@ -1232,8 +1319,10 @@ std::string AutoStory_Checkpoint_0002::end_text() const { return "Standing outside Lake Verity after picking starter."; } -void AutoStory_Checkpoint_0002::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0002::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0002(env, context, options, stats); } @@ -1252,8 +1341,10 @@ std::string AutoStory_Checkpoint_0003::end_text() const { return "Standing in front of The professor after recieving the Pokedex"; } -void AutoStory_Checkpoint_0003::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0003::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0003(env, context, options, stats); } // ======================== @@ -1270,8 +1361,10 @@ std::string AutoStory_Checkpoint_0004::end_text() const { return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; } -void AutoStory_Checkpoint_0004::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0004::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0004(env, context, options, stats); } // ======================== @@ -1288,8 +1381,10 @@ std::string AutoStory_Checkpoint_0005::end_text() const { return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; } -void AutoStory_Checkpoint_0005::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0005::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0005(env, context, options, stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h index d683b0ac7c..681d09caa8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h @@ -17,16 +17,20 @@ namespace PokemonBDSP { // DIAGNOSTIC ONLY: Remove before pushing to production. // Starts at the post-catch save and runs only the rest of checkpoint 00.04. -void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats); +void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Segment_00 : 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0000 : public AutoStory_Checkpoint { @@ -34,8 +38,10 @@ class AutoStory_Checkpoint_0000 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint { @@ -43,8 +49,10 @@ class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint { @@ -52,8 +60,10 @@ class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint { @@ -61,8 +71,10 @@ class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint { @@ -70,8 +82,10 @@ class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0005 : public AutoStory_Checkpoint { @@ -80,39 +94,51 @@ class AutoStory_Checkpoint_0005 : public AutoStory_Checkpoint { virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: game launched, in front of the TV. // end: Standing outside lake varety before starter //Segment 00-00 begins -void checkpoint_000(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats); +void checkpoint_000(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats); // start: Standing outside lake varety before starter // end: Standing outside lake varety after starter battle //Segment 00-001 begins -void checkpoint_001(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats); +void checkpoint_001(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats); // start: Standing outside lake varety after starter battle // end: recieved Pokedex, standing in front of the professor //Segment 00-002 begins -void checkpoint_002(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats); +void checkpoint_002(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats); // start: recieved Pokedex, standing in front of the professor // end: Standing in the entrance to Jubilife City after speaking to dawn //Segment 00-003 begins -void checkpoint_003(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats); +void checkpoint_003(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats); // start: Standing in the entrance to Jubilife City after speaking to dawn // end: Standing in the Pokemon Center ready to deposit pokemon after recieving poketech. //Segment 00-004 begins -void checkpoint_004(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, AutoStoryStats& stats); +void checkpoint_004(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + EventNotificationOption& notif_status_update, + AutoStoryStats& stats); } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 1885900e93..03d1aa16e2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -73,8 +73,11 @@ static std::pair joystick_for(MoveDirection direction){ // Watches for a short dialogue box over several attempts. Returns true as soon as one is seen. // Returns false (without throwing) only if every attempt times out, so callers can skip // an optional interaction. Multiple attempts guard against a single missed detection. -static bool try_detect_dialogue(VideoStream& stream, ProControllerContext& context, const std::string& label, - size_t attempts = 3, std::chrono::milliseconds window = 5000ms){ +static bool try_detect_dialogue(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + size_t attempts = 3, + std::chrono::milliseconds window = 5000ms){ for (size_t attempt = 0; attempt < attempts; attempt++){ ShortDialogWatcher watcher_cyan(COLOR_CYAN); ShortDialogWatcher watcher_red(COLOR_RED); @@ -83,8 +86,8 @@ static bool try_detect_dialogue(VideoStream& stream, ProControllerContext& conte return true; } stream.log(label + " dialogue not detected (attempt " + std::to_string(attempt + 1) + "/" + - std::to_string(attempts) + ").", - COLOR_ORANGE); + std::to_string(attempts) + ").", + COLOR_ORANGE); } return false; } @@ -112,9 +115,11 @@ struct DoorwayWalkOptions { // travel_time + options.grace, if more than options.max_battles occur, or if a battle // cannot be escaped. // Returns the total active walking time used. -static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, ProControllerContext& context, - MoveDirection direction, std::chrono::milliseconds travel_time, - const DoorwayWalkOptions& options = DoorwayWalkOptions()){ +static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, + ProControllerContext& context, + MoveDirection direction, + std::chrono::milliseconds travel_time, + const DoorwayWalkOptions& options = DoorwayWalkOptions()){ enum class NavState { WALKING, BLACK_SCREEN, @@ -133,8 +138,8 @@ static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, ProCont switch (state){ case NavState::WALKING: { if (walked >= max_travel){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "walk_until_doorway: Doorway not found within time budget.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "walk_until_doorway: Doorway not found within time budget.", stream); } context.wait_for_all_requests(); @@ -169,12 +174,13 @@ static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, ProCont } case NavState::IN_BATTLE: { if (++battles > options.max_battles){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "walk_until_doorway: Too many battles while walking.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "walk_until_doorway: Too many battles while walking.", + stream); } stream.log("[AutoStory] Wild battle. Escaping. Walked " + std::to_string(walked.count()) + "ms of " + - std::to_string(travel_time.count()) + "ms.", - COLOR_ORANGE); + std::to_string(travel_time.count()) + "ms.", + COLOR_ORANGE); escape_wild_battle(stream, context, options.battle_type); state = NavState::WALKING; break; @@ -184,7 +190,7 @@ static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, ProCont } } stream.log("[AutoStory] Doorway transition detected after " + std::to_string(walked.count()) + "ms of walking.", - COLOR_GREEN); + COLOR_GREEN); return walked; } @@ -194,8 +200,9 @@ static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, ProCont // Navigates the player from Jubilife City to the Oreburgh Gate, including the // first Barry fight. // --------------------------------------------------------------------------- -void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, + VideoStream& stream, + ProControllerContext& context){ // Wait for game to fully accept input after save/overworld return pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -289,12 +296,12 @@ void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, VideoStream& str bool black_screen_found = false; for (size_t attempt = 0; attempt < 3 && !black_screen_found; attempt++){ BlackScreenWatcher black_screen; - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0.000000, -1.000000}, 4740ms, 0ms); - }, - {black_screen}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0.000000, -1.000000}, 4740ms, 0ms); + }, + {black_screen}); if (ret == 0){ stream.log("Black screen detected."); black_screen_found = true; @@ -303,8 +310,9 @@ void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, VideoStream& str context.wait_for_all_requests(); if (!black_screen_found){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "oreburgh_gate_to_mine: No black screen after 3 attempts.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "oreburgh_gate_to_mine: No black screen after 3 attempts.", + stream); } // Let the mine entrance transition fully finish so the next step's black screen @@ -338,8 +346,8 @@ static MineCenterOffset measure_mine_center_offset(const ImageViewRGB32& referen // Entrance beam and both posts. Avoid the player and shadow. const std::array boxes = {ImageFloatBox(0.35, 0.285, 0.30, 0.07), - ImageFloatBox(0.355, 0.37, 0.035, 0.23), - ImageFloatBox(0.615, 0.37, 0.035, 0.23)}; + ImageFloatBox(0.355, 0.37, 0.035, 0.23), + ImageFloatBox(0.615, 0.37, 0.035, 0.23)}; const int search_radius = (int)std::lround(0.10 * live_scaled.width()); std::vector scores(2 * search_radius + 1); @@ -396,7 +404,7 @@ static MineCenterOffset measure_mine_center_offset(const ImageViewRGB32& referen const size_t best_index = static_cast(best_shift + search_radius); const bool valid = best_score <= 20.0 && worst_regions[best_index] <= 30.0 && - alternative_score - best_score >= 3.0 && std::abs(best_shift) < search_radius; + alternative_score - best_score >= 3.0 && std::abs(best_shift) < search_radius; return {valid, static_cast(best_shift) / live_scaled.width(), best_score}; } @@ -420,7 +428,7 @@ static bool oreburgh_mine_alignment_test(VideoStream& stream, ProControllerConte const MineCenterOffset measurement = measure_mine_center_offset(reference, frame); stream.log("[Mine align] Offset=" + std::to_string(measurement.offset) + - ", score=" + std::to_string(measurement.score) + (measurement.valid ? ", trusted" : ", unsure")); + ", score=" + std::to_string(measurement.score) + (measurement.valid ? ", trusted" : ", unsure")); if (!measurement.valid){ stream.log("[Mine align] Entrance position unsure. Stopping.", COLOR_ORANGE); @@ -459,18 +467,19 @@ static bool oreburgh_mine_alignment_test(VideoStream& stream, ProControllerConte BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); BattleDialogWatcher dialog(COLOR_YELLOW); - const int result = run_until(stream, context, - [x, duration](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {x, 0.0}, duration, 0ms); - pbf_wait(ctx, 500ms); - ctx.wait_for_all_requests(); - }, - {black, menu, dialog}); + const int result = run_until(stream, + context, + [x, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, 0.0}, duration, 0ms); + pbf_wait(ctx, 500ms); + ctx.wait_for_all_requests(); + }, + {black, menu, dialog}); if (result >= 0){ stream.log("[Mine align] Battle or transition detected. " - "Stopping the alignment test.", - COLOR_ORANGE); + "Stopping the alignment test.", + COLOR_ORANGE); return false; } @@ -491,8 +500,12 @@ static bool mine_escape_and_settle(VideoStream& stream, ProControllerContext& co context.wait_for_all_requests(); // Check the return waits instead of ignoring their results. - BlackScreenWatcher visible(COLOR_YELLOW, ImageFloatBox(0.1, 0.1, 0.8, 0.8), 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms); + BlackScreenWatcher visible(COLOR_YELLOW, + ImageFloatBox(0.1, 0.1, 0.8, 0.8), + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 8s, {{visible}}) != 0){ stream.log("[Mine encounter] Screen did not return. Stopping.", COLOR_ORANGE); @@ -523,13 +536,14 @@ static bool mine_force_encounter_test(VideoStream& stream, ProControllerContext& BattleMenuWatcher menu(BattleType::STANDARD, COLOR_YELLOW); BattleDialogWatcher dialog(COLOR_YELLOW); - const int result = run_until(stream, context, - [y](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0.0, y}, 120ms, 0ms); - pbf_wait(ctx, 400ms); - ctx.wait_for_all_requests(); - }, - {black, menu, dialog}); + const int result = run_until(stream, + context, + [y](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, y}, 120ms, 0ms); + pbf_wait(ctx, 400ms); + ctx.wait_for_all_requests(); + }, + {black, menu, dialog}); if (result >= 0){ stream.log("[Mine encounter] Wiggle interrupted. Checking for battle."); @@ -540,8 +554,8 @@ static bool mine_force_encounter_test(VideoStream& stream, ProControllerContext& } stream.log("[Mine encounter] No encounter within 60 seconds. " - "Test incomplete; stopping.", - COLOR_ORANGE); + "Test incomplete; stopping.", + COLOR_ORANGE); return false; } @@ -555,11 +569,12 @@ static bool mine_run_with_encounters(VideoStream& stream, ProControllerContext& BattleDialogWatcher dialog(COLOR_YELLOW); bool completed = false; - const int result = run_until(stream, context, - [&](ProControllerContext& ctx){ - completed = action(ctx); - }, - {black, menu, dialog}); + const int result = run_until(stream, + context, + [&](ProControllerContext& ctx){ + completed = action(ctx); + }, + {black, menu, dialog}); if (result < 0){ return completed; @@ -578,8 +593,11 @@ static bool mine_run_with_encounters(VideoStream& stream, ProControllerContext& } } -static bool mine_timed_walk(VideoStream& stream, ProControllerContext& context, double x, double y, - std::chrono::milliseconds duration){ +static bool mine_timed_walk(VideoStream& stream, + ProControllerContext& context, + double x, + double y, + std::chrono::milliseconds duration){ auto remaining = duration; return mine_run_with_encounters(stream, context, [&](ProControllerContext& ctx){ @@ -608,9 +626,11 @@ struct MinePictureScore { // Supports four, five, or six regions. Existing four-region callers keep // their original search ranges through the default arguments. template -static MinePictureScore mine_score_picture(const ImageRGB32& reference, const ImageRGB32& live, - const std::array& boxes, - double horizontal_range = 1.0 / 60, double vertical_range = 2.0 / 135){ +static MinePictureScore mine_score_picture(const ImageRGB32& reference, + const ImageRGB32& live, + const std::array& boxes, + double horizontal_range = 1.0 / 60, + double vertical_range = 2.0 / 135){ MinePictureScore best; const int radius_x = (int)std::lround(horizontal_range * live.width()); const int radius_y = (int)std::lround(vertical_range * live.height()); @@ -640,9 +660,14 @@ static MinePictureScore mine_score_picture(const ImageRGB32& reference, const Im } -static bool mine_check_picture(VideoStream& stream, ProControllerContext& context, const char* filename, - const std::array& boxes, const std::string& label, - size_t max_attempts = 8, double max_average = 20.0, double max_patch = 30.0){ +static bool mine_check_picture(VideoStream& stream, + ProControllerContext& context, + const char* filename, + const std::array& boxes, + const std::string& label, + size_t max_attempts = 8, + double max_average = 20.0, + double max_patch = 30.0){ const ImageRGB32 reference(mine_navigation_resource_path() + filename); const ImageRGB32 reference_scaled = reference.scale_to(960, 540); @@ -666,8 +691,8 @@ static bool mine_check_picture(VideoStream& stream, ProControllerContext& contex const ImageRGB32 live = frame->scale_to(960, 540); const MinePictureScore score = mine_score_picture(reference_scaled, live, boxes); - stream.log(label + ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst)); + stream.log( + label + ": average=" + std::to_string(score.average) + ", worst patch=" + std::to_string(score.worst)); if (score.average <= max_average && score.worst <= max_patch){ if (++matching_frames >= 3){ @@ -702,11 +727,13 @@ struct MineWalkOptions { }; template -static bool mine_walk_to_endpoints(VideoStream& stream, ProControllerContext& context, - const std::array& files, - const std::array, Endpoints>& boxes, - const MineWalkOptions& options, const std::string& label, - bool force_encounter = false){ +static bool mine_walk_to_endpoints(VideoStream& stream, + ProControllerContext& context, + const std::array& files, + const std::array, Endpoints>& boxes, + const MineWalkOptions& options, + const std::string& label, + bool force_encounter = false){ std::array, Endpoints> references; for (size_t index = 0; index < Endpoints; index++){ const ImageRGB32 image(mine_navigation_resource_path() + files[index]); @@ -740,12 +767,15 @@ static bool mine_walk_to_endpoints(VideoStream& stream, ProControllerContext& co double accepted_average = 1000.0; for (size_t endpoint = 0; endpoint < Endpoints; endpoint++){ - const MinePictureScore score = mine_score_picture(*references[endpoint], live, boxes[endpoint], - options.horizontal_range, options.vertical_range); + const MinePictureScore score = mine_score_picture(*references[endpoint], + live, + boxes[endpoint], + options.horizontal_range, + options.vertical_range); stream.log(label + " Endpoint " + std::to_string(endpoint + 1) + - ": average=" + std::to_string(score.average) + - ", worst patch=" + std::to_string(score.worst) + ", screen-relative offset=(" + - std::to_string(score.horizontal) + "," + std::to_string(score.vertical) + ")"); + ": average=" + std::to_string(score.average) + ", worst patch=" + std::to_string(score.worst) + + ", screen-relative offset=(" + std::to_string(score.horizontal) + "," + + std::to_string(score.vertical) + ")"); if (score.average <= options.max_average && score.worst <= options.max_patch && score.average < accepted_average){ @@ -803,13 +833,15 @@ static bool mine_walk_to_endpoints(VideoStream& stream, ProControllerContext& co } } -static bool oreburgh_mine_walk_to_first_rock(VideoStream& stream, ProControllerContext& context, - bool force_encounter = false){ +static bool +oreburgh_mine_walk_to_first_rock(VideoStream& stream, ProControllerContext& context, bool force_encounter = false){ const std::array files = {"mine_path_01.png"}; - const std::array, 1> boxes = { - {{{ImageFloatBox(0.482, 0.585, 0.055, 0.055), ImageFloatBox(0.335, 0.670, 0.050, 0.055), - ImageFloatBox(0.412, 0.670, 0.050, 0.055), ImageFloatBox(0.570, 0.752, 0.045, 0.055), - ImageFloatBox(0.645, 0.752, 0.045, 0.055), ImageFloatBox(0.570, 0.290, 0.080, 0.120)}}}}; + const std::array, 1> boxes = {{{{ImageFloatBox(0.482, 0.585, 0.055, 0.055), + ImageFloatBox(0.335, 0.670, 0.050, 0.055), + ImageFloatBox(0.412, 0.670, 0.050, 0.055), + ImageFloatBox(0.570, 0.752, 0.045, 0.055), + ImageFloatBox(0.645, 0.752, 0.045, 0.055), + ImageFloatBox(0.570, 0.290, 0.080, 0.120)}}}}; MineWalkOptions options; options.x = 0.0; @@ -822,13 +854,18 @@ static bool oreburgh_mine_walk_to_first_rock(VideoStream& stream, ProControllerC static bool oreburgh_mine_move_right(VideoStream& stream, ProControllerContext& context){ const std::array files = {"mine_path_01_1.png", "mine_path_01_2.png"}; - const std::array, 2> boxes = { - {{{ImageFloatBox(0.400, 0.585, 0.055, 0.055), ImageFloatBox(0.245, 0.670, 0.050, 0.055), - ImageFloatBox(0.322, 0.670, 0.050, 0.055), ImageFloatBox(0.480, 0.752, 0.045, 0.055), - ImageFloatBox(0.555, 0.752, 0.045, 0.055), ImageFloatBox(0.485, 0.290, 0.080, 0.090)}}, - {{ImageFloatBox(0.325, 0.585, 0.055, 0.055), ImageFloatBox(0.170, 0.670, 0.050, 0.055), - ImageFloatBox(0.244, 0.670, 0.050, 0.055), ImageFloatBox(0.395, 0.752, 0.045, 0.055), - ImageFloatBox(0.470, 0.752, 0.045, 0.055), ImageFloatBox(0.415, 0.290, 0.080, 0.090)}}}}; + const std::array, 2> boxes = {{{{ImageFloatBox(0.400, 0.585, 0.055, 0.055), + ImageFloatBox(0.245, 0.670, 0.050, 0.055), + ImageFloatBox(0.322, 0.670, 0.050, 0.055), + ImageFloatBox(0.480, 0.752, 0.045, 0.055), + ImageFloatBox(0.555, 0.752, 0.045, 0.055), + ImageFloatBox(0.485, 0.290, 0.080, 0.090)}}, + {{ImageFloatBox(0.325, 0.585, 0.055, 0.055), + ImageFloatBox(0.170, 0.670, 0.050, 0.055), + ImageFloatBox(0.244, 0.670, 0.050, 0.055), + ImageFloatBox(0.395, 0.752, 0.045, 0.055), + ImageFloatBox(0.470, 0.752, 0.045, 0.055), + ImageFloatBox(0.415, 0.290, 0.080, 0.090)}}}}; MineWalkOptions options; options.x = 1.0; @@ -841,13 +878,18 @@ static bool oreburgh_mine_move_right(VideoStream& stream, ProControllerContext& static bool oreburgh_mine_down_past_central_rock(VideoStream& stream, ProControllerContext& context){ const std::array files = {"mine_path_02_1.png", "mine_path_02_2.png"}; - const std::array, 2> boxes = { - {{{ImageFloatBox(0.345, 0.420, 0.040, 0.045), ImageFloatBox(0.195, 0.500, 0.040, 0.045), - ImageFloatBox(0.270, 0.500, 0.040, 0.045), ImageFloatBox(0.410, 0.590, 0.040, 0.045), - ImageFloatBox(0.480, 0.590, 0.040, 0.045), ImageFloatBox(0.445, 0.180, 0.060, 0.090)}}, - {{ImageFloatBox(0.420, 0.420, 0.040, 0.045), ImageFloatBox(0.270, 0.500, 0.040, 0.045), - ImageFloatBox(0.345, 0.500, 0.040, 0.045), ImageFloatBox(0.485, 0.590, 0.040, 0.045), - ImageFloatBox(0.555, 0.590, 0.040, 0.045), ImageFloatBox(0.520, 0.180, 0.060, 0.090)}}}}; + const std::array, 2> boxes = {{{{ImageFloatBox(0.345, 0.420, 0.040, 0.045), + ImageFloatBox(0.195, 0.500, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.410, 0.590, 0.040, 0.045), + ImageFloatBox(0.480, 0.590, 0.040, 0.045), + ImageFloatBox(0.445, 0.180, 0.060, 0.090)}}, + {{ImageFloatBox(0.420, 0.420, 0.040, 0.045), + ImageFloatBox(0.270, 0.500, 0.040, 0.045), + ImageFloatBox(0.345, 0.500, 0.040, 0.045), + ImageFloatBox(0.485, 0.590, 0.040, 0.045), + ImageFloatBox(0.555, 0.590, 0.040, 0.045), + ImageFloatBox(0.520, 0.180, 0.060, 0.090)}}}}; MineWalkOptions options; options.x = 0.0; @@ -863,10 +905,11 @@ static bool oreburgh_mine_down_past_central_rock(VideoStream& stream, ProControl static bool oreburgh_mine_move_left_to_gap(VideoStream& stream, ProControllerContext& context){ const std::array files = {"mine_path_03.png"}; - const std::array, 1> boxes = { - {{{ImageFloatBox(0.340, 0.495, 0.040, 0.045), ImageFloatBox(0.410, 0.495, 0.040, 0.045), - ImageFloatBox(0.555, 0.580, 0.040, 0.045), ImageFloatBox(0.625, 0.580, 0.040, 0.045), - ImageFloatBox(0.580, 0.180, 0.060, 0.090)}}}}; + const std::array, 1> boxes = {{{{ImageFloatBox(0.340, 0.495, 0.040, 0.045), + ImageFloatBox(0.410, 0.495, 0.040, 0.045), + ImageFloatBox(0.555, 0.580, 0.040, 0.045), + ImageFloatBox(0.625, 0.580, 0.040, 0.045), + ImageFloatBox(0.580, 0.180, 0.060, 0.090)}}}}; MineWalkOptions options; options.x = -1.0; @@ -913,16 +956,17 @@ static bool mine_run_picture_step(VideoStream& stream, ProControllerContext& con static bool oreburgh_mine_confirm_05(VideoStream& stream, ProControllerContext& context){ const MinePictureStep step = {"mine_path_05.png", - "[Mine endpoint 05]", - {ImageFloatBox(0.240, 0.240, 0.055, 0.080), ImageFloatBox(0.390, 0.220, 0.050, 0.090), - ImageFloatBox(0.360, 0.090, 0.030, 0.030), - ImageFloatBox(0.550, 0.140, 0.040, 0.040)}, - 0.0, - -1.0, - 60ms, - 10, - 20.0, - 30.0}; + "[Mine endpoint 05]", + {ImageFloatBox(0.240, 0.240, 0.055, 0.080), + ImageFloatBox(0.390, 0.220, 0.050, 0.090), + ImageFloatBox(0.360, 0.090, 0.030, 0.030), + ImageFloatBox(0.550, 0.140, 0.040, 0.040)}, + 0.0, + -1.0, + 60ms, + 10, + 20.0, + 30.0}; return mine_run_picture_step(stream, context, step); } @@ -945,9 +989,10 @@ static bool oreburgh_mine_timed_to_07(VideoStream& stream, ProControllerContext& } // Path 6: crate, nearby small rock, large rock, upper-right small rock. - const std::array path6_boxes = { - ImageFloatBox(0.350, 0.250, 0.085, 0.075), ImageFloatBox(0.405, 0.510, 0.035, 0.040), - ImageFloatBox(0.520, 0.300, 0.050, 0.080), ImageFloatBox(0.620, 0.350, 0.030, 0.040)}; + const std::array path6_boxes = {ImageFloatBox(0.350, 0.250, 0.085, 0.075), + ImageFloatBox(0.405, 0.510, 0.035, 0.040), + ImageFloatBox(0.520, 0.300, 0.050, 0.080), + ImageFloatBox(0.620, 0.350, 0.030, 0.040)}; if (!mine_check_picture(stream, context, "mine_path_06.png", path6_boxes, "[Mine midpoint 06]")){ return false; @@ -960,9 +1005,10 @@ static bool oreburgh_mine_timed_to_07(VideoStream& stream, ProControllerContext& } // Path 7: crate and three visible rock surfaces. - const std::array path7_boxes = { - ImageFloatBox(0.365, 0.025, 0.070, 0.055), ImageFloatBox(0.420, 0.270, 0.035, 0.040), - ImageFloatBox(0.560, 0.415, 0.025, 0.040), ImageFloatBox(0.530, 0.650, 0.060, 0.090)}; + const std::array path7_boxes = {ImageFloatBox(0.365, 0.025, 0.070, 0.055), + ImageFloatBox(0.420, 0.270, 0.035, 0.040), + ImageFloatBox(0.560, 0.415, 0.025, 0.040), + ImageFloatBox(0.530, 0.650, 0.060, 0.090)}; return mine_check_picture(stream, context, "mine_path_07.png", path7_boxes, "[Mine endpoint 07]"); } @@ -970,16 +1016,17 @@ static bool oreburgh_mine_timed_to_07(VideoStream& stream, ProControllerContext& // Move left from path 7 to the position shown in path 8. static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& context){ const MinePictureStep step = {"mine_path_08.png", - "[Mine endpoint 08]", - {ImageFloatBox(0.495, 0.035, 0.070, 0.055), ImageFloatBox(0.550, 0.270, 0.035, 0.040), - ImageFloatBox(0.695, 0.420, 0.030, 0.040), - ImageFloatBox(0.555, 0.585, 0.035, 0.040)}, - -1.0, - 0.0, - 40ms, - 20, - 20.0, - 30.0}; + "[Mine endpoint 08]", + {ImageFloatBox(0.495, 0.035, 0.070, 0.055), + ImageFloatBox(0.550, 0.270, 0.035, 0.040), + ImageFloatBox(0.695, 0.420, 0.030, 0.040), + ImageFloatBox(0.555, 0.585, 0.035, 0.040)}, + -1.0, + 0.0, + 40ms, + 20, + 20.0, + 30.0}; return mine_run_picture_step(stream, context, step); } @@ -987,16 +1034,17 @@ static bool oreburgh_mine_left_to_08(VideoStream& stream, ProControllerContext& static bool oreburgh_mine_down_to_09(VideoStream& stream, ProControllerContext& context){ const MinePictureStep step = {"mine_path_09.png", - "[Mine endpoint 09]", - {ImageFloatBox(0.550, 0.115, 0.030, 0.040), ImageFloatBox(0.550, 0.390, 0.035, 0.040), - ImageFloatBox(0.620, 0.390, 0.025, 0.035), - ImageFloatBox(0.500, 0.720, 0.085, 0.080)}, - 0.0, - -1.0, - 40ms, - 25, - 25.0, - 35.0}; + "[Mine endpoint 09]", + {ImageFloatBox(0.550, 0.115, 0.030, 0.040), + ImageFloatBox(0.550, 0.390, 0.035, 0.040), + ImageFloatBox(0.620, 0.390, 0.025, 0.035), + ImageFloatBox(0.500, 0.720, 0.085, 0.080)}, + 0.0, + -1.0, + 40ms, + 25, + 25.0, + 35.0}; return mine_run_picture_step(stream, context, step); } @@ -1066,28 +1114,30 @@ static bool oreburgh_mine_09_to_14(VideoStream& stream, ProControllerContext& co ImageFloatBox(0.550, 0.720, 0.030, 0.040) // Lower small rock }; - const MinePictureStep path10_boxes_step = { - "mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; + const MinePictureStep path10_boxes_step = + {"mine_path_10.png", "[Mine endpoint 10]", path10_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; if (!mine_run_picture_step(stream, context, path10_boxes_step)){ return false; } - const std::array path11_boxes = { - ImageFloatBox(0.405, 0.560, 0.070, 0.080), ImageFloatBox(0.475, 0.270, 0.030, 0.040), - ImageFloatBox(0.575, 0.290, 0.045, 0.080), ImageFloatBox(0.550, 0.575, 0.030, 0.040)}; + const std::array path11_boxes = {ImageFloatBox(0.405, 0.560, 0.070, 0.080), + ImageFloatBox(0.475, 0.270, 0.030, 0.040), + ImageFloatBox(0.575, 0.290, 0.045, 0.080), + ImageFloatBox(0.550, 0.575, 0.030, 0.040)}; - const MinePictureStep path11_boxes_step = { - "mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; + const MinePictureStep path11_boxes_step = + {"mine_path_11.png", "[Mine endpoint 11]", path11_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; if (!mine_run_picture_step(stream, context, path11_boxes_step)){ return false; } - const std::array path12_boxes = { - ImageFloatBox(0.260, 0.560, 0.080, 0.080), ImageFloatBox(0.355, 0.270, 0.025, 0.035), - ImageFloatBox(0.435, 0.290, 0.040, 0.070), ImageFloatBox(0.415, 0.585, 0.025, 0.035)}; + const std::array path12_boxes = {ImageFloatBox(0.260, 0.560, 0.080, 0.080), + ImageFloatBox(0.355, 0.270, 0.025, 0.035), + ImageFloatBox(0.435, 0.290, 0.040, 0.070), + ImageFloatBox(0.415, 0.585, 0.025, 0.035)}; - const MinePictureStep path12_boxes_step = { - "mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; + const MinePictureStep path12_boxes_step = + {"mine_path_12.png", "[Mine endpoint 12]", path12_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; if (!mine_run_picture_step(stream, context, path12_boxes_step)){ return false; } @@ -1099,8 +1149,8 @@ static bool oreburgh_mine_09_to_14(VideoStream& stream, ProControllerContext& co ImageFloatBox(0.405, 0.580, 0.030, 0.040) // Bottom-left small rock }; - const MinePictureStep path13_boxes_step = { - "mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; + const MinePictureStep path13_boxes_step = + {"mine_path_13.png", "[Mine endpoint 13]", path13_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; if (!mine_run_picture_step(stream, context, path13_boxes_step)){ return false; } @@ -1123,8 +1173,11 @@ static bool oreburgh_mine_09_to_14(VideoStream& stream, ProControllerContext& co // Compare a reference patch with a shifted live patch using the same // brightness adjustment as the native ImageMatchDetector. -static double mine_offset_patch_score(const ImageRGB32& reference, const ImageRGB32& live, const ImageFloatBox& box, - double horizontal, double vertical){ +static double mine_offset_patch_score(const ImageRGB32& reference, + const ImageRGB32& live, + const ImageFloatBox& box, + double horizontal, + double vertical){ const ImageViewRGB32 reference_crop = extract_box_reference(reference, box); ImageFloatBox shifted = box; shifted.x += horizontal; @@ -1198,9 +1251,11 @@ static MineRoarkOffsetScore mine_score_roark_offsets(const ImageRGB32& reference const int helmet_radius_y = (int)std::lround(live.height() / 90.0); for (int extra_y = -helmet_radius_y; extra_y <= helmet_radius_y; extra_y++){ for (int extra_x = -helmet_radius_x; extra_x <= helmet_radius_x; extra_x++){ - const double score = - mine_offset_patch_score(reference, live, helmet_box, best.horizontal + (double)extra_x / live.width(), - best.vertical + (double)extra_y / live.height()); + const double score = mine_offset_patch_score(reference, + live, + helmet_box, + best.horizontal + (double)extra_x / live.width(), + best.vertical + (double)extra_y / live.height()); if (score < best.helmet){ best.helmet = score; best.helmet_horizontal = (double)extra_x / live.width(); @@ -1235,10 +1290,10 @@ static bool oreburgh_mine_align_to_18(VideoStream& stream, ProControllerContext& const MineRoarkOffsetScore score = mine_score_roark_offsets(reference_scaled, live); stream.log("[Mine endpoint 18] Left rock=" + std::to_string(score.left) + - ", Right rock=" + std::to_string(score.right) + ", Helmet=" + std::to_string(score.helmet) + - ", shared screen-relative offset=(" + std::to_string(score.horizontal) + "," + - std::to_string(score.vertical) + "), helmet screen-relative extra=(" + - std::to_string(score.helmet_horizontal) + "," + std::to_string(score.helmet_vertical) + ")"); + ", Right rock=" + std::to_string(score.right) + ", Helmet=" + std::to_string(score.helmet) + + ", shared screen-relative offset=(" + std::to_string(score.horizontal) + "," + + std::to_string(score.vertical) + "), helmet screen-relative extra=(" + + std::to_string(score.helmet_horizontal) + "," + std::to_string(score.helmet_vertical) + ")"); if (score.left <= max_rmsd && score.right <= max_rmsd && score.helmet <= max_rmsd){ if (++matching_frames >= 3){ @@ -1278,8 +1333,8 @@ static bool oreburgh_mine_14_to_19(VideoStream& stream, ProControllerContext& co ImageFloatBox(0.325, 0.560, 0.035, 0.045) // Lower rock }; - const MinePictureStep path15_boxes_step = { - "mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; + const MinePictureStep path15_boxes_step = + {"mine_path_15_actual.png", "[Mine endpoint 15]", path15_boxes, 1.0, 0.0, 80ms, 30, 20.0, 30.0}; if (!mine_run_picture_step(stream, context, path15_boxes_step)){ return false; } @@ -1292,8 +1347,8 @@ static bool oreburgh_mine_14_to_19(VideoStream& stream, ProControllerContext& co ImageFloatBox(0.330, 0.495, 0.035, 0.045) // Lower rock }; - const MinePictureStep path16_boxes_step = { - "mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; + const MinePictureStep path16_boxes_step = + {"mine_path_16.png", "[Mine endpoint 16]", path16_boxes, 0.0, -1.0, 80ms, 30, 20.0, 30.0}; if (!mine_run_picture_step(stream, context, path16_boxes_step)){ return false; } @@ -1320,18 +1375,18 @@ bool oreburgh_mine_route(VideoStream& stream, ProControllerContext& context, con }; const std::array steps = {{{"Align entrance", oreburgh_mine_alignment_test}, - {"Walk to first rock", - [](VideoStream& video, ProControllerContext& controller){ - return oreburgh_mine_walk_to_first_rock(video, controller, false); - }}, - {"Move right of first rock", oreburgh_mine_move_right}, - {"Move below first rock", oreburgh_mine_down_past_central_rock}, - {"Move left into center gap", oreburgh_mine_move_left_to_gap}, - {"Reach position 07", oreburgh_mine_timed_to_07}, - {"Reach position 08", oreburgh_mine_left_to_08}, - {"Reach position 09", oreburgh_mine_down_to_09}, - {"Reach 14 and collect item", oreburgh_mine_09_to_14}, - {"Reach 18 and turn north for 19", oreburgh_mine_14_to_19}}}; + {"Walk to first rock", + [](VideoStream& video, ProControllerContext& controller){ + return oreburgh_mine_walk_to_first_rock(video, controller, false); + }}, + {"Move right of first rock", oreburgh_mine_move_right}, + {"Move below first rock", oreburgh_mine_down_past_central_rock}, + {"Move left into center gap", oreburgh_mine_move_left_to_gap}, + {"Reach position 07", oreburgh_mine_timed_to_07}, + {"Reach position 08", oreburgh_mine_left_to_08}, + {"Reach position 09", oreburgh_mine_down_to_09}, + {"Reach 14 and collect item", oreburgh_mine_09_to_14}, + {"Reach 18 and turn north for 19", oreburgh_mine_14_to_19}}}; const size_t first = static_cast(options.first); const size_t last = static_cast(options.last); @@ -1365,8 +1420,9 @@ bool oreburgh_mine_route(VideoStream& stream, ProControllerContext& context, con stream.log(std::string("[Mine route] Stage failed: ") + step.name, COLOR_ORANGE); if (options.reset_on_failure){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, std::string("Mine route failed during: ") + step.name, stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + std::string("Mine route failed during: ") + step.name, + stream); } return false; @@ -1422,8 +1478,9 @@ static bool mine_talk_to_roark(VideoStream& stream, ProControllerContext& contex } // Retain the developer test entry point; both callers share the same route. -bool oreburgh_mine_first_leg_test(VideoStream& stream, ProControllerContext& context, - const MineRouteTestOptions& options){ +bool oreburgh_mine_first_leg_test(VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options){ return oreburgh_mine_route(stream, context, options); } @@ -1470,14 +1527,16 @@ void oreburgh_mine_nav(VideoStream& stream, ProControllerContext& context){ route.reset_on_failure = true; if (!oreburgh_mine_route(stream, context, route)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Mine route did not complete.", - stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mine route did not complete.", + stream); } if (!mine_talk_and_escape(stream, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Mine route: Roark conversation or Escape Rope exit failed.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mine route: Roark conversation or Escape Rope exit failed.", + stream); } } // --------------------------------------------------------------------------- @@ -1557,7 +1616,13 @@ void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro int count; }; const Move sequence[] = { - {DPAD_UP, 3}, {DPAD_LEFT, 4}, {DPAD_UP, 5}, {DPAD_RIGHT, 8}, {DPAD_UP, 11}, {DPAD_LEFT, 5}, {DPAD_UP, 4}, + {DPAD_UP, 3}, + {DPAD_LEFT, 4}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 8}, + {DPAD_UP, 11}, + {DPAD_LEFT, 5}, + {DPAD_UP, 4}, }; for (const auto& move : sequence){ @@ -1583,8 +1648,9 @@ void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro while (std::chrono::steady_clock::now() < dialogue_deadline){ const VideoSnapshot frame = stream.video().snapshot(); if (!frame){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Gym post battle: No video frame while clearing dialogue.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Gym post battle: No video frame while clearing dialogue.", + stream); } const auto now = std::chrono::steady_clock::now(); if (post_battle_dialogue.detect(frame)){ @@ -1597,8 +1663,9 @@ void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro context.wait_for_all_requests(); } if (!dialogue_finished){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Gym post battle: Dialogue did not finish within 180 seconds.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Gym post battle: Dialogue did not finish within 180 seconds.", + stream); } stream.log("[Gym post battle] Dialogue absent for 4 seconds. Walking down."); @@ -1621,8 +1688,10 @@ std::string AutoStory_Segment_01::end_text() const { return "End: First Gym Badge acquired (Oreburgh City)."; } -void AutoStory_Segment_01::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_01::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -1648,16 +1717,23 @@ std::string AutoStory_checkpoint_0101::end_text() const { // ======================== // Checkpoint 0101 Wrapper // ======================== -void AutoStory_checkpoint_0101::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0101::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0101(env, context, options, stats); } -void checkpoint_0101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0101(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ jubilife_to_oreburgh_gate(env, env.console, context); oreburgh_gate_nav(env, env.console, context); @@ -1679,16 +1755,23 @@ std::string AutoStory_checkpoint_0102::end_text() const { return "Inside Oreburgh Gym, ready to navigate to Roark."; } -void AutoStory_checkpoint_0102::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0102::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0102(env, context, options, stats); } -void checkpoint_0102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0102(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ oreburgh_gate_to_mine(env, env.console, context); oreburgh_mine_nav(env.console, context); @@ -1715,16 +1798,23 @@ std::string AutoStory_checkpoint_0103::end_text() const { return "Standing at Roark after defeating him and acquiring the first badge."; } -void AutoStory_checkpoint_0103::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0103::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0103(env, context, options, stats); } -void checkpoint_0103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0103(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ //#. Oreburgh Gym navigation and battle gym_Puzzle_01(env, env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h index 0646ee9306..a9cd8d6a76 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -57,16 +57,19 @@ bool oreburgh_mine_route_and_escape_test(VideoStream& stream, ProControllerConte // Shared stage runner for production navigation and isolated route tests. bool oreburgh_mine_route(VideoStream& stream, ProControllerContext& context, const MineRouteTestOptions& options); -bool oreburgh_mine_first_leg_test(VideoStream& stream, ProControllerContext& context, - const MineRouteTestOptions& options = MineRouteTestOptions()); +bool oreburgh_mine_first_leg_test(VideoStream& stream, + ProControllerContext& context, + const MineRouteTestOptions& options = MineRouteTestOptions()); class AutoStory_Segment_01 : 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint { @@ -74,41 +77,53 @@ class AutoStory_checkpoint_0101 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0101(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0101(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0102 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0102(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0102(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0103 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: starter Pokemon taken out of PC at level 100 and ready to be used in battle // end: first Gym Badge acquired (Oreburgh City) -void checkpoint_0103(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0103(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index c1edd06c50..bca31cf01c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -50,7 +50,8 @@ static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& co const VideoSnapshot frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mart alignment: No video frame.", stream); + "Mart alignment: No video frame.", + stream); } const OverworldMatchResult result = match_oreburgh_mart(*frame.frame); VideoOverlaySet overlays(stream.overlay()); @@ -60,13 +61,14 @@ static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& co overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Mart alignment] score=" + std::to_string(landmark.score) + ", screen-relative offset=(" + - std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } if (!result.located){ confirmations = 0; if (++missing >= 8){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: Emblem match missing or ambiguous.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mart alignment: Emblem match missing or ambiguous.", + stream); } // A passing shadow must not cause an immediate checkpoint reset. // Stay still until a trusted match returns; never steer by a rejected candidate. @@ -88,14 +90,16 @@ static void align_to_oreburgh_mart(VideoStream& stream, ProControllerContext& co const double error = horizontal ? dx : dy * 9.0 / 16; const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 0.015625 ? 40ms : 16ms; pbf_move_left_joystick(context, - {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, - duration, 0ms); + {horizontal ? (dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (dy < 0 ? 1.0 : -1.0)}, + duration, + 0ms); pbf_wait(context, 700ms); } context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Mart alignment: Could not reach the doorway reference position.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Mart alignment: Could not reach the doorway reference position.", + stream); } static void enter_oreburgh_gate_from_city(VideoStream& stream, ProControllerContext& context){ @@ -104,28 +108,30 @@ static void enter_oreburgh_gate_from_city(VideoStream& stream, ProControllerCont // Observe before the first move so entry during the upward // alignment or either search direction cannot be missed. BlackScreenWatcher black; - const int result = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0.0, 1.0}, 800ms, 100ms); - for (size_t attempt = 0; attempt < 30; attempt++){ - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 80ms); - pbf_move_left_joystick(ctx, {0.0, -1.0}, 120ms, 80ms); - ctx.wait_for_all_requests(); - } - // Keep watching if the last pulse starts a delayed fade. - pbf_wait(ctx, 1500ms); - }, - {{black}}); + const int result = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 800ms, 100ms); + for (size_t attempt = 0; attempt < 30; attempt++){ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 80ms); + pbf_move_left_joystick(ctx, {0.0, -1.0}, 120ms, 80ms); + ctx.wait_for_all_requests(); + } + // Keep watching if the last pulse starts a delayed fade. + pbf_wait(ctx, 1500ms); + }, + {{black}}); if (result != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Oreburgh Gate entry: No black transition during left/down search.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Oreburgh Gate entry: No black transition during left/down search.", stream); } stream.log("[Oreburgh Gate entry] Black transition detected; movement stopped."); BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 15s, {{visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate entry: Transition did not finish.", stream); + "Oreburgh Gate entry: Transition did not finish.", + stream); } pbf_wait(context, 700ms); context.wait_for_all_requests(); @@ -133,44 +139,47 @@ static void enter_oreburgh_gate_from_city(VideoStream& stream, ProControllerCont } // Call at a safe walking point, away from trainer/NPC prompts. -void force_repel_expiry(VideoStream& stream, ProControllerContext& context, RepelRenewalChoice choice, bool north_south, - bool require_expiry){ +void force_repel_expiry(VideoStream& stream, + ProControllerContext& context, + RepelRenewalChoice choice, + bool north_south, + bool require_expiry){ context.wait_for_all_requests(); stream.log(std::string("[Repel expiry] Wiggling ") + (north_south ? "north/south" : "west/east") + - " until expiry; renewal response=" + (choice == RepelRenewalChoice::YES ? "Yes." : "No.")); + " until expiry; renewal response=" + (choice == RepelRenewalChoice::YES ? "Yes." : "No.")); const Button response = choice == RepelRenewalChoice::YES ? BUTTON_A : BUTTON_B; ShortDialogWatcher expired(COLOR_CYAN); // 108 fresh Max Repels, two calibration runs: mean 41.244682s, // sample SD 3.668059s. Round mean + SD up to the next millisecond. // Match the calibration's 500ms movement / 100ms rest pulses. const auto movement_deadline = std::chrono::steady_clock::now() + 44913ms; - const int result = - run_until(stream, context, - [north_south, movement_deadline](ProControllerContext& ctx){ - while (std::chrono::steady_clock::now() + 1200ms <= movement_deadline){ - // Complete steps before reversing so the Repel counter advances. - if (north_south){ - pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 100ms); - pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 100ms); - }else{ - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 500ms, 100ms); - pbf_move_left_joystick(ctx, {1.0, 0.0}, 500ms, 100ms); - } - ctx.wait_for_all_requests(); - } - // Finish the observation window without an unpaired move. - const auto remaining = std::chrono::duration_cast( - movement_deadline - std::chrono::steady_clock::now()); - if (remaining > 0ms){ - pbf_wait(ctx, remaining); - } - }, - {{expired}}); + const int result = run_until(stream, + context, + [north_south, movement_deadline](ProControllerContext& ctx){ + while (std::chrono::steady_clock::now() + 1200ms <= movement_deadline){ + // Complete steps before reversing so the Repel counter advances. + if (north_south){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 500ms, 100ms); + pbf_move_left_joystick(ctx, {0.0, -1.0}, 500ms, 100ms); + }else{ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 500ms, 100ms); + pbf_move_left_joystick(ctx, {1.0, 0.0}, 500ms, 100ms); + } + ctx.wait_for_all_requests(); + } + // Finish the observation window without an unpaired move. + const auto remaining = + std::chrono::duration_cast(movement_deadline - std::chrono::steady_clock::now()); + if (remaining > 0ms){ + pbf_wait(ctx, remaining); + } + }, + {{expired}}); if (result != 0){ if (require_expiry){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Repel renewal: Required expiry prompt not observed within calibrated limit.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: Required expiry prompt not observed within calibrated limit.", + stream); } stream.log( "[Repel expiry] Calibrated 44.913s limit reached without an expiry prompt; checking clear overworld before continuing.", @@ -193,7 +202,8 @@ void force_repel_expiry(VideoStream& stream, ProControllerContext& context, Repe const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Repel renewal: No video frame during cleanup.", stream); + "Repel renewal: No video frame during cleanup.", + stream); } const auto now = std::chrono::steady_clock::now(); if (dialogue.detect(*frame.frame)){ @@ -211,8 +221,8 @@ void force_repel_expiry(VideoStream& stream, ProControllerContext& context, Repe context.wait_for_all_requests(); if (result == 0){ stream.log(std::string("[Repel expiry] Renewal ") + - (choice == RepelRenewalChoice::YES ? "accepted" : "declined") + - "; three continuous seconds of overworld confirmed."); + (choice == RepelRenewalChoice::YES ? "accepted" : "declined") + + "; three continuous seconds of overworld confirmed."); }else{ stream.log( "[Repel expiry] No expiry observed within the calibrated limit; three continuous seconds of clear overworld confirmed. Continuing the route."); @@ -224,8 +234,9 @@ void force_repel_expiry(VideoStream& stream, ProControllerContext& context, Repe } context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Repel renewal: Three clear seconds not confirmed after renewal.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Repel renewal: Three clear seconds not confirmed after renewal.", + stream); } static void wait_for_trainer_battle_menu(VideoStream& stream, ProControllerContext& context){ @@ -242,7 +253,8 @@ static void wait_for_trainer_battle_menu(VideoStream& stream, ProControllerConte const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Trainer approach: No video frame.", stream); + "Trainer approach: No video frame.", + stream); } const bool dialog = dialogue.detect(*frame.frame); if (dialog && !previous_dialogue){ @@ -262,28 +274,31 @@ static void wait_for_trainer_battle_menu(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Trainer approach: Battle menu did not appear after dialogue cleanup.", - stream); + "Trainer approach: Battle menu did not appear after dialogue cleanup.", + stream); } static void exit_oreburgh_gate_to_route203(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); stream.log("[Oreburgh Gate exit] Moving west until the white transition starts."); WhiteScreenWatcher white; - const int result = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 6000ms, 0ms); - pbf_wait(ctx, 1000ms); - }, - {{white}}); + const int result = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 6000ms, 0ms); + pbf_wait(ctx, 1000ms); + }, + {{white}}); if (result != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate exit: White transition did not start.", stream); + "Oreburgh Gate exit: White transition did not start.", + stream); } WhiteScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 15s, {{visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Oreburgh Gate exit: White transition did not finish.", stream); + "Oreburgh Gate exit: White transition did not finish.", + stream); } pbf_wait(context, 700ms); context.wait_for_all_requests(); @@ -296,7 +311,8 @@ static void approach_galactic_dialogue(VideoStream& stream, ProControllerContext const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic approach: No video frame.", stream); + "Galactic approach: No video frame.", + stream); } // Alignment may already have handed off after the story started. if (dialogue.detect(*frame.frame) || battle.detect(*frame.frame)){ @@ -306,42 +322,45 @@ static void approach_galactic_dialogue(VideoStream& stream, ProControllerContext "[Galactic approach] Plaza position confirmed; sidestepping left around the sign, then moving up until story dialogue."); ShortDialogWatcher started(COLOR_YELLOW); BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); - const int result = run_until(stream, context, - [](ProControllerContext& ctx){ - // The reference position faces the sign, which blocks a - // straight upward approach and consumes A as a sign read. - pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 150ms); - ctx.wait_for_all_requests(); - for (size_t pulse = 0; pulse < 20; pulse++){ - pbf_move_left_joystick(ctx, {0.0, 1.0}, 100ms, 100ms); - ctx.wait_for_all_requests(); - } - // If an NPC blocks the final approach, initiate interaction. - pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); - pbf_wait(ctx, 1500ms); - }, - {{started}, {menu}}); + const int result = run_until(stream, + context, + [](ProControllerContext& ctx){ + // The reference position faces the sign, which blocks a + // straight upward approach and consumes A as a sign read. + pbf_move_left_joystick(ctx, {-1.0, 0.0}, 200ms, 150ms); + ctx.wait_for_all_requests(); + for (size_t pulse = 0; pulse < 20; pulse++){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 100ms, 100ms); + ctx.wait_for_all_requests(); + } + // If an NPC blocks the final approach, initiate interaction. + pbf_press_button(ctx, BUTTON_A, 80ms, 1000ms); + pbf_wait(ctx, 1500ms); + }, + {{started}, {menu}}); if (result < 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Galactic approach: Story dialogue/battle did not start during the bounded upward approach.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic approach: Story dialogue/battle did not start during the bounded upward approach.", + stream); } stream.log("[Galactic approach] Story dialogue/battle detected; movement stopped."); } // Dialogue indicates the trainer interaction started; combat still waits // for a confirmed battle menu before invoking the battle handler. -static bool watch_route204_trainer_event(VideoStream& stream, ProControllerContext& context, - const std::function& action, - const std::string& label){ +static bool watch_route204_trainer_event(VideoStream& stream, + ProControllerContext& context, + const std::function& action, + const std::string& label){ context.wait_for_all_requests(); ShortDialogWatcher dialogue(COLOR_YELLOW); BattleMenuWatcher menu(BattleType::TRAINER, COLOR_GREEN); - const int result = run_until(stream, context, - [&action](ProControllerContext& ctx){ - action(ctx); - }, - {{dialogue}, {menu}}); + const int result = run_until(stream, + context, + [&action](ProControllerContext& ctx){ + action(ctx); + }, + {{dialogue}, {menu}}); const bool detected = result >= 0; stream.log("[" + label + "] Trainer dialogue/battle detected=" + std::to_string(detected)); return detected; @@ -349,7 +368,8 @@ static bool watch_route204_trainer_event(VideoStream& stream, ProControllerConte static bool approach_route204_female_trainer(VideoStream& stream, ProControllerContext& context){ return watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0.0, 1.0}, 8000ms, 0ms); // Replaces the 2374ms settle plus the unbounded-looking @@ -362,7 +382,8 @@ static bool approach_route204_female_trainer(VideoStream& stream, ProControllerC static void recover_route204_female_trainer(VideoStream& stream, ProControllerContext& context){ stream.log("[Route 204 female trainer] Missed initial trigger; crossing her sight line down/up."); const bool recovered = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ for (size_t pair = 0; pair < 8; pair++){ // We have passed her: go back down first, then up. @@ -374,9 +395,9 @@ static void recover_route204_female_trainer(VideoStream& stream, ProControllerCo }, "Route 204 female trainer recovery"); if (!recovered){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 female trainer: No dialogue/battle after bounded down/up recovery.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 female trainer: No dialogue/battle after bounded down/up recovery.", + stream); } } @@ -384,7 +405,8 @@ static void recover_route204_youngster(VideoStream& stream, ProControllerContext // The recording ends above the youngster after passing left. // Cross his row down/up instead of resetting after one miss. const bool recovered = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ for (size_t pair = 0; pair < 6; pair++){ pbf_move_left_joystick(ctx, {0, -1}, 500ms, 200ms); @@ -399,23 +421,25 @@ static void recover_route204_youngster(VideoStream& stream, ProControllerContext "Route 204 youngster sightline recovery"); if (!recovered){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 youngster: No trigger after bounded down/up recovery.", - stream); + "Route 204 youngster: No trigger after bounded down/up recovery.", + stream); } } static void floaroma_house_transition(VideoStream& stream, ProControllerContext& context, double y){ context.wait_for_all_requests(); BlackScreenWatcher black; - const int result = run_until(stream, context, - [y](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, y}, 2500ms, 0ms); - pbf_wait(ctx, 1000ms); - }, - {{black}}); + const int result = run_until(stream, + context, + [y](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, y}, 2500ms, 0ms); + pbf_wait(ctx, 1000ms); + }, + {{black}}); if (result != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma house: Door transition not detected.", stream); + "Floaroma house: Door transition not detected.", + stream); } context.wait_for_all_requests(); BlackScreenDetector black_frame; @@ -425,7 +449,8 @@ static void floaroma_house_transition(VideoStream& stream, ProControllerContext& const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma house: No video frame after transition.", stream); + "Floaroma house: No video frame after transition.", + stream); } const auto now = std::chrono::steady_clock::now(); if (black_frame.detect(*frame.frame)){ @@ -439,7 +464,8 @@ static void floaroma_house_transition(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma house: Door transition did not finish.", stream); + "Floaroma house: Door transition did not finish.", + stream); } static void establish_floaroma_house_exit(VideoStream& stream, ProControllerContext& context){ @@ -453,7 +479,8 @@ static void recover_teighler_trigger(VideoStream& stream, ProControllerContext& // The first pass ended east of the corner. Return left, then // cross her sightline along the same bottom ledge for five seconds. const bool recovered = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ // The old 500ms sweeps stopped beneath/west of Teighler. // Cross the full bottom ledge in both directions, still @@ -469,8 +496,8 @@ static void recover_teighler_trigger(VideoStream& stream, ProControllerContext& "Teighler five-second ledge recovery"); if (!recovered){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Teighler: No battle after five-second left/right ledge recovery.", - stream); + "Teighler: No battle after five-second left/right ledge recovery.", + stream); } } @@ -491,7 +518,8 @@ static bool align_to_route204_floaroma_turn(VideoStream& stream, ProControllerCo const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: No video frame.", stream); + "Route 204 north turn: No video frame.", + stream); } if (dialogue.detect(*frame.frame) || battle_menu.detect(*frame.frame)){ return true; @@ -512,7 +540,7 @@ static bool align_to_route204_floaroma_turn(VideoStream& stream, ProControllerCo if (result.located){ missing = 0; stream.log("[Route 204 north turn] Sign offset=(" + std::to_string(result.dx) + "," + - std::to_string(result.dy) + ")"); + std::to_string(result.dy) + ")"); const bool horizontal = std::abs(result.dx) > 1.0 / 50; if (previously_left && std::abs(result.dx - previous_dx) < 1.0 / 384){ blocked_left++; @@ -535,23 +563,25 @@ static bool align_to_route204_floaroma_turn(VideoStream& stream, ProControllerCo previously_left = false; blocked_left = 0; if (++missing > 12){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: Sign unavailable during bounded leftward acquisition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Sign unavailable during bounded leftward acquisition.", + stream); } } if (watch_route204_trainer_event( - stream, context, - [x, y, duration](ProControllerContext& ctx){ + stream, + context, + [x, y, duration](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); pbf_wait(ctx, 250ms); }, - "Route 204 north sign alignment")){ + "Route 204 north sign alignment")){ return true; } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 north turn: Sign position did not converge.", stream); + "Route 204 north turn: Sign position did not converge.", + stream); } static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& context){ @@ -563,7 +593,8 @@ static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& c const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks approach: No video frame.", stream); + "Windworks approach: No video frame.", + stream); } const auto result = match_windworks_sign_approach(*frame.frame); if (result.at_target){ @@ -586,15 +617,15 @@ static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& c y = vertical ? (result.dy > 0 ? -1 : 1) : 0; duration = error < .08 ? 40ms : 100ms; stream.log("[Windworks approach] Sign offset=(" + std::to_string(result.dx) + "," + - std::to_string(result.dy) + ")"); + std::to_string(result.dy) + ")"); }else if (previously_located && ++missing <= 12){ pbf_wait(context, 250ms); context.wait_for_all_requests(); continue; }else if (previously_located || ++missing > 12){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Windworks approach: Sign unavailable during bounded northward acquisition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks approach: Sign unavailable during bounded northward acquisition.", + stream); } pbf_move_left_joystick(context, {x, y}, duration, 0ms); pbf_wait(context, 250ms); @@ -602,7 +633,8 @@ static void align_to_windworks_sign(VideoStream& stream, ProControllerContext& c context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks approach: Sign position did not converge.", stream); + "Windworks approach: Sign position did not converge.", + stream); } struct Route204ArrivalTracker { @@ -649,7 +681,7 @@ struct Route204ArrivalTracker { } } return anchors[0].seen && anchors[1].seen && std::abs(anchors[0].dx - anchors[1].dx) <= 1.0 / 64 && - std::abs(anchors[0].dy - anchors[1].dy) <= 1.0 / 54; + std::abs(anchors[0].dy - anchors[1].dy) <= 1.0 / 54; } bool wait_for_shadow(Clock::time_point now) const { // Bound the stationary wait even if one anchor keeps matching. @@ -671,7 +703,8 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 sign: No video frame.", stream); + "Route 204 sign: No video frame.", + stream); } if (dialogue.detect(*frame.frame) || menu.detect(*frame.frame)){ stream.log("[Route 204 sign] Next trainer triggered; stopping alignment."); @@ -698,9 +731,8 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Route 204 sign] " + landmark.name + " score=" + std::to_string(landmark.score) + - ", margin=" + std::to_string(landmark.margin) + - ", accepted=" + std::to_string(landmark.accepted) + ", offset=(" + - std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + ", margin=" + std::to_string(landmark.margin) + ", accepted=" + std::to_string(landmark.accepted) + + ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } const auto now = Route204ArrivalTracker::Clock::now(); // The broad pre-battle cache tolerance can accept the sign @@ -720,8 +752,8 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co if (arrived){ if (++confirmations >= 3){ stream.log(northbound - ? "[Route 204 sign] Left-of-sign northbound position confirmed." - : "[Route 204 sign] Position confirmed; ready for the next trainer approach."); + ? "[Route 204 sign] Left-of-sign northbound position confirmed." + : "[Route 204 sign] Position confirmed; ready for the next trainer approach."); return false; } }else if (arrival.wait_for_shadow(now)){ @@ -735,11 +767,11 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co confirmations = 0; missing = 0; stream.log(std::string("[Route 204 sign] ") + - (result.coarse ? "Sign-guided acquisition." : "Both landmarks located.")); + (result.coarse ? "Sign-guided acquisition." : "Both landmarks located.")); // At the wrong height, leftward movement can hit the // grass boundary. Acquire the sign's height first. const bool horizontal = (!result.coarse || std::abs(result.dy) <= 1.0 / 54) && - std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16; + std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16; const double error = horizontal ? result.dx : result.dy * 9.0 / 16; x = horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0; y = horizontal ? 0.0 : (result.dy < 0 ? 1.0 : -1.0); @@ -753,9 +785,9 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co }else{ confirmations = 0; if (++missing > 24){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 sign: Landmarks missing during bounded acquisition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 sign: Landmarks missing during bounded acquisition.", + stream); } // The sign is north of the defeated female trainer. // Search that height first, then return below it. @@ -782,8 +814,8 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co } if (blocked_east >= 3){ if (++detours > 2){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 204 north approach: Tyler obstruction did not clear.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north approach: Tyler obstruction did not clear.", stream); } stream.log("[Route 204 sign] Eastward movement blocked; passing below defeated Tyler."); @@ -797,8 +829,9 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co // Observe throughout each correction and its settle period, // so an early next-trainer trigger cancels remaining movement. if (watch_route204_trainer_event( - stream, context, - [x, y, duration, detour](ProControllerContext& ctx){ + stream, + context, + [x, y, duration, detour](ProControllerContext& ctx){ if (detour){ pbf_move_left_joystick(ctx, {0, -1}, 180ms, 0ms); pbf_move_left_joystick(ctx, {1, 0}, 400ms, 0ms); @@ -811,16 +844,18 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co pbf_wait(ctx, duration); } }, - "Route 204 sign alignment")){ + "Route 204 sign alignment")){ return true; } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 sign: Position could not be confirmed.", stream); + "Route 204 sign: Position could not be confirmed.", + stream); } -static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env, + VideoStream& stream, + ProControllerContext& context){ context.wait_for_all_requests(); // The cave's brown corridor and black map void distinguish an // indoor return from a battle fade or an outdoor scene. All ROIs @@ -863,7 +898,8 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env const auto before = stream.video().snapshot(); if (!before){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path entrance: No video frame.", stream); + "Ravaged Path entrance: No video frame.", + stream); } ShortDialogDetector current_dialogue; BattleMenuDetector current_battle(BattleType::TRAINER); @@ -890,9 +926,8 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Ravaged Path locator] " + landmark.name + " score=" + std::to_string(landmark.score) + - ", margin=" + std::to_string(landmark.margin) + - ", accepted=" + std::to_string(landmark.accepted) + ", offset=(" + - std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); + ", margin=" + std::to_string(landmark.margin) + ", accepted=" + std::to_string(landmark.accepted) + + ", offset=(" + std::to_string(landmark.dx) + "," + std::to_string(landmark.dy) + ")"); } const auto now = std::chrono::steady_clock::now(); auto fixed = result; @@ -912,7 +947,7 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env if (arrived){ if (++position_confirmations >= 3){ stream.log("[Ravaged Path locator] Entrance position confirmed; moving north into cave.", - COLOR_GREEN); + COLOR_GREEN); y = 1; duration = 1200ms; } @@ -939,8 +974,7 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env previous_north_dy = result.dy; previous_coarse_north = result.coarse && y > 0; if (blocked_north >= 4){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path locator: Northward coarse acquisition blocked; refusing repeated movement.", stream); } @@ -949,8 +983,7 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env // small height error that may still change the // sign's perspective enough to prevent matching. if (std::abs(result.dy) <= 1.0 / 72){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path locator: Near entrance, but sign remained shadowed; refusing blind correction.", stream); } @@ -970,9 +1003,9 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env if (boundary - 0.55 <= 0.12){ y = 0; stream.log("[Ravaged Path locator] Downward correction blocked by lass boundary.", COLOR_RED); - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path locator: Southern safety boundary reached.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Southern safety boundary reached.", + stream); } } }else{ @@ -983,7 +1016,7 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env // Only this recognizable route authorizes search. const auto route = match_route204_sign(*before.frame); const bool known_route = !route.landmarks.empty() && route.landmarks[0].accepted && - route.landmarks[0].score >= .94 && route.landmarks[0].margin >= .30; + route.landmarks[0].score >= .94 && route.landmarks[0].margin >= .30; if (known_route && acquisition_pulses < 8){ const bool sidestep = acquisition_pulses++ % 2 == 0; x = sidestep ? -1.0 : 0.0; @@ -991,9 +1024,9 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env duration = sidestep ? 150ms : 500ms; stream.log("[Ravaged Path locator] Recognized Route 204 sign; bounded left/north acquisition."); }else{ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Ravaged Path locator: Landmarks unavailable after bounded acquisition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path locator: Landmarks unavailable after bounded acquisition.", + stream); } } } @@ -1003,16 +1036,17 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env position_confirmations = 0; missing_since = std::chrono::steady_clock::now(); } - const int ret = run_until(stream, context, - [x, y, duration](ProControllerContext& ctx){ - if (x != 0 || y != 0){ - pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); - pbf_wait(ctx, 500ms); - }else{ - pbf_wait(ctx, duration); - } - }, - {{entrance}, {dialogue}, {battle}}); + const int ret = run_until(stream, + context, + [x, y, duration](ProControllerContext& ctx){ + if (x != 0 || y != 0){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 500ms); + }else{ + pbf_wait(ctx, duration); + } + }, + {{entrance}, {dialogue}, {battle}}); context.wait_for_all_requests(); if (ret == 1 || ret == 2){ handle_trainer(); @@ -1032,8 +1066,9 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env while (std::chrono::steady_clock::now() < deadline){ const auto frame = stream.video().snapshot(); if (!frame){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path entrance: No frame after transition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path entrance: No frame after transition.", + stream); } if (current_dialogue.detect(*frame.frame) || current_battle.detect(*frame.frame)){ trainer_seen = true; @@ -1059,8 +1094,8 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env position_confirmations = 0; missing_since = std::chrono::steady_clock::now(); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path entrance: Could not confirm entry after bounded approach.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path entrance: Could not confirm entry after bounded approach.", stream); } @@ -1070,31 +1105,33 @@ static void exit_ravaged_path_to_route204(VideoStream& stream, ProControllerCont // Observe the entire approach: reaching the exit during either // wall movement must cancel the remaining queued commands. WhiteScreenWatcher white; - const int result = run_until(stream, context, - [](ProControllerContext& ctx){ - // Deliberate overtravel establishes the walls despite - // variation in the position after Rock Smash. - pbf_move_left_joystick(ctx, {1, 0}, 3000ms, 100ms); - pbf_move_left_joystick(ctx, {0, -1}, 1800ms, 100ms); - for (size_t attempt = 0; attempt < 30; attempt++){ - pbf_move_left_joystick(ctx, {-1, 0}, 160ms, 80ms); - pbf_move_left_joystick(ctx, {0, -1}, 120ms, 80ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx, 1500ms); - }, - {{white}}); + const int result = run_until(stream, + context, + [](ProControllerContext& ctx){ + // Deliberate overtravel establishes the walls despite + // variation in the position after Rock Smash. + pbf_move_left_joystick(ctx, {1, 0}, 3000ms, 100ms); + pbf_move_left_joystick(ctx, {0, -1}, 1800ms, 100ms); + for (size_t attempt = 0; attempt < 30; attempt++){ + pbf_move_left_joystick(ctx, {-1, 0}, 160ms, 80ms); + pbf_move_left_joystick(ctx, {0, -1}, 120ms, 80ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx, 1500ms); + }, + {{white}}); if (result != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path north exit: No white transition during left/down search.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path north exit: No white transition during left/down search.", stream); } context.wait_for_all_requests(); stream.log("[Ravaged Path north exit] White transition detected; movement stopped."); WhiteScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 15s, {{visible}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Ravaged Path north exit: White transition did not finish.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Ravaged Path north exit: White transition did not finish.", + stream); } pbf_wait(context, 700ms); context.wait_for_all_requests(); @@ -1133,13 +1170,15 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry conversation: No video frame.", stream); + "Barry conversation: No video frame.", + stream); } const auto now = std::chrono::steady_clock::now(); if (now - started >= 180s){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry conversation: Dialogue did not finish.", stream); + "Barry conversation: Dialogue did not finish.", + stream); } if (dialogue.detect(*frame.frame)){ @@ -1199,16 +1238,21 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, // now supplies vertical travel instead of the blind 2579ms up move. pbf_move_left_joystick(context, {-1.0, 0.0}, 3700ms, 0ms); pbf_wait(context, 850ms); - align_to_overworld_landmarks(stream, context, match_jubilife_street_approach, "Jubilife street alignment", false, - true); + align_to_overworld_landmarks(stream, + context, + match_jubilife_street_approach, + "Jubilife street alignment", + false, + true); // Stop the upward approach if the story dialogue starts early. ShortDialogWatcher plaza_story(COLOR_YELLOW); - run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0.0, 1.0}, 2600ms, 0ms); - pbf_wait(ctx, 250ms); - }, - {{plaza_story}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 2600ms, 0ms); + pbf_wait(ctx, 250ms); + }, + {{plaza_story}}); align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, "Jubilife plaza alignment", true); approach_galactic_dialogue(stream, context); wait_for_trainer_battle_menu(stream, context); @@ -1222,8 +1266,10 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, pbf_move_left_joystick(context, {0.0, -1.0}, 600ms, 0ms); pbf_wait(context, 700ms); context.wait_for_all_requests(); - align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, - "Jubilife post-battle plaza alignment"); + align_to_overworld_landmarks(stream, + context, + match_jubilife_plaza_approach, + "Jubilife post-battle plaza alignment"); pbf_move_left_joystick(context, {0.000000, -1.000000}, 5141ms, 0ms); pbf_wait(context, 749ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 868ms, 0ms); @@ -1236,7 +1282,8 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, align_and_enter_jubilife_center(stream, context); if (!heal_pokemon(stream, context, "Jubilife")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Jubilife Center: Healing/exit did not complete.", stream); + "Jubilife Center: Healing/exit did not complete.", + stream); } pbf_move_left_joystick(context, {0.000000, -1.000000}, 1440ms, 0ms); @@ -1254,14 +1301,15 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, // Resume encounter protection after the Center and the following battle. if (!activate_repel(stream, context, 6)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Post-Center battle: Could not activate a new Repel from the Bag.", - stream); + "Post-Center battle: Could not activate a new Repel from the Bag.", + stream); } context.wait_for_all_requests(); const bool next_trainer_started = align_to_route204_sign(stream, context); if (!next_trainer_started){ const bool triggered = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {-1.0, 0.0}, 1200ms, 0ms); pbf_wait(ctx, 2500ms); @@ -1290,8 +1338,9 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, clear_post_battle_dialogue(stream, context); } if (!northbound_aligned){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 204 north approach: Trainer interruptions exhausted.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north approach: Trainer interruptions exhausted.", + stream); } enter_ravaged_path_from_route204(env, stream, context); //cave navigation @@ -1334,14 +1383,15 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, // to its bottom ledge, then cross right through her sightline. // Watch the entire approach so an early trigger stops movement. if (!watch_route204_trainer_event( - stream, context, - [](ProControllerContext& ctx){ + stream, + context, + [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {1, 0}, 600ms, 100ms); pbf_move_left_joystick(ctx, {0, -1}, 600ms, 100ms); pbf_move_left_joystick(ctx, {1, 0}, 1000ms, 0ms); pbf_wait(ctx, 3000ms); }, - "Aroma Lady Teighler timed ledge approach")){ + "Aroma Lady Teighler timed ledge approach")){ recover_teighler_trigger(stream, context); } stream.log("[Segment 02 battle] Aroma Lady Teighler: move slot 2."); @@ -1364,19 +1414,21 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 204 northbound: No video frame.", stream); + "Route 204 northbound: No video frame.", + stream); } const bool interrupted = northbound_dialogue.detect(*frame.frame) || northbound_menu.detect(*frame.frame) || - watch_route204_trainer_event( - stream, context, - [x, y, step](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {x, y}, step, 0ms); - }, - "Route 204 northbound movement"); + watch_route204_trainer_event( + stream, + context, + [x, y, step](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, step, 0ms); + }, + "Route 204 northbound movement"); if (interrupted){ if (++northbound_battles > 2){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 204 northbound: Too many trainer interruptions.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 northbound: Too many trainer interruptions.", stream); } battle("Route 204 north Bug Catcher"); @@ -1403,8 +1455,9 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, // directly if skipped. Do not seek out a missed trainer. for (size_t recovery = 0; align_to_route204_floaroma_turn(stream, context); recovery++){ if (recovery >= 2){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 204 north turn: Too many trainer interruptions.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 204 north turn: Too many trainer interruptions.", + stream); } battle("Route 204 north Bug Catcher"); } @@ -1435,19 +1488,21 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s stream.log("[Route transition] " + label); BlackScreenWatcher black; WhiteScreenWatcher white; - const int ret = run_until(stream, context, - [x, y, duration](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); - pbf_wait(ctx, 1500ms); - // Bounded doorway correction if the recorded move stops short. - for (size_t pulse = 0; pulse < 10; pulse++){ - pbf_move_left_joystick(ctx, {x, y}, 100ms, 100ms); - } - }, - {{black}, {white}}); + const int ret = run_until(stream, + context, + [x, y, duration](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, 1500ms); + // Bounded doorway correction if the recorded move stops short. + for (size_t pulse = 0; pulse < 10; pulse++){ + pbf_move_left_joystick(ctx, {x, y}, 100ms, 100ms); + } + }, + {{black}, {white}}); if (ret < 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Entrance/exit transition not detected.", stream); + label + ": Entrance/exit transition not detected.", + stream); } context.wait_for_all_requests(); BlackScreenDetector black_frame; @@ -1459,7 +1514,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame during transition.", stream); + label + ": No video frame during transition.", + stream); } const auto now = std::chrono::steady_clock::now(); if (black_frame.detect(*frame.frame) || white_frame.detect(*frame.frame)){ @@ -1473,7 +1529,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not finish.", stream); + label + ": Transition did not finish.", + stream); }; auto battle = [&](const std::string& label){ stream.log("[Segment 02 battle] " + label); @@ -1504,7 +1561,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s // heal_pokemon includes a watched exit; do not exit a second time. if (!heal_pokemon(stream, context, "Floaroma")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma Center: Healing/exit did not complete.", stream); + "Floaroma Center: Healing/exit did not complete.", + stream); } pbf_wait(context, 1000ms); context.wait_for_all_requests(); @@ -1537,7 +1595,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s talk("Floaroma Meadow honey seller and Works Key"); if (!clear_new_item_flags(stream, context, 6)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Floaroma Meadow: Bag NEW flags did not clear.", stream); + "Floaroma Meadow: Bag NEW flags did not clear.", + stream); } transition(0, -1, 1000ms, "Floaroma Meadow exit"); move(0, -1, 2600ms); @@ -1561,20 +1620,21 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s bool door_dialogue = false; for (size_t attempt = 0; attempt < 20 && !door_dialogue; attempt++){ ShortDialogWatcher dialogue(COLOR_YELLOW); - door_dialogue = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, 1}, 100ms, 0ms); - pbf_press_button(ctx, BUTTON_A, 150ms, 400ms); - pbf_move_left_joystick(ctx, {1, 0}, 40ms, 0ms); - pbf_wait(ctx, 250ms); - }, - {{dialogue}}) >= 0; + door_dialogue = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 100ms, 0ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 400ms); + pbf_move_left_joystick(ctx, {1, 0}, 40ms, 0ms); + pbf_wait(ctx, 250ms); + }, + {{dialogue}}) >= 0; context.wait_for_all_requests(); } if (!door_dialogue){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Valley Windworks: Door dialogue not detected after bounded up/A/right probes.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Valley Windworks: Door dialogue not detected after bounded up/A/right probes.", + stream); } stream.log("[Windworks door] Dialogue detected; confirm Works Key for two seconds."); pbf_mash_button(context, BUTTON_A, 2000ms); @@ -1586,7 +1646,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s clear_post_battle_dialogue(stream, context); // Entrance warning from the grunt. move(-1, 0, 2000ms); // Combined 1210ms + 817ms, separated by only 5ms. bool fought_grunt = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, 1}, 1250ms, 0ms); pbf_wait(ctx, 2500ms); @@ -1608,9 +1669,9 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s } pbf_wait(context, 250ms); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Windworks interior: Could not determine player position relative to the grunt.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: Could not determine player position relative to the grunt.", + stream); return position; }; auto position = grunt_position(); @@ -1618,7 +1679,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s stream.log("[Windworks interior] Below/level with grunt; recover north and interact."); for (size_t attempt = 0; attempt < 20 && !fought_grunt; attempt++){ fought_grunt = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, 1}, 100ms, 0ms); pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); @@ -1626,9 +1688,9 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s "Windworks interior below-grunt recovery"); } if (!fought_grunt){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Windworks interior: North/A recovery did not trigger the grunt battle.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Windworks interior: North/A recovery did not trigger the grunt battle.", + stream); } battle("Valley Windworks interior Galactic grunt (below recovery)"); } @@ -1647,8 +1709,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s } if (!above){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Windworks interior: Could not get above the defeated grunt.", - stream); + "Windworks interior: Could not get above the defeated grunt.", + stream); } }; if (fought_grunt){ @@ -1657,7 +1719,8 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s stream.log("[Windworks interior] Above grunt; try the rightward crossing for his battle."); } const bool crossing_event = watch_route204_trainer_event( - stream, context, + stream, + context, [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {1, 0}, 750ms, 0ms); pbf_wait(ctx, 2500ms); @@ -1700,7 +1763,8 @@ static void align_to_route205_marker(VideoStream& stream, ProControllerContext& const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); + label + ": No video frame.", + stream); } const auto result = forest ? match_route205_forest_entrance(*frame.frame) : match_route205_bridge(*frame.frame); if (result.at_target){ @@ -1719,7 +1783,7 @@ static void align_to_route205_marker(VideoStream& stream, ProControllerContext& acquired = true; missing = 0; stream.log("[" + label + "] Sign offset=(" + std::to_string(result.dx) + "," + - std::to_string(result.dy) + ")"); + std::to_string(result.dy) + ")"); const bool horizontal = std::abs(result.dx) > 1.0 / 50; const double error = horizontal ? std::abs(result.dx) : std::abs(result.dy); x = horizontal ? (result.dx < 0 ? -1 : 1) : 0; @@ -1727,8 +1791,8 @@ static void align_to_route205_marker(VideoStream& stream, ProControllerContext& duration = error < .08 ? 40ms : 80ms; }else if (++missing > 12){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Sign unavailable during bounded acquisition.", - stream); + label + ": Sign unavailable during bounded acquisition.", + stream); }else if (acquired){ // Never walk past a sign after losing a trusted match. pbf_wait(context, 250ms); @@ -1741,7 +1805,8 @@ static void align_to_route205_marker(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Sign position did not converge.", stream); + label + ": Sign position did not converge.", + stream); } static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContext& context){ @@ -1751,15 +1816,16 @@ static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContex const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 205 bank tree: No video frame.", stream); + "Route 205 bank tree: No video frame.", + stream); } const auto result = match_route205_bank_tree(*frame.frame); if (!result.located){ confirmed = 0; if (++missing >= 12){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 205 bank tree: Tree and blue-hat child unavailable; holding position.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 205 bank tree: Tree and blue-hat child unavailable; holding position.", + stream); } pbf_wait(context, 250ms); }else if (result.at_target){ @@ -1772,8 +1838,8 @@ static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContex }else{ confirmed = 0; missing = 0; - stream.log("[Route 205 bank tree] Offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + - ")"); + stream.log( + "[Route 205 bank tree] Offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); // Clear the north bridge lip before attempting to step west. // Once at bank height, keep horizontal priority beside the tree. const bool clear_bank = result.dy < -1.0 / 54; @@ -1787,26 +1853,29 @@ static void align_to_route205_bank_tree(VideoStream& stream, ProControllerContex context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 205 bank tree: Position did not converge.", stream); + "Route 205 bank tree: Position did not converge.", + stream); } -static void align_route205_patrol_position(VideoStream& stream, ProControllerContext& context, - const std::function& match, - const std::string& label){ +static void align_route205_patrol_position(VideoStream& stream, + ProControllerContext& context, + const std::function& match, + const std::string& label){ size_t confirmed = 0, missing = 0; for (size_t attempt = 0; attempt < 100; attempt++){ context.wait_for_all_requests(); const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame during patrol positioning.", stream); + label + ": No video frame during patrol positioning.", + stream); } const auto result = match(*frame.frame); if (!result.located){ confirmed = 0; if (++missing >= 16){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Patrol landmarks unavailable; holding position.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Patrol landmarks unavailable; holding position.", stream); } pbf_wait(context, 250ms); @@ -1820,20 +1889,22 @@ static void align_route205_patrol_position(VideoStream& stream, ProControllerCon }else{ confirmed = 0; missing = 0; - stream.log("[" + label + "] Patrol offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + - ")"); + stream.log( + "[" + label + "] Patrol offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); // Correct height first so the subsequent horizontal step // stays on the stair landing rather than hitting its cliff. const bool vertical = std::abs(result.dy) > 1.0 / 54; const double error = vertical ? std::abs(result.dy) : std::abs(result.dx); - pbf_move_left_joystick( - context, {vertical ? 0 : (result.dx < 0 ? -1.0 : 1.0), vertical ? (result.dy > 0 ? -1.0 : 1.0) : 0}, - error < .08 ? 40ms : 80ms, 0ms); + pbf_move_left_joystick(context, + {vertical ? 0 : (result.dx < 0 ? -1.0 : 1.0), vertical ? (result.dy > 0 ? -1.0 : 1.0) : 0}, + error < .08 ? 40ms : 80ms, + 0ms); pbf_wait(context, 250ms); } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Patrol position did not converge.", stream); + label + ": Patrol position did not converge.", + stream); } void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ @@ -1849,21 +1920,22 @@ void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& str clear_post_battle_dialogue(stream, context); }; auto patrol_battle = [&](const std::string& label, - const std::function& match, - const std::function& approach){ + const std::function& match, + const std::function& approach){ // One watcher covers every blind step, locator correction and // stationary wait. An early encounter cancels queued movement. if (!watch_route204_trainer_event( - stream, context, - [&](ProControllerContext& ctx){ + stream, + context, + [&](ProControllerContext& ctx){ approach(ctx); ctx.wait_for_all_requests(); align_route205_patrol_position(stream, ctx, match, label); pbf_wait(ctx, 45000ms); }, - label)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Trainer did not approach the confirmed patrol position.", + label)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Trainer did not approach the confirmed patrol position.", stream); } battle(label); @@ -1894,7 +1966,8 @@ void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& str move(0, 1, 800ms, 500ms); if (!activate_repel(stream, context, 6)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 205: Could not activate Repel before the grass.", stream); + "Route 205: Could not activate Repel before the grass.", + stream); } // Cross beside the golden tree first, then travel through the @@ -1915,21 +1988,20 @@ void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& str context.wait_for_all_requests(); const auto frame = stream.video().snapshot(); if (!frame){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Daniel north pass: No frame while checking for a dodged battle.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Daniel north pass: No frame while checking for a dodged battle.", stream); } return hiker_dialogue.detect(*frame.frame) || hiker_intro.detect(*frame.frame) || - hiker_menu.detect(*frame.frame) || hiker_transition.detect(*frame.frame); + hiker_menu.detect(*frame.frame) || hiker_transition.detect(*frame.frame); }; ShortDialogWatcher hiker_dialogue_watch(COLOR_YELLOW); DetectorToFinder hiker_intro_watch("Daniel battle intro", 250ms, COLOR_YELLOW); BattleMenuWatcher hiker_menu_watch(BattleType::TRAINER, COLOR_GREEN); BlackScreenWatcher hiker_transition_watch; - const bool hiker_encounter = - hiker_started() || - run_until( - stream, context, + const bool hiker_encounter = hiker_started() || + run_until(stream, + context, [&](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, 1}, 1200ms, 0ms); ctx.wait_for_all_requests(); @@ -1947,7 +2019,7 @@ void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& str stream.log("[Route 205] Daniel defeated; moving north from his battle landing to Zackary."); }else{ stream.log("[Route 205] Daniel dodged; continuing north up the stairs before turning right to Zackary.", - COLOR_GREEN); + COLOR_GREEN); } // Daniel stops us at his row when he engages. A skipped pass // has already carried us farther north. Resume from each pose @@ -2008,8 +2080,9 @@ void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro clear_post_battle_dialogue(stream, context); auto encounter = [&](const std::string& label, const std::function& approach){ if (!watch_route204_trainer_event(stream, context, approach, label)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": No trainer encounter on the recorded approach.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": No trainer encounter on the recorded approach.", + stream); } stream.log("[Eterna Forest battle] " + label); wait_for_trainer_battle_menu(stream, context); @@ -2093,8 +2166,9 @@ void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro context.wait_for_all_requests(); stream.log("[Eterna Forest] East exit and Route 205 bridges complete; at Eterna City approach.", COLOR_GREEN); } -void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ +void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, + VideoStream& stream, + ProControllerContext& context){ (void)env; // Clear Repel on the open street before locator corrections. // Reuse the helper's horizontal wiggles and decline renewal. @@ -2103,20 +2177,23 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& // door before entering. Healing starts only after map transition. align_to_overworld_landmarks(stream, context, match_eterna_center_entrance, "Eterna Center doorway", false, true); BlackScreenOverWatcher center_entry(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int entry = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{center_entry}}); + int entry = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{center_entry}}); if (entry != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Center doorway: Entry transition not confirmed.", stream); + "Eterna Center doorway: Entry transition not confirmed.", + stream); } context.wait_for_all_requests(); if (!heal_pokemon(stream, context, "Eterna")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Center exit was not confirmed.", stream); + "Eterna Center exit was not confirmed.", + stream); } pbf_wait(context, 500ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 500ms, 0ms); @@ -2126,45 +2203,63 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& pbf_move_left_joystick(context, {1.000000, -0.000000}, 144ms, 0ms); pbf_wait(context, 508ms); bool entered_house = false; - align_to_overworld_landmarks(stream, context, match_eterna_underground_house, "Underground Man house doorway", - false, true, false, match_eterna_underground_man, &entered_house); + align_to_overworld_landmarks(stream, + context, + match_eterna_underground_house, + "Underground Man house doorway", + false, + true, + false, + match_eterna_underground_man, + &entered_house); BlackScreenOverWatcher house_entry(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - if (!entered_house && run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{house_entry}}) != 0){ + if (!entered_house && + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{house_entry}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Underground Man house: Entry not confirmed.", stream); + "Underground Man house: Entry not confirmed.", + stream); } pbf_wait(context, 500ms); // Retain the recorded northward approach from the fixed entrance. pbf_move_left_joystick(context, {0, 1}, 290ms, 0ms); - align_to_overworld_landmarks(stream, context, match_eterna_underground_man, "Underground Man interaction", false, - true); + align_to_overworld_landmarks(stream, + context, + match_eterna_underground_man, + "Underground Man interaction", + false, + true); pbf_move_left_joystick(context, {0, 1}, 80ms, 200ms); pbf_press_button(context, BUTTON_A, 80ms, 200ms); wait_for_dialogue(stream, context, "Underground Man introduction"); // Recorded forced walk: 4.7s between house and street dialogue. // Keep watching for that gap plus a 500ms hardware buffer. - clear_post_battle_dialogue(stream, context, BUTTON_A, 5200ms, - "PokemonBDSP/Autostory/EternaUndergroundMan/ExplorerKitCard.png"); + clear_post_battle_dialogue(stream, + context, + BUTTON_A, + 5200ms, + "PokemonBDSP/Autostory/EternaUndergroundMan/ExplorerKitCard.png"); // The first-use tutorial opens the Bag, uses the Kit, and takes // us underground automatically. The dialogue helper also clears // Roark's introduction. Do not reopen the Bag to enter again. const auto underground_frame = stream.video().snapshot(); const char* underground_reference = underground_frame && underground_frame.frame->width() >= 1600 - ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrival.png" - : underground_frame && underground_frame.frame->width() >= 1100 - ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalMedium.png" - : "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalSmall.png"; + ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrival.png" + : underground_frame && underground_frame.frame->width() >= 1100 + ? "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalMedium.png" + : "PokemonBDSP/Autostory/EternaUndergroundMan/UndergroundArrivalSmall.png"; ImageMatchDetector underground_map(std::make_shared(RESOURCE_PATH() + underground_reference), - ImageFloatBox{.005, .110, .085, .130}, 30.0); + ImageFloatBox{.005, .110, .085, .130}, + 30.0); if (!underground_frame || !underground_map.detect(*underground_frame.frame)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Grand Underground tutorial: Underground arrival not confirmed.", - stream); + "Grand Underground tutorial: Underground arrival not confirmed.", + stream); } stream.log("[Grand Underground] Tutorial dialogue complete; returning with Y."); // User-requested full settling interval immediately before Y. @@ -2209,13 +2304,23 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& // Clear rewards before the approach can cross the gym doorway. context.wait_for_all_requests(); if (!clear_new_item_flags(stream, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "New reward flags could not be cleared before continuing.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "New reward flags could not be cleared before continuing.", + stream); } // Keep a loose horizontal street lane, but normalize the row: // the following fixed south leg requires a repeatable start. - align_to_overworld_landmarks(stream, context, match_eterna_center_entrance, "Eterna Center gym departure", false, - true, true, nullptr, nullptr, .03, .02); + align_to_overworld_landmarks(stream, + context, + match_eterna_center_entrance, + "Eterna Center gym departure", + false, + true, + true, + nullptr, + nullptr, + .03, + .02); // User's eternapctogym.txt with a shorter south leg; omit its final left127ms correction. // Start acquiring north of the old fence collision, then let // gym landmarks correct the final row and rightward approach. @@ -2239,12 +2344,21 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& // Watch throughout alignment, not just the final north // hold: a correction can cross the doorway first. BlackScreenWatcher entering(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - const int outcome = run_until( - stream, context, + const int outcome = run_until(stream, + context, [&](ProControllerContext& ctx){ - align_to_overworld_landmarks(stream, ctx, match_eterna_gym_entrance, "Eterna Gym doorway", false, - true, false, match_eterna_gym_statues, &already_inside, 0, 0, - GYM_MODERATE_ENTRY_CONFIDENCE); + align_to_overworld_landmarks(stream, + ctx, + match_eterna_gym_entrance, + "Eterna Gym doorway", + false, + true, + false, + match_eterna_gym_statues, + &already_inside, + 0, + 0, + GYM_MODERATE_ENTRY_CONFIDENCE); if (!already_inside){ pbf_move_left_joystick(ctx, {0, 1}, 3000ms, 0ms); pbf_wait(ctx, 5000ms); @@ -2253,11 +2367,15 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& {{entering}}); transition_seen = outcome == 0 || already_inside; stream.log("[Eterna Gym entry] Black transition detected=" + std::to_string(outcome == 0) + - ", interior seen during alignment=" + std::to_string(already_inside)); + ", interior seen during alignment=" + std::to_string(already_inside)); } if (transition_seen){ - BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{visible}}) != 0){ stream.log("[Eterna Gym entry] Transition has not finished; retrying within limit."); continue; @@ -2272,17 +2390,18 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Gym: No interior verification frame.", stream); + "Eterna Gym: No interior verification frame.", + stream); } const auto interior = match_eterna_gym_statues(*frame.frame); for (const auto& statue : interior.landmarks){ stream.log("[Eterna Gym statues] " + statue.name + " score=" + std::to_string(statue.score) + - ", margin=" + std::to_string(statue.margin) + ", offset=(" + std::to_string(statue.dx) + - "," + std::to_string(statue.dy) + ")"); + ", margin=" + std::to_string(statue.margin) + ", offset=(" + std::to_string(statue.dx) + "," + + std::to_string(statue.dy) + ")"); } stable = interior.located ? stable + 1 : 0; stream.log("[Eterna Gym statues] Both in expected positions=" + std::to_string(interior.located) + - ", stable frames=" + std::to_string(stable) + "/3"); + ", stable frames=" + std::to_string(stable) + "/3"); if (stable >= 3){ gym_confirmed = true; break; @@ -2294,9 +2413,9 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& } } if (!gym_confirmed){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Gym: Both entrance statues were not verified after three attempts.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna Gym: Both entrance statues were not verified after three attempts.", + stream); } stream.log("[Eterna City] Watched Explorer Kit/Cynthia events and verified gym entry complete.", COLOR_GREEN); } @@ -2310,35 +2429,85 @@ static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stre }; const Move sequence1[] = { - {DPAD_UP, 5}, {DPAD_LEFT, 2}, {DPAD_UP, 9}, {DPAD_RIGHT, 2}, {DPAD_UP, 7}, - {DPAD_LEFT, 3}, {DPAD_UP, 2}, {DPAD_LEFT, 5}, {DPAD_DOWN, 4}, + {DPAD_UP, 5}, + {DPAD_LEFT, 2}, + {DPAD_UP, 9}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 7}, + {DPAD_LEFT, 3}, + {DPAD_UP, 2}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 4}, }; const Move sequence2[] = { - {DPAD_UP, 6}, {DPAD_LEFT, 6}, {DPAD_DOWN, 9}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 5}, - {DPAD_LEFT, 2}, {DPAD_DOWN, 4}, {DPAD_LEFT, 2}, {DPAD_DOWN, 6}, {DPAD_RIGHT, 5}, + {DPAD_UP, 6}, + {DPAD_LEFT, 6}, + {DPAD_DOWN, 9}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 5}, }; const Move sequence3[] = { - {DPAD_LEFT, 5}, {DPAD_UP, 6}, {DPAD_RIGHT, 2}, {DPAD_UP, 5}, {DPAD_RIGHT, 2}, - {DPAD_UP, 5}, {DPAD_LEFT, 2}, {DPAD_UP, 8}, {DPAD_RIGHT, 7}, {DPAD_DOWN, 2}, - {DPAD_RIGHT, 5}, {DPAD_DOWN, 6}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 5}, {DPAD_LEFT, 2}, - {DPAD_DOWN, 4}, {DPAD_RIGHT, 6}, {DPAD_UP, 3}, {DPAD_RIGHT, 2}, + {DPAD_LEFT, 5}, + {DPAD_UP, 6}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 5}, + {DPAD_LEFT, 2}, + {DPAD_UP, 8}, + {DPAD_RIGHT, 7}, + {DPAD_DOWN, 2}, + {DPAD_RIGHT, 5}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 5}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_RIGHT, 6}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, }; const Move sequence4[] = { - {DPAD_LEFT, 2}, {DPAD_DOWN, 4}, {DPAD_LEFT, 6}, {DPAD_DOWN, 6}, {DPAD_RIGHT, 11}, - {DPAD_UP, 3}, {DPAD_RIGHT, 2}, {DPAD_UP, 16}, {DPAD_LEFT, 6}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 6}, + {DPAD_DOWN, 6}, + {DPAD_RIGHT, 11}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 16}, + {DPAD_LEFT, 6}, }; const Move sequence5[] = { - {DPAD_RIGHT, 6}, {DPAD_DOWN, 17}, {DPAD_LEFT, 5}, {DPAD_DOWN, 2}, {DPAD_LEFT, 8}, - {DPAD_UP, 11}, {DPAD_RIGHT, 2}, {DPAD_UP, 7}, {DPAD_LEFT, 2}, {DPAD_UP, 7}, + {DPAD_RIGHT, 6}, + {DPAD_DOWN, 17}, + {DPAD_LEFT, 5}, + {DPAD_DOWN, 2}, + {DPAD_LEFT, 8}, + {DPAD_UP, 11}, + {DPAD_RIGHT, 2}, + {DPAD_UP, 7}, + {DPAD_LEFT, 2}, + {DPAD_UP, 7}, }; const Move sequence6[] = { - {DPAD_DOWN, 10}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 4}, {DPAD_LEFT, 2}, - {DPAD_DOWN, 13}, {DPAD_RIGHT, 2}, {DPAD_DOWN, 2}, + {DPAD_DOWN, 10}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 4}, + {DPAD_LEFT, 2}, + {DPAD_DOWN, 13}, + {DPAD_RIGHT, 2}, + {DPAD_DOWN, 2}, }; for (int i = 0; i < 7; i++){ @@ -2461,8 +2630,10 @@ std::string AutoStory_Segment_02::end_text() const { return "End: Second Gym Badge acquired (Eterna City)."; } -void AutoStory_Segment_02::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_02::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -2488,16 +2659,23 @@ std::string AutoStory_checkpoint_0201::end_text() const { return "Outside the central house in Floaroma Town immediately after exiting, facing south, with Repel inactive."; } -void AutoStory_checkpoint_0201::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0201::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0201(env, context, options, stats); } -void checkpoint_0201(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0201(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ roark_to_floaroma(env, env.console, context); }, @@ -2513,10 +2691,15 @@ std::string AutoStory_checkpoint_0202::start_text() const { std::string AutoStory_checkpoint_0202::end_text() const { return "Standing in Valley Windworks after defeating Commander Mars."; } -void AutoStory_checkpoint_0202::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0202::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ floaroma_to_mars(env, env.console, context); }, @@ -2533,16 +2716,23 @@ std::string AutoStory_checkpoint_0203::end_text() const { return "On Route 205 outside Eterna Forest, west of the northern 205 sign, facing north with Repel active."; } -void AutoStory_checkpoint_0203::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0203::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0203(env, context, options, stats); } -void checkpoint_0203(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0203(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ mars_to_eterna_forest(env, env.console, context); }, @@ -2560,16 +2750,23 @@ std::string AutoStory_checkpoint_0204::end_text() const { return "At the Eterna City west approach after leaving Eterna Forest and crossing the Route 205 bridges, facing east."; } -void AutoStory_checkpoint_0204::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0204::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0204(env, context, options, stats); } -void checkpoint_0204(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0204(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // TODO: implement Segment 02 gameplay logic eterna_forest(env, env.console, context); @@ -2588,16 +2785,23 @@ std::string AutoStory_checkpoint_0205::end_text() const { return "Inside Eterna Gym at the entrance, facing north."; } -void AutoStory_checkpoint_0205::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0205::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0205(env, context, options, stats); } -void checkpoint_0205(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0205(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // TODO: implement Segment 02 gameplay logic eterna_forest_exit_to_gym(env, env.console, context); @@ -2615,16 +2819,23 @@ std::string AutoStory_checkpoint_0206::end_text() const { return "Outside Eterna Gym after defeating Gardenia and acquiring the Forest Badge."; } -void AutoStory_checkpoint_0206::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_checkpoint_0206::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0206(env, context, options, stats); } -void checkpoint_0206(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0206(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ //#. Eterna Gym navigation and battle gym_Puzzle_02(env, env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h index 198590774f..0d9b7e8d61 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.h @@ -20,16 +20,21 @@ enum class RepelRenewalChoice { YES, NO }; -void force_repel_expiry(VideoStream& stream, ProControllerContext& context, RepelRenewalChoice choice, - bool north_south = false, bool require_expiry = false); +void force_repel_expiry(VideoStream& stream, + ProControllerContext& context, + RepelRenewalChoice choice, + bool north_south = false, + bool require_expiry = false); class AutoStory_Segment_02 : 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint { @@ -37,14 +42,18 @@ class AutoStory_checkpoint_0201 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: first badge acquired // end: Floaroma central house exit, facing south, Repel inactive -void checkpoint_0201(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0201(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); // Save/retry boundary at the central Floaroma house exit, matching 02.01 end. class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { @@ -52,8 +61,10 @@ class AutoStory_checkpoint_0202 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { @@ -61,55 +72,71 @@ class AutoStory_checkpoint_0203 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0203(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0203(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0204 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0204(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0204(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0205 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0205(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0205(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_checkpoint_0206 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: first badge acquired // end: second Gym Badge acquired (Eterna City) -void checkpoint_0206(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0206(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 9e965562a5..2cdad75774 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -46,7 +46,8 @@ static void segment03_finish_transition(VideoStream& stream, ProControllerContex BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); if (wait_until(stream, context, 30s, {{visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not finish.", stream); + label + ": Transition did not finish.", + stream); } pbf_wait(context, 500ms); context.wait_for_all_requests(); @@ -54,11 +55,14 @@ static void segment03_finish_transition(VideoStream& stream, ProControllerContex // Stationary destination confirmation: do not steer during an NPC reward // scene or declare arrival from a fade that has only just started. -static void segment03_confirm_location(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, - size_t observations = 20, bool advance_dialogue = false, - bool require_target = false, - OverworldMatchResult (*instruction_card)(const ImageRGB32&) = nullptr){ +static void segment03_confirm_location(VideoStream& stream, + ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), + const std::string& label, + size_t observations = 20, + bool advance_dialogue = false, + bool require_target = false, + OverworldMatchResult (*instruction_card)(const ImageRGB32&) = nullptr){ context.wait_for_all_requests(); size_t confirmations = 0; for (size_t observation = 0; observation < observations; observation++){ @@ -90,45 +94,55 @@ static void segment03_confirm_location(VideoStream& stream, ProControllerContext context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Expected scene not confirmed.", stream); + label + ": Expected scene not confirmed.", + stream); } // Watch the approach itself, cancelling queued movement as soon as the // trainer speaks or a battle starts. The existing battle engine owns combat. -static void segment03_expected_encounter(VideoStream& stream, ProControllerContext& context, const std::string& label, - const std::function& approach, - Milliseconds encounter_wait = 45000ms, bool require_encounter = true){ +static void segment03_expected_encounter(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + const std::function& approach, + Milliseconds encounter_wait = 45000ms, + bool require_encounter = true){ context.wait_for_all_requests(); ShortDialogWatcher dialogue; StartBattleDetector starting(stream.overlay()); BattleMenuWatcher menu(BattleType::TRAINER); - const int encountered = run_until(stream, context, - [&](ProControllerContext& ctx){ - approach(ctx); - pbf_wait(ctx, encounter_wait); - }, - {{dialogue}, {starting}, {menu}}); + const int encountered = run_until(stream, + context, + [&](ProControllerContext& ctx){ + approach(ctx); + pbf_wait(ctx, encounter_wait); + }, + {{dialogue}, {starting}, {menu}}); if (encountered < 0){ if (!require_encounter){ stream.log("[" + label + "] No encounter within " + std::to_string(encounter_wait.count()) + - "ms after approach; trainer dodged, continuing.", - COLOR_GREEN); + "ms after approach; trainer dodged, continuing.", + COLOR_GREEN); return; } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Expected trainer encounter did not start.", stream); + label + ": Expected trainer encounter did not start.", + stream); } stream.log("[" + label + "] Encounter detected; route movement cancelled."); fight_trainer_using_moves(stream, context, 2, label, false); } -static void segment03_wait_image(VideoStream& stream, ProControllerContext& context, const std::string& resource, - const ImageFloatBox& box, const std::string& label){ +static void segment03_wait_image(VideoStream& stream, + ProControllerContext& context, + const std::string& resource, + const ImageFloatBox& box, + const std::string& label){ auto reference = std::make_shared(RESOURCE_PATH() + resource); ImageMatchWatcher ready(reference, box, 25.0, false, 250ms); if (wait_until(stream, context, 5s, {{ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Visual confirmation missing.", stream); + label + ": Visual confirmation missing.", + stream); } } @@ -136,7 +150,8 @@ static void segment03_register_bike(VideoStream& stream, ProControllerContext& c // Receiving Bike adds new flags after the earlier pre-shop Bag pass. if (!clear_new_item_flags(stream, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Bike: Could not clear reward flags.", stream); + "Eterna Bike: Could not clear reward flags.", + stream); } BagItemTemplate bike; bike.reference_path = "PokemonBDSP/Autostory/EternaBikeShop/BikeBag.png"; @@ -144,7 +159,8 @@ static void segment03_register_bike(VideoStream& stream, ProControllerContext& c bike.reference_row = 4; if (!find_bag_item(stream, context, bike, 8)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Bike: Bike selection not confirmed.", stream); + "Eterna Bike: Bike selection not confirmed.", + stream); } // Locate the Key Items tab and Bike first, then use the fixed Register // sequence without depending on popup, wheel, or confirmation images. @@ -171,18 +187,19 @@ static void segment03_align_lower_stair_lane(VideoStream& stream, ProControllerC const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 lower stair lane: No video frame.", stream); + "Route 208 lower stair lane: No video frame.", + stream); } const auto match = match_route208_lower_stair_lane(*frame.frame); stream.log("[Route 208 lower stair lane] located=" + std::to_string(match.located) + - ", target=" + std::to_string(match.at_target) + ", offset=(" + std::to_string(match.dx) + "," + - std::to_string(match.dy) + ")"); + ", target=" + std::to_string(match.at_target) + ", offset=(" + std::to_string(match.dx) + "," + + std::to_string(match.dy) + ")"); if (!match.located){ confirmations = 0; if (++missing > 6){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 lower stair lane: Cliff/tree anchors missing; no blind search.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: Cliff/tree anchors missing; no blind search.", + stream); } pbf_wait(context, 500ms); continue; @@ -200,28 +217,35 @@ static void segment03_align_lower_stair_lane(VideoStream& stream, ProControllerC const double error = vertical ? match.dy * 9.0 / 16 : match.dx; const auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 1.0 / 64 ? 40ms : 16ms; pbf_move_left_joystick(context, - {vertical ? 0.0 : (error > 0 ? 1.0 : -1.0), vertical ? (error < 0 ? 1.0 : -1.0) : 0.0}, - duration, 0ms); + {vertical ? 0.0 : (error > 0 ? 1.0 : -1.0), vertical ? (error < 0 ? 1.0 : -1.0) : 0.0}, + duration, + 0ms); pbf_wait(context, 500ms); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 208 lower stair lane: Could not confirm the safe descent endpoint.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 lower stair lane: Could not confirm the safe descent endpoint.", stream); } -static bool segment03_cycling_move(VideoStream& stream, ProControllerContext& context, double x, double y, - Milliseconds duration, Milliseconds rest, const std::string& label, - bool require_transition = false){ +static bool segment03_cycling_move(VideoStream& stream, + ProControllerContext& context, + double x, + double y, + Milliseconds duration, + Milliseconds rest, + const std::string& label, + bool require_transition = false){ context.wait_for_all_requests(); BlackScreenWatcher black; WhiteScreenWatcher white; BattleMenuWatcher battle(BattleType::TRAINER); - const int result = run_until(stream, context, - [&](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); - pbf_wait(ctx, rest); - }, - {{black}, {white}, {battle}}); + const int result = run_until(stream, + context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {x, y}, duration, 0ms); + pbf_wait(ctx, rest); + }, + {{black}, {white}, {battle}}); if (result == 2){ fight_trainer_using_moves(stream, context, 2, label + " encounter", false); return false; @@ -229,19 +253,29 @@ static bool segment03_cycling_move(VideoStream& stream, ProControllerContext& co if (result < 0){ if (require_transition){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not start.", stream); + label + ": Transition did not start.", + stream); } return false; } - BlackScreenWatcher black_gone(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); - WhiteScreenWatcher white_gone(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 500, 10, WhiteScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher black_gone(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); + WhiteScreenWatcher white_gone(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 500, + 10, + WhiteScreenWatcher::FinderType::GONE, + 500ms); const int finished = result == 1 ? wait_until(stream, context, 30s, {{white_gone}}) - : wait_until(stream, context, 30s, {{black_gone}}); + : wait_until(stream, context, 30s, {{black_gone}}); if (finished != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Transition did not finish.", stream); + label + ": Transition did not finish.", + stream); } // A cleared fade can precede restored movement control. pbf_wait(context, 2000ms); @@ -261,7 +295,7 @@ static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContex const auto frame = stream.video().snapshot(); const auto match = frame ? match_cycling_road_bridge_approach(*frame.frame) : OverworldMatchResult{}; const bool stable = match.located && have_pose && std::abs(match.dx - previous_dx) <= 1.0 / 192 && - std::abs(match.dy - previous_dy) <= 1.0 / 108; + std::abs(match.dy - previous_dy) <= 1.0 / 108; // Reliable rail/deck geometry and a settled pose renew the budget. // Keep correcting while the visual alignment remains strong. if (stable){ @@ -269,15 +303,14 @@ static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContex }else{ ++observations_without_strong_alignment; } - std::string diagnostic = - "[Cycling Road bridge] observation=" + std::to_string(observation + 1) + + std::string diagnostic = "[Cycling Road bridge] observation=" + std::to_string(observation + 1) + " located=" + std::to_string(match.located) + " target=" + std::to_string(match.at_target) + " stable=" + std::to_string(stable) + " observations_without_strong_alignment=" + std::to_string(observations_without_strong_alignment) + " offset=" + std::to_string(match.dx) + "," + std::to_string(match.dy); for (const auto& anchor : match.landmarks){ diagnostic += " [" + anchor.name + " coverage=" + std::to_string(anchor.score) + - " accepted=" + std::to_string(anchor.accepted) + "]"; + " accepted=" + std::to_string(anchor.accepted) + "]"; } stream.log(diagnostic); double x = 0, y = 0; @@ -287,16 +320,16 @@ static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContex have_pose = false; if (++missing >= 6){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Bridge deck and right rail not acquired.", - stream); + "Cycling Road: Bridge deck and right rail not acquired.", + stream); } rest = 500ms; }else{ missing = 0; if (std::abs(match.dx) > .32 || std::abs(match.dy) > .60){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Bridge alignment offset exceeds bounds.", - stream); + "Cycling Road: Bridge alignment offset exceeds bounds.", + stream); } if (!stable){ // The camera keeps easing after the bicycle stops. Require @@ -332,28 +365,30 @@ static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContex ShortDialogWatcher dialogue; StartBattleDetector starting(stream.overlay()); BattleMenuWatcher menu(BattleType::TRAINER); - const int interrupted = run_until(stream, context, - [&](ProControllerContext& ctx){ - if (hold > 0ms){ - pbf_move_left_joystick(ctx, {x, y}, hold, rest); - }else{ - pbf_wait(ctx, rest); - } - ctx.wait_for_all_requests(); - }, - {{black}, {white}, {dialogue}, {starting}, {menu}}); + const int interrupted = run_until(stream, + context, + [&](ProControllerContext& ctx){ + if (hold > 0ms){ + pbf_move_left_joystick(ctx, {x, y}, hold, rest); + }else{ + pbf_wait(ctx, rest); + } + ctx.wait_for_all_requests(); + }, + {{black}, {white}, {dialogue}, {starting}, {menu}}); if (interrupted >= 2){ stream.log("[Cycling Road] Encounter during bridge approach; alignment movement cancelled."); return true; } if (interrupted >= 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Unexpected transition during bridge alignment.", - stream); + "Cycling Road: Unexpected transition during bridge alignment.", + stream); } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Bridge lane alignment did not converge.", stream); + "Cycling Road: Bridge lane alignment did not converge.", + stream); } @@ -364,17 +399,18 @@ static void segment03_cycle_road_battles(VideoStream& stream, ProControllerConte ShortDialogWatcher dialogue; StartBattleDetector starting(stream.overlay()); BattleMenuWatcher menu(BattleType::TRAINER); - return run_until(stream, context, - [&](ProControllerContext& ctx){ - if (initial){ - pbf_move_left_joystick(ctx, {0, -1}, 3300ms, 0ms); - } - // From the first encounter onward the controller stays - // neutral. The bicycle rolls naturally into the next trainer. - pbf_wait(ctx, completed >= 3 ? 7000ms : 45000ms); - ctx.wait_for_all_requests(); - }, - {{dialogue}, {starting}, {menu}}); + return run_until(stream, + context, + [&](ProControllerContext& ctx){ + if (initial){ + pbf_move_left_joystick(ctx, {0, -1}, 3300ms, 0ms); + } + // From the first encounter onward the controller stays + // neutral. The bicycle rolls naturally into the next trainer. + pbf_wait(ctx, completed >= 3 ? 7000ms : 45000ms); + ctx.wait_for_all_requests(); + }, + {{dialogue}, {starting}, {menu}}); }; while (completed < 4){ const int result = encounter_pending ? 0 : watch(completed == 0); @@ -384,9 +420,9 @@ static void segment03_cycle_road_battles(VideoStream& stream, ProControllerConte stream.log("[Cycling Road] Three battles complete; 7s without another encounter. Continuing south."); return; } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Cycling Road: Expected at least three battles before the south gate.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Expected at least three battles before the south gate.", + stream); } fight_trainer_using_moves(stream, context, 2, "Cycling Road encounter " + std::to_string(completed + 1), false); ++completed; @@ -395,44 +431,56 @@ static void segment03_cycle_road_battles(VideoStream& stream, ProControllerConte stream.log("[Cycling Road] Four battles complete; skipping the quiet wait."); } -static void segment03_doorway(VideoStream& stream, ProControllerContext& context, bool north, - std::chrono::milliseconds duration, const std::string& label, double horizontal = 0){ +static void segment03_doorway(VideoStream& stream, + ProControllerContext& context, + bool north, + std::chrono::milliseconds duration, + const std::string& label, + double horizontal = 0){ context.wait_for_all_requests(); // Horizontal HQ stairs must cancel at fade-out, before loading can // swallow or carry movement onto the next floor. Vertical door callers // retain their existing transition behavior. if (horizontal != 0){ BlackScreenWatcher entering; - if (run_until(stream, context, - [&](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {horizontal, 0}, duration, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{entering}}) != 0){ + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {horizontal, 0}, duration, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{entering}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Stair transition did not start.", stream); + label + ": Stair transition did not start.", + stream); } - BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Stair transition did not finish.", stream); + label + ": Stair transition did not finish.", + stream); } pbf_wait(context, 500ms); context.wait_for_all_requests(); return; } BlackScreenOverWatcher transition(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int result = - run_until(stream, context, - [&](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, north ? 1.0 : -1.0}, duration, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{transition}}); + int result = run_until(stream, + context, + [&](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, north ? 1.0 : -1.0}, duration, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{transition}}); if (result != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Doorway transition not confirmed.", stream); + label + ": Doorway transition not confirmed.", + stream); } segment03_finish_transition(stream, context, label); } @@ -453,7 +501,8 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 2621ms); if (!heal_pokemon(stream, context, "Eterna before Galactic HQ")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Center exit was not confirmed.", stream); + "Eterna Center exit was not confirmed.", + stream); } pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1895ms, 0ms); pbf_wait(context, 130ms); @@ -474,14 +523,14 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ const auto position = frame ? match_galactic_hq_cut(*frame.frame) : OverworldMatchResult{}; for (const auto& anchor : position.landmarks){ stream.log("[Galactic HQ Cut] " + anchor.name + " score=" + std::to_string(anchor.score) + - ", margin=" + std::to_string(anchor.margin) + ", offset=(" + std::to_string(anchor.dx) + "," + - std::to_string(anchor.dy) + ")"); + ", margin=" + std::to_string(anchor.margin) + ", offset=(" + std::to_string(anchor.dx) + "," + + std::to_string(anchor.dy) + ")"); } if (!position.located){ hq_cut_confirmations = 0; if (++hq_cut_missing > 6){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Galactic HQ: Front bush sign/Center alignment not confirmed.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush sign/Center alignment not confirmed.", stream); } // Let a passing shadow clear; never search by moving blindly. @@ -491,8 +540,9 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ } hq_cut_missing = 0; if (std::abs(position.dx) > .20){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Galactic HQ: Front bush alignment offset exceeds bounds.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush alignment offset exceeds bounds.", + stream); } if (position.at_target){ if (++hq_cut_confirmations >= 3){ @@ -500,44 +550,48 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ } }else{ hq_cut_confirmations = 0; - pbf_move_left_joystick(context, {position.dx > 0 ? 1.0 : -1.0, 0}, - std::abs(position.dx) > 1.0 / 24 ? 80ms : 40ms, 0ms); + pbf_move_left_joystick(context, + {position.dx > 0 ? 1.0 : -1.0, 0}, + std::abs(position.dx) > 1.0 / 24 ? 80ms : 40ms, + 0ms); } pbf_wait(context, 250ms); context.wait_for_all_requests(); } if (hq_cut_confirmations < 3){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Front bush column did not converge.", stream); + "Galactic HQ: Front bush column did not converge.", + stream); } // Some attempts reach the bush before the interaction accepts input. // Confirm its dialogue while stationary, then finish the Cut animation. ShortDialogWatcher hq_cut_dialogue; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - for (size_t press = 0; press < 30; press++){ - pbf_move_left_joystick(ctx, {0, 1}, 80ms, 100ms); - pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); - } - }, - {{hq_cut_dialogue}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Front bush interaction did not produce dialogue.", - stream); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 30; press++){ + pbf_move_left_joystick(ctx, {0, 1}, 80ms, 100ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + } + }, + {{hq_cut_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Front bush interaction did not produce dialogue.", + stream); } clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); context.wait_for_all_requests(); // Cut succeeds, but the Center exit can leave us one tile west of // the HQ doorway. Do not replay the interior route against its wall. BlackScreenWatcher hq_entering; - const int hq_entry = run_until( - stream, context, + const int hq_entry = run_until(stream, + context, [&](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, 1}, 1507ms, 193ms); ctx.wait_for_all_requests(); for (size_t correction = 0; correction < 4; correction++){ stream.log("[Galactic HQ entry] No transition; correcting east toward doorway, attempt " + - std::to_string(correction + 1) + "/4."); + std::to_string(correction + 1) + "/4."); pbf_move_left_joystick(ctx, {1, 0}, 80ms, 100ms); pbf_move_left_joystick(ctx, {0, 1}, 350ms, 250ms); ctx.wait_for_all_requests(); @@ -546,16 +600,21 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ }, {{hq_entering}}); if (hq_entry != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Doorway transition not detected after bounded east correction.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Doorway transition not detected after bounded east correction.", + stream); } stream.log("[Galactic HQ entry] Doorway transition detected; waiting for interior."); - BlackScreenWatcher hq_visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher hq_visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{hq_visible}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Entry transition did not finish.", stream); + "Galactic HQ: Entry transition did not finish.", + stream); } pbf_wait(context, 500ms); context.wait_for_all_requests(); @@ -571,11 +630,12 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ // stopped and reuse the battle handler only if its command menu appears. // A party that fought both together needs no second battle handler. BattleMenuWatcher hq_next_grunt(BattleType::TRAINER); - const int hq_next_battle = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 30000ms); - }, - {{hq_next_grunt}}); + const int hq_next_battle = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 30000ms); + }, + {{hq_next_grunt}}); if (hq_next_battle == 0){ stream.log("[Galactic HQ] Second entrance grunt detected before stair movement."); fight("Galactic HQ second entrance grunt", false); @@ -626,17 +686,18 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ // Advance the introduction before entering the battle handler's // B-based waiting loop. A single press may only open the first text. BattleMenuWatcher jupiter_menu(BattleType::TRAINER); - if (run_until(stream, context, - [](ProControllerContext& ctx){ - for (size_t press = 0; press < 30; press++){ - pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); - } - pbf_wait(ctx, 15000ms); - }, - {{jupiter_menu}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Jupiter interaction did not reach the battle menu.", - stream); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 30; press++){ + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + } + pbf_wait(ctx, 15000ms); + }, + {{jupiter_menu}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Galactic HQ: Jupiter interaction did not reach the battle menu.", + stream); } fight("Commander Jupiter", true); // Jupiter's departure and the rescued family have chained dialogue. @@ -668,15 +729,17 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 1092ms); context.wait_for_all_requests(); BlackScreenOverWatcher hq_leaving(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, -1}, 2350ms, 0ms); - pbf_move_left_joystick(ctx, {-1, -1}, 2744ms, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{hq_leaving}}) != 0){ + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 2350ms, 0ms); + pbf_move_left_joystick(ctx, {-1, -1}, 2744ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{hq_leaving}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Galactic HQ: Exit transition not confirmed.", stream); + "Galactic HQ: Exit transition not confirmed.", + stream); } segment03_finish_transition(stream, context, "Galactic HQ exit"); segment03_confirm_location(stream, context, match_galactic_hq_exterior, "Galactic HQ exterior"); @@ -697,7 +760,8 @@ static void eterna_mart_escape_ropes(VideoStream& stream, ProControllerContext& pbf_move_left_joystick(context, {0, 1}, 885ms, 300ms); if (!buy_items(stream, context, {{ShopItem::EscapeRope, 3}})){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Mart: Could not buy 3 Escape Ropes.", stream); + "Eterna Mart: Could not buy 3 Escape Ropes.", + stream); } segment03_doorway(stream, context, false, 5000ms, "Eterna Mart exit"); @@ -707,8 +771,12 @@ static void eterna_mart_escape_ropes(VideoStream& stream, ProControllerContext& pbf_move_left_joystick(context, {-1, 0}, 587ms, 100ms); segment03_doorway(stream, context, true, 5000ms, "Eterna Center return after Mart"); segment03_doorway(stream, context, false, 5000ms, "Eterna Center exit after Mart"); - align_to_overworld_landmarks(stream, context, match_eterna_center_departure, "Eterna Center departure after Mart", - false, true); + align_to_overworld_landmarks(stream, + context, + match_eterna_center_departure, + "Eterna Center departure after Mart", + false, + true); } static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext& context){ @@ -722,8 +790,8 @@ static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext // Watch the entire approach AND the locator. A fade cancels all queued // input, even if the doorway is crossed before alignment has finished. BlackScreenWatcher entering; - const int result = run_until( - stream, context, + const int result = run_until(stream, + context, [&](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, -1}, 1514ms, 0ms); pbf_wait(ctx, 177ms); @@ -731,8 +799,15 @@ static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext pbf_wait(ctx, 522ms); pbf_move_left_joystick(ctx, {1, 0}, 144ms, 0ms); pbf_wait(ctx, 439ms); - align_to_overworld_landmarks(stream, ctx, match_eterna_cycle_shop_entrance, "Eterna bicycle shop entrance", - false, true, false, match_eterna_cycle_shop_interior, &entered); + align_to_overworld_landmarks(stream, + ctx, + match_eterna_cycle_shop_entrance, + "Eterna bicycle shop entrance", + false, + true, + false, + match_eterna_cycle_shop_interior, + &entered); if (!entered){ pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); pbf_wait(ctx, 3000ms); @@ -741,7 +816,8 @@ static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext {{entering}}); if (result != 0 && !entered){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna bicycle shop: Entry transition not detected.", stream); + "Eterna bicycle shop: Entry transition not detected.", + stream); } segment03_finish_transition(stream, context, "Eterna bicycle shop entrance"); segment03_confirm_location(stream, context, match_eterna_cycle_shop_interior, "Eterna bicycle shop interior"); @@ -760,16 +836,25 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 27ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 745ms, 0ms); pbf_wait(context, 868ms); - align_to_overworld_landmarks(stream, context, match_eterna_center_entrance, "Eterna Center after Jupiter", false, - true); + align_to_overworld_landmarks(stream, + context, + match_eterna_center_entrance, + "Eterna Center after Jupiter", + false, + true); segment03_doorway(stream, context, true, 4000ms, "Eterna Center entrance after Jupiter"); if (!heal_pokemon(stream, context, "Eterna after Jupiter")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Eterna Center exit not confirmed.", stream); + "Eterna Center exit not confirmed.", + stream); } segment03_finish_transition(stream, context, "Eterna Center heal exit"); - align_to_overworld_landmarks(stream, context, match_eterna_center_departure, "Eterna Center departure to Mart", - false, true); + align_to_overworld_landmarks(stream, + context, + match_eterna_center_departure, + "Eterna Center departure to Mart", + false, + true); eterna_mart_escape_ropes(stream, context); segment03_enter_cycle_shop(stream, context); pbf_move_left_joystick(context, {0.000000, 1.000000}, 505ms, 0ms); @@ -783,22 +868,29 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ // Cancel Up immediately on dialogue; all subsequent inputs are A only. context.wait_for_all_requests(); ShortDialogWatcher keeper_dialogue; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - for (size_t press = 0; press < 20; press++){ - pbf_move_left_joystick(ctx, {0, 1}, 80ms, 100ms); - pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); - } - }, - {{keeper_dialogue}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Eterna bicycle shop: Keeper interaction did not produce dialogue.", + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + for (size_t press = 0; press < 20; press++){ + pbf_move_left_joystick(ctx, {0, 1}, 80ms, 100ms); + pbf_press_button(ctx, BUTTON_A, 150ms, 500ms); + } + }, + {{keeper_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Eterna bicycle shop: Keeper interaction did not produce dialogue.", stream); } // The reward scene returns us outside already mounted. Wait for that // positive destination instead of replaying a fixed dialogue burst. - segment03_confirm_location(stream, context, match_eterna_cycle_shop_reward, "Eterna bicycle reward complete", 180, - true, true, match_eterna_bike_riding_card); + segment03_confirm_location(stream, + context, + match_eterna_cycle_shop_reward, + "Eterna bicycle reward complete", + 180, + true, + true, + match_eterna_bike_riding_card); segment03_register_bike(stream, context); segment03_cycling_move(stream, context, -1, 0, 733ms, 316ms, "Cycling Road approach"); segment03_cycling_move(stream, context, 0, -1, 3280ms, 3000ms, "Cycling Road north gate entrance", true); @@ -809,8 +901,8 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); OverworldWatcher north_gate_overworld; if (wait_until(stream, context, 10s, {{north_gate_overworld}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Overworld not visible after north gate exit settle.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Overworld not visible after north gate exit settle.", stream); } BlackScreenWatcher north_gate_black; @@ -818,16 +910,16 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ ShortDialogWatcher north_gate_dialogue; StartBattleDetector north_gate_starting(stream.overlay()); BattleMenuWatcher north_gate_menu(BattleType::TRAINER); - const int clearance = run_until( - stream, context, + const int clearance = run_until(stream, + context, [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, -1}, 750ms, 250ms); ctx.wait_for_all_requests(); }, {{north_gate_black}, {north_gate_white}, {north_gate_dialogue}, {north_gate_starting}, {north_gate_menu}}); if (clearance == 0 || clearance == 1){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Unexpected transition while clearing north gate steps.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Unexpected transition while clearing north gate steps.", stream); } const bool cycling_encounter = clearance >= 2 || segment03_align_cycle_start(stream, context); @@ -847,13 +939,16 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ if (!south_gate_entered){ segment03_cycling_move(stream, context, 0, -1, 1977ms, 1544ms, "Cycling Road south gate entrance", true); } - segment03_confirm_location(stream, context, match_cycling_road_south_gate_interior, - "Cycling Road south gate interior"); + segment03_confirm_location(stream, + context, + match_cycling_road_south_gate_interior, + "Cycling Road south gate interior"); segment03_cycling_move(stream, context, 0, -1, 1977ms, 1544ms, "Cycling Road south gate exit", true); OverworldWatcher south_gate_overworld; if (wait_until(stream, context, 10s, {{south_gate_overworld}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Cycling Road: Overworld not visible after south gate exit.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Cycling Road: Overworld not visible after south gate exit.", + stream); } // Stay southbound until the scripted Dawn/Lucas dialogue actually // appears. Cancel movement at the dialogue instead of spending inputs @@ -872,8 +967,8 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ BattleMenuWatcher dawn_menu(BattleType::TRAINER); const Milliseconds south = attempt == 0 ? 1546ms : 3000ms; stream.log("[Route 207 Dawn] South approach attempt=" + std::to_string(attempt + 1)); - const int result = run_until( - stream, context, + const int result = run_until(stream, + context, [south](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, -1}, south, 500ms); ctx.wait_for_all_requests(); @@ -882,14 +977,14 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ if (result == 0){ dawn_dialogue_seen = true; }else if (result >= 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 207 Dawn: Unexpected transition or battle before dialogue.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 207 Dawn: Unexpected transition or battle before dialogue.", stream); } } if (!dawn_dialogue_seen){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 207 Dawn: Dialogue not encountered after southward retries.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 207 Dawn: Dialogue not encountered after southward retries.", stream); } stream.log("[Route 207 Dawn] Dialogue detected; south movement cancelled.", COLOR_GREEN); @@ -910,7 +1005,8 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ } if (!dawn_dialogue_cleared){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 207 Dawn: Dialogue did not remain cleared.", stream); + "Route 207 Dawn: Dialogue did not remain cleared.", + stream); } pbf_wait(context, 6000ms); stream.log("[Route 207 Dawn] Dialogue cleared; continuing to Camper Anthony.", COLOR_GREEN); @@ -926,13 +1022,16 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ // Justin can be dodged. After the recorded approach, wait three seconds // for an encounter; otherwise keep the remaining Coronet route. segment03_expected_encounter( - stream, context, "Route 207 Hiker Justin", + stream, + context, + "Route 207 Hiker Justin", [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {1, 0}, 994ms, 508ms); pbf_move_left_joystick(ctx, {0, 1}, 534ms, 457ms); pbf_move_left_joystick(ctx, {1, 0}, 2076ms, 149ms); }, - 3000ms, false); + 3000ms, + false); // Keep the recorded route after Justin; replace its final blind doorway // excerpt with the same watched down/right search used at Oreburgh Gate. bool coronet_entered = segment03_cycling_move(stream, context, 1, 0, 942ms, 443ms, "Mount Coronet approach"); @@ -955,7 +1054,8 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ OverworldWatcher coronet_overworld; if (wait_until(stream, context, 10s, {{coronet_overworld}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Mount Coronet: Interior not ready after west entrance.", stream); + "Mount Coronet: Interior not ready after west entrance.", + stream); } context.wait_for_all_requests(); } @@ -1046,8 +1146,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ ShortDialogWatcher dialogue; StartBattleDetector starting(stream.overlay()); BattleMenuWatcher menu(BattleType::TRAINER); - const int engaged = run_until( - stream, context, + const int engaged = run_until(stream, + context, [&](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {1, 0}, 4828ms, 104ms); ctx.wait_for_all_requests(); @@ -1080,8 +1180,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ StartBattleDetector dialogue_starting(stream.overlay()); BattleMenuWatcher dialogue_menu(BattleType::TRAINER); bool dialogue_cleared = false; - const int battle_started = run_until( - stream, context, + const int battle_started = run_until(stream, + context, [&](ProControllerContext& ctx){ mash_until_dialogue_ends(stream, ctx, BUTTON_A, 20s); ctx.wait_for_all_requests(); @@ -1095,9 +1195,9 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ {{dialogue_starting}, {dialogue_menu}}); if (battle_started < 0){ if (!dialogue_cleared || ++approach_dialogues > 2){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 bridge: Approach dialogue did not clear within bounded recovery.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Approach dialogue did not clear within bounded recovery.", + stream); } stream.log("[Route 208 bridge] Nonbattle dialogue cleared; resuming watched crossing."); continue; @@ -1105,19 +1205,21 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ } fight_trainer_using_moves(stream, context, 2, "Route 208 Hiker", false); if (++bridge_battles > 2){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 208 bridge: Unexpected additional Hiker battle.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 bridge: Unexpected additional Hiker battle.", + stream); } pbf_wait(context, 500ms); context.wait_for_all_requests(); } if (!bridge_approach_clear){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 bridge: Watched east approach did not clear.", stream); + "Route 208 bridge: Watched east approach did not clear.", + stream); } stream.log("[Route 208 bridge] Full east approach clear after " + std::to_string(bridge_battles) + - " battle(s); continuing toward the stairs.", - COLOR_GREEN); + " battle(s); continuing toward the stairs.", + COLOR_GREEN); pbf_move_left_joystick(context, {1.000000, -0.000000}, 421ms, 0ms); pbf_wait(context, 178ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 1436ms, 0ms); @@ -1127,11 +1229,23 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ // Fixed eastward travel can stop beyond the stair column. Center above // the stairs using the native left-rock pose before moving south. bool rock_acquisition_timed_out = false; - align_to_overworld_landmarks(stream, context, match_route208_stair_top, "Route 208 stair-top rocks", false, true, - false, nullptr, nullptr, 0, 0, 0, &rock_acquisition_timed_out); + align_to_overworld_landmarks(stream, + context, + match_route208_stair_top, + "Route 208 stair-top rocks", + false, + true, + false, + nullptr, + nullptr, + 0, + 0, + 0, + &rock_acquisition_timed_out); if (!activate_repel(stream, context, 5)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 stair top: Repel activation failed.", stream); + "Route 208 stair top: Repel activation failed.", + stream); } context.wait_for_all_requests(); if (rock_acquisition_timed_out){ @@ -1142,26 +1256,26 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ BlackScreenWatcher fallback_black; WhiteScreenWatcher fallback_white; BattleMenuWatcher fallback_battle(BattleType::TRAINER); - const int fallback_interrupted = - run_until(stream, context, - [&](ProControllerContext& ctx){ - // East: clamp against the cliff edge. - pbf_move_left_joystick(ctx, {1, 0}, 2000ms, 0ms); - ctx.wait_for_all_requests(); - // Southwest: drag toward the stair column. - pbf_move_left_joystick(ctx, {-1, -1}, 1500ms, 0ms); - ctx.wait_for_all_requests(); - // South: user-requested 1650ms trial; recovery may start farther down the stairs. - pbf_move_left_joystick(ctx, {0, -1}, 1650ms, 0ms); - pbf_wait(ctx, 643ms); - ctx.wait_for_all_requests(); - segment03_align_lower_stair_lane(stream, ctx); - }, - {{fallback_black}, {fallback_white}, {fallback_battle}}); + const int fallback_interrupted = run_until(stream, + context, + [&](ProControllerContext& ctx){ + // East: clamp against the cliff edge. + pbf_move_left_joystick(ctx, {1, 0}, 2000ms, 0ms); + ctx.wait_for_all_requests(); + // Southwest: drag toward the stair column. + pbf_move_left_joystick(ctx, {-1, -1}, 1500ms, 0ms); + ctx.wait_for_all_requests(); + // South: user-requested 1650ms trial; recovery may start farther down the stairs. + pbf_move_left_joystick(ctx, {0, -1}, 1650ms, 0ms); + pbf_wait(ctx, 643ms); + ctx.wait_for_all_requests(); + segment03_align_lower_stair_lane(stream, ctx); + }, + {{fallback_black}, {fallback_white}, {fallback_battle}}); if (fallback_interrupted >= 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Route 208 stair fallback: Unexpected transition or battle before west gate approach.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 208 stair fallback: Unexpected transition or battle before west gate approach.", + stream); } }else{ // Pass just above the honey tree, between the southern trainer @@ -1185,20 +1299,24 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ } if (!hearthome_gate_entered){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome west gate: Interior transition not confirmed.", stream); + "Hearthome west gate: Interior transition not confirmed.", + stream); } if (!segment03_cycling_move(stream, context, 1, 0, 1800ms, 1588ms, "Hearthome west gate exit", true)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome west gate: Exterior transition not confirmed.", stream); + "Hearthome west gate: Exterior transition not confirmed.", + stream); } OverworldWatcher hearthome_outside; if (wait_until(stream, context, 10s, {{hearthome_outside}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome west gate: Overworld not ready after exit.", stream); + "Hearthome west gate: Overworld not ready after exit.", + stream); } if (segment03_cycling_move(stream, context, 1, 0, 2200ms, 6189ms, "Hearthome Buneary approach")){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Buneary approach: Unexpected extra transition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Buneary approach: Unexpected extra transition.", + stream); } // Begin the scene deadline after the approach requests finish, not // while the recorded movement and neutral waits remain queued. @@ -1206,7 +1324,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ ShortDialogWatcher buneary_dialogue; if (wait_until(stream, context, 15s, {{buneary_dialogue}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Buneary: Dialogue did not start.", stream); + "Hearthome Buneary: Dialogue did not start.", + stream); } bool buneary_cleared = false; for (size_t pass = 0; pass < 6 && !buneary_cleared; pass++){ @@ -1226,7 +1345,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ } if (!buneary_cleared){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Buneary: Scene did not clear.", stream); + "Hearthome Buneary: Scene did not clear.", + stream); } stream.log("[Hearthome Buneary] Scene cleared; expiring Repel without renewal before Center travel.", COLOR_GREEN); pbf_move_left_joystick(context, {1, 0}, 500ms, 0ms); @@ -1237,8 +1357,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ // remaining outdoor inputs; the room must then be confirmed visually. bool center_entered = false; BlackScreenWatcher center_transition; - const int center_entry = run_until( - stream, context, + const int center_entry = run_until(stream, + context, [&](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 3350ms, 0ms); pbf_wait(ctx, 437ms); @@ -1261,9 +1381,15 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ pbf_move_left_joystick(ctx, {-1.000000, -0.000000}, 500ms, 0ms); pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 250ms, 0ms); pbf_wait(ctx, 59ms); - align_to_overworld_landmarks(stream, ctx, match_hearthome_center_entrance, - "Hearthome Center exterior alignment", false, true, false, - match_hearthome_center_interior, ¢er_entered); + align_to_overworld_landmarks(stream, + ctx, + match_hearthome_center_entrance, + "Hearthome Center exterior alignment", + false, + true, + false, + match_hearthome_center_interior, + ¢er_entered); if (!center_entered){ pbf_move_left_joystick(ctx, {0, 1}, 4000ms, 0ms); pbf_wait(ctx, 3000ms); @@ -1272,7 +1398,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ {{center_transition}}); if (center_entry != 0 && !center_entered){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Center: Entry transition not detected.", stream); + "Hearthome Center: Entry transition not detected.", + stream); } segment03_finish_transition(stream, context, "Hearthome Center entrance"); pbf_wait(context, 1500ms); @@ -1280,7 +1407,8 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ segment03_confirm_location(stream, context, match_hearthome_center_interior, "Hearthome Center interior"); if (!heal_pokemon(stream, context, "Hearthome")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Center exit not confirmed.", stream); + "Hearthome Center exit not confirmed.", + stream); } // Let the exit scene and camera settle before spending the Hall travel // budget. The old east hold began while the exit was still fading in. @@ -1290,32 +1418,37 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ OverworldWatcher center_exit_ready; if (wait_until(stream, context, 10s, {{center_exit_ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Center: Exterior not ready after healing.", stream); + "Hearthome Center: Exterior not ready after healing.", + stream); } pbf_move_left_joystick(context, {1, 0}, 2092ms, 185ms); // Bring the Hall into view without the old 1004ms north overshoot. pbf_move_left_joystick(context, {0, 1}, 600ms, 500ms); context.wait_for_all_requests(); - align_to_overworld_landmarks(stream, context, match_hearthome_contest_fantina, "Hearthome Contest Hall Fantina", - false, true); + align_to_overworld_landmarks(stream, + context, + match_hearthome_contest_fantina, + "Hearthome Contest Hall Fantina", + false, + true); // Repeat the existing short Up/A interaction within the 15s window. // Dialogue cancels the remaining inputs before any text cleanup. ShortDialogWatcher fantina_dialogue; BlackScreenWatcher fantina_black; WhiteScreenWatcher fantina_white; - const int fantina_started = - run_until(stream, context, - [](ProControllerContext& ctx){ - constexpr auto pulse = 16ms + 100ms + 194ms + 277ms; - Milliseconds elapsed = 0ms; - for (; elapsed + pulse <= 15s; elapsed += pulse){ - pbf_move_left_joystick(ctx, {0, 1}, 16ms, 100ms); - pbf_press_button(ctx, BUTTON_A, 194ms, 277ms); - ctx.wait_for_all_requests(); - } - pbf_wait(ctx, 15s - elapsed); - }, - {{fantina_dialogue}, {fantina_black}, {fantina_white}}); + const int fantina_started = run_until(stream, + context, + [](ProControllerContext& ctx){ + constexpr auto pulse = 16ms + 100ms + 194ms + 277ms; + Milliseconds elapsed = 0ms; + for (; elapsed + pulse <= 15s; elapsed += pulse){ + pbf_move_left_joystick(ctx, {0, 1}, 16ms, 100ms); + pbf_press_button(ctx, BUTTON_A, 194ms, 277ms); + ctx.wait_for_all_requests(); + } + pbf_wait(ctx, 15s - elapsed); + }, + {{fantina_dialogue}, {fantina_black}, {fantina_white}}); if (fantina_started > 0){ // Fantina's opening camera cut fades to black before the text box. // The watcher has cancelled Up/A; wait without further movement. @@ -1323,13 +1456,14 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ segment03_finish_transition(stream, context, "Hearthome Fantina opening"); ShortDialogWatcher opening_dialogue; if (wait_until(stream, context, 15s, {{opening_dialogue}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Fantina: Opening fade was not followed by dialogue.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Fantina: Opening fade was not followed by dialogue.", stream); } }else if (fantina_started != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Fantina dialogue did not start.", stream); + "Hearthome Contest Hall: Fantina dialogue did not start.", + stream); } clear_post_battle_dialogue(stream, context, BUTTON_B); pbf_wait(context, 2000ms); @@ -1340,8 +1474,9 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); ShortDialogWatcher contest_dialogue; if (wait_until(stream, context, 15s, {{contest_dialogue}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Contest Hall: Interior dialogue did not start.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Contest Hall: Interior dialogue did not start.", + stream); } // Use Buneary's resumed-text and scene-clear checks, with B to clear. bool contest_cleared = false; @@ -1361,18 +1496,21 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ } if (!contest_cleared){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Interior scene did not clear.", stream); + "Hearthome Contest Hall: Interior scene did not clear.", + stream); } // Own the exit before handing off the checkpoint. The transition watcher // cancels southward movement at the fade, leaving the fixed outdoor spawn. if (!segment03_cycling_move(stream, context, 0, -1, 903ms, 1455ms, "Hearthome Contest Hall exit", true)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Exit transition not confirmed.", stream); + "Hearthome Contest Hall: Exit transition not confirmed.", + stream); } OverworldWatcher hall_outside; if (wait_until(stream, context, 10s, {{hall_outside}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall: Overworld not ready after exit.", stream); + "Hearthome Contest Hall: Overworld not ready after exit.", + stream); } // The approach landmark view is not the post-exit doorway view. // The watched south exit plus restored overworld completes this boundary. @@ -1384,8 +1522,8 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context OverworldWatcher hall_start; if (wait_until(stream, context, 10s, {{hall_start}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome Contest Hall checkpoint start: Overworld not ready.", - stream); + "Hearthome Contest Hall checkpoint start: Overworld not ready.", + stream); } // butters' recorded Contest Hall-to-rival route. pbf_move_left_joystick(context, {0.000000, -1.000000}, 1294ms, 0ms); @@ -1403,39 +1541,53 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context context.wait_for_all_requests(); // Cancel at each gate fade, then allow a defined settling wait. BlackScreenWatcher east_gate_entry; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {1, 0}, 3446ms, 0ms); - pbf_wait(ctx, 983ms); - }, - {{east_gate_entry}}) != 0){ + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 3446ms, 0ms); + pbf_wait(ctx, 983ms); + }, + {{east_gate_entry}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Entrance transition did not start.", stream); + "Hearthome east gate: Entrance transition did not start.", + stream); } - BlackScreenWatcher east_gate_entry_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms); + BlackScreenWatcher east_gate_entry_finished(COLOR_RED, + {.1, .1, .8, .8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{east_gate_entry_finished}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Hearthome east gate: Entrance transition did not finish.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome east gate: Entrance transition did not finish.", + stream); } pbf_wait(context, 983ms); context.wait_for_all_requests(); BlackScreenWatcher east_gate_exit; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {1, 0}, 2095ms, 0ms); - pbf_wait(ctx, 1660ms); - }, - {{east_gate_exit}}) != 0){ + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1, 0}, 2095ms, 0ms); + pbf_wait(ctx, 1660ms); + }, + {{east_gate_exit}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Exit transition did not start.", stream); + "Hearthome east gate: Exit transition did not start.", + stream); } - BlackScreenWatcher east_gate_exit_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms); + BlackScreenWatcher east_gate_exit_finished(COLOR_RED, + {.1, .1, .8, .8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{east_gate_exit_finished}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Hearthome east gate: Exit transition did not finish.", stream); + "Hearthome east gate: Exit transition did not finish.", + stream); } pbf_wait(context, 1660ms); context.wait_for_all_requests(); @@ -1445,7 +1597,8 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context // Use the existing Bag helper instead of replaying recorded menu inputs. if (!activate_repel(stream, context, 8, 0)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 209: Repel activation failed.", stream); + "Route 209: Repel activation failed.", + stream); } pbf_move_left_joystick(context, {0.000000, 1.000000}, 1412ms, 0ms); pbf_wait(context, 77ms); @@ -1465,11 +1618,19 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context pbf_wait(context, 1001ms); context.wait_for_all_requests(); // butters' marked road pose: sign frame and rock pair. - align_to_overworld_landmarks(stream, context, match_route209_repel_point, "Route 209 Repel point before expiry", - false, true); + align_to_overworld_landmarks(stream, + context, + match_route209_repel_point, + "Route 209 Repel point before expiry", + false, + true); force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - align_to_overworld_landmarks(stream, context, match_route209_repel_point, "Route 209 Repel point after renewal", - false, true); + align_to_overworld_landmarks(stream, + context, + match_route209_repel_point, + "Route 209 Repel point after renewal", + false, + true); pbf_move_left_joystick(context, {0.000000, 1.000000}, 1345ms, 0ms); pbf_wait(context, 309ms); @@ -1490,47 +1651,63 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context // Normalize the checkpoint boundary with a complete enter/exit cycle. // Cancel movement at each fade so it cannot continue onto the next map. BlackScreenWatcher tower_entry; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, 1}, 1311ms, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{tower_entry}}) != 0){ + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, 1}, 1311ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{tower_entry}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Entry transition did not start.", stream); + "Lost Tower handoff: Entry transition did not start.", + stream); } - BlackScreenWatcher tower_entry_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher tower_entry_finished(COLOR_RED, + {.1, .1, .8, .8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{tower_entry_finished}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Entry transition did not finish.", stream); + "Lost Tower handoff: Entry transition did not finish.", + stream); } OverworldWatcher tower_inside; if (wait_until(stream, context, 10s, {{tower_inside}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Indoor overworld not ready.", stream); + "Lost Tower handoff: Indoor overworld not ready.", + stream); } BlackScreenWatcher tower_exit; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0, -1}, 1311ms, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{tower_exit}}) != 0){ + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, -1}, 1311ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{tower_exit}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Exit transition did not start.", stream); + "Lost Tower handoff: Exit transition did not start.", + stream); } - BlackScreenWatcher tower_exit_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + BlackScreenWatcher tower_exit_finished(COLOR_RED, + {.1, .1, .8, .8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{tower_exit_finished}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Exit transition did not finish.", stream); + "Lost Tower handoff: Exit transition did not finish.", + stream); } OverworldWatcher tower_outside; if (wait_until(stream, context, 10s, {{tower_outside}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower handoff: Outdoor overworld not ready.", stream); + "Lost Tower handoff: Outdoor overworld not ready.", + stream); } context.wait_for_all_requests(); stream.log("[Lost Tower handoff] Outside doorway, facing south; checkpoint complete.", COLOR_GREEN); @@ -1540,29 +1717,37 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ // Turn north, then enter using the same single-step timing as Segment 02. context.wait_for_all_requests(); BlackScreenWatcher entering_tower; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - for (int i = 0; i < 2; i++){ - pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); - } - pbf_wait(ctx, 2389ms); - }, - {{entering_tower}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower entry: Transition did not start.", stream); - } - BlackScreenWatcher tower_entry_finished(COLOR_RED, {.1, .1, .8, .8}, 100, 10, BlackScreenWatcher::FinderType::GONE, - 500ms); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + for (int i = 0; i < 2; i++){ + pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); + } + pbf_wait(ctx, 2389ms); + }, + {{entering_tower}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower entry: Transition did not start.", + stream); + } + BlackScreenWatcher tower_entry_finished(COLOR_RED, + {.1, .1, .8, .8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{tower_entry_finished}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower entry: Transition did not finish.", stream); + "Lost Tower entry: Transition did not finish.", + stream); } pbf_wait(context, 500ms); context.wait_for_all_requests(); OverworldWatcher tower_entry_ready; if (wait_until(stream, context, 10s, {{tower_entry_ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower entry: Overworld not ready.", stream); + "Lost Tower entry: Overworld not ready.", + stream); } // butters' route, using the same counted D-pad steps as the Eterna Gym. @@ -1573,28 +1758,36 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ int count; }; const Move to_floor2[] = { - {DPAD_UP, 4}, {DPAD_LEFT, 4}, {DPAD_UP, 6}, {DPAD_LEFT, 2}, {DPAD_UP, 3}, {DPAD_RIGHT, 2}, + {DPAD_UP, 4}, + {DPAD_LEFT, 4}, + {DPAD_UP, 6}, + {DPAD_LEFT, 2}, + {DPAD_UP, 3}, + {DPAD_RIGHT, 2}, }; BlackScreenWatcher stairs_to_floor2; - if (run_until(stream, context, - [&](ProControllerContext& ctx){ - for (const auto& move : to_floor2){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, - {{stairs_to_floor2}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 2: Stair transition did not start.", stream); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor2){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor2}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 2: Stair transition did not start.", + stream); } segment03_finish_transition(stream, context, "Lost Tower floor 2"); OverworldWatcher floor2_ready; if (wait_until(stream, context, 10s, {{floor2_ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 2: Overworld not ready after stairs.", stream); + "Lost Tower floor 2: Overworld not ready after stairs.", + stream); } const Move to_floor3[] = { @@ -1609,25 +1802,28 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ {DPAD_RIGHT, 4}, }; BlackScreenWatcher stairs_to_floor3; - if (run_until(stream, context, - [&](ProControllerContext& ctx){ - for (const auto& move : to_floor3){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, - {{stairs_to_floor3}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 3: Stair transition did not start.", stream); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor3){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor3}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 3: Stair transition did not start.", + stream); } segment03_finish_transition(stream, context, "Lost Tower floor 3"); OverworldWatcher floor3_ready; if (wait_until(stream, context, 10s, {{floor3_ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 3: Overworld not ready after stairs.", stream); + "Lost Tower floor 3: Overworld not ready after stairs.", + stream); } const Move to_repel[] = { @@ -1642,12 +1838,12 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower Repel: No frame before the next down step.", - stream); + "Lost Tower Repel: No frame before the next down step.", + stream); } if (pending_repel_dialogue.detect(*frame.frame)){ stream.log("[Lost Tower Repel] Pending renewal before down press " + std::to_string(i + 1) + - "; clearing before movement."); + "; clearing before movement."); clear_post_battle_dialogue(stream, context, BUTTON_A); repel_renewed = true; } @@ -1656,16 +1852,17 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ // fresh dialogue watcher's 250ms confirmation can cancel the rest. // The triggering step counts; all subsequent steps remain unqueued. ShortDialogWatcher step_repel_dialogue(COLOR_YELLOW); - const int expiry = run_until(stream, context, - [&](ProControllerContext& ctx){ - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - ctx.wait_for_all_requests(); - pbf_wait(ctx, 500ms); - }, - {{step_repel_dialogue}}); + const int expiry = run_until(stream, + context, + [&](ProControllerContext& ctx){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + ctx.wait_for_all_requests(); + pbf_wait(ctx, 500ms); + }, + {{step_repel_dialogue}}); if (expiry == 0){ stream.log("[Lost Tower Repel] Renewal after down press " + std::to_string(i + 1) + "/" + - std::to_string(move.count) + "; clearing before the remaining steps."); + std::to_string(move.count) + "; clearing before the remaining steps."); clear_post_battle_dialogue(stream, context, BUTTON_A); repel_renewed = true; } @@ -1711,8 +1908,9 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); const auto frame = stream.video().snapshot(); if (!frame){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Lost Tower biker: No frame before the next up press.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower biker: No frame before the next up press.", + stream); } if (pending_biker_dialogue.detect(*frame.frame)){ biker_challenged = true; @@ -1722,14 +1920,15 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ // A fresh 250ms dialogue watch cannot cancel the 80ms hold itself. // Count that press, then preserve any subsequent unqueued presses. ShortDialogWatcher approaching_biker(COLOR_YELLOW); - const int challenge = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); - ctx.wait_for_all_requests(); - // Observe the walk-up before another up press can be queued. - pbf_wait(ctx, 5000ms); - }, - {{approaching_biker}}); + const int challenge = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_press_dpad(ctx, DPAD_UP, 80ms, 300ms); + ctx.wait_for_all_requests(); + // Observe the walk-up before another up press can be queued. + pbf_wait(ctx, 5000ms); + }, + {{approaching_biker}}); biker_up_completed++; if (challenge == 0){ biker_challenged = true; @@ -1739,7 +1938,7 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ } if (biker_challenged){ stream.log("[Lost Tower biker] Dialogue detected after " + std::to_string(biker_up_completed) + - " up presses; handling the trainer battle."); + " up presses; handling the trainer battle."); fight_trainer_using_moves(stream, context, std::vector{2}, "Lost Tower biker", false); clear_post_battle_dialogue(stream, context, BUTTON_B); @@ -1759,54 +1958,71 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ } const Move to_floor4[] = { - {DPAD_UP, 1}, {DPAD_LEFT, 2}, {DPAD_UP, 4}, {DPAD_LEFT, 6}, - {DPAD_UP, 4}, {DPAD_RIGHT, 3}, {DPAD_UP, 4}, {DPAD_RIGHT, 3}, + {DPAD_UP, 1}, + {DPAD_LEFT, 2}, + {DPAD_UP, 4}, + {DPAD_LEFT, 6}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 3}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 3}, }; BlackScreenWatcher stairs_to_floor4; - if (run_until(stream, context, - [&](ProControllerContext& ctx){ - for (const auto& move : to_floor4){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, - {{stairs_to_floor4}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 4: Stair transition did not start.", stream); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor4){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor4}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 4: Stair transition did not start.", + stream); } segment03_finish_transition(stream, context, "Lost Tower floor 4"); OverworldWatcher floor4_ready; if (wait_until(stream, context, 10s, {{floor4_ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 4: Overworld not ready after stairs.", stream); + "Lost Tower floor 4: Overworld not ready after stairs.", + stream); } const Move to_floor5[] = { - {DPAD_DOWN, 6}, {DPAD_LEFT, 8}, {DPAD_UP, 3}, {DPAD_LEFT, 2}, {DPAD_UP, 4}, {DPAD_RIGHT, 3}, + {DPAD_DOWN, 6}, + {DPAD_LEFT, 8}, + {DPAD_UP, 3}, + {DPAD_LEFT, 2}, + {DPAD_UP, 4}, + {DPAD_RIGHT, 3}, }; BlackScreenWatcher stairs_to_floor5; - if (run_until(stream, context, - [&](ProControllerContext& ctx){ - for (const auto& move : to_floor5){ - for (int i = 0; i < move.count; i++){ - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - } - } - // Keep watching if the last step starts a delayed stair fade. - pbf_wait(ctx, 3000ms); - }, - {{stairs_to_floor5}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 5: Stair transition did not start.", stream); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + for (const auto& move : to_floor5){ + for (int i = 0; i < move.count; i++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + } + // Keep watching if the last step starts a delayed stair fade. + pbf_wait(ctx, 3000ms); + }, + {{stairs_to_floor5}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower floor 5: Stair transition did not start.", + stream); } segment03_finish_transition(stream, context, "Lost Tower floor 5"); OverworldWatcher floor5_ready; if (wait_until(stream, context, 10s, {{floor5_ready}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower floor 5: Overworld not ready after stairs.", stream); + "Lost Tower floor 5: Overworld not ready after stairs.", + stream); } const Move to_rewards[] = { @@ -1829,7 +2045,8 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ stream.log("[Lost Tower reward] Dialogue absent for four seconds; opening the Bag."); if (!use_escape_rope(stream, context, 8)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Lost Tower: Escape Rope exit was not confirmed.", stream); + "Lost Tower: Escape Rope exit was not confirmed.", + stream); } // The exterior is visible before the Rope arrival camera finishes settling. // Keep the following south/left movement out of that arrival animation. @@ -1837,8 +2054,9 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); OverworldWatcher rope_exit_ready; if (wait_until(stream, context, 10s, {{rope_exit_ready}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Lost Tower: Overworld not ready after Escape Rope settling.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Lost Tower: Overworld not ready after Escape Rope settling.", + stream); } context.wait_for_all_requests(); stream.log("[Lost Tower] Reward dialogue cleared; Escape Rope exit settled.", COLOR_GREEN); @@ -1922,14 +2140,16 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont ShortDialogWatcher calvin_dialogue; StartBattleDetector calvin_start(stream.overlay()); BattleMenuWatcher calvin_menu(BattleType::TRAINER); - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 794ms, 0ms); - pbf_wait(ctx, 15000ms); - }, - {{calvin_dialogue}, {calvin_start}, {calvin_menu}}) < 0){ + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 794ms, 0ms); + pbf_wait(ctx, 15000ms); + }, + {{calvin_dialogue}, {calvin_start}, {calvin_menu}}) < 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 Calvin: Trainer encounter did not start.", stream); + "Route 215 Calvin: Trainer encounter did not start.", + stream); } fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ruin Maniac Calvin", false); // Renew in the clear area after Calvin, before the next walking strip. @@ -1946,7 +2166,7 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont constexpr double lane_pitch = 70.0 / 1080; constexpr double lane_tolerance = 1.0 / 54; size_t missing_pose = 0, stale_frames = 0, stalled = 0, lane_changes = 0, lane_presses = 0, arrival_frames = 0, - jogger_battles = 0; + jogger_battles = 0; int changing_to = 0; bool east_pending = false, last_coarse = false, corner_ready = false; double last_x = 0; @@ -1958,18 +2178,20 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont context.wait_for_all_requests(); if (std::chrono::steady_clock::now() - bypass_started > 90s){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Lane search timed out.", stream); + "Route 215 jogger: Lane search timed out.", + stream); } const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: No video frame.", stream); + "Route 215 jogger: No video frame.", + stream); } if (frame.timestamp <= fresh_after || frame.timestamp <= last_frame_time){ if (++stale_frames >= 8){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Video is not advancing after movement.", - stream); + "Route 215 jogger: Video is not advancing after movement.", + stream); } pbf_wait(context, 500ms); continue; @@ -1978,8 +2200,9 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont stale_frames = 0; if (jogger_dialogue.detect(*frame.frame) || jogger_battle.detect(*frame.frame)){ if (++jogger_battles > 1){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 215 jogger: Repeated unexpected encounter.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Repeated unexpected encounter.", + stream); } // The recorded failure is the jogger's trainer introduction. // Complete it with the shared move-list handler, then measure @@ -2000,26 +2223,28 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont } if (jogger_transition.detect(*frame.frame)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Unexpected transition.", stream); + "Route 215 jogger: Unexpected transition.", + stream); } const auto pose = match_route215_jogger_corner(*frame.frame); for (const auto& anchor : pose.landmarks){ stream.log("[Route 215 jogger] " + anchor.name + " score=" + std::to_string(anchor.score) + - ", margin=" + std::to_string(anchor.margin) + ", accepted=" + std::to_string(anchor.accepted) + - ", offset=(" + std::to_string(anchor.dx) + "," + std::to_string(anchor.dy) + ")"); + ", margin=" + std::to_string(anchor.margin) + ", accepted=" + std::to_string(anchor.accepted) + + ", offset=(" + std::to_string(anchor.dx) + "," + std::to_string(anchor.dy) + ")"); } - const int lane = pose.located && std::abs(pose.dy) <= lane_tolerance ? 4 - : pose.located && std::abs(pose.dy - lane_pitch) <= lane_tolerance ? 3 - : 0; + const int lane = pose.located && std::abs(pose.dy) <= lane_tolerance ? 4 + : pose.located && std::abs(pose.dy - lane_pitch) <= lane_tolerance ? 3 + : 0; stream.log("[Route 215 jogger] lane=" + std::to_string(lane) + ", coarse=" + std::to_string(pose.coarse) + - ", offset=(" + std::to_string(pose.dx) + "," + std::to_string(pose.dy) + ")"); + ", offset=(" + std::to_string(pose.dx) + "," + std::to_string(pose.dy) + ")"); if (!lane){ // Wait out rain/clouds or a settling step; never search vertically // from a missing or ambiguous pose, including after a blocked step. arrival_frames = 0; if (++missing_pose >= 8){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Cannot verify lane 3 or 4.", stream); + "Route 215 jogger: Cannot verify lane 3 or 4.", + stream); } pbf_wait(context, 500ms); continue; @@ -2051,8 +2276,9 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont arrival_frames = 0; if (!changing_to && ((near_corner && lane == 3) || stalled >= 2)){ if (++lane_changes > 6){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Route 215 jogger: Both safe lanes remain blocked.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 jogger: Both safe lanes remain blocked.", + stream); } changing_to = lane == 4 ? 3 : 4; lane_presses = 0; @@ -2072,7 +2298,8 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont } if (bypass_movement + 80ms > movement_limit){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Movement budget exhausted.", stream); + "Route 215 jogger: Movement budget exhausted.", + stream); } pbf_press_dpad(context, changing_to == 3 ? DPAD_UP : DPAD_DOWN, 80ms, 300ms); context.wait_for_all_requests(); @@ -2083,12 +2310,14 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont } if (!pose.coarse && pose.dx < -1.0 / 32){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Passed the expected east corner.", stream); + "Route 215 jogger: Passed the expected east corner.", + stream); } const auto duration = !pose.coarse && std::abs(pose.dx) <= 1.0 / 24 ? 40ms : 160ms; if (bypass_movement + duration > movement_limit){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: Movement budget exhausted.", stream); + "Route 215 jogger: Movement budget exhausted.", + stream); } last_x = pose.dx; last_coarse = pose.coarse; @@ -2102,7 +2331,8 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont } if (!corner_ready){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 jogger: East corner not confirmed.", stream); + "Route 215 jogger: East corner not confirmed.", + stream); } pbf_move_left_joystick(context, {0.000000, -1.000000}, 1682ms, 0ms); pbf_wait(context, 501ms); @@ -2126,17 +2356,19 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont ShortDialogWatcher derek_dialogue; StartBattleDetector derek_start(stream.overlay()); BattleMenuWatcher derek_menu(BattleType::TRAINER); - if (run_until(stream, context, - [](ProControllerContext& ctx){ - // The uninterrupted 1000ms approach ends one step into grass. - // Stop one recorded step earlier on Derek's sightline while - // the existing watchers wait for him to turn toward the player. - pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 850ms, 0ms); - pbf_wait(ctx, 15000ms); - }, - {{derek_dialogue}, {derek_start}, {derek_menu}}) < 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 Derek: Trainer encounter did not start.", stream); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + // The uninterrupted 1000ms approach ends one step into grass. + // Stop one recorded step earlier on Derek's sightline while + // the existing watchers wait for him to turn toward the player. + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 850ms, 0ms); + pbf_wait(ctx, 15000ms); + }, + {{derek_dialogue}, {derek_start}, {derek_menu}}) < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Derek: Trainer encounter did not start.", + stream); } fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Black Belt Derek", false); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1929ms, 0ms); @@ -2145,8 +2377,12 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont pbf_wait(context, 88ms); // Renew on the clear landing below the stairs, then restore its column. force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - align_to_overworld_landmarks(stream, context, match_route215_derek_stairs_renewal, - "Route 215 below Derek stairs after renewal", false, true); + align_to_overworld_landmarks(stream, + context, + match_route215_derek_stairs_renewal, + "Route 215 below Derek stairs after renewal", + false, + true); pbf_move_left_joystick(context, {0.000000, 1.000000}, 501ms, 0ms); pbf_wait(context, 390ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 954ms, 0ms); @@ -2155,8 +2391,12 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont pbf_wait(context, 453ms); // Stay west/east on the clear road row; south would enter tall grass. force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); - align_to_overworld_landmarks(stream, context, match_route215_derek_stair_top_renewal, - "Route 215 above Derek stairs after renewal", false, true); + align_to_overworld_landmarks(stream, + context, + match_route215_derek_stair_top_renewal, + "Route 215 above Derek stairs after renewal", + false, + true); pbf_move_left_joystick(context, {1.000000, -0.000000}, 1855ms, 0ms); pbf_wait(context, 171ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1385ms, 0ms); @@ -2175,26 +2415,32 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont pbf_wait(context, 1767ms); // Resume from the road/fence pose shown at renewal in the recording. // A fixed leftover west input compounds the variable expiry offset. - align_to_overworld_landmarks(stream, context, match_route215_ace_renewal, - "Route 215 before Ace Trainers after renewal", false, true); + align_to_overworld_landmarks(stream, + context, + match_route215_ace_renewal, + "Route 215 before Ace Trainers after renewal", + false, + true); context.wait_for_all_requests(); ShortDialogWatcher dennis_dialogue; StartBattleDetector dennis_start(stream.overlay()); BattleMenuWatcher dennis_menu(BattleType::TRAINER); - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 1323ms, 0ms); - pbf_wait(ctx, 106ms); - pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 834ms, 0ms); - pbf_wait(ctx, 206ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 221ms, 0ms); - pbf_wait(ctx, 516ms); - pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 667ms, 0ms); - pbf_wait(ctx, 15000ms); - }, - {{dennis_dialogue}, {dennis_start}, {dennis_menu}}) < 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Route 215 Dennis: Trainer encounter did not start.", stream); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.000000, -1.000000}, 1323ms, 0ms); + pbf_wait(ctx, 106ms); + pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 834ms, 0ms); + pbf_wait(ctx, 206ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 221ms, 0ms); + pbf_wait(ctx, 516ms); + pbf_move_left_joystick(ctx, {1.000000, 0.000000}, 667ms, 0ms); + pbf_wait(ctx, 15000ms); + }, + {{dennis_dialogue}, {dennis_start}, {dennis_menu}}) < 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Route 215 Dennis: Trainer encounter did not start.", + stream); } // Dennis and Maya are separate consecutive battles. fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ace Trainer Dennis", false); @@ -2213,8 +2459,13 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont // Veilstone needs no Repel. Clear the remaining expiry without renewal // on the paved north/south strip before normalizing the city approach. force_repel_expiry(stream, context, RepelRenewalChoice::NO, true); - align_to_overworld_landmarks(stream, context, match_veilstone_city_passage, "Veilstone passage after expiry", false, - true, true); + align_to_overworld_landmarks(stream, + context, + match_veilstone_city_passage, + "Veilstone passage after expiry", + false, + true, + true); // Replay the successful street approach, then correct onto the western // stair lane before climbing. Do not use the NPC pair as a wall stop. pbf_move_left_joystick(context, {0.0, -1.0}, 1342ms, 0ms); @@ -2226,21 +2477,26 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont // north leg; correct its lane before continuing the remaining ascent. pbf_move_left_joystick(context, {0.0, 1.0}, 200ms, 0ms); context.wait_for_all_requests(); - align_to_overworld_landmarks(stream, context, match_veilstone_dawn_stairs, "Veilstone western stair approach", - false, true); + align_to_overworld_landmarks(stream, + context, + match_veilstone_dawn_stairs, + "Veilstone western stair approach", + false, + true); stream.log("[Veilstone approach] Following the western stairs toward Dawn."); ShortDialogWatcher dawn_dialogue; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0.0, 1.0}, 3023ms, 0ms); - pbf_press_button(ctx, BUTTON_A, 80ms, 100ms); - // Dawn's turn, walk and camera cut precede the first box. - // Keep watching through the cutscene; cancel as soon as it opens. - pbf_wait(ctx, 15000ms); - }, - {{dawn_dialogue}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Veilstone Dawn: Dialogue did not start from the west stair approach.", + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.0, 1.0}, 3023ms, 0ms); + pbf_press_button(ctx, BUTTON_A, 80ms, 100ms); + // Dawn's turn, walk and camera cut precede the first box. + // Keep watching through the cutscene; cancel as soon as it opens. + pbf_wait(ctx, 15000ms); + }, + {{dawn_dialogue}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Dawn: Dialogue did not start from the west stair approach.", stream); } clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); @@ -2248,18 +2504,20 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont // The new recording already enters the Gym. Cancel at its first fade. context.wait_for_all_requests(); BlackScreenWatcher gym_entry; - if (run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1954ms, 0ms); - pbf_wait(ctx, 223ms); - pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 110ms, 0ms); - pbf_wait(ctx, 190ms); - pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 910ms, 0ms); - pbf_wait(ctx, 3000ms); - }, - {{gym_entry}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Veilstone Gym: Entrance transition did not start.", stream); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 1954ms, 0ms); + pbf_wait(ctx, 223ms); + pbf_move_left_joystick(ctx, {1.000000, -0.000000}, 110ms, 0ms); + pbf_wait(ctx, 190ms); + pbf_move_left_joystick(ctx, {0.000000, 1.000000}, 910ms, 0ms); + pbf_wait(ctx, 3000ms); + }, + {{gym_entry}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Gym: Entrance transition did not start.", + stream); } segment03_finish_transition(stream, context, "Veilstone Gym entrance"); // The first visit pans across the Gym before returning to the entrance. @@ -2267,8 +2525,9 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont context.wait_for_all_requests(); OverworldWatcher gym_ready; if (wait_until(stream, context, 10s, {{gym_ready}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Veilstone Gym: Overworld not ready after the entrance scene.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Veilstone Gym: Overworld not ready after the entrance scene.", + stream); } context.wait_for_all_requests(); } @@ -2284,39 +2543,109 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ }; const Move sequence1[] = { - {DPAD_RIGHT, 8, 0ms}, {DPAD_UP, 6, 0ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 1000ms}, {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 8, 0ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_UP, 2, 0ms}, }; const Move sequence2[] = { - {DPAD_UP, 5, 0ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 1000ms}, {DPAD_DOWN, 8, 0ms}, - {DPAD_RIGHT, 2, 0ms}, {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 5, 0ms}, {DPAD_UP, 7, 0ms}, - {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 1000ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 2, 0ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 2000ms}, - {DPAD_RIGHT, 1, 0ms}, {DPAD_DOWN, 10, 0ms}, {DPAD_RIGHT, 6, 0ms}, {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 1000ms}, {DPAD_UP, 6, 0ms}, {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 2, 0ms}, {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 1000ms}, {DPAD_LEFT, 1, 0ms}, - {DPAD_DOWN, 13, 0ms}, {DPAD_LEFT, 3, 0ms}, {DPAD_UP, 4, 0ms}, {DPAD_LEFT, 2, 0ms}, - {DPAD_UP, 8, 0ms}, {DPAD_RIGHT, 2, 0ms}, {DPAD_UP, 4, 0ms}, {DPAD_LEFT, 4, 0ms}, - {DPAD_DOWN, 2, 0ms}, {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 1000ms}, {DPAD_UP, 2, 0ms}, - {DPAD_RIGHT, 4, 0ms}, {DPAD_DOWN, 6, 0ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_DOWN, 7, 0ms}, - {DPAD_RIGHT, 2, 0ms}, {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 5, 0ms}, {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 4, 0ms}, {DPAD_LEFT, 1, 2000ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 5, 0ms}, - {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 2000ms}, {DPAD_DOWN, 5, 0ms}, {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_DOWN, 10, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_DOWN, 13, 0ms}, + {DPAD_LEFT, 3, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 1000ms}, + {DPAD_UP, 2, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 6, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 7, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 4, 0ms}, {DPAD_DOWN, 3, 0ms}, }; const Move sequence3[] = { - {DPAD_RIGHT, 2, 0ms}, {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 1, 0ms}, {DPAD_LEFT, 1, 3000ms}, - {DPAD_DOWN, 3, 0ms}, {DPAD_LEFT, 4, 0ms}, {DPAD_UP, 5, 0ms}, {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 2000ms}, {DPAD_DOWN, 5, 0ms}, {DPAD_LEFT, 8, 0ms}, {DPAD_UP, 3, 0ms}, - {DPAD_RIGHT, 1, 0ms}, {DPAD_RIGHT, 1, 3000ms}, {DPAD_UP, 11, 0ms}, {DPAD_RIGHT, 1, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, {DPAD_DOWN, 13, 0ms}, {DPAD_RIGHT, 4, 0ms}, {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 3000ms}, + {DPAD_DOWN, 3, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_UP, 5, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_LEFT, 8, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 3000ms}, + {DPAD_UP, 11, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_DOWN, 13, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 3, 0ms}, }; const Move sequence4[] = { - {DPAD_RIGHT, 2, 0ms}, {DPAD_UP, 9, 0ms}, {DPAD_LEFT, 2, 0ms}, {DPAD_UP, 4, 0ms}, {DPAD_RIGHT, 4, 0ms}, - {DPAD_RIGHT, 1, 1000ms}, {DPAD_RIGHT, 1, 0ms}, {DPAD_DOWN, 8, 0ms}, {DPAD_RIGHT, 4, 0ms}, {DPAD_UP, 3, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 4, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_RIGHT, 1, 1000ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 3, 0ms}, }; const Move sequence5[] = { @@ -2421,8 +2750,10 @@ std::string AutoStory_Segment_03::end_text() const { return "End: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } -void AutoStory_Segment_03::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_03::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -2450,16 +2781,23 @@ std::string AutoStory_Checkpoint_0301::end_text() const { return "Outside Eterna Galactic HQ after defeating Jupiter and completing the rescue dialogue, facing south against the uncut front bush."; } -void AutoStory_Checkpoint_0301::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0301::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0301(env, context, options, stats); } -void Checkpoint_0301(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0301(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. eterna_gym_to_jupiter(env.console, context); @@ -2477,16 +2815,23 @@ std::string AutoStory_Checkpoint_0302::end_text() const { return "Inside Mount Coronet at the west entrance after defeating Hiker Justin, facing east."; } -void AutoStory_Checkpoint_0302::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0302::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0302(env, context, options, stats); } -void Checkpoint_0302(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0302(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Set daylight once for this outdoor locator strip, including the // Center, Mart return, bicycle shop, and north Route 206 gate. @@ -2515,16 +2860,23 @@ std::string AutoStory_Checkpoint_0303::end_text() const { return "Inside Mount Coronet near the east exit, after the first Cyrus meeting."; } -void AutoStory_Checkpoint_0303::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0303::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0303(env, context, options, stats); } -void Checkpoint_0303(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0303(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. coronet1(env.console, context); @@ -2542,16 +2894,23 @@ std::string AutoStory_Checkpoint_0304::end_text() const { return "Just outside the Hearthome Contest Hall doorway, facing south with Repel inactive, after the Keira/Johanna introduction."; } -void AutoStory_Checkpoint_0304::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0304::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0304(env, context, options, stats); } -void Checkpoint_0304(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0304(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. coronet_to_heartholme(env.console, context); @@ -2569,16 +2928,23 @@ std::string AutoStory_Checkpoint_0305::end_text() const { return "Just outside the Lost Tower doorway on Route 209 after entering and exiting, facing south with Repel active."; } -void AutoStory_Checkpoint_0305::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0305::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0305(env, context, options, stats); } -void Checkpoint_0305(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0305(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Restore night lighting on every attempt, including after a reset. context.wait_for_all_requests(); @@ -2608,16 +2974,23 @@ std::string AutoStory_Checkpoint_0306::end_text() const { return "Inside Veilstone Gym at the entrance, facing north."; } -void AutoStory_Checkpoint_0306::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0306::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0306(env, context, options, stats); } -void Checkpoint_0306(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0306(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // Restore night before the route on every attempt, including resets. context.wait_for_all_requests(); @@ -2646,16 +3019,23 @@ std::string AutoStory_Checkpoint_0307::end_text() const { return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; } -void AutoStory_Checkpoint_0307::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0307::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { Checkpoint_0307(env, context, options, stats); } -void Checkpoint_0307(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void Checkpoint_0307(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ //#. Veilstone Gym navigation and battle gym_Puzzle_03(env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h index 494bd84afd..c2a1686089 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.h @@ -20,8 +20,10 @@ class AutoStory_Segment_03 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0301 : public AutoStory_Checkpoint { @@ -29,97 +31,125 @@ class AutoStory_Checkpoint_0301 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0301(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0301(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0302 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0302(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0302(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0303 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0303(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0303(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0304 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0304(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0304(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0305 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0305(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0305(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0306 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0306(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0306(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0307 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: second badge acquired // end: third Gym Badge acquired (Hearthome City) -void Checkpoint_0307(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void Checkpoint_0307(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 042c2a1576..3803d48255 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -5,6 +5,8 @@ * */ +#include + #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" @@ -21,6 +23,7 @@ #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" #include "PokemonBDSP_AutoStory_Segment_04.h" @@ -42,8 +45,10 @@ std::string AutoStory_Segment_04::end_text() const { } -void AutoStory_Segment_04::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_04::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -82,8 +87,10 @@ std::string AutoStory_Checkpoint_0401::start_text() const { std::string AutoStory_Checkpoint_0401::end_text() const { return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; } -void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0401(env, context, options, stats); } @@ -96,8 +103,10 @@ std::string AutoStory_Checkpoint_0402::start_text() const { std::string AutoStory_Checkpoint_0402::end_text() const { return "Entered Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0402(env, context, options, stats); } @@ -110,8 +119,10 @@ std::string AutoStory_Checkpoint_0403::start_text() const { std::string AutoStory_Checkpoint_0403::end_text() const { return "Healed and exited Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0403(env, context, options, stats); } @@ -124,8 +135,10 @@ std::string AutoStory_Checkpoint_0404::start_text() const { std::string AutoStory_Checkpoint_0404::end_text() const { return "Reached Galactic Warehouse, battle started."; } -void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0404(env, context, options, stats); } @@ -138,8 +151,10 @@ std::string AutoStory_Checkpoint_0405::start_text() const { std::string AutoStory_Checkpoint_0405::end_text() const { return "Defeated Galactic Grunt at Veilstone."; } -void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0405(env, context, options, stats); } @@ -152,8 +167,10 @@ std::string AutoStory_Checkpoint_0406::start_text() const { std::string AutoStory_Checkpoint_0406::end_text() const { return "Entered Galactic Warehouse."; } -void AutoStory_Checkpoint_0406::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0406::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0406(env, context, options, stats); } @@ -166,8 +183,10 @@ std::string AutoStory_Checkpoint_0407::start_text() const { std::string AutoStory_Checkpoint_0407::end_text() const { return "Exited Galactic Warehouse with TM Fly."; } -void AutoStory_Checkpoint_0407::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0407::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0407(env, context, options, stats); } @@ -180,8 +199,10 @@ std::string AutoStory_Checkpoint_0408::start_text() const { std::string AutoStory_Checkpoint_0408::end_text() const { return "Arrived at Route 214, battle started."; } -void AutoStory_Checkpoint_0408::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0408::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0408(env, context, options, stats); } @@ -194,8 +215,10 @@ std::string AutoStory_Checkpoint_0409::start_text() const { std::string AutoStory_Checkpoint_0409::end_text() const { return "Defeated Psychic Abigail on Route 214."; } -void AutoStory_Checkpoint_0409::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0409::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0409(env, context, options, stats); } @@ -208,8 +231,10 @@ std::string AutoStory_Checkpoint_0410::start_text() const { std::string AutoStory_Checkpoint_0410::end_text() const { return "Walked through Route 214."; } -void AutoStory_Checkpoint_0410::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0410::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0410(env, context, options, stats); } @@ -222,8 +247,10 @@ std::string AutoStory_Checkpoint_0411::start_text() const { std::string AutoStory_Checkpoint_0411::end_text() const { return "Passed Valor Lakefront, entered building."; } -void AutoStory_Checkpoint_0411::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0411::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0411(env, context, options, stats); } @@ -236,8 +263,10 @@ std::string AutoStory_Checkpoint_0412::start_text() const { std::string AutoStory_Checkpoint_0412::end_text() const { return "Exited building onto Route 213."; } -void AutoStory_Checkpoint_0412::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0412::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0412(env, context, options, stats); } @@ -250,8 +279,10 @@ std::string AutoStory_Checkpoint_0413::start_text() const { std::string AutoStory_Checkpoint_0413::end_text() const { return "Walked through Route 213, entered east Pastoria building."; } -void AutoStory_Checkpoint_0413::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0413::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0413(env, context, options, stats); } @@ -264,8 +295,10 @@ std::string AutoStory_Checkpoint_0414::start_text() const { std::string AutoStory_Checkpoint_0414::end_text() const { return "Exited east Pastoria building into Pastoria City."; } -void AutoStory_Checkpoint_0414::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0414::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0414(env, context, options, stats); } @@ -278,8 +311,10 @@ std::string AutoStory_Checkpoint_0415::start_text() const { std::string AutoStory_Checkpoint_0415::end_text() const { return "Entered Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0415::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0415::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0415(env, context, options, stats); } @@ -292,8 +327,10 @@ std::string AutoStory_Checkpoint_0416::start_text() const { std::string AutoStory_Checkpoint_0416::end_text() const { return "Healed and exited Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0416(env, context, options, stats); } @@ -306,8 +343,10 @@ std::string AutoStory_Checkpoint_0417::start_text() const { std::string AutoStory_Checkpoint_0417::end_text() const { return "Inside Pastoria Gym at the entrance, facing north."; } -void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0417(env, context, options, stats); } @@ -318,18 +357,124 @@ std::string AutoStory_Checkpoint_0418::start_text() const { return "Inside Pastoria Gym at the entrance, facing north."; } std::string AutoStory_Checkpoint_0418::end_text() const { - return "Not implemented: Pastoria Gym puzzle and Crasher Wake battle."; + return "Inside Pastoria Gym in front of Crasher Wake, facing north, after acquiring the fourth badge and clearing his reward dialogue."; } -void AutoStory_Checkpoint_0418::run_checkpoint(SingleSwitchProgramEnvironment&, ProControllerContext& context, - AutoStoryOptions, AutoStoryStats&) const { - context.wait_for_all_requests(); - // Stop before any movement until the puzzle and leader battle are implemented. - throw OperationFailedException(ErrorReportMode::NO_ERROR_REPORT, - "04.18 - Pastoria Gym Puzzle is not implemented. Stopped inside the Gym."); +void AutoStory_Checkpoint_0418::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + VideoStream& stream = env.console; + struct Move { + DpadPosition dir; + int count; + Milliseconds delay; + }; + + // Recorded walking counts; repeat_dpad adds the facing press on each turn. + const std::vector sequences[] = { + { + // Trainer 1 + {DPAD_UP, 16, 0ms}, + {DPAD_LEFT, 9, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_RIGHT, 8, 0ms}, + {DPAD_UP, 6, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_UP, 13, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 4, 0ms}, + {DPAD_RIGHT, 5, 0ms}, + {DPAD_DOWN, 9, 0ms}, + {DPAD_LEFT, 20, 0ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + }, + { + // Trainer 2 + {DPAD_RIGHT, 2, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 3, 0ms}, + {DPAD_DOWN, 11, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + }, + { + // Trainer 3 + {DPAD_RIGHT, 8, 0ms}, + {DPAD_UP, 2, 0ms}, + }, + { + // Trainer 4 + {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_UP, 13, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 17, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_UP, 19, 0ms}, + {DPAD_LEFT, 8, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_LEFT, 10, 0ms}, + {DPAD_DOWN, 10, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 10, 0ms}, + }, + { + // Crasher Wake approach; the battle is added after the recording ends. + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 6, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_UP, 1, 0ms}, + {DPAD_RIGHT, 8, 0ms}, + {DPAD_UP, 4, 0ms}, + }, + }; + + DpadState dpad; + dpad.last_dir = DPAD_UP; + for (size_t battle = 0; battle < 5; battle++){ + const bool gym_leader = battle == 4; + const std::string label = + gym_leader ? "Crasher Wake" : "Pastoria Gym trainer " + std::to_string(battle + 1); + stream.log("[Pastoria Gym puzzle] Approaching " + label + "."); + for (const auto& move : sequences[battle]){ + repeat_dpad(context, dpad, move.dir, 80ms, 300ms, move.count, true); + if (move.delay > 0ms){ + pbf_wait(context, move.delay); + } + } + context.wait_for_all_requests(); + + // Replace recorded battle/menu inputs with the existing slot-based handler. + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + fight_trainer_using_moves(stream, context, 1, label, gym_leader); + clear_post_battle_dialogue(stream, context, BUTTON_B, gym_leader ? 6000ms : 3000ms); + } + stream.log( + "[Pastoria Gym puzzle] Crasher Wake defeated and reward dialogue cleared; remaining inside the Gym.", + COLOR_GREEN); + }, + true); } -static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, - const std::string& trainerid){ +static void +move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -373,11 +518,12 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -416,11 +562,12 @@ static bool leave_gym(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 0ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 0ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: black screen not detected!", COLOR_RED); return false; @@ -452,11 +599,12 @@ static bool go_to_veilstone_pokemon_center(VideoStream& stream, ProControllerCon /* Entering the Pokemon Center */ BlackScreenOverWatcher black_screen1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen1}}); if (ret < 0){ stream.log("Didn't enter pokemon center!", COLOR_RED); return false; @@ -487,11 +635,12 @@ static bool go_to_galactic_warehouse(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); BlackScreenOverWatcher battle_black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 12000ms); - }, - {{battle_black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 12000ms); + }, + {{battle_black_screen}}); if (ret < 0){ stream.log("go_to_galactic_warehouse: battle black screen not detected (2)", COLOR_RED); return false; @@ -515,11 +664,12 @@ static bool enter_galactic_warehouse(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen_1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("Entering Galactic warehouse black screen not detected!", COLOR_RED); return false; @@ -542,11 +692,12 @@ static bool exit_galactic_warehouse(VideoStream& stream, ProControllerContext& c // Get fly ShortDialogWatcher getting_fly_TM(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 2000ms); - }, - {{getting_fly_TM}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{getting_fly_TM}}); if (ret < 0){ stream.log("enter_galactic_warehouse_getting_fly_TM: dialog box not detected!", COLOR_RED); } @@ -564,11 +715,12 @@ static bool exit_galactic_warehouse(VideoStream& stream, ProControllerContext& c // Black screen detector here when exiting the warehouse BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_2}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_2}}); if (ret < 0){ stream.log("enter_galactic_warehouse: black screen not detected!", COLOR_RED); return false; @@ -600,11 +752,12 @@ static bool go_to_route_214(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); return false; @@ -615,11 +768,12 @@ static bool go_to_route_214(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); // Add a black screen detector here when entering the connecting house BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_2}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_2}}); if (ret < 0){ stream.log("go_to_route_214_exit_house: black screen not detected!", COLOR_RED); return false; @@ -630,11 +784,12 @@ static bool go_to_route_214(VideoStream& stream, ProControllerContext& context){ pbf_move_left_joystick(context, {0, -1}, 6000ms, 100ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_route_214: black screen not detected!", COLOR_RED); return false; @@ -658,19 +813,21 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co { MarkDetector battle_dialogue_1(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); // 13+ - }, - {{battle_dialogue_1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); // 13+ + }, + {{battle_dialogue_1}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214_collector_brady: black screen not detected!", COLOR_RED); return false; @@ -694,19 +851,21 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co { MarkDetector battle_dialogue_2(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_wait(context, 30000ms); - }, - {{battle_dialogue_2}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_wait(context, 30000ms); + }, + {{battle_dialogue_2}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214_psychic_mitchell: black screen not detected!", COLOR_RED); return false; @@ -742,16 +901,17 @@ static bool pass_by_valor_lakefront(VideoStream& stream, ProControllerContext& c { ShortDialogWatcher repel_ran_out(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - DpadState dpad; - dpad.last_dir = DPAD_DOWN; - for (int c = 0; c < 20; c++){ - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, true); - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); - } - }, - {{repel_ran_out}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + DpadState dpad; + dpad.last_dir = DPAD_DOWN; + for (int c = 0; c < 20; c++){ + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 1, true); + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); + } + }, + {{repel_ran_out}}); if (ret == 0){ pbf_mash_button(context, BUTTON_A, 2000ms); }; @@ -770,11 +930,12 @@ static bool pass_by_valor_lakefront(VideoStream& stream, ProControllerContext& c context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214: Didn't enter building!", COLOR_RED); return false; @@ -788,11 +949,12 @@ static bool go_through_building_route_213(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); pbf_wait(context, 2000ms); ShortDialogWatcher repel_ran_out(COLOR_CYAN); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); - }, - {{repel_ran_out}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{repel_ran_out}}); if (ret == 0){ stream.log("go_through_building_route_214: Not reapplying repel.", COLOR_RED); pbf_mash_button(context, BUTTON_B, 2000ms); @@ -801,11 +963,12 @@ static bool go_through_building_route_213(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 30000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_building_route_214: Didn't exit building!", COLOR_RED); return false; @@ -824,22 +987,24 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4, true); MarkDetector tuber_chelsea_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - }, - {{tuber_chelsea_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 5 + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + }, + {{tuber_chelsea_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!", COLOR_RED); return false; @@ -853,22 +1018,24 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont context.wait_for_all_requests(); MarkDetector tuber_jared_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3 - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - }, - {{tuber_jared_fight}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 1000ms, 100ms); // 4+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3 + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + }, + {{tuber_jared_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_jared: Didn't detect battle starting!", COLOR_RED); return false; @@ -887,18 +1054,20 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 13+ MarkDetector beauty_cindy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5000ms, 100ms); // 15+ - }, - {{beauty_cindy_fight}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5000ms, 100ms); // 15+ + }, + {{beauty_cindy_fight}}); if (ret == 0){ BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_beauty_cindy: Didn't detect battle starting!", COLOR_RED); return false; @@ -922,11 +1091,12 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont pbf_wait(context, 1000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213: Didn't enter building!", COLOR_RED); return false; @@ -939,11 +1109,12 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont static bool go_through_house_east_pastoria(VideoStream& stream, ProControllerContext& context){ stream.log("Going through the building at the entrance of Pastoria city"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_east_pastoria: Didn't exit building!", COLOR_RED); return false; @@ -965,14 +1136,15 @@ static bool go_through_pastoria(VideoStream& stream, ProControllerContext& conte pbf_move_left_joystick(context, {0, -1}, 600ms, 100ms); // 4 ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 - pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 - pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 - pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {-1, 0}, 3400ms, 100ms); // 19 + pbf_move_left_joystick(context, {+1, 0}, 3400ms, 100ms); // 19 + }, + {{repel_dialog}}); if (ret == 0){ pbf_mash_button(context, BUTTON_B, 3000ms); }else if (ret < 0){ @@ -987,11 +1159,12 @@ static bool go_through_pastoria(VideoStream& stream, ProControllerContext& conte repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 8, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_pastoria: Didn't enter Pokemon center!", COLOR_RED); return false; @@ -1015,11 +1188,12 @@ static bool go_to_pastoria_gym(VideoStream& stream, ProControllerContext& contex repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_pastoria_gym: Didn't enter building!", COLOR_RED); return false; @@ -1028,234 +1202,324 @@ static bool go_to_pastoria_gym(VideoStream& stream, ProControllerContext& contex return true; } -void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0401(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "leave_gym: transition not detected.", env.console); + "leave_gym: transition not detected.", + env.console); } }, true); } -void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0402(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_veilstone_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_veilstone_pokemon_center: transition not detected.", - env.console); + "go_to_veilstone_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0403(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_pokemon_center: transition not detected.", - env.console); + "heal_at_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0404(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_galactic_warehouse(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_galactic_warehouse: transition not detected.", - env.console); + "go_to_galactic_warehouse: transition not detected.", + env.console); } }, false); } -void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0405(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "handle_battle_veilstone_galactic_grunts_1: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "handle_battle_veilstone_galactic_grunts_1: transition not detected.", + env.console); } }, false); } -void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0406(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!enter_galactic_warehouse(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_galactic_warehouse: transition not detected.", - env.console); + "enter_galactic_warehouse: transition not detected.", + env.console); } }, false); } -void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0407(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!exit_galactic_warehouse(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "exit_galactic_warehouse: transition not detected.", - env.console); + "exit_galactic_warehouse: transition not detected.", + env.console); } }, false); } -void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0408(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_route_214(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_route_214: transition not detected.", env.console); + "go_to_route_214: transition not detected.", + env.console); } }, false); } -void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0409(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "handle_battle_route_214_psychic_abigail: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "handle_battle_route_214_psychic_abigail: transition not detected.", + env.console); } }, false); } -void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0410(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "walk_through_route_214: transition not detected.", - env.console); + "walk_through_route_214: transition not detected.", + env.console); } }, false); } -void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0411(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!pass_by_valor_lakefront(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "pass_by_valor_lakefront: transition not detected.", - env.console); + "pass_by_valor_lakefront: transition not detected.", + env.console); } }, false); } -void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0412(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_building_route_213: transition not detected.", - env.console); + "go_through_building_route_213: transition not detected.", + env.console); } }, false); } -void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0413(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_213: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_213: transition not detected.", + env.console); } }, false); } -void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0414(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_east_pastoria: transition not detected.", - env.console); + "go_through_house_east_pastoria: transition not detected.", + env.console); } }, false); } -void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0415(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_pastoria: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_pastoria: transition not detected.", + env.console); } }, false); } -void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0416(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_pokemon_center: transition not detected.", - env.console); + "heal_at_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0417(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_pastoria_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_pastoria_gym: transition not detected.", + env.console); } }, false); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h index feeab61651..6b35d624b9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -20,8 +20,10 @@ class AutoStory_Segment_04 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0401 : public AutoStory_Checkpoint { @@ -29,187 +31,255 @@ class AutoStory_Checkpoint_0401 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0401(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0402 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0402(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0403 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0403(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0404 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0404(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0405 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0405(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0406 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0406(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0406(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0407 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0407(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0407(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0408 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0408(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0408(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0409 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0409(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0409(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0410 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0410(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0410(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0411 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0411(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0411(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0412 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0412(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0412(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0413 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0413(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0413(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0414 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0414(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0414(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0415 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0415(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0415(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0416 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0416(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0416(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0417 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0417(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0417(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0418 : public AutoStory_Checkpoint { @@ -217,8 +287,10 @@ class AutoStory_Checkpoint_0418 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index a4dbaafb32..a232e233a5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -39,8 +39,10 @@ std::string AutoStory_Segment_05::end_text() const { return "End: Fifth Gym Badge acquired (Veilstone City)."; } -void AutoStory_Segment_05::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_05::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -78,8 +80,10 @@ std::string AutoStory_Checkpoint_0501::start_text() const { std::string AutoStory_Checkpoint_0501::end_text() const { return "Entered Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0501(env, context, options, stats); } @@ -92,8 +96,10 @@ std::string AutoStory_Checkpoint_0502::start_text() const { std::string AutoStory_Checkpoint_0502::end_text() const { return "Healed and exited Pastoria City Pokemon Center."; } -void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0502(env, context, options, stats); } @@ -106,8 +112,10 @@ std::string AutoStory_Checkpoint_0503::start_text() const { std::string AutoStory_Checkpoint_0503::end_text() const { return "Pastoria City drama complete, exited east building."; } -void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0503(env, context, options, stats); } @@ -120,8 +128,10 @@ std::string AutoStory_Checkpoint_0504::start_text() const { std::string AutoStory_Checkpoint_0504::end_text() const { return "Exited east Pastoria building onto Route 213."; } -void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0504(env, context, options, stats); } @@ -134,8 +144,10 @@ std::string AutoStory_Checkpoint_0505::start_text() const { std::string AutoStory_Checkpoint_0505::end_text() const { return "Entered east Route 213 building."; } -void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0505(env, context, options, stats); } @@ -148,8 +160,10 @@ std::string AutoStory_Checkpoint_0506::start_text() const { std::string AutoStory_Checkpoint_0506::end_text() const { return "Exited building onto Route 214."; } -void AutoStory_Checkpoint_0506::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0506::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0506(env, context, options, stats); } @@ -162,8 +176,10 @@ std::string AutoStory_Checkpoint_0507::start_text() const { std::string AutoStory_Checkpoint_0507::end_text() const { return "Flew to Solaceon Town."; } -void AutoStory_Checkpoint_0507::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0507::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0507(env, context, options, stats); } @@ -176,8 +192,10 @@ std::string AutoStory_Checkpoint_0508::start_text() const { std::string AutoStory_Checkpoint_0508::end_text() const { return "Entered Solaceon Town Pokemon Center."; } -void AutoStory_Checkpoint_0508::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0508::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0508(env, context, options, stats); } @@ -190,8 +208,10 @@ std::string AutoStory_Checkpoint_0509::start_text() const { std::string AutoStory_Checkpoint_0509::end_text() const { return "Healed and exited Solaceon Town Pokemon Center."; } -void AutoStory_Checkpoint_0509::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0509::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0509(env, context, options, stats); } @@ -204,8 +224,10 @@ std::string AutoStory_Checkpoint_0510::start_text() const { std::string AutoStory_Checkpoint_0510::end_text() const { return "Reached Celestic Town Pokemon Center via Route 210."; } -void AutoStory_Checkpoint_0510::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0510::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0510(env, context, options, stats); } @@ -218,8 +240,10 @@ std::string AutoStory_Checkpoint_0511::start_text() const { std::string AutoStory_Checkpoint_0511::end_text() const { return "Healed and exited Celestic Town Pokemon Center."; } -void AutoStory_Checkpoint_0511::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0511::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0511(env, context, options, stats); } @@ -232,8 +256,10 @@ std::string AutoStory_Checkpoint_0512::start_text() const { std::string AutoStory_Checkpoint_0512::end_text() const { return "Entered Celestic Town cave."; } -void AutoStory_Checkpoint_0512::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0512::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0512(env, context, options, stats); } @@ -246,8 +272,10 @@ std::string AutoStory_Checkpoint_0513::start_text() const { std::string AutoStory_Checkpoint_0513::end_text() const { return "Discovered paintings and exited cave."; } -void AutoStory_Checkpoint_0513::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0513::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0513(env, context, options, stats); } @@ -260,8 +288,10 @@ std::string AutoStory_Checkpoint_0514::start_text() const { std::string AutoStory_Checkpoint_0514::end_text() const { return "Entered Hearthome City Pokemon Center."; } -void AutoStory_Checkpoint_0514::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0514::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0514(env, context, options, stats); } @@ -274,8 +304,10 @@ std::string AutoStory_Checkpoint_0515::start_text() const { std::string AutoStory_Checkpoint_0515::end_text() const { return "Healed and exited Hearthome City Pokemon Center."; } -void AutoStory_Checkpoint_0515::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0515::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0515(env, context, options, stats); } @@ -288,8 +320,10 @@ std::string AutoStory_Checkpoint_0516::start_text() const { std::string AutoStory_Checkpoint_0516::end_text() const { return "Fifth Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_0516::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0516::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0516(env, context, options, stats); } @@ -302,13 +336,15 @@ std::string AutoStory_Checkpoint_0517::start_text() const { std::string AutoStory_Checkpoint_0517::end_text() const { return "Fifth Gym Badge acquired (Hearthome City)."; } -void AutoStory_Checkpoint_0517::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0517::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0517(env, context, options, stats); } -static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, - const std::string& trainerid){ +static void +move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ stream.log("Starting battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -349,22 +385,23 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); }else if (trainerid == "route_214_galactic_grunt" or trainerid == "route_210_rancher_marco" or - trainerid == "route_210_breeder_amber" or trainerid == "route_210_blackbelt_adam"){ + trainerid == "route_210_breeder_amber" or trainerid == "route_210_blackbelt_adam"){ // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); }else if (trainerid == "route_210_ace_alyssa" or trainerid == "route_210_birdkeeper_brianna" or - trainerid == "celestic_town_galactic_grunt"){ + trainerid == "celestic_town_galactic_grunt"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -392,11 +429,12 @@ static bool leave_gym(VideoStream& stream, ProControllerContext& context){ dpad.last_dir = DPAD_DOWN; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: Didn't enter building!", COLOR_RED); return false; @@ -419,11 +457,12 @@ static bool go_to_pastoria_pokemon_center(VideoStream& stream, ProControllerCont repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7, true); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}}); + int ret1 = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret1 < 0){ stream.log("go_to_pastoria_pokemon_centert: Didn't detect transition!", COLOR_RED); return false; @@ -454,11 +493,12 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte { ShortDialogWatcher talk_to_grunt_1(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 2000ms); - }, - {{talk_to_grunt_1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{talk_to_grunt_1}}); if (ret < 0){ stream.log("pastoria_city_drama_talk_to_grunt_1: dialog box not detected!", COLOR_RED); } @@ -478,11 +518,12 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte { ShortDialogWatcher talk_to_grunt_2(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 2000ms); - }, - {{talk_to_grunt_2}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 2000ms); + }, + {{talk_to_grunt_2}}); if (ret < 0){ stream.log("pastoria_city_drama_talk_to_grunt_2: dialog box not detected!", COLOR_RED); } @@ -490,11 +531,12 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte stream.log("pastoria_city_drama_talk_to_grunt_2: talking to grunt!"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 200000ms); - }, - {{black_screen}}); + int ret1 = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_B, 200000ms); + }, + {{black_screen}}); if (ret1 < 0){ stream.log("pastoria_city_drama_talk_to_clint: Didn't detect start of battle with Clint!", COLOR_RED); return false; @@ -514,11 +556,12 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("pastoria_city_drama: transition not detected!", COLOR_RED); return false; @@ -532,11 +575,12 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte static bool go_through_house_east_pastoria(VideoStream& stream, ProControllerContext& context){ stream.log("Going through the building at the entrance of Pastoria city"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_east_pastoria: Didn't exit building!", COLOR_RED); return false; @@ -563,18 +607,20 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 5, true); MarkDetector beauty_cindy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 15+ - }, - {{beauty_cindy_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 15+ + }, + {{beauty_cindy_fight}}); if (ret == 0){ BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_beauty_cindy: Didn't detect battle starting!", COLOR_RED); return false; @@ -603,12 +649,13 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); ShortDialogWatcher talk_to_grunt(COLOR_BLUE); - ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}}); if (ret < 0){ stream.log("go_through_route_213_chasing_talk_to_grunt: dialog box not detected!", COLOR_RED); } @@ -624,14 +671,15 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); ShortDialogWatcher repel_dialog(COLOR_RED); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); - }, - {{repel_dialog}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); + }, + {{repel_dialog}}); if (ret == 0){ pbf_mash_button(context, BUTTON_B, 3000ms); }else if (ret < 0){ @@ -645,22 +693,24 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); MarkDetector tuber_jared_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 17 - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 3 - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - }, - {{tuber_jared_fight}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 17 + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 3 + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + }, + {{tuber_jared_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_jared: Didn't detect battle starting!", COLOR_RED); return false; @@ -675,23 +725,25 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); MarkDetector tuber_chelsea_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - DpadState dpad; - pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ - dpad.last_dir = DPAD_LEFT; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6, true); - }, - {{tuber_chelsea_fight}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + DpadState dpad; + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 3+ + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 6, true); + }, + {{tuber_chelsea_fight}}); if (ret == 0){ context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!"); }else if (ret == 0){ @@ -703,11 +755,12 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte } BlackScreenOverWatcher route_213_east_building(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{route_213_east_building}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{route_213_east_building}}); if (ret < 0){ stream.log("go_through_route_213_east_building: Didn't detect entering the building!", COLOR_RED); return false; @@ -722,11 +775,12 @@ static bool go_through_building_route_213(VideoStream& stream, ProControllerCont pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 30000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 30000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_building_route_214: Didn't exit building!", COLOR_RED); return false; @@ -753,12 +807,13 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont context.wait_for_all_requests(); ShortDialogWatcher talk_to_grunt(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}}); if (ret < 0){ stream.log("go_through_route_214_talk_to_grunt: dialog box not detected!", COLOR_RED); } @@ -775,12 +830,13 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 2, true); ShortDialogWatcher talk_to_grunt_2(COLOR_BLUE); - ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt_2}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt_2}}); if (ret < 0){ stream.log("go_through_route_214_talk_to_grunt_2: dialog box not detected!", COLOR_RED); } @@ -788,11 +844,12 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont stream.log("go_through_route_214_talk_to_grunt_2: talking to grunt!"); context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_214_galactic_grunt: Didn't detect battle starting!", COLOR_RED); return false; @@ -810,12 +867,13 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 1, true); ShortDialogWatcher talk_to_cynthia(COLOR_BLUE); - ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 10000ms); - ssf_mash1_button(context, BUTTON_A, 10000ms); - }, - {{talk_to_cynthia}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + {{talk_to_cynthia}}); if (ret < 0){ stream.log("go_through_route_214_talk_to_cynthia: dialog box not detected!", COLOR_RED); } @@ -838,11 +896,12 @@ static bool go_inside_solaceon_pokemon_center(VideoStream& stream, ProController pbf_wait(context, 3000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 200000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 200000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_inside_solaceon_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -885,22 +944,24 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); - }, - {{battle_dialogue}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); stream.log("go_to_the_psyduck_route_210_rancher_marco: marker detected!", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_rancher_marco: black screen not detected!", COLOR_RED); return false; @@ -919,29 +980,31 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); ShortDialogWatcher battle_dialogue_2(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - DpadState dpad; - pbf_move_left_joystick(context, {0, +1}, 6600ms, - 100ms); // 22+ tall grass - - dpad.last_dir = DPAD_UP; - repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); - repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{battle_dialogue}, {battle_dialogue_2}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + DpadState dpad; + pbf_move_left_joystick(context, {0, +1}, 6600ms, + 100ms); // 22+ tall grass + + dpad.last_dir = DPAD_UP; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 4); + repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{battle_dialogue}, {battle_dialogue_2}}); if (ret >= 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); stream.log("go_to_the_psyduck_route_210_breeder_amber: marker detected!", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_breeder_amber: black screen not detected!", COLOR_RED); return false; @@ -958,12 +1021,13 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3); ShortDialogWatcher talk_to_psyducks(COLOR_BLUE); - int ret_psyducks = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_psyducks}}); + int ret_psyducks = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_psyducks}}); if (ret_psyducks < 0){ stream.log("go_to_the_psyduck_route_210_talk_to_psyducks: dialog box not detected!", COLOR_RED); } @@ -987,11 +1051,12 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_the_psyduck_route_210: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -1015,11 +1080,12 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, +1}, 10000ms, 100ms); - }, - {{battle_dialogue}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, +1}, 10000ms, 100ms); + }, + {{battle_dialogue}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_ace_alyssa: dialog box not detected!", COLOR_RED); return false; @@ -1028,11 +1094,12 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_ace_alyssa: black screen not detected!", COLOR_RED); return false; @@ -1053,21 +1120,23 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ - }, - {{battle_dialogue}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3800ms, 100ms); // 19+ + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_ace_ernest: black screen not detected!", COLOR_RED); return false; @@ -1086,21 +1155,23 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); - }, - {{battle_dialogue}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_blackbelt_adam: black screen not detected!", COLOR_RED); return false; @@ -1114,14 +1185,15 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); // 1+ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_the_psyduck_route_210: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -1142,21 +1214,23 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); - }, - {{battle_dialogue}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 20000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_birdkeeper_brianna: black screen not detected!", COLOR_RED); return false; @@ -1174,21 +1248,23 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); - }, - {{battle_dialogue}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 6000ms, 100ms); + }, + {{battle_dialogue}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_veteran_brian: black screen not detected!", COLOR_RED); return false; @@ -1206,14 +1282,15 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_psyduck_route_210_enter_celestic_town_pokemon_center: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ return true; @@ -1240,12 +1317,13 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte { ShortDialogWatcher talk_to_cynthias_grandma(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_cynthias_grandma}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_cynthias_grandma}}); if (ret < 0){ stream.log("celestic_town_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); } @@ -1265,12 +1343,13 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte { ShortDialogWatcher talk_to_the_grunt(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {-1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_the_grunt}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {-1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_the_grunt}}); if (ret < 0){ stream.log("celestic_town_talk_to_the_grunt: dialog box not detected!", COLOR_RED); } @@ -1278,11 +1357,12 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte stream.log("celestic_town_talk_to_the_grunt: talking to the grunt!"); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret1 = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret1 < 0){ stream.log("celestic_town_talk_to_the_grunt: Didn't detect start of battle with Clint!", COLOR_RED); return false; @@ -1299,11 +1379,12 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("celestic_town_enter_cave: black screen not detected!", COLOR_RED); return false; @@ -1331,12 +1412,13 @@ static bool discover_the_paintings(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher talk_to_cynthias_grandma(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_cynthias_grandma}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_cynthias_grandma}}); if (ret < 0){ stream.log("discover_the_paintings_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); } @@ -1358,11 +1440,12 @@ static bool discover_the_paintings(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("discover_the_paintings: black screen for exiting the building not detected!", COLOR_RED); return false; @@ -1393,14 +1476,15 @@ static bool talk_to_cyrus_and_fly_hearthome(VideoStream& stream, ProControllerCo { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("talk_to_cyrus_and_fly_hearthome: black screen for entering pokemon center not detected!", - COLOR_RED); + COLOR_RED); return false; } } @@ -1428,11 +1512,12 @@ static bool get_into_hearthome_gym(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("get_into_hearthome_gym: black screen not detected!", COLOR_RED); return false; @@ -1442,236 +1527,324 @@ static bool get_into_hearthome_gym(VideoStream& stream, ProControllerContext& co return true; } -void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0501(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_pastoria_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_pastoria_pokemon_center: transition not detected.", - env.console); + "go_to_pastoria_pokemon_center: transition not detected.", + env.console); } }, true); } -void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0502(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!exit_pastoria_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "exit_pastoria_pokemon_center: transition not detected.", - env.console); + "exit_pastoria_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0503(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!pastoria_city_drama(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "pastoria_city_drama: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "pastoria_city_drama: transition not detected.", + env.console); } }, false); } -void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0504(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_house_east_pastoria(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_east_pastoria: transition not detected.", - env.console); + "go_through_house_east_pastoria: transition not detected.", + env.console); } }, false); } -void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0505(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_route_213_chasing(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_route_213_chasing: transition not detected.", - env.console); + "go_through_route_213_chasing: transition not detected.", + env.console); } }, false); } -void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0506(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_building_route_213(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_building_route_213: transition not detected.", - env.console); + "go_through_building_route_213: transition not detected.", + env.console); } }, false); } -void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0507(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_214: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_214: transition not detected.", + env.console); } }, false); } -void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0508(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_inside_solaceon_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_inside_solaceon_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_inside_solaceon_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0509(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_solaceon_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "heal_at_solaceon_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_solaceon_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0510(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_the_psyduck_route_210(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_the_psyduck_route_210: transition not detected.", - env.console); + "go_to_the_psyduck_route_210: transition not detected.", + env.console); } }, false); } -void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0511(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_celestic_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "heal_at_celestic_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_celestic_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0512(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!celestic_town_drama(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "celestic_town_drama: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "celestic_town_drama: transition not detected.", + env.console); } }, false); } -void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0513(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!discover_the_paintings(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "discover_the_paintings: transition not detected.", - env.console); + "discover_the_paintings: transition not detected.", + env.console); } }, false); } -void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0514(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "talk_to_cyrus_and_fly_hearthome: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_to_cyrus_and_fly_hearthome: transition not detected.", env.console); } }, false); } -void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0515(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_hearthome_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "heal_at_hearthome_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_hearthome_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0516(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_into_hearthome_gym: transition not detected.", - env.console); + "get_into_hearthome_gym: transition not detected.", + env.console); } }, false); } -void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0517(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!get_into_hearthome_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_into_hearthome_gym: transition not detected.", - env.console); + "get_into_hearthome_gym: transition not detected.", + env.console); } }, false); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h index f5f53a21c1..9c24bad367 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h @@ -20,8 +20,10 @@ class AutoStory_Segment_05 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0501 : public AutoStory_Checkpoint { @@ -29,187 +31,255 @@ class AutoStory_Checkpoint_0501 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0501(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0502 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0502(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0503 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0503(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0504 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0504(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0505 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0505(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0506 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0506(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0506(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0507 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0507(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0507(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0508 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0508(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0508(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0509 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0509(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0509(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0510 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0510(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0510(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0511 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0511(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0511(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0512 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0512(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0512(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0513 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0513(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0513(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0514 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0514(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0514(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0515 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0515(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0515(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0516 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0516(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0516(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0517 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0517(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0517(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 03ca3b0a7b..176f79a152 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -39,8 +39,10 @@ std::string AutoStory_Segment_06::end_text() const { return "End: Sixth Gym Badge acquired (Canalave City)."; } -void AutoStory_Segment_06::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_06::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -72,8 +74,10 @@ std::string AutoStory_Checkpoint_0601::start_text() const { std::string AutoStory_Checkpoint_0601::end_text() const { return "Flew to Jubilife City."; } -void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0601(env, context, options, stats); } @@ -86,8 +90,10 @@ std::string AutoStory_Checkpoint_0602::start_text() const { std::string AutoStory_Checkpoint_0602::end_text() const { return "Entered Jubilife City Pokemon Center."; } -void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0602(env, context, options, stats); } @@ -100,8 +106,10 @@ std::string AutoStory_Checkpoint_0603::start_text() const { std::string AutoStory_Checkpoint_0603::end_text() const { return "Healed and exited Jubilife City Pokemon Center."; } -void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0603(env, context, options, stats); } @@ -114,8 +122,10 @@ std::string AutoStory_Checkpoint_0604::start_text() const { std::string AutoStory_Checkpoint_0604::end_text() const { return "Entered west exit building of Jubilife City."; } -void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0604(env, context, options, stats); } @@ -128,8 +138,10 @@ std::string AutoStory_Checkpoint_0605::start_text() const { std::string AutoStory_Checkpoint_0605::end_text() const { return "Exited onto Route 218."; } -void AutoStory_Checkpoint_0605::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0605::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0605(env, context, options, stats); } @@ -142,8 +154,10 @@ std::string AutoStory_Checkpoint_0606::start_text() const { std::string AutoStory_Checkpoint_0606::end_text() const { return "Entered building at end of Route 218."; } -void AutoStory_Checkpoint_0606::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0606::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0606(env, context, options, stats); } @@ -156,8 +170,10 @@ std::string AutoStory_Checkpoint_0607::start_text() const { std::string AutoStory_Checkpoint_0607::end_text() const { return "Exited building into Canalave City."; } -void AutoStory_Checkpoint_0607::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0607::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0607(env, context, options, stats); } @@ -170,8 +186,10 @@ std::string AutoStory_Checkpoint_0608::start_text() const { std::string AutoStory_Checkpoint_0608::end_text() const { return "Battled Clint and entered Canalave City Pokemon Center."; } -void AutoStory_Checkpoint_0608::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0608::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0608(env, context, options, stats); } @@ -184,8 +202,10 @@ std::string AutoStory_Checkpoint_0609::start_text() const { std::string AutoStory_Checkpoint_0609::end_text() const { return "Healed and exited Canalave City Pokemon Center."; } -void AutoStory_Checkpoint_0609::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0609::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0609(env, context, options, stats); } @@ -198,8 +218,10 @@ std::string AutoStory_Checkpoint_0610::start_text() const { std::string AutoStory_Checkpoint_0610::end_text() const { return "Sixth Gym Badge acquired (Canalave City)."; } -void AutoStory_Checkpoint_0610::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0610::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0610(env, context, options, stats); } @@ -212,14 +234,16 @@ std::string AutoStory_Checkpoint_0611::start_text() const { std::string AutoStory_Checkpoint_0611::end_text() const { return "Sixth Gym Badge acquired (Canalave City)."; } -void AutoStory_Checkpoint_0611::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0611::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0611(env, context, options, stats); } -static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, - const std::string& trainerid){ +static void +move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -254,11 +278,12 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -319,11 +344,12 @@ static bool enter_jubilife_pokemon_center(VideoStream& stream, ProControllerCont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_jubilife_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -351,11 +377,12 @@ static bool move_to_jubilife_west_exit(VideoStream& stream, ProControllerContext { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); // 46 - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); // 46 + }, + {{black_screen}}); if (ret < 0){ stream.log("move_to_jubilife_west_exit: transition not detected!", COLOR_RED); return false; @@ -375,11 +402,12 @@ static bool go_through_jubilife_west_exit(VideoStream& stream, ProControllerCont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_jubilife_west_exit: transition not detected!", COLOR_RED); return false; @@ -413,24 +441,26 @@ static bool go_through_route_218(VideoStream& stream, ProControllerContext& cont { MarkDetector sailor_skyler_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {-1, 0}, 6000ms, - 100ms); // 28++ (getting out of the water) - pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ - }, - {{sailor_skyler_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {-1, 0}, 6000ms, + 100ms); // 28++ (getting out of the water) + pbf_move_left_joystick(context, {0, -1}, 2800ms, 100ms); // 14+ + }, + {{sailor_skyler_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_218_sailor_skyler: black screen not detected!", COLOR_RED); return false; @@ -448,11 +478,12 @@ static bool go_through_route_218(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_218: transition not detected!", COLOR_RED); return false; @@ -471,11 +502,12 @@ static bool go_through_building_route_218(VideoStream& stream, ProControllerCont { ShortDialogWatcher talk_to_scientist(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{talk_to_scientist}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{talk_to_scientist}}); if (ret < 0){ stream.log("go_through_building_route_218_scientist: dialog box not detected!", COLOR_RED); return false; @@ -488,11 +520,12 @@ static bool go_through_building_route_218(VideoStream& stream, ProControllerCont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_building_route_218: transition not detected!", COLOR_RED); return false; @@ -519,11 +552,12 @@ static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerCon { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_clint_and_pokemon_center: Not activating repel again.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -540,11 +574,12 @@ static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerCon { ShortDialogWatcher talk_to_clint(COLOR_BLUE); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); - }, - {{talk_to_clint}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 100000ms, 100ms); + }, + {{talk_to_clint}}); if (ret < 0){ stream.log("go_to_clint_and_pokemon_center_clint: dialog box not detected!", COLOR_RED); return false; @@ -554,11 +589,12 @@ static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerCon pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_clint_and_pokemon_center_clint: black screen not detected!", COLOR_RED); return false; @@ -581,11 +617,12 @@ static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerCon { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_clint_and_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -620,11 +657,12 @@ static bool go_to_canalave_gym(VideoStream& stream, ProControllerContext& contex { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_canalave_gym: black screen not detected!", COLOR_RED); return false; @@ -635,155 +673,213 @@ static bool go_to_canalave_gym(VideoStream& stream, ProControllerContext& contex } -void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0601(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ /*if (!test(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "test: transition not detected.", env.console); }*/ if (!cynthia_and_fly_to_jubilife(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "cynthia_and_fly_to_jubilife: transition not detected.", - env.console); + "cynthia_and_fly_to_jubilife: transition not detected.", + env.console); } }, true); } -void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0602(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!enter_jubilife_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_jubilife_pokemon_center: transition not detected.", - env.console); + "enter_jubilife_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0603(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_jubilife_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_jubilife_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0604(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "move_to_jubilife_west_exit: transition not detected.", - env.console); + "move_to_jubilife_west_exit: transition not detected.", + env.console); } }, false); } -void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0605(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_jubilife_west_exit: transition not detected.", - env.console); + "go_through_jubilife_west_exit: transition not detected.", + env.console); } }, false); } -void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0606(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_route_218(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_218: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_218: transition not detected.", + env.console); } }, false); } -void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0607(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_building_route_218: transition not detected.", - env.console); + "go_through_building_route_218: transition not detected.", + env.console); } }, false); } -void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0608(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_clint_and_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_clint_and_pokemon_center: transition not detected.", - env.console); + "go_to_clint_and_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0609(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_canalave_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_canalave_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0610(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_canalave_gym: transition not detected.", + env.console); } }, false); } -void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0611(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_canalave_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_canalave_gym: transition not detected.", + env.console); } }, false); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h index 17820d197a..4c99db317b 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h @@ -20,8 +20,10 @@ class AutoStory_Segment_06 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0601 : public AutoStory_Checkpoint { @@ -29,121 +31,165 @@ class AutoStory_Checkpoint_0601 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0601(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0602 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0602(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0603 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0603(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0604 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0604(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0605 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0605(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0605(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0606 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0606(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0606(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0607 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0607(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0607(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0608 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0608(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0608(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0609 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0609(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0609(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0610 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0610(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0610(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0611 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0611(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0611(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index 9254617a68..cc96681cd3 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -40,8 +40,10 @@ std::string AutoStory_Segment_07::end_text() const { return "End: Seventh Gym Badge acquired (Snowpoint City)."; } -void AutoStory_Segment_07::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_07::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -81,8 +83,10 @@ std::string AutoStory_Checkpoint_0701::start_text() const { std::string AutoStory_Checkpoint_0701::end_text() const { return "Exited Canalave City gym."; } -void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0701(env, context, options, stats); } @@ -95,8 +99,10 @@ std::string AutoStory_Checkpoint_0702::start_text() const { std::string AutoStory_Checkpoint_0702::end_text() const { return "Entered Canalave City library."; } -void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0702(env, context, options, stats); } @@ -109,8 +115,10 @@ std::string AutoStory_Checkpoint_0703::start_text() const { std::string AutoStory_Checkpoint_0703::end_text() const { return "Library drama complete, exited library."; } -void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0703(env, context, options, stats); } @@ -123,8 +131,10 @@ std::string AutoStory_Checkpoint_0704::start_text() const { std::string AutoStory_Checkpoint_0704::end_text() const { return "Entered Mt. Coronet."; } -void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0704(env, context, options, stats); } @@ -137,8 +147,10 @@ std::string AutoStory_Checkpoint_0705::start_text() const { std::string AutoStory_Checkpoint_0705::end_text() const { return "Exited Mt. Coronet."; } -void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0705(env, context, options, stats); } @@ -151,8 +163,10 @@ std::string AutoStory_Checkpoint_0706::start_text() const { std::string AutoStory_Checkpoint_0706::end_text() const { return "Entered small chalet on Route 216."; } -void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0706(env, context, options, stats); } @@ -165,8 +179,10 @@ std::string AutoStory_Checkpoint_0707::start_text() const { std::string AutoStory_Checkpoint_0707::end_text() const { return "Rested in chalet and exited."; } -void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0707(env, context, options, stats); } @@ -179,8 +195,10 @@ std::string AutoStory_Checkpoint_0708::start_text() const { std::string AutoStory_Checkpoint_0708::end_text() const { return "Entered Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0708::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0708::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0708(env, context, options, stats); } @@ -193,8 +211,10 @@ std::string AutoStory_Checkpoint_0709::start_text() const { std::string AutoStory_Checkpoint_0709::end_text() const { return "Healed and exited Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0709::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0709::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0709(env, context, options, stats); } @@ -207,8 +227,10 @@ std::string AutoStory_Checkpoint_0710::start_text() const { std::string AutoStory_Checkpoint_0710::end_text() const { return "Entered house south of Veilstone City."; } -void AutoStory_Checkpoint_0710::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0710::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0710(env, context, options, stats); } @@ -221,8 +243,10 @@ std::string AutoStory_Checkpoint_0711::start_text() const { std::string AutoStory_Checkpoint_0711::end_text() const { return "Exited house onto Route 214."; } -void AutoStory_Checkpoint_0711::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0711::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0711(env, context, options, stats); } @@ -235,8 +259,10 @@ std::string AutoStory_Checkpoint_0712::start_text() const { std::string AutoStory_Checkpoint_0712::end_text() const { return "Entered Valor Lakefront."; } -void AutoStory_Checkpoint_0712::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0712::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0712(env, context, options, stats); } @@ -249,8 +275,10 @@ std::string AutoStory_Checkpoint_0713::start_text() const { std::string AutoStory_Checkpoint_0713::end_text() const { return "Flew to Twinleaf Town and entered own house."; } -void AutoStory_Checkpoint_0713::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0713::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0713(env, context, options, stats); } @@ -263,8 +291,10 @@ std::string AutoStory_Checkpoint_0714::start_text() const { std::string AutoStory_Checkpoint_0714::end_text() const { return "Healed at home and exited."; } -void AutoStory_Checkpoint_0714::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0714::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0714(env, context, options, stats); } @@ -277,8 +307,10 @@ std::string AutoStory_Checkpoint_0715::start_text() const { std::string AutoStory_Checkpoint_0715::end_text() const { return "Entered Verity Lakefront."; } -void AutoStory_Checkpoint_0715::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0715::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0715(env, context, options, stats); } @@ -291,8 +323,10 @@ std::string AutoStory_Checkpoint_0716::start_text() const { std::string AutoStory_Checkpoint_0716::end_text() const { return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_0716::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0716::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0716(env, context, options, stats); } @@ -305,8 +339,10 @@ std::string AutoStory_Checkpoint_0717::start_text() const { std::string AutoStory_Checkpoint_0717::end_text() const { return "Healed and exited Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_0717::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0717::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0717(env, context, options, stats); } @@ -319,8 +355,10 @@ std::string AutoStory_Checkpoint_0718::start_text() const { std::string AutoStory_Checkpoint_0718::end_text() const { return "Seventh Gym Badge acquired (Snowpoint City)."; } -void AutoStory_Checkpoint_0718::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0718::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0718(env, context, options, stats); } @@ -333,13 +371,15 @@ std::string AutoStory_Checkpoint_0719::start_text() const { std::string AutoStory_Checkpoint_0719::end_text() const { return "Seventh Gym Badge acquired (Snowpoint City)."; } -void AutoStory_Checkpoint_0719::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0719::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0719(env, context, options, stats); } -static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, - const std::string& trainerid){ +static void +move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -378,18 +418,19 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); }else if (trainerid == "verity_lakefront_grunt_2" or trainerid == "verity_lakefront_grunt_4" or - trainerid == "verity_lakefront_commander_mars"){ + trainerid == "verity_lakefront_commander_mars"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -410,11 +451,12 @@ static bool leave_gym(VideoStream& stream, ProControllerContext& context){ { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: black screen not detected!", COLOR_RED); return false; @@ -455,11 +497,12 @@ static bool go_to_the_library(VideoStream& stream, ProControllerContext& context { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_the_library: black screen not detected!", COLOR_RED); return false; @@ -480,11 +523,12 @@ static bool drama_at_the_library(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -500,11 +544,12 @@ static bool drama_at_the_library(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -537,11 +582,12 @@ static bool drama_at_the_library(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -559,11 +605,12 @@ static bool drama_at_the_library(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -576,11 +623,12 @@ static bool drama_at_the_library(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("drama_at_the_library: black screen not detected!", COLOR_RED); return false; @@ -639,11 +687,12 @@ static bool talk_fly_and_enter_mount_coronet(VideoStream& stream, ProControllerC { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("talk_fly_and_enter_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -676,11 +725,12 @@ static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -697,11 +747,12 @@ static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_mount_coronet: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -715,11 +766,12 @@ static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -735,11 +787,12 @@ static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& { WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{white_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{white_screen}}); if (ret < 0){ stream.log("go_through_mount_coronet: white screen not detected!", COLOR_RED); return false; @@ -762,11 +815,12 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 9600ms, 100ms); // 48+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 9600ms, 100ms); // 48+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_route_216: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -782,21 +836,23 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont { MarkDetector ace_maria_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3200ms, 100ms); // 16 - }, - {{ace_maria_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3200ms, 100ms); // 16 + }, + {{ace_maria_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_216_ace_maria: black screen not detected!", COLOR_RED); return false; @@ -815,22 +871,23 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont { MarkDetector skier_edward_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 7500ms, 100ms); // 25++ (snow) - }, - {{skier_edward_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 7500ms, 100ms); // 25++ (snow) + }, + {{skier_edward_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_216_skier_edward: black screen not detected!", COLOR_RED); return false; @@ -849,11 +906,12 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_route_216: black screen not detected!", COLOR_RED); return false; @@ -876,23 +934,25 @@ static bool heal_small_house_route_216(VideoStream& stream, ProControllerContext { ShortDialogWatcher sleep_in_a_bed(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{sleep_in_a_bed}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{sleep_in_a_bed}}); if (ret == 0){ stream.log("heal_small_house_route_216_sleep_in_a_bed: taking a nap...", COLOR_GREEN); const ImageFloatBox box{0.670000, 0.600000, 0.100000, 0.150000}; SelectionArrowFinder arrow(stream.overlay(), box, COLOR_GREEN); - int arrow_ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 10000ms); - }, - {{arrow}}); + int arrow_ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 10000ms); + }, + {{arrow}}); if (arrow_ret != 0){ stream.log("heal_small_house_route_216_sleep_in_a_bed: selection arrow not found.", COLOR_RED); return false; @@ -916,11 +976,12 @@ static bool heal_small_house_route_216(VideoStream& stream, ProControllerContext { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_small_house_route_216: black screen not detected!", COLOR_RED); return false; @@ -946,11 +1007,12 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("continue_through_route_216: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -970,22 +1032,23 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { MarkDetector ace_dalton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 7500ms, 100ms); // 25++ (snow) - }, - {{ace_dalton_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 7500ms, 100ms); // 25++ (snow) + }, + {{ace_dalton_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_ace_dalton: black screen not detected!", COLOR_RED); return false; @@ -1003,12 +1066,13 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { ShortDialogWatcher pick_up_TM100(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, -1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{pick_up_TM100}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_TM100}}); if (ret == 0){ stream.log("continue_through_route_216: Picking up TM100.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 10000ms); @@ -1023,22 +1087,23 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { MarkDetector skier_lexie_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 6000ms, 100ms); // 20++ (snow) - }, - {{skier_lexie_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 6000ms, 100ms); // 20++ (snow) + }, + {{skier_lexie_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_skier_lexie: black screen not detected!", COLOR_RED); return false; @@ -1065,22 +1130,23 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { MarkDetector blackbelt_luke_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 5400ms, 100ms); // 21++ (snow) - }, - {{blackbelt_luke_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5400ms, 100ms); // 21++ (snow) + }, + {{blackbelt_luke_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_blackbelt_luke: black screen not detected!", COLOR_RED); return false; @@ -1103,21 +1169,23 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { MarkDetector ace_olivia_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{ace_olivia_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{ace_olivia_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216_ace_olivia: black screen not detected!", COLOR_RED); return false; @@ -1151,11 +1219,12 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("continue_through_route_216: black screen not detected!", COLOR_RED); return false; @@ -1187,12 +1256,13 @@ static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContex { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ - pbf_move_left_joystick(context, {+1, 0}, 4000ms, 100ms); // 20+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 4000ms, 100ms); // 20+ + pbf_move_left_joystick(context, {+1, 0}, 4000ms, 100ms); // 20+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_house_south_veilstone: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -1222,11 +1292,12 @@ static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContex { BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); return false; @@ -1244,11 +1315,12 @@ static bool go_through_house_south_veilstone(VideoStream& stream, ProControllerC { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); return false; @@ -1299,11 +1371,12 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 11000ms, 100ms); // 55+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 11000ms, 100ms); // 55+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("walk_through_route_214: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -1323,11 +1396,12 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("walk_through_route_214: black screen not detected!", COLOR_RED); return false; @@ -1362,16 +1436,17 @@ static bool go_through_lake_valor(VideoStream& stream, ProControllerContext& con { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ + pbf_move_left_joystick(context, {+1, 0}, 2400ms, 100ms); // 12+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_lake_valor: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -1394,11 +1469,12 @@ static bool go_through_lake_valor(VideoStream& stream, ProControllerContext& con { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_lake_valor: black screen not detected!", COLOR_RED); return false; @@ -1421,12 +1497,13 @@ static bool heal_at_home(VideoStream& stream, ProControllerContext& context){ { ShortDialogWatcher mom_heal_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{mom_heal_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{mom_heal_dialog}}); if (ret == 0){ stream.log("heal_at_home: Sleeping...", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 4000ms); @@ -1445,11 +1522,12 @@ static bool heal_at_home(VideoStream& stream, ProControllerContext& context){ { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_at_home: black screen not detected!", COLOR_RED); return false; @@ -1477,11 +1555,12 @@ static bool go_to_lake_verity(VideoStream& stream, ProControllerContext& context { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_lake_verity: black screen not detected!", COLOR_RED); return false; @@ -1518,21 +1597,23 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte { MarkDetector grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 - }, - {{grunt_1_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 + }, + {{grunt_1_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_1: black screen not detected!", COLOR_RED); return false; @@ -1547,11 +1628,12 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); return false; @@ -1574,21 +1656,23 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte { MarkDetector grunt_3_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 - }, - {{grunt_3_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 3200ms, 100ms); // 16 + }, + {{grunt_3_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_3: black screen not detected!", COLOR_RED); return false; @@ -1603,11 +1687,12 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); return false; @@ -1625,12 +1710,13 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte { ShortDialogWatcher commander_mars_fight(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{commander_mars_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{commander_mars_fight}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint: Didn't detect Commander Mars dialog box!", COLOR_RED); return false; @@ -1640,11 +1726,12 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint_commander_mars: black screen not detected!", COLOR_RED); return false; @@ -1668,11 +1755,12 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); return false; @@ -1709,11 +1797,12 @@ static bool go_to_snowpoint_gym(VideoStream& stream, ProControllerContext& conte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); return false; @@ -1725,258 +1814,362 @@ static bool go_to_snowpoint_gym(VideoStream& stream, ProControllerContext& conte } -void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0701(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "leave_gym: transition not detected.", env.console); + "leave_gym: transition not detected.", + env.console); } }, true); } -void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0702(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_the_library(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_the_library: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_the_library: transition not detected.", + env.console); } }, false); } -void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0703(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!drama_at_the_library(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "drama_at_the_library: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "drama_at_the_library: transition not detected.", + env.console); } }, false); } -void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0704(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!talk_fly_and_enter_mount_coronet(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "talk_fly_and_enter_mount_coronet: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_fly_and_enter_mount_coronet: transition not detected.", env.console); } }, false); } -void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0705(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_mount_coronet: transition not detected.", - env.console); + "go_through_mount_coronet: transition not detected.", + env.console); } }, false); } -void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0706(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_route_216(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_route_216: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_216: transition not detected.", + env.console); } }, false); } -void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0707(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_small_house_route_216(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_small_house_route_216: transition not detected.", - env.console); + "heal_small_house_route_216: transition not detected.", + env.console); } }, false); } -void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0708(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!continue_through_route_216(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "continue_through_route_216: transition not detected.", - env.console); + "continue_through_route_216: transition not detected.", + env.console); } }, false); } -void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0709(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0710(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_house_south_veilstone: transition not detected.", - env.console); + "go_to_house_south_veilstone: transition not detected.", + env.console); } }, false); } -void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0711(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_south_veilstone: transition not detected.", env.console); } }, false); } -void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0712(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "walk_through_route_214: transition not detected.", - env.console); + "walk_through_route_214: transition not detected.", + env.console); } }, false); } -void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0713(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_lake_valor(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_lake_valor: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_lake_valor: transition not detected.", + env.console); } }, false); } -void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0714(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_home(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_home: transition not detected.", env.console); + "heal_at_home: transition not detected.", + env.console); } }, false); } -void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0715(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_lake_verity(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_verity: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_lake_verity: transition not detected.", + env.console); } }, false); } -void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0716(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!commander_mars_and_snowpoint(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "commander_mars_and_snowpoint: transition not detected.", - env.console); + "commander_mars_and_snowpoint: transition not detected.", + env.console); } }, false); } -void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0717(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "heal_at_snowpoint_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_snowpoint_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0718(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_snowpoint_gym: transition not detected.", + env.console); } }, false); } -void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0719(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_snowpoint_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_snowpoint_gym: transition not detected.", + env.console); } }, false); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h index 55cd7ad00b..e69a7f863c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h @@ -20,8 +20,10 @@ class AutoStory_Segment_07 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0701 : public AutoStory_Checkpoint { @@ -29,209 +31,285 @@ class AutoStory_Checkpoint_0701 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0701(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0702 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0702(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0703 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0703(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0704 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0704(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0705 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0705(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0706 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0706(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0707 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0707(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0708 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0708(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0708(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0709 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0709(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0709(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0710 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0710(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0710(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0711 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0711(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0711(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0712 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0712(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0712(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0713 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0713(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0713(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0714 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0714(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0714(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0715 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0715(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0715(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0716 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0716(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0716(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0717 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0717(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0717(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0718 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0718(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0718(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0719 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0719(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0719(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index 1be1bba576..e1f6138fb4 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -92,8 +92,10 @@ std::string AutoStory_Segment_08::end_text() const { return "End: Eighth Gym Badge acquired (Sunyshore City)."; } -void AutoStory_Segment_08::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_08::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -143,8 +145,8 @@ std::string AutoStory_Checkpoint_021::end_text() const { // ------------------------------------------------------------------ Functions logic --------------------------------------------------------- // -static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, - const std::string& trainerid){ +static void +move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -176,24 +178,24 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); }else if (trainerid == "galactic_hq_grunt_1" or trainerid == "galactic_hq_double_grunts_1" or - trainerid == "route_222_sailor_luther"){ + trainerid == "route_222_sailor_luther"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); }else if (trainerid == "veilstone_warehouse_warehouse_grunt_1" or trainerid == "galactic_hq_scientist_fredrick" or - trainerid == "galactic_hq_grunt_2" or trainerid == "galactic_hq_grunt_3" or - trainerid == "galactic_hq_cyrus" or trainerid == "galactic_hq_commander_saturn" or - trainerid == "route_207_hiker_justin"){ + trainerid == "galactic_hq_grunt_2" or trainerid == "galactic_hq_grunt_3" or trainerid == "galactic_hq_cyrus" or + trainerid == "galactic_hq_commander_saturn" or trainerid == "route_207_hiker_justin"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -214,11 +216,12 @@ static bool leave_gym(VideoStream& stream, ProControllerContext& context){ { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("leave_gym: black screen not detected!", COLOR_RED); return false; @@ -241,11 +244,12 @@ static bool heal_at_snowpoint_city_pokemon_center(VideoStream& stream, ProContro { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_at_snowpoint_city_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -292,11 +296,12 @@ static bool go_to_lake_acuity(VideoStream& stream, ProControllerContext& context { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_lake_acuity: black screen not detected!", COLOR_RED); return false; @@ -317,11 +322,11 @@ static bool talk_to_clint_then_go_veilstone(VideoStream& stream, ProControllerCo int ret = wait_until(stream, context, std::chrono::seconds(30), {{listen_to_clint_and_jupiter}}); if (ret < 0){ stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ stream.log("talk_to_clint_then_go_veilstone_listen_to_clint_and_jupiter: dialog box detected.", - COLOR_GREEN); + COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 22000ms); context.wait_for_all_requests(); } @@ -334,11 +339,12 @@ static bool talk_to_clint_then_go_veilstone(VideoStream& stream, ProControllerCo { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("talk_to_clint_then_go_veilstone: black screen not detected!", COLOR_RED); return false; @@ -366,11 +372,12 @@ static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContex { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 4600ms, 100ms); // 23+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_mount_coronet: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -386,12 +393,13 @@ static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContex { ShortDialogWatcher talk_to_grunt(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_grunt}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_grunt}}); if (ret == 0){ stream.log("get_key_and_enter_warehouse: Talking to the grunt.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 9000ms); @@ -406,12 +414,13 @@ static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContex { ShortDialogWatcher pick_up_key(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{pick_up_key}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_key}}); if (ret == 0){ stream.log("get_key_and_enter_warehouse: Got the key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 5000ms); @@ -438,11 +447,12 @@ static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContex { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("get_key_and_enter_warehouse: black screen not detected!", COLOR_RED); return false; @@ -464,12 +474,13 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_warehouse: Opened the door.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); @@ -484,11 +495,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -506,23 +518,25 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { MarkDetector warehouse_grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ - pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ - pbf_move_left_joystick(context, {+1, 0}, 10800ms, 100ms); // 54+ - }, - {{warehouse_grunt_1_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 5+ + pbf_move_left_joystick(context, {0, -1}, 400ms, 100ms); // 2+ + pbf_move_left_joystick(context, {+1, 0}, 10800ms, 100ms); // 54+ + }, + {{warehouse_grunt_1_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("veilstone_warehouse_warehouse_grunt_1: black screen not detected!", COLOR_RED); return false; @@ -542,21 +556,23 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { MarkDetector warehouse_grunt_2_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 13000ms, 100ms); // 54+ - }, - {{warehouse_grunt_2_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 13000ms, 100ms); // 54+ + }, + {{warehouse_grunt_2_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("veilstone_warehouse_warehouse_grunt_2: black screen not detected!", COLOR_RED); return false; @@ -575,11 +591,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -597,11 +614,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -618,12 +636,13 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); MarkDetector galactic_hq_grunt_1_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ - pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6 - }, - {{black_screen}, {galactic_hq_grunt_1_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2200ms, 100ms); // 11+ + pbf_move_left_joystick(context, {0, +1}, 1200ms, 100ms); // 6 + }, + {{black_screen}, {galactic_hq_grunt_1_fight}}); if (ret == 0){ stream.log("go_through_warehouse: got into Galactic HQ 1F, second room.", COLOR_GREEN); }else if (ret == 1){ @@ -631,11 +650,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen_2(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen_2}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen_2}}); if (ret < 0){ stream.log("galactic_hq_grunt_1: black screen not detected!", COLOR_RED); return false; @@ -651,11 +671,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenFlashWatcher room_transition(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{room_transition}}); + int ret1 = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{room_transition}}); if (ret1 < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -672,11 +693,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -691,11 +713,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -711,11 +734,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -731,11 +755,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -752,22 +777,24 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { MarkDetector galactic_hq_scientist_fredrick_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 6 - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 6+ - }, - {{galactic_hq_scientist_fredrick_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); // 6 + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 6+ + }, + {{galactic_hq_scientist_fredrick_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_scientist_fredrick: black screen not detected!", COLOR_RED); return false; @@ -784,11 +811,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -807,12 +835,13 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { ShortDialogWatcher pick_up_galactic_key(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{pick_up_galactic_key}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{pick_up_galactic_key}}); if (ret == 0){ stream.log("go_through_warehouse: Picked up the Galactic key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -830,12 +859,13 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_warehouse: Opened the first door with the Galactic Key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); @@ -855,11 +885,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -874,11 +905,12 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_warehouse: black screen not detected!", COLOR_RED); return false; @@ -903,11 +935,12 @@ static bool heal_at_veilstone_pokemon_center(VideoStream& stream, ProControllerC { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("heal_at_veilstone_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -939,14 +972,15 @@ static bool go_to_galactic_hq(VideoStream& stream, ProControllerContext& context { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_galactic_hq, entering the Galactic HQ from the front door: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; } stream.log("go_to_galactic_hq: entered the Galactic HQ from the front door.", COLOR_GREEN); @@ -969,12 +1003,13 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_galactic_hq: Opened the door.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); @@ -990,14 +1025,15 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq, entering Galactic HQ's 2F, first room: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; } stream.log("go_through_galactic_hq: entered Galactic HQ's 2F, first room.", COLOR_GREEN); @@ -1014,14 +1050,15 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co MarkDetector galactic_hq_grunt_2_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); // First run to the right, and see grunt 2 detects you - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - }, - {{galactic_hq_grunt_2_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + }, + {{galactic_hq_grunt_2_fight}}); if (ret < 0){ // If it doesn't, move on, but consider that grunt_2 hasn't been done stream.log("galactic_hq_grunt_2: mark not detected, moving on..."); @@ -1031,11 +1068,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; @@ -1061,12 +1099,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co // Now run downwards and check if either mark is detected (it should) MarkDetector galactic_hq_grunt_2_3_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 16+ - }, - {{galactic_hq_grunt_2_3_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5000ms, 100ms); // 16+ + }, + {{galactic_hq_grunt_2_3_fight}}); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1079,11 +1117,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co context.wait_for_all_requests(); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; @@ -1098,12 +1137,13 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); - ssf_mash1_button(context, BUTTON_A, 10000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, -1}, 0ms, 10000ms); + ssf_mash1_button(context, BUTTON_A, 10000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; @@ -1128,11 +1168,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1147,11 +1188,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1169,22 +1211,23 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { MarkDetector galactic_hq_grunt_4_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = - run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); // 5+ - }, - {{galactic_hq_grunt_4_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); // 5+ + }, + {{galactic_hq_grunt_4_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 3000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_4: black screen not detected!", COLOR_RED); return false; @@ -1196,11 +1239,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 10000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 10000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_grunt_5: black screen not detected!", COLOR_RED); return false; @@ -1219,11 +1263,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1240,11 +1285,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1262,12 +1308,13 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher open_door(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{open_door}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{open_door}}); if (ret == 0){ stream.log("go_through_galactic_hq: Opened the door with the Galactic Key.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); @@ -1282,20 +1329,22 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher cyrus_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 5+ - }, - {{cyrus_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); // 5+ + }, + {{cyrus_dialog}}); if (ret == 0){ stream.log("go_through_galactic_hq: Cyrus dialog box detected.", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_cyrus: black screen not detected!", COLOR_RED); return false; @@ -1320,11 +1369,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1344,11 +1394,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1365,12 +1416,13 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher talk_to_commander_saturn(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_commander_saturn}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_commander_saturn}}); if (ret < 0){ stream.log("go_through_galactic_hq: Didn't detect Commander Saturn dialog box!", COLOR_RED); return false; @@ -1378,11 +1430,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("go_through_galactic_hq: Starting the battle with Commander Saturn.", COLOR_GREEN); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("galactic_hq_commander_saturn: black screen not detected!", COLOR_RED); return false; @@ -1403,12 +1456,13 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { ShortDialogWatcher unlock_guardians(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{unlock_guardians}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {0, +1}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{unlock_guardians}}); if (ret == 0){ stream.log("go_through_galactic_hq: Released the Lake Guardians.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 4000ms); @@ -1429,11 +1483,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1449,11 +1504,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1472,11 +1528,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { BlackScreenFlashWatcher black_screen(COLOR_BLUE, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: black screen not detected!", COLOR_RED); return false; @@ -1493,11 +1550,12 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co { WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{white_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{white_screen}}); if (ret < 0){ stream.log("go_through_galactic_hq: white screen not detected!", COLOR_RED); return false; @@ -1522,11 +1580,12 @@ static bool fly_and_heal_at_oreburgh(VideoStream& stream, ProControllerContext& { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("fly_and_heal_at_oreburgh: black screen not detected!", COLOR_RED); return false; @@ -1567,21 +1626,23 @@ static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& conte { MarkDetector hiker_justin_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 5000ms, 100ms); // 11+ - }, - {{hiker_justin_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5000ms, 100ms); // 11+ + }, + {{hiker_justin_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_mount_coronet, route_207_hiker_justin: black screen not detected!", COLOR_RED); return false; @@ -1599,11 +1660,12 @@ static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& conte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_mount_coronet: black screen not detected!", COLOR_RED); return false; @@ -1638,11 +1700,12 @@ static bool fly_and_heal_at_veilstone(VideoStream& stream, ProControllerContext& { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("fly_and_heal_at_veilstone: black screen not detected!", COLOR_RED); return false; @@ -1681,11 +1744,12 @@ static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContex { BlackScreenOverWatcher black_screen_1(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen_1}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen_1}}); if (ret < 0){ stream.log("go_to_route_214_enter_house: black screen not detected!", COLOR_RED); return false; @@ -1695,8 +1759,8 @@ static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContex return true; } -static bool go_through_house_south_veilstone(VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options){ +static bool +go_through_house_south_veilstone(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1704,11 +1768,12 @@ static bool go_through_house_south_veilstone(VideoStream& stream, ProControllerC { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); return false; @@ -1759,12 +1824,13 @@ static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_house_west_sunyshore: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -1788,12 +1854,13 @@ static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 7400ms, 100ms); // 37+ - pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 7400ms, 100ms); // 37+ + pbf_move_left_joystick(context, {+1, 0}, 7400ms, 100ms); // 37+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_house_west_sunyshore: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -1822,21 +1889,23 @@ static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext { MarkDetector route_222_sailor_luther_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); // 5+ - }, - {{route_222_sailor_luther_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); // 5+ + }, + {{route_222_sailor_luther_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_house_west_sunyshore_route_222_sailor_luther: black screen not detected!", COLOR_RED); return false; @@ -1855,11 +1924,12 @@ static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_house_west_sunyshore: black screen not detected!", COLOR_RED); return false; @@ -1870,8 +1940,8 @@ static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext return true; } -static bool go_through_house_west_sunyshore(VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options){ +static bool +go_through_house_west_sunyshore(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1879,11 +1949,12 @@ static bool go_through_house_west_sunyshore(VideoStream& stream, ProControllerCo { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_south_veilstone: black screen not detected!", COLOR_RED); return false; @@ -1930,11 +2001,12 @@ static bool go_to_sunyshore_lighthouse(VideoStream& stream, ProControllerContext { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_house_west_sunyshore: black screen not detected!", COLOR_RED); return false; @@ -1945,8 +2017,8 @@ static bool go_to_sunyshore_lighthouse(VideoStream& stream, ProControllerContext return true; } -static bool talk_to_volkner_at_the_lighthouse(VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options){ +static bool +talk_to_volkner_at_the_lighthouse(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 1000ms); @@ -1975,12 +2047,13 @@ static bool talk_to_volkner_at_the_lighthouse(VideoStream& stream, ProController { ShortDialogWatcher talk_to_volkner(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_volkner}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_volkner}}); if (ret == 0){ stream.log("talk_to_volkner_at_the_lighthouse: Talking to Volkner.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 11000ms); @@ -1999,14 +2072,15 @@ static bool talk_to_volkner_at_the_lighthouse(VideoStream& stream, ProController { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("talk_to_volkner_at_the_lighthouse: entering elevator to exit black screen not detected!", - COLOR_RED); + COLOR_RED); return false; } stream.log("talk_to_volkner_at_the_lighthouse: entered the elevator", COLOR_GREEN); @@ -2049,12 +2123,13 @@ static bool go_to_sunyshore_gym(VideoStream& stream, ProControllerContext& conte { ShortDialogWatcher talk_to_flint(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); - }, - {{talk_to_flint}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); + ssf_mash1_button(context, BUTTON_A, 5000ms); + }, + {{talk_to_flint}}); if (ret == 0){ stream.log("go_to_sunyshore_gym: Talking to Flint.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 9000ms); @@ -2071,11 +2146,12 @@ static bool go_to_sunyshore_gym(VideoStream& stream, ProControllerContext& conte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_sunyshore_gym: black screen entering the gym not detected!", COLOR_RED); return false; @@ -2090,358 +2166,495 @@ static bool go_to_sunyshore_gym(VideoStream& stream, ProControllerContext& conte // ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // -void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0801(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "leave_gym: transition not detected.", env.console); + "leave_gym: transition not detected.", + env.console); } }, true); } -void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0802(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_snowpoint_city_pokemon_center: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_snowpoint_city_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0803(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Snowpoint City")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_snowpoint_city: transition not detected.", - env.console); + "heal_and_exit_snowpoint_city: transition not detected.", + env.console); } }, false); } -void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0804(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_lake_acuity(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_lake_acuity: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_lake_acuity: transition not detected.", + env.console); } }, false); } -void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0805(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!talk_to_clint_then_go_veilstone(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "talk_to_clint_then_go_veilstone: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_to_clint_then_go_veilstone: transition not detected.", env.console); } }, false); } -void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0806(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_veilstone_city: transition not detected.", - env.console); + "heal_and_exit_veilstone_city: transition not detected.", + env.console); } }, false); } -void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0807(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!get_key_and_enter_warehouse(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_key_and_enter_warehouse: transition not detected.", - env.console); + "get_key_and_enter_warehouse: transition not detected.", + env.console); } }, false); } -void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0808(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_warehouse: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_warehouse: transition not detected.", + env.console); } }, false); } -void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0809(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_at_veilstone_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "heal_at_veilstone_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_veilstone_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0810(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_veilstone_city: transition not detected.", - env.console); + "heal_and_exit_veilstone_city: transition not detected.", + env.console); } }, false); } -void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0811(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_galactic_hq(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_galactic_hq: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_galactic_hq: transition not detected.", + env.console); } }, false); } -void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0812(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_galactic_hq(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_galactic_hq: transition not detected.", - env.console); + "go_through_galactic_hq: transition not detected.", + env.console); } }, false); } -void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0813(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_oreburgh(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "fly_and_heal_at_oreburgh: transition not detected.", - env.console); + "fly_and_heal_at_oreburgh: transition not detected.", + env.console); } }, false); } -void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0814(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Oreburgh City")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_oreburgh_city: transition not detected.", - env.console); + "heal_and_exit_oreburgh_city: transition not detected.", + env.console); } }, false); } -void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0815(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_mount_coronet(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_mount_coronet: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_mount_coronet: transition not detected.", + env.console); } }, false); } -void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0816(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_mount_coronet(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_mount_coronet: transition not detected.", - env.console); + "go_through_mount_coronet: transition not detected.", + env.console); } }, false); } -void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0817(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!fly_and_heal_at_veilstone(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "fly_and_heal_at_veilstone: transition not detected.", - env.console); + "fly_and_heal_at_veilstone: transition not detected.", + env.console); } }, false); } -void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0818(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Veilstone City")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_veilstone_city: transition not detected.", - env.console); + "heal_and_exit_veilstone_city: transition not detected.", + env.console); } }, false); } -void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0819(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_house_south_veilstone: transition not detected.", - env.console); + "go_to_house_south_veilstone: transition not detected.", + env.console); } }, false); } -void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0820(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_south_veilstone: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_south_veilstone: transition not detected.", env.console); } }, false); } -void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0821(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_house_west_sunyshore(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_house_west_sunyshore: transition not detected.", - env.console); + "go_to_house_west_sunyshore: transition not detected.", + env.console); } }, false); } -void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0822(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_house_west_sunyshore(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_through_house_west_sunyshore: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_west_sunyshore: transition not detected.", env.console); } }, false); } -void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0823(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_lighthouse(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_sunyshore_lighthouse: transition not detected.", - env.console); + "go_to_sunyshore_lighthouse: transition not detected.", + env.console); } }, false); } -void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0824(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "talk_to_volkner_at_the_lighthouse: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_to_volkner_at_the_lighthouse: transition not detected.", env.console); } }, false); } -void checkpoint_0825(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0825(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_sunyshore_gym: transition not detected.", + env.console); } }, false); } -void checkpoint_0826(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0826(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_sunyshore_gym: transition not detected.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_sunyshore_gym: transition not detected.", + env.console); } }, false); @@ -2457,8 +2670,10 @@ std::string AutoStory_Checkpoint_0801::start_text() const { std::string AutoStory_Checkpoint_0801::end_text() const { return "Leave Snowpoint Gym."; } -void AutoStory_Checkpoint_0801::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0801::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0801(env, context, options, stats); } @@ -2471,8 +2686,10 @@ std::string AutoStory_Checkpoint_0802::start_text() const { std::string AutoStory_Checkpoint_0802::end_text() const { return "Arrived at Snowpoint City Pokémon Center."; } -void AutoStory_Checkpoint_0802::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0802::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0802(env, context, options, stats); } @@ -2485,8 +2702,10 @@ std::string AutoStory_Checkpoint_0803::start_text() const { std::string AutoStory_Checkpoint_0803::end_text() const { return "Healed at Snowpoint City Pokemon Center."; } -void AutoStory_Checkpoint_0803::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0803::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0803(env, context, options, stats); } @@ -2499,8 +2718,10 @@ std::string AutoStory_Checkpoint_0804::start_text() const { std::string AutoStory_Checkpoint_0804::end_text() const { return "Entered Lake Acuity Lakefront."; } -void AutoStory_Checkpoint_0804::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0804::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0804(env, context, options, stats); } @@ -2513,8 +2734,10 @@ std::string AutoStory_Checkpoint_0805::start_text() const { std::string AutoStory_Checkpoint_0805::end_text() const { return "Arrived at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0805::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0805::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0805(env, context, options, stats); } @@ -2527,8 +2750,10 @@ std::string AutoStory_Checkpoint_0806::start_text() const { std::string AutoStory_Checkpoint_0806::end_text() const { return "Healed at Veilstone City Pokemon Center."; } -void AutoStory_Checkpoint_0806::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0806::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0806(env, context, options, stats); } @@ -2541,8 +2766,10 @@ std::string AutoStory_Checkpoint_0807::start_text() const { std::string AutoStory_Checkpoint_0807::end_text() const { return "Entered Team Galactic Warehouse."; } -void AutoStory_Checkpoint_0807::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0807::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0807(env, context, options, stats); } @@ -2555,8 +2782,10 @@ std::string AutoStory_Checkpoint_0808::start_text() const { std::string AutoStory_Checkpoint_0808::end_text() const { return "Got out of the warehouse with the key."; } -void AutoStory_Checkpoint_0808::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0808::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0808(env, context, options, stats); } @@ -2569,8 +2798,10 @@ std::string AutoStory_Checkpoint_0809::start_text() const { std::string AutoStory_Checkpoint_0809::end_text() const { return "Entered Veilstone Pokemon Center for healing."; } -void AutoStory_Checkpoint_0809::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0809::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0809(env, context, options, stats); } @@ -2583,8 +2814,10 @@ std::string AutoStory_Checkpoint_0810::start_text() const { std::string AutoStory_Checkpoint_0810::end_text() const { return "Exited Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_0810::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0810::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0810(env, context, options, stats); } @@ -2597,8 +2830,10 @@ std::string AutoStory_Checkpoint_0811::start_text() const { std::string AutoStory_Checkpoint_0811::end_text() const { return "Entered Galactic HQ."; } -void AutoStory_Checkpoint_0811::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0811::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0811(env, context, options, stats); } @@ -2611,8 +2846,10 @@ std::string AutoStory_Checkpoint_0812::start_text() const { std::string AutoStory_Checkpoint_0812::end_text() const { return "Dealt with Cyrus and left the Galactic HQ."; } -void AutoStory_Checkpoint_0812::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0812::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0812(env, context, options, stats); } @@ -2625,8 +2862,10 @@ std::string AutoStory_Checkpoint_0813::start_text() const { std::string AutoStory_Checkpoint_0813::end_text() const { return "Flew to Oreburgh city and entered Pokemon Center."; } -void AutoStory_Checkpoint_0813::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0813::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0813(env, context, options, stats); } @@ -2639,8 +2878,10 @@ std::string AutoStory_Checkpoint_0814::start_text() const { std::string AutoStory_Checkpoint_0814::end_text() const { return "Healed Pokemon at Oreburgh City Pokemon Center."; } -void AutoStory_Checkpoint_0814::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0814::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0814(env, context, options, stats); } @@ -2653,8 +2894,10 @@ std::string AutoStory_Checkpoint_0815::start_text() const { std::string AutoStory_Checkpoint_0815::end_text() const { return "Entered Mount Coronet."; } -void AutoStory_Checkpoint_0815::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0815::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0815(env, context, options, stats); } @@ -2667,8 +2910,10 @@ std::string AutoStory_Checkpoint_0816::start_text() const { std::string AutoStory_Checkpoint_0816::end_text() const { return "Exited Mt. Coronet, having dealt with Cyrus."; } -void AutoStory_Checkpoint_0816::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0816::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0816(env, context, options, stats); } @@ -2681,8 +2926,10 @@ std::string AutoStory_Checkpoint_0817::start_text() const { std::string AutoStory_Checkpoint_0817::end_text() const { return "Entered the Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_0817::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0817::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0817(env, context, options, stats); } @@ -2695,8 +2942,10 @@ std::string AutoStory_Checkpoint_0818::start_text() const { std::string AutoStory_Checkpoint_0818::end_text() const { return "Exited the Veilstone Pokemon Center."; } -void AutoStory_Checkpoint_0818::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0818::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0818(env, context, options, stats); } @@ -2709,8 +2958,10 @@ std::string AutoStory_Checkpoint_0819::start_text() const { std::string AutoStory_Checkpoint_0819::end_text() const { return "Arrived at the house South of Veilstone City."; } -void AutoStory_Checkpoint_0819::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0819::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0819(env, context, options, stats); } @@ -2723,8 +2974,10 @@ std::string AutoStory_Checkpoint_0820::start_text() const { std::string AutoStory_Checkpoint_0820::end_text() const { return "Exited the house South of Veilstone City."; } -void AutoStory_Checkpoint_0820::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0820::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0820(env, context, options, stats); } @@ -2737,8 +2990,10 @@ std::string AutoStory_Checkpoint_0821::start_text() const { std::string AutoStory_Checkpoint_0821::end_text() const { return "Entered the house West of Sunyshore City."; } -void AutoStory_Checkpoint_0821::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0821::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0821(env, context, options, stats); } @@ -2751,8 +3006,10 @@ std::string AutoStory_Checkpoint_0822::start_text() const { std::string AutoStory_Checkpoint_0822::end_text() const { return "Entered Sunyshore City."; } -void AutoStory_Checkpoint_0822::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0822::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0822(env, context, options, stats); } @@ -2765,8 +3022,10 @@ std::string AutoStory_Checkpoint_0823::start_text() const { std::string AutoStory_Checkpoint_0823::end_text() const { return "Entered Sunyshore City's lighthouse."; } -void AutoStory_Checkpoint_0823::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0823::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0823(env, context, options, stats); } @@ -2779,8 +3038,10 @@ std::string AutoStory_Checkpoint_0824::start_text() const { std::string AutoStory_Checkpoint_0824::end_text() const { return "Exited Sunyshore City's lighthouse."; } -void AutoStory_Checkpoint_0824::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0824::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0824(env, context, options, stats); } @@ -2793,8 +3054,10 @@ std::string AutoStory_Checkpoint_0825::start_text() const { std::string AutoStory_Checkpoint_0825::end_text() const { return "Entered Sunyshore City's gym."; } -void AutoStory_Checkpoint_0825::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0825::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0825(env, context, options, stats); } @@ -2807,31 +3070,45 @@ std::string AutoStory_Checkpoint_0826::start_text() const { std::string AutoStory_Checkpoint_0826::end_text() const { return "Entered Sunyshore City's gym."; } -void AutoStory_Checkpoint_0826::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0826::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0826(env, context, options, stats); } -void AutoStory_Checkpoint_021::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_021::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_021(env, context, options, stats); } -void checkpoint_021(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_021(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ // TODO: implement Segment 08 gameplay logic (seventh badge area) }, true); } -void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ +void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ approach_legendary(env.console, context); enter_legendary_battle(env.console, context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index 5eccc15225..36aff87cb0 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -20,8 +20,10 @@ class AutoStory_Segment_08 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0801 : public AutoStory_Checkpoint { @@ -29,286 +31,390 @@ class AutoStory_Checkpoint_0801 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0801(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0802 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0802(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0803 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0803(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0803(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0804 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0804(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0804(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0805 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0805(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0806 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0806(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0807 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0807(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0808 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0808(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0809 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0809(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0810 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0810(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0810(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0811 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0811(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0811(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0812 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0812(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0812(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0813 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0813(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0813(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0814 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0814(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0814(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0815 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0815(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0815(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0816 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0816(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0816(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0817 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0817(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0817(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0818 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0818(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0818(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0819 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0819(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0819(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0820 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0820(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0820(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0821 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0821(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0821(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0822 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0822(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0822(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0823 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0823(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0823(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0824 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0824(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0824(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0825 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0825(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0825(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0826 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0826(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0826(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint { @@ -316,19 +422,25 @@ class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; // start: seventh badge acquired // end: legendary encounter (Spear Pillar / Mt. Coronet) -void checkpoint_021(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_021(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); // start: legendary encounter // end: eighth Gym Badge acquired (Sunyshore City) -void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats); +void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 8701e21242..3452998b48 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -43,8 +43,10 @@ std::string AutoStory_Segment_09::end_text() const { return "End: Elite Four and Champion defeated, credits roll."; } -void AutoStory_Segment_09::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Segment_09::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { stats.m_segment++; env.update_stats(); context.wait_for_all_requests(); @@ -69,8 +71,8 @@ void AutoStory_Segment_09::run_segment(SingleSwitchProgramEnvironment& env, ProC env.console.log("End Segment " + name(), COLOR_GREEN); } -static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, - const std::string& trainerid){ +static void +move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -104,24 +106,25 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co // Move the battle-move cursor down. pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); }else if (trainerid == "route_223_swimmer_miranda" or trainerid == "route_223_swimmer_oscar" or - trainerid == "route_223_swimmer_crystal" or trainerid == "route_223_swimmer_gabrielle" or - trainerid == "route_223_swimmer_troy" or trainerid == "victory_road_ace_sydney" or - trainerid == "victory_road_psychic_valencia"){ + trainerid == "route_223_swimmer_crystal" or trainerid == "route_223_swimmer_gabrielle" or + trainerid == "route_223_swimmer_troy" or trainerid == "victory_road_ace_sydney" or + trainerid == "victory_road_psychic_valencia"){ // Move the battle-move cursor up. pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); }else if (trainerid == "victory_road_ace_omar" or trainerid == "victory_road_veteran_clayton" or - trainerid == "victory_road_dragontamer_clinton"){ + trainerid == "victory_road_dragontamer_clinton"){ // Keep the current battle-move selection. } context.wait_for_all_requests(); { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); return false; @@ -134,8 +137,8 @@ static bool handle_battle(VideoStream& stream, ProControllerContext& context, co return true; } -static bool enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options){ +static bool +enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -148,11 +151,12 @@ static bool enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerCon { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_sunyshore_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -163,8 +167,8 @@ static bool enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerCon return true; } -static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options){ +static bool +go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -186,11 +190,12 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { MarkDetector jasmine_dialog(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 6800ms, 100ms); // 34+ - }, - {{jasmine_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 6800ms, 100ms); // 34+ + }, + {{jasmine_dialog}}); if (ret == 0){ stream.log("go_to_victory_road_pokemon_center: talking to Jasmine.", COLOR_GREEN); context.wait_for_all_requests(); @@ -223,24 +228,26 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { MarkDetector route_223_swimmer_miranda_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{route_223_swimmer_miranda_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{route_223_swimmer_miranda_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_miranda: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_miranda"); @@ -258,24 +265,26 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { MarkDetector route_223_swimmer_oscar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{route_223_swimmer_oscar_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{route_223_swimmer_oscar_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_oscar: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_oscar"); @@ -291,24 +300,26 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { MarkDetector route_223_swimmer_crystal_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ - }, - {{route_223_swimmer_crystal_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5600ms, 100ms); // 28+ + }, + {{route_223_swimmer_crystal_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_crystal: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_crystal"); @@ -323,12 +334,13 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_victory_road_pokemon_center: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -369,30 +381,32 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { MarkDetector route_223_swimmer_gabrielle_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - DpadState dpad; - dpad.last_dir = DPAD_RIGHT; - repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); - pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - }, - {{route_223_swimmer_gabrielle_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + DpadState dpad; + dpad.last_dir = DPAD_RIGHT; + repeat_dpad(context, dpad, DPAD_UP, 80ms, 300ms, 4); + pbf_move_left_joystick(context, {-1, 0}, 600ms, 100ms); // 2+ + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + }, + {{route_223_swimmer_gabrielle_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_gabrielle: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_gabrielle"); @@ -415,27 +429,29 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { MarkDetector route_223_swimmer_troy_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ - context.wait_for_all_requests(); - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - }, - {{route_223_swimmer_troy_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ + context.wait_for_all_requests(); + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + }, + {{route_223_swimmer_troy_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center, route_223_swimmer_troy: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "route_223_swimmer_troy"); @@ -462,12 +478,13 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_to_victory_road_pokemon_center: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -486,11 +503,12 @@ static bool go_to_victory_road_pokemon_center(VideoStream& stream, ProController { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_to_victory_road_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -517,11 +535,12 @@ static bool enter_victory_road_cave(VideoStream& stream, ProControllerContext& c { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_victory_road_cave: black screen not detected!", COLOR_RED); return false; @@ -563,24 +582,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_ace_mariah_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_ace_mariah_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_mariah_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_ace_mariah: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_ace_mariah"); @@ -594,12 +615,13 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -616,11 +638,12 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (1)!", COLOR_RED); return false; @@ -653,24 +676,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_ace_omar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_ace_omar_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_omar_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_ace_omar: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_ace_omar"); @@ -708,24 +733,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_ace_sydney_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_ace_sydney_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_ace_sydney_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_ace_sydney: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_ace_sydney"); @@ -746,24 +773,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_veteran_clayton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_veteran_clayton_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_veteran_clayton_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_veteran_clayton: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_veteran_clayton"); @@ -778,12 +807,13 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {0, -1}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -801,11 +831,12 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (2)!", COLOR_RED); return false; @@ -834,24 +865,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_blackbelt_miles_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_blackbelt_miles_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_blackbelt_miles_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_blackbelt_miles: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_blackbelt_miles"); @@ -864,11 +897,12 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (3)!", COLOR_RED); return false; @@ -894,24 +928,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_psychic_valencia_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_psychic_valencia_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_psychic_valencia_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_psychic_valencia: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_psychic_valencia"); @@ -931,14 +967,15 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 6+ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 6+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -964,24 +1001,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_dragontamer_andre_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ - }, - {{victory_road_dragontamer_andre_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 5800ms, 100ms); // 29+ + }, + {{victory_road_dragontamer_andre_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_dragontamer_andre: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_dragontamer_andre"); @@ -1006,11 +1045,12 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: black screen not detected (4)!", COLOR_RED); return false; @@ -1025,12 +1065,13 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 3000ms, 100ms); // 15+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 3000ms, 100ms); // 15+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("go_through_victory_road_cave: Reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_A, 3000ms); @@ -1047,24 +1088,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_veteran_edgar_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_veteran_edgar_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, -1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_veteran_edgar_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_veteran_edgar: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_veteran_edgar"); @@ -1090,24 +1133,26 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { MarkDetector victory_road_dragontamer_clinton_fight(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ - }, - {{victory_road_dragontamer_clinton_fight}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 5800ms, 100ms); // 29+ + }, + {{victory_road_dragontamer_clinton_fight}}); if (ret == 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); + ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_A, 200000ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave, victory_road_dragontamer_clinton: black screen not detected!", - COLOR_RED); + COLOR_RED); return false; }else if (ret == 0){ handle_battle(stream, context, "victory_road_dragontamer_clinton"); @@ -1124,11 +1169,12 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte { WhiteScreenOverWatcher white_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); - }, - {{white_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 10000ms, 100ms); + }, + {{white_screen}}); if (ret < 0){ stream.log("go_through_victory_road_cave: white screen not detected!", COLOR_RED); return false; @@ -1163,11 +1209,12 @@ static bool enter_elite_4_pokemon_center(VideoStream& stream, ProControllerConte { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); - }, - {{black_screen}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {0, +1}, 10000ms, 100ms); + }, + {{black_screen}}); if (ret < 0){ stream.log("enter_elite_4_pokemon_center: black screen not detected!", COLOR_RED); return false; @@ -1187,12 +1234,13 @@ static bool ready_in_front_of_elite_4(VideoStream& stream, ProControllerContext& { ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ - }, - {{repel_dialog}}); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ + pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); // 10+ + }, + {{repel_dialog}}); if (ret == 0){ stream.log("ready_in_front_of_elite_4: Not reactivating repel.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 3000ms); @@ -1230,8 +1278,8 @@ static bool ready_in_front_of_elite_4(VideoStream& stream, ProControllerContext& return true; } -static bool get_into_the_elite_4_entrance(VideoStream& stream, ProControllerContext& context, - AutoStoryOptions options){ +static bool +get_into_the_elite_4_entrance(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -1285,195 +1333,267 @@ static bool deal_with_cynthia_and_finish(VideoStream& stream, ProControllerConte return true; } -void checkpoint_0901(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0901(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!enter_sunyshore_pokemon_center(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_sunyshore_pokemon_center: transition not detected.", - env.console); + "enter_sunyshore_pokemon_center: transition not detected.", + env.console); } }, true); } -void checkpoint_0902(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0902(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Sunyshore City")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_1: transition not detected.", env.console); + "heal_and_exit_1: transition not detected.", + env.console); } }, false); } -void checkpoint_0903(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0903(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_to_victory_road_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "go_to_victory_road_pokemon_center: transition not detected.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_victory_road_pokemon_center: transition not detected.", env.console); } }, false); } -void checkpoint_0904(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0904(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!heal_and_exit(env.console, context, "Victory Road")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_2: transition not detected.", env.console); + "heal_and_exit_2: transition not detected.", + env.console); } }, false); } -void checkpoint_0905(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0905(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!enter_victory_road_cave(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_victory_road_cave: transition not detected.", - env.console); + "enter_victory_road_cave: transition not detected.", + env.console); } }, false); } -void checkpoint_0906(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0906(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!go_through_victory_road_cave(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_victory_road_cave: transition not detected.", - env.console); + "go_through_victory_road_cave: transition not detected.", + env.console); } }, false); } -void checkpoint_0907(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0907(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!enter_elite_4_pokemon_center(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_elite_4_pokemon_center: transition not detected.", - env.console); + "enter_elite_4_pokemon_center: transition not detected.", + env.console); } }, false); } -void checkpoint_0908(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0908(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!ready_in_front_of_elite_4(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "ready_in_front_of_elite_4: transition not detected.", - env.console); + "ready_in_front_of_elite_4: transition not detected.", + env.console); } }, false); } -void checkpoint_0909(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0909(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!get_into_the_elite_4_entrance(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_into_the_elite_4_entrance: transition not detected.", - env.console); + "get_into_the_elite_4_entrance: transition not detected.", + env.console); } }, true); } -void checkpoint_0910(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0910(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!deal_with_aaron_elite_4(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_aaron_elite_4: transition not detected.", - env.console); + "deal_with_aaron_elite_4: transition not detected.", + env.console); } }, false); } -void checkpoint_0911(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0911(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!deal_with_bertha_elite_4(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_bertha_elite_4: transition not detected.", - env.console); + "deal_with_bertha_elite_4: transition not detected.", + env.console); } }, false); } -void checkpoint_0912(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0912(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!deal_with_flint_elite_4(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_flint_elite_4: transition not detected.", - env.console); + "deal_with_flint_elite_4: transition not detected.", + env.console); } }, false); } -void checkpoint_0913(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0913(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!deal_with_lucian_elite_4(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_lucian_elite_4: transition not detected.", - env.console); + "deal_with_lucian_elite_4: transition not detected.", + env.console); } }, false); } -void checkpoint_0914(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ +void checkpoint_0914(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ checkpoint_reattempt_loop( - env, context, options, stats, + env, + context, + options, + stats, [&](size_t /*attempt*/){ if (!deal_with_cynthia_and_finish(env.console, context, options)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_cynthia_and_finish: transition not detected.", - env.console); + "deal_with_cynthia_and_finish: transition not detected.", + env.console); } }, false); @@ -1488,8 +1608,10 @@ std::string AutoStory_Checkpoint_0901::start_text() const { std::string AutoStory_Checkpoint_0901::end_text() const { return "Enter the Sunyshore Pokemon Center."; } -void AutoStory_Checkpoint_0901::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0901::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0901(env, context, options, stats); } @@ -1502,8 +1624,10 @@ std::string AutoStory_Checkpoint_0902::start_text() const { std::string AutoStory_Checkpoint_0902::end_text() const { return "Exit the Sunyshore Pokemon Center."; } -void AutoStory_Checkpoint_0902::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0902::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0902(env, context, options, stats); } @@ -1517,8 +1641,10 @@ std::string AutoStory_Checkpoint_0903::start_text() const { std::string AutoStory_Checkpoint_0903::end_text() const { return "Enter the Victory Road Pokemon Center."; } -void AutoStory_Checkpoint_0903::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0903::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0903(env, context, options, stats); } @@ -1531,8 +1657,10 @@ std::string AutoStory_Checkpoint_0904::start_text() const { std::string AutoStory_Checkpoint_0904::end_text() const { return "Exit Victory Road Pokemon Center."; } -void AutoStory_Checkpoint_0904::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0904::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0904(env, context, options, stats); } @@ -1545,8 +1673,10 @@ std::string AutoStory_Checkpoint_0905::start_text() const { std::string AutoStory_Checkpoint_0905::end_text() const { return "Enter the Victory Road cave."; } -void AutoStory_Checkpoint_0905::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0905::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0905(env, context, options, stats); } @@ -1559,8 +1689,10 @@ std::string AutoStory_Checkpoint_0906::start_text() const { std::string AutoStory_Checkpoint_0906::end_text() const { return "Exit Victory Road cave."; } -void AutoStory_Checkpoint_0906::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0906::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0906(env, context, options, stats); } @@ -1573,8 +1705,10 @@ std::string AutoStory_Checkpoint_0907::start_text() const { std::string AutoStory_Checkpoint_0907::end_text() const { return "Enter the Elite 4 building."; } -void AutoStory_Checkpoint_0907::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0907::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0907(env, context, options, stats); } @@ -1587,8 +1721,10 @@ std::string AutoStory_Checkpoint_0908::start_text() const { std::string AutoStory_Checkpoint_0908::end_text() const { return "Ready in front of the Elite 4."; } -void AutoStory_Checkpoint_0908::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0908::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0908(env, context, options, stats); } @@ -1601,8 +1737,10 @@ std::string AutoStory_Checkpoint_0909::start_text() const { std::string AutoStory_Checkpoint_0909::end_text() const { return "Enter the Elite 4's first room."; } -void AutoStory_Checkpoint_0909::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0909::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0909(env, context, options, stats); } @@ -1615,8 +1753,10 @@ std::string AutoStory_Checkpoint_0910::start_text() const { std::string AutoStory_Checkpoint_0910::end_text() const { return "Deal with Aaron and enter the second room."; } -void AutoStory_Checkpoint_0910::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0910::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0910(env, context, options, stats); } @@ -1629,8 +1769,10 @@ std::string AutoStory_Checkpoint_0911::start_text() const { std::string AutoStory_Checkpoint_0911::end_text() const { return "Deal with Bertha and enter the third room."; } -void AutoStory_Checkpoint_0911::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0911::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0911(env, context, options, stats); } @@ -1643,8 +1785,10 @@ std::string AutoStory_Checkpoint_0912::start_text() const { std::string AutoStory_Checkpoint_0912::end_text() const { return "Deal with Flint and enter the fourth room."; } -void AutoStory_Checkpoint_0912::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0912::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0912(env, context, options, stats); } @@ -1657,8 +1801,10 @@ std::string AutoStory_Checkpoint_0913::start_text() const { std::string AutoStory_Checkpoint_0913::end_text() const { return "Deal with Lucian and enter the last room."; } -void AutoStory_Checkpoint_0913::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0913::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0913(env, context, options, stats); } @@ -1671,8 +1817,10 @@ std::string AutoStory_Checkpoint_0914::start_text() const { std::string AutoStory_Checkpoint_0914::end_text() const { return "Deal with Cynthia and finish the game."; } -void AutoStory_Checkpoint_0914::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const { +void AutoStory_Checkpoint_0914::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { checkpoint_0914(env, context, options, stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h index b21fde71ca..a9674f13ce 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h @@ -20,8 +20,10 @@ class AutoStory_Segment_09 : public AutoStory_Segment { 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, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; class AutoStory_Checkpoint_0901 : public AutoStory_Checkpoint { @@ -29,55 +31,75 @@ class AutoStory_Checkpoint_0901 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0901(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0901(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0902 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0902(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0902(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0903 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0903(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0903(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0904 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0904(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0904(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0905 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0905(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0905(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0906 : public AutoStory_Checkpoint { @@ -85,11 +107,15 @@ class AutoStory_Checkpoint_0906 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0906(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0906(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0907 : public AutoStory_Checkpoint { @@ -97,11 +123,15 @@ class AutoStory_Checkpoint_0907 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0907(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0907(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0908 : public AutoStory_Checkpoint { @@ -109,11 +139,15 @@ class AutoStory_Checkpoint_0908 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0908(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0908(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0909 : public AutoStory_Checkpoint { @@ -121,11 +155,15 @@ class AutoStory_Checkpoint_0909 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0909(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0909(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0910 : public AutoStory_Checkpoint { @@ -133,11 +171,15 @@ class AutoStory_Checkpoint_0910 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0910(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0910(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0911 : public AutoStory_Checkpoint { @@ -145,11 +187,15 @@ class AutoStory_Checkpoint_0911 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0911(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0911(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0912 : public AutoStory_Checkpoint { @@ -157,33 +203,45 @@ class AutoStory_Checkpoint_0912 : public AutoStory_Checkpoint { virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0912(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0912(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0913 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0913(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0913(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0914 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats) const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; }; -void checkpoint_0914(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats); +void checkpoint_0914(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp index cd031880ec..14c1ac23bf 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.cpp @@ -54,18 +54,20 @@ using namespace std::chrono_literals; void run_battle_press_A(const ProgramInfo& /*info*/, VideoStream& stream, ProControllerContext& context){ EndBattleWatcher end_battle; - run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_A, 120000ms); - }, - {{end_battle}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{end_battle}}); OverworldWatcher overworld; - run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_A, 120000ms); - }, - {{overworld}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{overworld}}); } @@ -73,18 +75,20 @@ void run_battle_press_A(const ProgramInfo& /*info*/, VideoStream& stream, ProCon // handle_unexpected_battle // --------------------------------------------------------------------------- -void handle_unexpected_battle( - const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, +void handle_unexpected_battle(const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, std::function&& action, std::function&& recovery_action, size_t max_attempts){ for (size_t attempt = 0; attempt < max_attempts; attempt++){ StartBattleDetector start_battle(stream.overlay()); - int result = run_until(stream, context, - [&](ProControllerContext& ctx){ - action(info, stream, ctx); - }, - {{start_battle}}); + int result = run_until(stream, + context, + [&](ProControllerContext& ctx){ + action(info, stream, ctx); + }, + {{start_battle}}); if (result < 0){ return; } @@ -92,7 +96,8 @@ void handle_unexpected_battle( recovery_action(info, stream, context); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "handle_unexpected_battle: exceeded max attempts", stream); + "handle_unexpected_battle: exceeded max attempts", + stream); } @@ -157,8 +162,8 @@ struct MoveScore { bool ohko_all = false; }; -static MoveScore score_move(const BDSPMoveData& mdata, const std::vector& on_field, - const BDSPTrainerData& trainer_data){ +static MoveScore +score_move(const BDSPMoveData& mdata, const std::vector& on_field, const BDSPTrainerData& trainer_data){ if (mdata.category == MoveCategory::STATUS){ return {}; } @@ -193,11 +198,9 @@ static MoveScore score_move(const BDSPMoveData& mdata, const std::vectorlevel, tp->ivs.hp, tp->evs.hp); - uint16_t def = - (mdata.category == MoveCategory::PHYSICAL) - ? calc_stats_nonhp(pdata.defense, tp->level, tp->ivs.defense, tp->evs.defense, - NatureAdjustment::NEUTRAL) - : calc_stats_nonhp(pdata.spdef, tp->level, tp->ivs.spdef, tp->evs.spdef, NatureAdjustment::NEUTRAL); + uint16_t def = (mdata.category == MoveCategory::PHYSICAL) + ? calc_stats_nonhp(pdata.defense, tp->level, tp->ivs.defense, tp->evs.defense, NatureAdjustment::NEUTRAL) + : calc_stats_nonhp(pdata.spdef, tp->level, tp->ivs.spdef, tp->evs.spdef, NatureAdjustment::NEUTRAL); double mult = type_multiplier(mdata.type, pdata.type[0], pdata.type[1]); if (mdata.spread && trainer_data.battle_type == BDSPBattleType::DOUBLES){ @@ -228,8 +231,11 @@ static void navigate_to_slot(ProControllerContext& context, int slot){ // handle_battle_with_outside_help // --------------------------------------------------------------------------- -void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& stream, ProControllerContext& context, - const std::string& trainer_id, const AutoStoryOptions& options){ +void handle_battle_with_outside_help(const ProgramInfo& /*info*/, + VideoStream& stream, + ProControllerContext& context, + const std::string& trainer_id, + const AutoStoryOptions& options){ Logger& logger = stream.logger(); const BDSPTrainerData& trainer_data = bdsp_trainer(trainer_id); const OutsideHelpConfig& help = options.outside_help; @@ -310,8 +316,8 @@ void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& s } bool prefer = (best_slot < 0) || (s.ohko_all && !best_ohko) || - (s.ohko_all && best_ohko && s.upper_sum < best_hi) || - (!s.ohko_all && !best_ohko && s.upper_sum > best_hi); + (s.ohko_all && best_ohko && s.upper_sum < best_hi) || + (!s.ohko_all && !best_ohko && s.upper_sum > best_hi); if (prefer){ best_slot = i; @@ -329,8 +335,8 @@ void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& s } logger.log("Move slot " + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ")" + - (best_ohko ? " [OHKO]" : " [best effort]"), - COLOR_WHITE); + (best_ohko ? " [OHKO]" : " [best effort]"), + COLOR_WHITE); navigate_to_slot(context, best_slot); pbf_mash_button(context, BUTTON_A, 500ms); @@ -362,11 +368,12 @@ void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& s // XP gain — mash through, then update on_field via OCR BattleMenuWatcher menu3(BattleType::TRAINER); EndBattleWatcher end2; - int after = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_A, 120000ms); - }, - {{menu3}, {end2}}); + int after = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{menu3}, {end2}}); if (after == 1 || after < 0){ battle_active = false; }else{ @@ -377,7 +384,7 @@ void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& s for (size_t i = 0; i < names.size() && i < on_field.size(); i++){ if (!names[i].empty() && names[i] != on_field[i]){ logger.log("Slot " + std::to_string(i) + ": " + on_field[i] + " → " + names[i], - COLOR_WHITE); + COLOR_WHITE); on_field[i] = names[i]; } } @@ -389,11 +396,12 @@ void handle_battle_with_outside_help(const ProgramInfo& /*info*/, VideoStream& s // Mash through XP/dialog until overworld OverworldWatcher overworld; - run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_A, 120000ms); - }, - {{overworld}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{overworld}}); } @@ -521,8 +529,11 @@ static void use_full_restore_in_battle(VideoStream& stream, ProControllerContext // OCR the opponent name box and restrict matches to `candidates`. // Returns the best matching slug in candidates, or the best unrestricted match // if no candidate qualifies. -static std::string ocr_opponent_restricted(Logger& logger, Language language, const ImageViewRGB32& frame, - const ImageFloatBox& box, const std::vector& candidates){ +static std::string ocr_opponent_restricted(Logger& logger, + Language language, + const ImageViewRGB32& frame, + const ImageFloatBox& box, + const std::vector& candidates){ using namespace Pokemon; ImageViewRGB32 cropped = extract_box_reference(frame, box); OCR::StringMatchResult result = @@ -574,8 +585,11 @@ static double intimidate_atk_mult(int count){ // Score move `slot` (0-3) against a single opponent pokemon. // Returns {lower_damage, upper_damage, is_ohko}. -static std::tuple score_move_expert(const BDSPMoveData& mdata, const BDSPTrainerPokemon& tp, - int effective_atk, int effective_spatk, bool doubles){ +static std::tuple score_move_expert(const BDSPMoveData& mdata, + const BDSPTrainerPokemon& tp, + int effective_atk, + int effective_spatk, + bool doubles){ if (mdata.category == MoveCategory::STATUS){ return {0, 0, false}; } @@ -584,10 +598,9 @@ static std::tuple score_move_expert(const BDSPMoveData& mdata, c uint16_t hp = calc_stats_hp(pdata.hp, tp.level, tp.ivs.hp, tp.evs.hp); int atk_stat = (mdata.category == MoveCategory::PHYSICAL) ? effective_atk : effective_spatk; - uint16_t def = - (mdata.category == MoveCategory::PHYSICAL) - ? calc_stats_nonhp(pdata.defense, tp.level, tp.ivs.defense, tp.evs.defense, NatureAdjustment::NEUTRAL) - : calc_stats_nonhp(pdata.spdef, tp.level, tp.ivs.spdef, tp.evs.spdef, NatureAdjustment::NEUTRAL); + uint16_t def = (mdata.category == MoveCategory::PHYSICAL) + ? calc_stats_nonhp(pdata.defense, tp.level, tp.ivs.defense, tp.evs.defense, NatureAdjustment::NEUTRAL) + : calc_stats_nonhp(pdata.spdef, tp.level, tp.ivs.spdef, tp.evs.spdef, NatureAdjustment::NEUTRAL); double mult = type_multiplier(mdata.type, pdata.type[0], pdata.type[1]); if (mdata.spread && doubles){ @@ -600,9 +613,13 @@ static std::tuple score_move_expert(const BDSPMoveData& mdata, c return {lo, hi, ohko}; } -static BattlePlan build_expert_plan(Logger& logger, const std::array& move_slugs, - const std::array& current_pp, const std::vector& all_trainers, - Trainer current_trainer, int base_atk, int base_spatk){ +static BattlePlan build_expert_plan(Logger& logger, + const std::array& move_slugs, + const std::array& current_pp, + const std::vector& all_trainers, + Trainer current_trainer, + int base_atk, + int base_spatk){ // Running PP budget across the full sequence std::array pp = current_pp; @@ -652,13 +669,13 @@ static BattlePlan build_expert_plan(Logger& logger, const std::array 0 && acc < 100){ int adj = 100 - 2 * (100 - acc); logger.log(move_slugs[i] + " vs " + tp.species + ": accuracy=" + std::to_string(acc) + - "% (adjusted=" + std::to_string(adj) + "%)", - COLOR_YELLOW); + "% (adjusted=" + std::to_string(adj) + "%)", + COLOR_YELLOW); } - bool prefer = (best_slot < 0) || (ohko && !best_ohko) // new OHKO > non-OHKO - || (ohko && best_ohko && hi < best_hi) // both OHKO: lower overkill - || (!ohko && !best_ohko && hi > best_hi); // no OHKO: higher damage + bool prefer = (best_slot < 0) || (ohko && !best_ohko) // new OHKO > non-OHKO + || (ohko && best_ohko && hi < best_hi) // both OHKO: lower overkill + || (!ohko && !best_ohko && hi > best_hi); // no OHKO: higher damage if (prefer){ best_slot = i; @@ -673,9 +690,9 @@ static BattlePlan build_expert_plan(Logger& logger, const std::array& candidates, std::vector& on_field){ +static void update_on_field(Logger& logger, + Language language, + VideoStream& stream, + BDSPBattleType battle_type, + const std::vector& candidates, + std::vector& on_field){ VideoSnapshot frame = stream.video().snapshot(); if (battle_type == BDSPBattleType::SINGLES){ static const ImageFloatBox BOX(0.505, 0.010, 0.420, 0.085); @@ -728,9 +749,12 @@ static void update_on_field(Logger& logger, Language language, VideoStream& stre } -void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProControllerContext& context, - const std::vector& all_trainers, Trainer current_trainer, - const AutoStoryOptions& options){ +void handle_expert_battle(const ProgramInfo& /*info*/, + VideoStream& stream, + ProControllerContext& context, + const std::vector& all_trainers, + Trainer current_trainer, + const AutoStoryOptions& options){ using namespace Pokemon; Logger& logger = stream.logger(); @@ -801,8 +825,8 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC bool has_status = detect_player_status(frame); if (hp < 0.33 || has_status){ logger.log("Full Restore needed (HP=" + std::to_string((int)(hp * 100)) + - "%, status=" + (has_status ? "yes" : "no") + ")", - COLOR_WHITE); + "%, status=" + (has_status ? "yes" : "no") + ")", + COLOR_WHITE); use_full_restore_in_battle(stream, context, options.language); } } @@ -824,8 +848,13 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC // 3. Build plan on first turn (after we have real PP values from OCR) if (!plan_built){ - plan = build_expert_plan(logger, move_slugs, pp_remaining, all_trainers, current_trainer, base_atk, - base_spatk); + plan = build_expert_plan(logger, + move_slugs, + pp_remaining, + all_trainers, + current_trainer, + base_atk, + base_spatk); plan_built = true; } @@ -847,8 +876,8 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC intimidate_seen.insert(slug); eff_atk = (int)(base_atk * intimidate_atk_mult(intimidate_count)); logger.log(slug + " has Intimidate — ATK stage -" + std::to_string(intimidate_count) + - " (effective ATK=" + std::to_string(eff_atk) + ")", - COLOR_YELLOW); + " (effective ATK=" + std::to_string(eff_atk) + ")", + COLOR_YELLOW); } } } @@ -896,7 +925,7 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC } bool prefer = (best_slot < 0) || (ohko && !best_ohko) || (ohko && best_ohko && hi < best_hi) || - (!ohko && !best_ohko && hi > best_hi); + (!ohko && !best_ohko && hi > best_hi); if (prefer){ best_slot = i; @@ -922,8 +951,8 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC context.wait_for_all_requests(); }else{ logger.log("Move slot " + std::to_string(best_slot) + " (" + move_slugs[best_slot] + ") vs " + - (on_field.empty() ? "?" : on_field[0]), - COLOR_WHITE); + (on_field.empty() ? "?" : on_field[0]), + COLOR_WHITE); navigate_to_slot(context, best_slot); pbf_mash_button(context, BUTTON_A, 500ms); context.wait_for_all_requests(); @@ -954,11 +983,12 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC // XP gain — mash until the NEXT battle menu appears (or battle ends) BattleMenuWatcher menu3(BattleType::TRAINER); EndBattleWatcher end2; - int after = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_A, 120000ms); - }, - {{menu3}, {end2}}); + int after = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{menu3}, {end2}}); if (after != 0){ // Battle ended during XP screens break; @@ -977,11 +1007,12 @@ void handle_expert_battle(const ProgramInfo& /*info*/, VideoStream& stream, ProC // Mash through any remaining XP/dialog until we're back in the overworld OverworldWatcher overworld; - run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_A, 120000ms); - }, - {{overworld}}); + run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 120000ms); + }, + {{overworld}}); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h index 2409113f20..1ab3a67b78 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h @@ -51,8 +51,9 @@ void run_battle_press_A(const ProgramInfo& info, VideoStream& stream, ProControl // retrying `action`. // The whole sequence retries up to `max_attempts` times. // Throws OperationFailedException if max_attempts is exceeded. -void handle_unexpected_battle( - const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, +void handle_unexpected_battle(const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, std::function&& action, std::function&& recovery_action, size_t max_attempts = 5); @@ -71,8 +72,11 @@ void handle_unexpected_battle( // 4. Mashes A to cover target selection and partner's move. // 5. After XP gain, updates on-field opponents via OCR. // Returns when the battle ends and the player is back in the overworld. -void handle_battle_with_outside_help(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, - const std::string& trainer_id, const AutoStoryOptions& options); +void handle_battle_with_outside_help(const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + const std::string& trainer_id, + const AutoStoryOptions& options); // --------------------------------------------------------------------------- @@ -93,9 +97,12 @@ void handle_battle_with_outside_help(const ProgramInfo& info, VideoStream& strea // 5. Selects the pre-planned optimal move and executes it. // 6. After XP gain, OCRs the new opponent and loops. // Returns when the battle ends and the player is back in the overworld. -void handle_expert_battle(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, - const std::vector& all_trainers, Trainer current_trainer, - const AutoStoryOptions& options); +void handle_expert_battle(const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + const std::vector& all_trainers, + Trainer current_trainer, + const AutoStoryOptions& options); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp index 0456f9fc6e..28a871f34a 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.cpp @@ -43,8 +43,10 @@ void walk_until_battle(VideoStream& stream, ProControllerContext& context, uint1 (void)timeout_sec; } -void recalibrate_after_battle(const ProgramInfo& /*info*/, VideoStream& /*stream*/, ProControllerContext& context, - std::function&& recovery_action){ +void recalibrate_after_battle(const ProgramInfo& /*info*/, + VideoStream& /*stream*/, + ProControllerContext& context, + std::function&& recovery_action){ // TODO: implement — wait for overworld, then run recovery_action recovery_action(context); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h index 150e5283ef..55bdab77f9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Navigation.h @@ -38,14 +38,18 @@ void walk_steps(ProControllerContext& context, uint16_t direction, uint16_t step // Walk in `direction` until a short dialog box is detected, or `timeout_sec` elapses. // Throws OperationFailedException on timeout. -void walk_until_dialog(VideoStream& stream, ProControllerContext& context, uint16_t direction, - uint16_t timeout_sec = 30); +void walk_until_dialog(VideoStream& stream, + ProControllerContext& context, + uint16_t direction, + uint16_t timeout_sec = 30); // Walk in `direction` until a battle starts (StartBattleDetector fires), // or `timeout_sec` elapses. // Throws OperationFailedException on timeout. -void walk_until_battle(VideoStream& stream, ProControllerContext& context, uint16_t direction, - uint16_t timeout_sec = 30); +void walk_until_battle(VideoStream& stream, + ProControllerContext& context, + uint16_t direction, + uint16_t timeout_sec = 30); // --------------------------------------------------------------------------- @@ -62,8 +66,10 @@ void walk_until_battle(VideoStream& stream, ProControllerContext& context, uint1 // on the calling segment and is passed as a lambda. // `recovery_action` should move the player from wherever they ended up back // to a defined starting tile for the current checkpoint. -void recalibrate_after_battle(const ProgramInfo& info, VideoStream& stream, ProControllerContext& context, - std::function&& recovery_action); +void recalibrate_after_battle(const ProgramInfo& info, + VideoStream& stream, + ProControllerContext& context, + std::function&& recovery_action); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index bb14ca1900..c7843b41cb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -96,7 +96,8 @@ void clown_dialog_helper(VideoStream& stream, ProControllerContext& context, Dpa if (std::chrono::steady_clock::now() > overall_deadline){ stream.log("[AutoStory] Clown helper timed out finding dialog.", COLOR_RED); OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] Clown helper timed out finding dialog.", stream); + "[AutoStory] Clown helper timed out finding dialog.", + stream); } context.wait_for_all_requests(); @@ -204,8 +205,8 @@ class TutorialOptionPositionDetector { // Compare binary visual shapes with small alignment tolerance. // No text recognition is needed for fixed English option labels. -static double option_image_score(const ImageViewRGB32& screen, const ImageFloatBox& box, const ImageRGB32& reference, - bool red_only){ +static double +option_image_score(const ImageViewRGB32& screen, const ImageFloatBox& box, const ImageRGB32& reference, bool red_only){ ImageRGB32 actual = extract_box_reference(screen, box).scale_to(384, 32); ImageRGB32 expected = reference.scale_to(384, 32); auto ink = [red_only](uint32_t p){ @@ -267,7 +268,8 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont MenuWatcher menu_open; if (wait_until(stream, context, 5s, {{menu_open}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Main menu not detected.", stream); + "Settings checkpoint: Main menu not detected.", + stream); } stream.log("[Tutorial settings] Main menu detected; settling for 500ms before Options navigation."); pbf_wait(context, 500ms); @@ -295,7 +297,8 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont } if (!options_open){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Options screen not detected.", stream); + "Settings checkpoint: Options screen not detected.", + stream); } stream.log("[Tutorial settings] Options screen detected."); pbf_wait(context, 500ms); @@ -305,9 +308,17 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont const char* value; }; const Setting settings[] = { - {"textspeed", "fast"}, {"changewindow", "type1"}, {"battleeffects", "off"}, {"battlestyle", "set"}, - {"sendtoboxes", "automatic"}, {"givenicknames", "dontgive"}, {"gyroscope", "use"}, {"autosave", "off"}, - {"shownicknames", "show"}, {"backgroundmusic", "8"}, {"soundeffects", "8"}, + {"textspeed", "fast"}, + {"changewindow", "type1"}, + {"battleeffects", "off"}, + {"battlestyle", "set"}, + {"sendtoboxes", "automatic"}, + {"givenicknames", "dontgive"}, + {"gyroscope", "use"}, + {"autosave", "off"}, + {"shownicknames", "show"}, + {"backgroundmusic", "8"}, + {"soundeffects", "8"}, }; for (size_t row = 0; row < sizeof(settings) / sizeof(settings[0]); row++){ // Detect the selected row afresh after every navigation/correction. @@ -329,8 +340,8 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont const double label_score = y >= 0 ? option_image_score(frame, {0.08, y - 0.025, 0.24, 0.05}, label_reference, false) : 0; stream.log("[Tutorial settings] Expected row=" + std::string(settings[row].label) + - " row_y=" + std::to_string(y) + " highlighted_value=" + std::to_string(value_visible) + - " label_match=" + std::to_string(label_score)); + " row_y=" + std::to_string(y) + " highlighted_value=" + std::to_string(value_visible) + + " label_match=" + std::to_string(label_score)); if (y < 0 || !value_visible || label_score < 0.80){ // A menu can be detected while its row/text is still // fading in. Re-read without directional inputs first. @@ -339,9 +350,9 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); continue; } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Unable to confirm expected row " + std::string(settings[row].label), stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Unable to confirm expected row " + std::string(settings[row].label), + stream); } // These are regular video-overlay boxes, so the existing // overlay display control can show/hide every active ROI. @@ -357,16 +368,17 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont if (row >= 9){ // Compare the numeric value rather than the long slider: // neighboring slider levels otherwise look too similar. - ImageViewRGB32 number = value_reference.sub_image( - value_reference.width() * 82 / 100, 0, value_reference.width() - value_reference.width() * 82 / 100, + ImageViewRGB32 number = value_reference.sub_image(value_reference.width() * 82 / 100, + 0, + value_reference.width() - value_reference.width() * 82 / 100, value_reference.height()); ImageRGB32 number_reference = number.scale_to(96, 32); score = option_image_score(frame, {0.7728, y - 0.025, 0.0972, 0.05}, number_reference, true); }else{ score = option_image_score(frame, {0.33, y - 0.025, 0.54, 0.05}, value_reference, true); } - stream.log("[Tutorial settings] " + std::string(settings[row].label) + - " target match=" + std::to_string(score)); + stream.log( + "[Tutorial settings] " + std::string(settings[row].label) + " target match=" + std::to_string(score)); // The reference recording uses inactive pale-red text; // the focused live row has a thicker dark-red glyph mask. // Verified Fast scores 0.734 while Slow/Normal score 0. @@ -379,16 +391,16 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont double check_y = check ? position.selected_row(check) : -1; if (!check || !options_screen.detect(check) || check_y < 0 || option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, label_reference, false) < - 0.80 || + 0.80 || !autosave_off_highlight(check, check_y)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Autosave Off could not be confirmed. Stopping before movement.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Autosave Off could not be confirmed. Stopping before movement.", + stream); } } } - stream.log("[Tutorial settings] Verified " + std::string(settings[row].label) + "=" + - settings[row].value); + stream.log( + "[Tutorial settings] Verified " + std::string(settings[row].label) + "=" + settings[row].value); correct = true; break; } @@ -406,9 +418,9 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); } if (!correct){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Could not apply " + std::string(settings[row].label), stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Could not apply " + std::string(settings[row].label), + stream); } if (row + 1 < sizeof(settings) / sizeof(settings[0])){ pbf_press_dpad(context, DPAD_DOWN, 80ms, 350ms); @@ -424,9 +436,9 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); OverworldWatcher overworld; if (wait_until(stream, context, 10s, {{overworld}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Settings checkpoint: Did not return to the overworld after saving options.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Settings checkpoint: Did not return to the overworld after saving options.", + stream); } // Reopen the saved Options screen and independently verify Autosave. // No route movement is permitted until this persisted-value check passes. @@ -435,7 +447,8 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont MenuWatcher verify_menu; if (wait_until(stream, context, 5s, {{verify_menu}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Autosave verification: Main menu not detected.", stream); + "Autosave verification: Main menu not detected.", + stream); } bool reopened = false; for (size_t attempt = 0; attempt < 2; attempt++){ @@ -456,7 +469,8 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont } if (!reopened){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Autosave verification: Could not reopen saved Options.", stream); + "Autosave verification: Could not reopen saved Options.", + stream); } // Options starts on Text Speed; Autosave is seven rows below it. for (size_t step = 0; step < 7; step++){ @@ -470,9 +484,9 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont if (!check || !options_screen.detect(check) || check_y < 0 || option_image_score(check, {0.08, check_y - 0.025, 0.24, 0.05}, autosave_label, false) < 0.80 || !autosave_off_highlight(check, check_y)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Saved Autosave setting is not verified Off. Stopping before movement.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Saved Autosave setting is not verified Off. Stopping before movement.", + stream); } pbf_wait(context, 200ms); context.wait_for_all_requests(); @@ -488,8 +502,9 @@ static void verify_tutorial_settings_once(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); OverworldWatcher verified_exit; if (wait_until(stream, context, 10s, {{verified_exit}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Autosave verification: Could not return to the overworld.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Autosave verification: Could not return to the overworld.", + stream); } // Report inferred values explicitly: these are visual matches, // allowing the user to compare our conclusions with the screen. @@ -540,7 +555,7 @@ void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context for (size_t check = 0; check < 15; check++){ const auto frame = stream.video().snapshot(); const bool clear = frame && !options_screen.detect(frame) && !menu.detect(frame) && - !black.detect(frame) && bag_pocket_position(frame) == 0 && overworld.detect(frame); + !black.detect(frame) && bag_pocket_position(frame) == 0 && overworld.detect(frame); if (clear){ if (++confirmations >= 3){ recovered = true; @@ -554,15 +569,17 @@ void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context context.wait_for_all_requests(); } stream.log(recovered ? "[Tutorial settings] Recovery confirmed outside Options/menu; retrying." - : "[Tutorial settings] Recovery not confirmed; next attempt will recheck the screen."); + : "[Tutorial settings] Recovery not confirmed; next attempt will recheck the screen."); pbf_wait(context, 500ms); context.wait_for_all_requests(); } } } -void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const DateTime& target, - bool preserve_date){ +void set_time_fast(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + const DateTime& target, + bool preserve_date){ env.log("[AutoStory] Fast date set starting...", COLOR_ORANGE); // 1) Home @@ -579,7 +596,8 @@ void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& co int ret = wait_until(env.console, context, 10s, {date_reader}); if (ret < 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Failed to enter Date Change screen.", env.console); + "Failed to enter Date Change screen.", + env.console); } { @@ -591,21 +609,22 @@ void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& co const auto snapshot = env.console.video().snapshot(); if (!snapshot){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Clock-only setup: No date-editor frame.", env.console); + "Clock-only setup: No date-editor frame.", + env.console); } const DateTime current = date_reader.read_date(env.console, snapshot.frame).second; if (current.year < 2000 || current.year > 2060 || current.month < 1 || current.month > 12 || current.day < 1 || current.day > 31){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Clock-only setup: Could not reliably read the current console date.", env.console); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Clock-only setup: Could not reliably read the current console date.", + env.console); } effective_target.year = current.year; effective_target.month = current.month; effective_target.day = current.day; env.log("[AutoStory] Clock-only target=" + std::to_string((int)target.hour) + ":" + - std::to_string((int)target.minute) + "; preserving date=" + std::to_string(current.year) + "-" + - std::to_string((int)current.month) + "-" + std::to_string((int)current.day) + "."); + std::to_string((int)target.minute) + "; preserving date=" + std::to_string(current.year) + "-" + + std::to_string((int)current.month) + "-" + std::to_string((int)current.day) + "."); } date_reader.set_date(env.program_info(), env.console, context, effective_target); } @@ -625,9 +644,13 @@ void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& co // --------------------------------------------------------------------------- // Natalie battle // --------------------------------------------------------------------------- -static void select_battle_move_slot(VideoStream& stream, ProControllerContext& context, BattleType battle_type, - size_t target, const std::string& label, bool confirm_default_target = false, - size_t current_move = 1){ +static void select_battle_move_slot(VideoStream& stream, + ProControllerContext& context, + BattleType battle_type, + size_t target, + const std::string& label, + bool confirm_default_target = false, + size_t current_move = 1){ BattleMenuDetector commands(battle_type); VideoOverlaySet overlays(stream.overlay()); commands.make_overlays(overlays); @@ -664,8 +687,9 @@ static void select_battle_move_slot(VideoStream& stream, ProControllerContext& c } } if (!fight){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Could not confirm Battle command; A not pressed.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Could not confirm Battle command; A not pressed.", + stream); } pbf_press_button(context, BUTTON_A, 80ms, confirm_default_target ? 1000ms : 500ms); if (confirm_default_target){ @@ -689,10 +713,11 @@ static void select_battle_move_slot(VideoStream& stream, ProControllerContext& c } if (stable < 3 || available < target || available < current_move){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Requested move position is not available.", stream); + label + ": Requested move position is not available.", + stream); } stream.log("[" + label + "] Retained move cursor " + std::to_string(current_move) + " -> " + - std::to_string(target) + "."); + std::to_string(target) + "."); for (size_t slot = current_move; slot < target; slot++){ pbf_press_dpad(context, DPAD_DOWN, 80ms, 400ms); } @@ -715,7 +740,7 @@ static void select_battle_move_slot(VideoStream& stream, ProControllerContext& c move_overlays.add(moves.selected_slot == i + 1 ? COLOR_GREEN : COLOR_YELLOW, moves.buttons[i]); } stream.log("[" + label + "] Available moves=" + std::to_string(moves.move_count) + - "; selected slot=" + std::to_string(slot) + "/4; target=" + std::to_string(target) + "."); + "; selected slot=" + std::to_string(slot) + "/4; target=" + std::to_string(target) + "."); if (moves.move_count == previous_move_count && moves.move_count > 0){ stable_move_count++; }else{ @@ -724,9 +749,9 @@ static void select_battle_move_slot(VideoStream& stream, ProControllerContext& c confirmed = 0; } if (moves.move_count > 0 && moves.move_count < target && stable_move_count >= 3){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Move slot " + std::to_string(target) + " is absent; no move confirmed.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Move slot " + std::to_string(target) + " is absent; no move confirmed.", + stream); } if (slot == (int)target && stable_move_count >= 3){ if (++confirmed >= 3){ @@ -745,9 +770,9 @@ static void select_battle_move_slot(VideoStream& stream, ProControllerContext& c } context.wait_for_all_requests(); } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Could not confirm move " + std::to_string(target) + "; A not pressed.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Could not confirm move " + std::to_string(target) + "; A not pressed.", + stream); } static void select_natalie_move_three(VideoStream& stream, ProControllerContext& context){ @@ -773,8 +798,8 @@ void fight_Natalie(VideoStream& stream, ProControllerContext& context){ // Detect move-learning arrows. SelectionArrowFinder learn_move(stream.overlay(), {0.50, 0.62, 0.40, 0.18}, COLOR_YELLOW); - int ret = run_until( - stream, context, + int ret = run_until(stream, + context, [](ProControllerContext& context){ // Continuously clear: // EXP gain @@ -839,7 +864,8 @@ void fight_Natalie(VideoStream& stream, ProControllerContext& context){ pbf_mash_button(context, BUTTON_B, 5000ms); OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] Natalie battle timed out.", stream); + "[AutoStory] Natalie battle timed out.", + stream); } } } @@ -864,14 +890,15 @@ void fight_starly(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); // Phase 1: wait for either battle menu or battle end. - int ret = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - { - {battle_menu}, - {end_battle}, - }); + int ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + { + {battle_menu}, + {end_battle}, + }); switch (ret){ case 0: { @@ -888,20 +915,23 @@ void fight_starly(VideoStream& stream, ProControllerContext& context){ // Mash through XP / level-up / dialog screens back to overworld. OverworldWatcher overworld; - int cleanup_ret = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{overworld}}); + int cleanup_ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}}); if (cleanup_ret != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Starly battle: Overworld cleanup timed out.", stream); + "Starly battle: Overworld cleanup timed out.", + stream); } return; } default: - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Starly battle timed out.", - stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Starly battle timed out.", + stream); } } } @@ -926,12 +956,14 @@ static void encounter_one_wild_pokemon(VideoStream& stream, ProControllerContext pbf_move_left_joystick(context, {-1, 0}, 120ms, 200ms); context.wait_for_all_requests(); - int ret = wait_until(stream, context, 1500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - }); + int ret = wait_until(stream, + context, + 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + }); if (ret >= 0){ encounter_found = true; @@ -942,12 +974,14 @@ static void encounter_one_wild_pokemon(VideoStream& stream, ProControllerContext pbf_move_left_joystick(context, {1, 0}, 120ms, 200ms); context.wait_for_all_requests(); - ret = wait_until(stream, context, 1500ms, - { - {battle_start}, - {battle_menu}, - {battle_dialog}, - }); + ret = wait_until(stream, + context, + 1500ms, + { + {battle_start}, + {battle_menu}, + {battle_dialog}, + }); if (ret >= 0){ encounter_found = true; @@ -956,24 +990,27 @@ static void encounter_one_wild_pokemon(VideoStream& stream, ProControllerContext } if (!encounter_found){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Wild encounter: Left/right wiggle did not find a battle within three minutes.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Wild encounter: Left/right wiggle did not find a battle within three minutes.", + stream); } stream.log("[AutoStory] Wild battle detected!", COLOR_GREEN); // Wait until the actual battle menu appears. BattleMenuWatcher confirm_menu(BattleType::STANDARD); - int menu_ret = wait_until(stream, context, 15s, - { - {confirm_menu}, - }); + int menu_ret = wait_until(stream, + context, + 15s, + { + {confirm_menu}, + }); if (menu_ret < 0){ stream.log("[AutoStory] Failed to detect battle menu.", COLOR_RED); OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] Failed to detect battle menu.", stream); + "[AutoStory] Failed to detect battle menu.", + stream); } stream.log("[AutoStory] Battle menu detected.", COLOR_GREEN); @@ -997,7 +1034,8 @@ static void confirm_battle_overworld(VideoStream& stream, ProControllerContext& const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); + label + ": No video frame.", + stream); } const auto now = std::chrono::steady_clock::now(); if (dialogue.detect(frame) || black.detect(frame) || !overworld.detect(frame)){ @@ -1010,22 +1048,26 @@ static void confirm_battle_overworld(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); } if (!finished){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Dialogue-free overworld not confirmed for four seconds.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Dialogue-free overworld not confirmed for four seconds.", stream); } context.wait_for_all_requests(); } -static void fight_using_move_list(VideoStream& stream, ProControllerContext& context, BattleType battle_type, - const std::vector& move_slots, const std::string& label, - bool confirm_default_target, bool post_battle_dialogue_cleanup){ +static void fight_using_move_list(VideoStream& stream, + ProControllerContext& context, + BattleType battle_type, + const std::vector& move_slots, + const std::string& label, + bool confirm_default_target, + bool post_battle_dialogue_cleanup){ if (move_slots.empty() || std::any_of(move_slots.begin(), move_slots.end(), [](size_t slot){ return slot < 1 || slot > 4; })){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Supply a nonempty move list containing only positions 1 through 4.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Supply a nonempty move list containing only positions 1 through 4.", + stream); } size_t move_index = 0; // The first Fight menu starts at move 1. Later turns retain @@ -1051,8 +1093,8 @@ static void fight_using_move_list(VideoStream& stream, ProControllerContext& con // Reuse the farming handlers' move-learning prompt recovery. SelectionArrowFinder learn_move(stream.overlay(), {0.50, 0.62, 0.40, 0.18}, COLOR_YELLOW); - int ret = run_until( - stream, context, + int ret = run_until(stream, + context, [](ProControllerContext& context){ // Clear narration with B while watching for commands. // A here can open Fight before selection begins. @@ -1075,9 +1117,14 @@ static void fight_using_move_list(VideoStream& stream, ProControllerContext& con stream.log("[AutoStory] Battle menu detected.", COLOR_BLUE); stream.log("[" + label + "] Move list position " + std::to_string(move_index + 1) + ": use move " + - std::to_string(move_slots[move_index]) + "."); - select_battle_move_slot(stream, context, battle_type, move_slots[move_index], label, confirm_default_target, - current_move); + std::to_string(move_slots[move_index]) + "."); + select_battle_move_slot(stream, + context, + battle_type, + move_slots[move_index], + label, + confirm_default_target, + current_move); current_move = move_slots[move_index]; // Advance once per move selection; retain the final entry. if (move_index + 1 < move_slots.size()){ @@ -1100,14 +1147,16 @@ static void fight_using_move_list(VideoStream& stream, ProControllerContext& con } // Story NPCs and gym leaders opt into the existing cleanup. OverworldWatcher overworld; - int cleanup_ret = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{overworld}}); + int cleanup_ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{overworld}}); if (cleanup_ret != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Post-battle cleanup timed out.", stream); + label + ": Post-battle cleanup timed out.", + stream); } context.wait_for_all_requests(); confirm_battle_overworld(stream, context, label + " cleanup"); @@ -1124,24 +1173,32 @@ static void fight_using_move_list(VideoStream& stream, ProControllerContext& con stream.log("[AutoStory] " + label + " battle timeout.", COLOR_RED); OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "[AutoStory] " + label + " battle timeout.", stream); + "[AutoStory] " + label + " battle timeout.", + stream); } } } // Preserve the existing early-game callers and their B text cleanup. -static void fight_using_move_slot(VideoStream& stream, ProControllerContext& context, BattleType battle_type, - size_t move_slot, const std::string& label){ +static void fight_using_move_slot(VideoStream& stream, + ProControllerContext& context, + BattleType battle_type, + size_t move_slot, + const std::string& label){ fight_using_move_list(stream, context, battle_type, {move_slot}, label, false, true); } -void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, Button button, - Milliseconds quiet_period, const char* instruction_card){ +void clear_post_battle_dialogue(VideoStream& stream, + ProControllerContext& context, + Button button, + Milliseconds quiet_period, + const char* instruction_card){ context.wait_for_all_requests(); std::unique_ptr card; if (instruction_card){ card = std::make_unique(std::make_shared(RESOURCE_PATH() + instruction_card), - ImageFloatBox{.04, .32, .92, .34}, 30.0); + ImageFloatBox{.04, .32, .92, .34}, + 30.0); } ShortDialogDetector dialogue(COLOR_YELLOW); BlackScreenDetector black; @@ -1154,7 +1211,8 @@ void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& conte const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Post-battle dialogue: No video frame.", stream); + "Post-battle dialogue: No video frame.", + stream); } const auto now = std::chrono::steady_clock::now(); const bool dialogue_visible = dialogue.detect(*frame.frame); @@ -1162,8 +1220,8 @@ void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& conte if (dialogue_visible || card_visible || black.detect(*frame.frame)){ absent_since = now; }else if (now - absent_since >= quiet_period){ - stream.log("[Post-battle dialogue] " + std::to_string(quiet_period.count()) + - "ms without dialogue confirmed."); + stream.log( + "[Post-battle dialogue] " + std::to_string(quiet_period.count()) + "ms without dialogue confirmed."); return; } // A can interact again once dialogue disappears. Only confirm @@ -1178,15 +1236,23 @@ void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& conte context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Post-battle dialogue: Three clear seconds not confirmed.", stream); + "Post-battle dialogue: Three clear seconds not confirmed.", + stream); } -void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, - bool allow_story_start, bool horizontal_first, bool search_down_first, - OverworldMatchResult (*entry_matcher)(const ImageRGB32&), bool* entry_detected, - double arrival_tolerance_x, double arrival_tolerance_y, - double moderate_entry_confidence, bool* rock_acquisition_timed_out){ +void align_to_overworld_landmarks(VideoStream& stream, + ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), + const std::string& label, + bool allow_story_start, + bool horizontal_first, + bool search_down_first, + OverworldMatchResult (*entry_matcher)(const ImageRGB32&), + bool* entry_detected, + double arrival_tolerance_x, + double arrival_tolerance_y, + double moderate_entry_confidence, + bool* rock_acquisition_timed_out){ if (rock_acquisition_timed_out){ *rock_acquisition_timed_out = false; } @@ -1202,29 +1268,34 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con size_t confirmations = 0, missing = 0; bool ace_bypass = false; const bool route215_renewal = matcher == match_route215_bridge_renewal || matcher == match_route215_ace_renewal || - matcher == match_route215_derek_stairs_renewal || - matcher == match_route215_derek_stair_top_renewal; + matcher == match_route215_derek_stairs_renewal || matcher == match_route215_derek_stair_top_renewal; Milliseconds correction_movement = 0ms; for (size_t attempt = 0; attempt < 80; attempt++){ const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No video frame.", stream); + label + ": No video frame.", + stream); } if (entry_matcher){ BlackScreenDetector transition; if (entry_matcher(*frame.frame).located || transition.detect(*frame.frame)){ - BlackScreenWatcher visible(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms); + BlackScreenWatcher visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 30s, {{visible}}) != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Building transition did not finish.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Building transition did not finish.", + stream); } const auto inside = stream.video().snapshot(); if (!inside || !entry_matcher(*inside.frame).located){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Expected interior not confirmed after transition.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Expected interior not confirmed after transition.", + stream); } if (entry_detected){ *entry_detected = true; @@ -1240,8 +1311,9 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con stream.log("[" + label + "] Story dialogue/battle started; handing off without further movement."); return; } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Unexpected dialogue/battle during alignment.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Unexpected dialogue/battle during alignment.", + stream); } const auto result = matcher(*frame.frame); if (result.located){ @@ -1254,12 +1326,12 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con overlays.add(COLOR_GREEN, landmark.found); } stream.log("[" + label + "] " + landmark.name + " score=" + std::to_string(landmark.score) + - ", margin=" + std::to_string(landmark.margin) + ", offset=(" + std::to_string(landmark.dx) + - "," + std::to_string(landmark.dy) + ")"); + ", margin=" + std::to_string(landmark.margin) + ", offset=(" + std::to_string(landmark.dx) + "," + + std::to_string(landmark.dy) + ")"); } stream.log("[" + label + "] located=" + std::to_string(result.located) + - ", coarse=" + std::to_string(result.coarse) + ", at_target=" + std::to_string(result.at_target) + - ", consensus offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); + ", coarse=" + std::to_string(result.coarse) + ", at_target=" + std::to_string(result.at_target) + + ", consensus offset=(" + std::to_string(result.dx) + "," + std::to_string(result.dy) + ")"); // The blue-haired NPC patrols the narrow eastern row. Shift // one tile north on the clear road before crossing west; // return to the original row only after clearing his column. @@ -1272,19 +1344,18 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con } const bool ace_row_recovery = ace_bypass && std::abs(result.dy - 70.0 / 1080) > 1.0 / 108; const bool ace_central_row = matcher == match_route215_ace_renewal && !ace_bypass && result.coarse && - std::abs(result.dx) <= 1.0 / 12 && std::abs(result.dy) > 1.0 / 54; + std::abs(result.dx) <= 1.0 / 12 && std::abs(result.dy) > 1.0 / 54; const double entry_confidence = moderate_entry_confidence > 0 ? overworld_entry_confidence(result) : 0; const bool moderate_arrival = moderate_entry_confidence > 0 && entry_confidence >= moderate_entry_confidence; if (moderate_entry_confidence > 0){ stream.log("[" + label + "] Exterior entry confidence=" + std::to_string(entry_confidence) + - ", moderate threshold=" + std::to_string(moderate_entry_confidence) + - ", strict target=" + std::to_string(result.at_target)); + ", moderate threshold=" + std::to_string(moderate_entry_confidence) + + ", strict target=" + std::to_string(result.at_target)); } // Explicit loose bounds are for non-interacting route handoffs. // Default callers still require the matcher's precise arrival. const bool rough_arrival = arrival_tolerance_x > 0 && arrival_tolerance_y > 0 && result.located && - std::abs(result.dx) <= arrival_tolerance_x && - std::abs(result.dy) <= arrival_tolerance_y; + std::abs(result.dx) <= arrival_tolerance_x && std::abs(result.dy) <= arrival_tolerance_y; if (result.at_target || rough_arrival || moderate_arrival){ if (++confirmations >= 3){ stream.log("[" + label + "] Position confirmed."); @@ -1297,7 +1368,7 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con // Wait at an aligned pose while strict visual support // recovers, rather than stepping away under a cloud. if ((matcher == match_galactic_hq_jupiter || matcher == match_route208_hannah_departure || - matcher == match_route208_stair_top) && + matcher == match_route208_stair_top) && result.coarse && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 54){ pbf_wait(context, 500ms); context.wait_for_all_requests(); @@ -1312,21 +1383,21 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con const auto& b = result.landmarks[1]; const double nearest = std::min(std::abs(a.dx), std::abs(b.dx)); coarse_horizontal = a.accepted && b.accepted && a.dx * b.dx > 0 && nearest > 1.0 / 64 && - nearest > 2 * std::abs(a.dx - b.dx); + nearest > 2 * std::abs(a.dx - b.dx); } const bool plaza_anchor_horizontal = matcher == match_jubilife_plaza_approach && result.coarse && - std::abs(result.dx) > 1.0 / 50 && std::abs(result.dy) <= 1.0 / 54; + std::abs(result.dx) > 1.0 / 50 && std::abs(result.dy) <= 1.0 / 54; // A uniquely matched Eterna ball may correct the door // column while the sign/bush enter the frame. Arrival // still requires the matcher's full three-anchor check. const bool eterna_anchor_horizontal = (matcher == match_eterna_center_entrance || matcher == match_eterna_underground_house || - matcher == match_eterna_underground_man) && + matcher == match_eterna_underground_man) && result.coarse && std::abs(result.dx) > 1.0 / 128; // Recover the gym's doorway row before moving right if // the south approach overshot toward the fence/cliff. const bool gym_anchor_horizontal = matcher == match_eterna_gym_entrance && result.coarse && - std::abs(result.dx) > 1.0 / 128 && std::abs(result.dy) <= .06; + std::abs(result.dx) > 1.0 / 128 && std::abs(result.dy) <= .06; const bool gym_row_recovery = matcher == match_eterna_gym_entrance && result.coarse && std::abs(result.dy) > .06; // Reach the row north of Jupiter before entering her @@ -1334,7 +1405,7 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con // moving south so the NPC cannot block the correction. const bool jupiter_row_recovery = matcher == match_galactic_hq_jupiter && result.dy < -1.0 / 54; const bool jupiter_anchor_horizontal = matcher == match_galactic_hq_jupiter && !jupiter_row_recovery && - result.coarse && std::abs(result.dx) > 1.0 / 128; + result.coarse && std::abs(result.dx) > 1.0 / 128; // Stay south of the Contest Hall wall/sign before crossing to Fantina. const bool contest_row_recovery = matcher == match_hearthome_contest_fantina && result.dy > 1.0 / 54; // Keep the stair approach horizontal-first until close to @@ -1343,14 +1414,14 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con // Fine correction changes axis priority, never arrival gates. const bool stair_fine_correction = matcher == match_route208_stair_top && !result.coarse && std::abs(result.dx) <= 1.0 / 64; - const bool horizontal = - !ace_row_recovery && !ace_central_row && - (ace_bypass || (!contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && - (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || - eterna_anchor_horizontal || gym_anchor_horizontal || - (!result.coarse && - ((horizontal_first && !stair_fine_correction && std::abs(result.dx) > 1.0 / 128) || - std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16))))); + const bool horizontal = !ace_row_recovery && !ace_central_row && + (ace_bypass || + (!contest_row_recovery && !gym_row_recovery && !jupiter_row_recovery && + (jupiter_anchor_horizontal || coarse_horizontal || plaza_anchor_horizontal || + eterna_anchor_horizontal || gym_anchor_horizontal || + (!result.coarse && + ((horizontal_first && !stair_fine_correction && std::abs(result.dx) > 1.0 / 128) || + std::abs(result.dx) >= std::abs(result.dy) * 9.0 / 16))))); if (stair_fine_correction && horizontal_first && !horizontal && std::abs(result.dx) > 1.0 / 128){ stream.log("[" + label + "] Near stair column; correcting the larger vertical error first."); } @@ -1360,7 +1431,7 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con stream.log("[" + label + "] Both road bends support restoring the arrival row."); }else if (coarse_horizontal && horizontal){ stream.log("[" + label + - "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); + "] Both landmarks agree on a large horizontal offset; correcting the street lane first."); } const double error = horizontal ? result.dx : (result.dy - (ace_bypass ? 70.0 / 1080 : 0.0)) * 9.0 / 16; auto duration = std::abs(error) > 1.0 / 24 ? 80ms : std::abs(error) > 1.0 / 64 ? 40ms : 16ms; @@ -1376,23 +1447,24 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con // to that row; a southward correction moves away. if (landmark.name == "StairBoundary" && landmark.accepted && landmark.found.y + landmark.found.height > .60 && (horizontal || result.dy < 0)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Correction would approach the stair boundary.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Correction would approach the stair boundary.", + stream); } } } if (route215_renewal){ if (correction_movement + duration > 1500ms){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, label + ": Renewal alignment movement budget exhausted.", + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Renewal alignment movement budget exhausted.", stream); } correction_movement += duration; } - pbf_move_left_joystick( - context, {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (error < 0 ? 1.0 : -1.0)}, - duration, 0ms); + pbf_move_left_joystick(context, + {horizontal ? (result.dx > 0 ? 1.0 : -1.0) : 0.0, horizontal ? 0.0 : (error < 0 ? 1.0 : -1.0)}, + duration, + 0ms); pbf_wait(context, 500ms); }else{ confirmations = 0; @@ -1401,9 +1473,9 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con // Do not sweep into a different doorway or around the Hall's // side, or onto a Veilstone terrace, while required anchors are hidden. if (++missing > 20){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Scene anchors missing; stationary reacquisition exhausted.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Scene anchors missing; stationary reacquisition exhausted.", + stream); } // Recorded expiry endpoints can be north of the passage. // Acquire its buildings along the clear south-only strip; @@ -1430,16 +1502,15 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con if (now - missing_rocks_since >= 10s){ context.wait_for_all_requests(); *rock_acquisition_timed_out = true; - stream.log( - "[" + label + + stream.log("[" + label + "] Rock acquisition timed out after 10s; handing off to the requested cliff recovery."); return; } }else if (++missing > (stair_rocks ? 20 : 6)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + (stair_rocks ? ": Rock anchors missing; no blind search near stairs." - : ": Chair/stair anchors missing; no blind search near stairs."), + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + + (stair_rocks ? ": Rock anchors missing; no blind search near stairs." + : ": Chair/stair anchors missing; no blind search near stairs."), stream); } pbf_wait(context, 500ms); @@ -1451,9 +1522,9 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con // Try that sweep at the initial height, above it, and below it; // no missing/weak landmark ever confirms the destination. if (++missing > (search_down_first ? 78 : 74)){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - label + ": Landmarks missing during bounded bidirectional search.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Landmarks missing during bounded bidirectional search.", + stream); } if (search_down_first && missing <= 4){ stream.log("[" + label + "] Post-battle acquisition: moving down before lateral search."); @@ -1465,8 +1536,8 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con const bool lateral = slot < 24; const double direction = slot < 6 || slot >= 18 ? 1.0 : -1.0; const auto search_duration = lateral ? 100ms : level == 0 ? 600ms : 1200ms; - stream.log("[" + label + "] Acquisition search step=" + std::to_string(missing) + ", direction=" + - (lateral ? (direction > 0 ? "right" : "left") : (level == 0 ? "up" : "down"))); + stream.log("[" + label + "] Acquisition search step=" + std::to_string(missing) + + ", direction=" + (lateral ? (direction > 0 ? "right" : "left") : (level == 0 ? "up" : "down"))); // On the street, a northward search from the east // edge can stop at the building/NPC collision row. // Step west around it before changing height, then @@ -1475,8 +1546,10 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con if (street_height_search){ pbf_move_left_joystick(context, {-1.0, 0.0}, 150ms, 0ms); } - pbf_move_left_joystick(context, {lateral ? direction : 0.0, lateral ? 0.0 : (level == 0 ? 1.0 : -1.0)}, - search_duration, 0ms); + pbf_move_left_joystick(context, + {lateral ? direction : 0.0, lateral ? 0.0 : (level == 0 ? 1.0 : -1.0)}, + search_duration, + 0ms); if (street_height_search){ pbf_move_left_joystick(context, {1.0, 0.0}, 150ms, 0ms); } @@ -1486,21 +1559,30 @@ void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& con context.wait_for_all_requests(); } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": Could not confirm the reference position.", stream); + label + ": Could not confirm the reference position.", + stream); } -void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, const std::vector& moves, - const std::string& label, bool post_battle_dialogue_cleanup){ +void fight_trainer_using_moves(VideoStream& stream, + ProControllerContext& context, + const std::vector& moves, + const std::string& label, + bool post_battle_dialogue_cleanup){ fight_using_move_list(stream, context, BattleType::TRAINER, moves, label, true, post_battle_dialogue_cleanup); } -void fight_trainer_using_moves(VideoStream& stream, ProControllerContext& context, size_t move, - const std::string& label, bool post_battle_dialogue_cleanup){ +void fight_trainer_using_moves(VideoStream& stream, + ProControllerContext& context, + size_t move, + const std::string& label, + bool post_battle_dialogue_cleanup){ fight_trainer_using_moves(stream, context, std::vector{move}, label, post_battle_dialogue_cleanup); } -void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, size_t move_slot, - const std::string& label){ +void fight_trainer_using_move_slot(VideoStream& stream, + ProControllerContext& context, + size_t move_slot, + const std::string& label){ fight_using_move_slot(stream, context, BattleType::TRAINER, move_slot, label); } @@ -1548,11 +1630,12 @@ void catch_1_pokemon(VideoStream& stream, ProControllerContext& context){ // Wait to see if the battle menu comes back (ball failed) // or if the battle ends (caught or fainted). BattleMenuWatcher menu_back(BattleType::STANDARD, COLOR_YELLOW); - int throw_ret = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{menu_back}, {received_pokemon}, {battle_ended}}); + int throw_ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{menu_back}, {received_pokemon}, {battle_ended}}); if (throw_ret == 1 || (throw_ret == 2 && received_pokemon.received())){ caught = true; @@ -1561,40 +1644,43 @@ void catch_1_pokemon(VideoStream& stream, ProControllerContext& context){ break; } if (throw_ret < 0 || throw_ret == 2){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Catch failed: No verified Pokemon receipt before battle ended or timed out.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Catch failed: No verified Pokemon receipt before battle ended or timed out.", + stream); } stream.log("[AutoStory] Ball failed, retrying (attempt " + std::to_string(throw_attempt + 2) + ")...", - COLOR_ORANGE); + COLOR_ORANGE); } context.wait_for_all_requests(); if (!caught){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Catch failed: Exhausted ten ball attempts.", stream); + "Catch failed: Exhausted ten ball attempts.", + stream); } // Receipt alone is not an overworld return: clear experience, // automatic move learning, dex registration and nickname screens // until the same completed-black-transition watcher used in battles fires. if (!exit_seen){ stream.log("[Catch cleanup] Receipt confirmed; waiting for battle exit transition."); - const int cleanup_ret = run_until(stream, context, - [](ProControllerContext& ctx){ - pbf_mash_button(ctx, BUTTON_B, 120000ms); - }, - {{battle_ended}}); + const int cleanup_ret = run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_B, 120000ms); + }, + {{battle_ended}}); if (cleanup_ret != 0){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Catch cleanup: Battle exit transition did not finish.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Catch cleanup: Battle exit transition did not finish.", + stream); } } context.wait_for_all_requests(); stream.log("[Catch cleanup] Battle exit transition confirmed; checking stable overworld."); confirm_battle_overworld(stream, context, "Catch cleanup"); stream.log("[Catch cleanup] Verified receipt, completed battle exit, and four seconds of clear overworld.", - COLOR_GREEN); + COLOR_GREEN); } // --------------------------------------------------------------------------- @@ -1834,7 +1920,7 @@ void leave_pokemon_center_before_barry_1(VideoStream& stream, ProControllerConte } stream.log("[AutoStory] Pokemon Center exit black screen not detected. Searching with right/down movement.", - COLOR_ORANGE); + COLOR_ORANGE); if (try_right_down_exit_search(stream, context, 20)){ pbf_wait(context, 2340ms); @@ -1849,18 +1935,24 @@ void leave_pokemon_center_before_barry_1(VideoStream& stream, ProControllerConte return; } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Failed to find Pokemon Center exit black screen before Barry 1. Triggering checkpoint retry.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Failed to find Pokemon Center exit black screen before Barry 1. Triggering checkpoint retry.", + stream); } // --------------------------------------------------------------------------- // Generic Movement Helper - Walk through grass in specified direction for // specified steps, attempting to run from any battles that are encountered. // --------------------------------------------------------------------------- -void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8_t x, int8_t y, size_t steps, - const std::string& direction_name, BattleType battle_type, Milliseconds movement_duration, - OverworldMatchResult (*destination)(const ImageRGB32&)){ +void walk_through_grass(VideoStream& stream, + ProControllerContext& context, + int8_t x, + int8_t y, + size_t steps, + const std::string& direction_name, + BattleType battle_type, + Milliseconds movement_duration, + OverworldMatchResult (*destination)(const ImageRGB32&)){ size_t movements = 0, confirmations = 0; while (true){ context.wait_for_all_requests(); @@ -1872,7 +1964,8 @@ void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8 const auto frame = stream.video().snapshot(); if (!frame){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Grass destination: No video frame.", stream); + "Grass destination: No video frame.", + stream); } location = destination(*frame.frame); for (const auto& landmark : location.landmarks){ @@ -1881,17 +1974,17 @@ void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8 overlays.add(COLOR_GREEN, landmark.found); } stream.log("[Grass destination] " + direction_name + " " + landmark.name + - " score=" + std::to_string(landmark.score) + " margin=" + std::to_string(landmark.margin) + - " accepted=" + std::to_string(landmark.accepted)); + " score=" + std::to_string(landmark.score) + " margin=" + std::to_string(landmark.margin) + + " accepted=" + std::to_string(landmark.accepted)); } stream.log("[Grass destination] located=" + std::to_string(location.located) + - " at_target=" + std::to_string(location.at_target) + " screen-relative offset=(" + - std::to_string(location.dx) + "," + std::to_string(location.dy) + ")"); + " at_target=" + std::to_string(location.at_target) + " screen-relative offset=(" + + std::to_string(location.dx) + "," + std::to_string(location.dy) + ")"); if (location.at_target){ if (++confirmations >= 3){ stream.log("[Grass] Destination confirmed; stopped " + direction_name + " after " + - std::to_string(movements) + "/" + std::to_string(steps) + " movements.", - COLOR_GREEN); + std::to_string(movements) + "/" + std::to_string(steps) + " movements.", + COLOR_GREEN); return; } pbf_wait(context, 250ms); @@ -1901,9 +1994,9 @@ void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8 } if (movements >= steps){ if (destination){ - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "Grass destination not confirmed within movement limit: " + direction_name, stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Grass destination not confirmed within movement limit: " + direction_name, + stream); } stream.log("[Grass] Completed " + direction_name + " (" + std::to_string(steps) + " steps).", COLOR_GREEN); return; @@ -1919,7 +2012,7 @@ void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8 move_x = horizontal ? (location.dx > 0 ? 1 : -1) : 0; move_y = horizontal ? 0 : (location.dy < 0 ? 1 : -1); stream.log("[Grass destination] Correcting " + std::string(horizontal ? "horizontal" : "vertical") + - " axis for " + std::to_string(duration.count()) + "ms."); + " axis for " + std::to_string(duration.count()) + "ms."); } // Each movement gets fresh watcher state. A completed white // transition from a prior encounter must not trigger again. @@ -1934,8 +2027,8 @@ void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8 if (ret >= 0){ confirmations = 0; stream.log("[Grass] " + direction_name + " step " + std::to_string(movements) + "/" + - std::to_string(steps) + ": encounter trigger=" + std::to_string(ret) + - "; using shared confirmed-Run escape."); + std::to_string(steps) + ": encounter trigger=" + std::to_string(ret) + + "; using shared confirmed-Run escape."); escape_wild_battle(stream, context, battle_type); context.wait_for_all_requests(); stream.log("[Grass] Escape confirmed; resuming " + direction_name + "."); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h index f71ec492e1..47a1de720e 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h @@ -28,21 +28,31 @@ class BattleDialogWatcher : public DetectorToFinder { // --------------------------------------------------------------------------- // Shared visual alignment and dialogue cleanup used by story routes. -void clear_post_battle_dialogue(VideoStream& stream, ProControllerContext& context, Button button = BUTTON_B, - Milliseconds quiet_period = Milliseconds(3000), const char* instruction_card = nullptr); -void align_to_overworld_landmarks(VideoStream& stream, ProControllerContext& context, - OverworldMatchResult (*matcher)(const ImageRGB32&), const std::string& label, - bool allow_story_start = false, bool horizontal_first = false, - bool search_down_first = false, - OverworldMatchResult (*entry_matcher)(const ImageRGB32&) = nullptr, - bool* entry_detected = nullptr, double arrival_tolerance_x = 0, - double arrival_tolerance_y = 0, double moderate_entry_confidence = 0, - bool* rock_acquisition_timed_out = nullptr); +void clear_post_battle_dialogue(VideoStream& stream, + ProControllerContext& context, + Button button = BUTTON_B, + Milliseconds quiet_period = Milliseconds(3000), + const char* instruction_card = nullptr); +void align_to_overworld_landmarks(VideoStream& stream, + ProControllerContext& context, + OverworldMatchResult (*matcher)(const ImageRGB32&), + const std::string& label, + bool allow_story_start = false, + bool horizontal_first = false, + bool search_down_first = false, + OverworldMatchResult (*entry_matcher)(const ImageRGB32&) = nullptr, + bool* entry_detected = nullptr, + double arrival_tolerance_x = 0, + double arrival_tolerance_y = 0, + double moderate_entry_confidence = 0, + bool* rock_acquisition_timed_out = nullptr); void verify_tutorial_settings(VideoStream& stream, ProControllerContext& context); -void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& context, const DateTime& target, - bool preserve_date = false // Keep current year/month/day; set only target time. +void set_time_fast(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + const DateTime& target, + bool preserve_date = false // Keep current year/month/day; set only target time. ); // --------------------------------------------------------------------------- @@ -52,8 +62,10 @@ void set_time_fast(SingleSwitchProgramEnvironment& env, ProControllerContext& co void fight_starly(VideoStream& stream, ProControllerContext& context); // Select the requested 1-based move slot using visual cursor confirmation. -void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, size_t move_slot, - const std::string& label); +void fight_trainer_using_move_slot(VideoStream& stream, + ProControllerContext& context, + size_t move_slot, + const std::string& label); void fight_tristan(VideoStream& stream, ProControllerContext& context); @@ -82,10 +94,15 @@ void use_potion_first_pokemon(VideoStream& stream, ProControllerContext& context // Grass movement helpers // --------------------------------------------------------------------------- -void walk_through_grass(VideoStream& stream, ProControllerContext& context, int8_t x, int8_t y, size_t steps, - const std::string& direction_name, BattleType battle_type = BattleType::STANDARD, - Milliseconds movement_duration = Milliseconds(200), - OverworldMatchResult (*destination)(const ImageRGB32&) = nullptr); +void walk_through_grass(VideoStream& stream, + ProControllerContext& context, + int8_t x, + int8_t y, + size_t steps, + const std::string& direction_name, + BattleType battle_type = BattleType::STANDARD, + Milliseconds movement_duration = Milliseconds(200), + OverworldMatchResult (*destination)(const ImageRGB32&) = nullptr); bool detect_black_screen_after_movement(VideoStream& stream, ProControllerContext& context, Milliseconds timeout); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp index a14cd54b8a..6d96ef3623 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.cpp @@ -42,24 +42,26 @@ namespace PokemonBDSP { // Extracted from the Segment 01 Oreburgh Gate approach. Both callers use // the same bounded down/right search and cancel queued input at the black fade. // Oreburgh preserves its original north sweep; Coronet uses only down/right. -void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, const std::string& label, - Milliseconds north_fallback){ +void enter_cave_with_down_right_search(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + Milliseconds north_fallback){ context.wait_for_all_requests(); for (size_t attempt = 0; attempt < 3; attempt++){ BlackScreenWatcher black_screen; - const int result = - run_until(stream, context, - [north_fallback](ProControllerContext& ctx){ - for (size_t i = 0; i < 10; i++){ - pbf_press_button(ctx, BUTTON_DOWN, 180ms, 0ms); - pbf_press_button(ctx, BUTTON_RIGHT, 180ms, 0ms); - pbf_press_button(ctx, BUTTON_RIGHT, 180ms, 0ms); - } - if (north_fallback > 0ms){ - pbf_move_left_joystick(ctx, {0, 1}, north_fallback, 0ms); - } - }, - {{black_screen}}); + const int result = run_until(stream, + context, + [north_fallback](ProControllerContext& ctx){ + for (size_t i = 0; i < 10; i++){ + pbf_press_button(ctx, BUTTON_DOWN, 180ms, 0ms); + pbf_press_button(ctx, BUTTON_RIGHT, 180ms, 0ms); + pbf_press_button(ctx, BUTTON_RIGHT, 180ms, 0ms); + } + if (north_fallback > 0ms){ + pbf_move_left_joystick(ctx, {0, 1}, north_fallback, 0ms); + } + }, + {{black_screen}}); context.wait_for_all_requests(); if (result == 0){ stream.log(label + ": Black screen detected."); @@ -67,7 +69,8 @@ void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - label + ": No black screen after 3 attempts.", stream); + label + ": No black screen after 3 attempts.", + stream); } static bool mine_confirm_escape_via_menu(VideoStream& stream, ProControllerContext& context, BattleType battle_type){ @@ -84,13 +87,14 @@ static bool mine_confirm_escape_via_menu(VideoStream& stream, ProControllerConte int detected = state; if (state != 0){ stream.log("[Mine escape] Main menu fallback attempt " + std::to_string(attempt) + "/3."); - detected = run_until(stream, context, - [](ProControllerContext& controller){ - pbf_press_button(controller, BUTTON_X, 160ms, 840ms); - pbf_wait(controller, 3s); - controller.wait_for_all_requests(); - }, - {game_menu, battle_menu}); + detected = run_until(stream, + context, + [](ProControllerContext& controller){ + pbf_press_button(controller, BUTTON_X, 160ms, 840ms); + pbf_wait(controller, 3s); + controller.wait_for_all_requests(); + }, + {game_menu, battle_menu}); if (detected == 1){ return false; } @@ -107,8 +111,12 @@ static bool mine_confirm_escape_via_menu(VideoStream& stream, ProControllerConte if (wait_until(stream, context, 1s, {{overworld}}) == 0){ return true; } - BlackScreenWatcher visible(COLOR_YELLOW, {0.1, 0.1, 0.8, 0.8}, 100, 10, - BlackScreenWatcher::FinderType::GONE, 500ms); + BlackScreenWatcher visible(COLOR_YELLOW, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); if (wait_until(stream, context, 2s, {{visible}}) == 0){ const VideoSnapshot frame = stream.video().snapshot(); if (frame && !menu_visible.detect(frame)){ @@ -133,8 +141,8 @@ static bool mine_select_run(VideoStream& stream, ProControllerContext& context, return false; } const BattleMenuOption selected = cursor.selected_option(frame); - stream.log("[Mine escape] Battle cursor row=" + std::to_string(static_cast(selected) + 1) + - "; target Run=4."); + stream.log( + "[Mine escape] Battle cursor row=" + std::to_string(static_cast(selected) + 1) + "; target Run=4."); if (selected == BattleMenuOption::RUN){ if (++matching_frames >= 3){ stream.log("[Mine escape] Run cursor confirmed.", COLOR_GREEN); @@ -156,8 +164,10 @@ static bool mine_select_run(VideoStream& stream, ProControllerContext& context, return false; } -void escape_wild_battle(VideoStream& stream, ProControllerContext& context, BattleType battle_type, - std::chrono::seconds timeout){ +void escape_wild_battle(VideoStream& stream, + ProControllerContext& context, + BattleType battle_type, + std::chrono::seconds timeout){ EndBattleWatcher battle_end; bool exit_transition = false; const auto deadline = std::chrono::steady_clock::now() + timeout; @@ -177,13 +187,14 @@ void escape_wild_battle(VideoStream& stream, ProControllerContext& context, Batt continue; } stream.log("[Mine escape] Run input sent; watching for black-screen exit."); - const int result = run_until(stream, context, - [](ProControllerContext& controller){ - pbf_press_button(controller, BUTTON_A, 60ms, 0ms); - pbf_wait(controller, 4s); - controller.wait_for_all_requests(); - }, - {battle_end}); + const int result = run_until(stream, + context, + [](ProControllerContext& controller){ + pbf_press_button(controller, BUTTON_A, 60ms, 0ms); + pbf_wait(controller, 4s); + controller.wait_for_all_requests(); + }, + {battle_end}); if (result == 0){ exit_transition = true; break; @@ -205,9 +216,9 @@ void escape_wild_battle(VideoStream& stream, ProControllerContext& context, Batt if (mine_confirm_escape_via_menu(stream, context, battle_type)){ return; } - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, - "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "escape_wild_battle: Exit transition and late main-menu fallback could not confirm escape.", + stream); } void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ @@ -219,14 +230,15 @@ void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro BattleMenuWatcher battle_menu(BattleType::STANDARD); EndBattleWatcher end_battle; - int ret = run_until(stream, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_ZL, 120000ms); - }, - { - battle_menu, - end_battle, - }); + int ret = run_until(stream, + context, + [](ProControllerContext& context){ + pbf_mash_button(context, BUTTON_ZL, 120000ms); + }, + { + battle_menu, + end_battle, + }); switch (ret){ case 0: stream.log("Battle menu detected - using first move."); @@ -238,13 +250,15 @@ void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro context.wait_for_all_requests(); return; default: - OperationFailedExceptionWithScreenshot::fire( - ErrorReportMode::SEND_ERROR_REPORT, "Barry_1_fight: Timed out waiting for battle menu or end.", stream); + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Barry_1_fight: Timed out waiting for battle menu or end.", + stream); } } OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Barry_1_fight: Battle did not end after 20 turns.", stream); + "Barry_1_fight: Battle did not end after 20 turns.", + stream); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h index 282eee3885..0a5d2378f8 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_Autostory_Segment_01_tools.h @@ -10,13 +10,16 @@ namespace PokemonBDSP { // Reuse the Oreburgh Gate down/right entry search. Returns at fade start; // caller owns destination-specific fade completion and interior confirmation. // north_fallback preserves Oreburgh's final north sweep when explicitly set. -void enter_cave_with_down_right_search(VideoStream& stream, ProControllerContext& context, const std::string& label, - Milliseconds north_fallback = Milliseconds(0)); +void enter_cave_with_down_right_search(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + Milliseconds north_fallback = Milliseconds(0)); // Shared confirmed-Run escape: verifies exit transition or main menu. -void escape_wild_battle(VideoStream& stream, ProControllerContext& context, - BattleType battle_type = BattleType::STANDARD, - std::chrono::seconds timeout = std::chrono::seconds(30)); +void escape_wild_battle(VideoStream& stream, + ProControllerContext& context, + BattleType battle_type = BattleType::STANDARD, + std::chrono::seconds timeout = std::chrono::seconds(30)); // Shared legacy first-move battle routine; consolidation remains preliminary. void Barry_1_fight(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp index fee13da09b..6b7c2b8182 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.cpp @@ -33,8 +33,11 @@ static constexpr double POSITION_TOLERANCE_Y = 1.0 / 54; static constexpr double AGREEMENT_X = 1.0 / 64; static constexpr double AGREEMENT_Y = 1.0 / 54; static cv::Mat gray_image(const ImageRGB32& image){ - cv::Mat pixels((int)image.height(), (int)image.width(), CV_8UC4, const_cast(image.data()), - image.bytes_per_row()); + cv::Mat pixels((int)image.height(), + (int)image.width(), + CV_8UC4, + const_cast(image.data()), + image.bytes_per_row()); cv::Mat gray; cv::cvtColor(pixels, gray, cv::COLOR_BGRA2GRAY); return gray; @@ -53,8 +56,11 @@ static cv::Mat normalize_landmark_illumination(const cv::Mat& gray){ // White Pokeball versus yellow fascia is a color-ratio boundary. A cast // shadow changes their brightness, so compare blue fraction rather than luma. static cv::Mat landmark_blue_fraction(const ImageRGB32& image){ - cv::Mat pixels((int)image.height(), (int)image.width(), CV_8UC4, const_cast(image.data()), - image.bytes_per_row()); + cv::Mat pixels((int)image.height(), + (int)image.width(), + CV_8UC4, + const_cast(image.data()), + image.bytes_per_row()); cv::Mat small, floating; cv::resize(pixels, small, cv::Size(960, 540), 0, 0, cv::INTER_AREA); small.convertTo(floating, CV_32F); @@ -65,9 +71,12 @@ static cv::Mat landmark_blue_fraction(const ImageRGB32& image){ ratio.convertTo(structure, CV_8U, 768.0); return structure; } -static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std::vector& configs, - size_t required, double tolerance_x, double tolerance_y, - bool smooth_resize = false){ +static OverworldMatchResult match_landmarks(const ImageRGB32& frame, + const std::vector& configs, + size_t required, + double tolerance_x, + double tolerance_y, + bool smooth_resize = false){ OverworldMatchResult result; if (!frame){ return result; @@ -97,8 +106,10 @@ static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std:: ImageRGB32 small = reference.scale_to(template_width, template_height); templ = gray_image(small); } - cv::Rect search((int)(config.search.x * 960), (int)(config.search.y * 540), (int)(config.search.width * 960), - (int)(config.search.height * 540)); + cv::Rect search((int)(config.search.x * 960), + (int)(config.search.y * 540), + (int)(config.search.width * 960), + (int)(config.search.height * 540)); search &= cv::Rect(0, 0, 960, 540); if (search.width < templ.cols || search.height < templ.rows){ result.landmarks.push_back(landmark); @@ -125,9 +136,11 @@ static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std:: cv::Mat competitors = scores.clone(); const int peak_radius_x = (int)std::lround(0.015625 * gray.cols); const int peak_radius_y = (int)std::lround((1.0 / 36) * gray.rows); - cv::Rect exclusion = cv::Rect(location.x - peak_radius_x, location.y - peak_radius_y, 2 * peak_radius_x + 1, - 2 * peak_radius_y + 1) & - cv::Rect(0, 0, scores.cols, scores.rows); + cv::Rect exclusion = cv::Rect(location.x - peak_radius_x, + location.y - peak_radius_y, + 2 * peak_radius_x + 1, + 2 * peak_radius_y + 1) & + cv::Rect(0, 0, scores.cols, scores.rows); competitors(exclusion).setTo(-1); double runner_up; cv::minMaxLoc(competitors, nullptr, &runner_up); @@ -136,8 +149,10 @@ static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std:: landmark.dx = (double)location.x / gray.cols - config.target.x; landmark.dy = (double)location.y / gray.rows - config.target.y; landmark.accepted = landmark.score >= config.threshold && landmark.margin >= config.margin; - landmark.found = {(double)location.x / 960, (double)location.y / 540, (double)templ.cols / 960, - (double)templ.rows / 540}; + landmark.found = {(double)location.x / 960, + (double)location.y / 540, + (double)templ.cols / 960, + (double)templ.rows / 540}; result.landmarks.push_back(landmark); } if (required == 1 && result.landmarks.size() == 1){ @@ -172,7 +187,7 @@ static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std:: result.dy = dy; ambiguous = false; }else if (support == best_support && - (std::abs(dx - result.dx) > AGREEMENT_X || std::abs(dy - result.dy) > AGREEMENT_Y)){ + (std::abs(dx - result.dx) > AGREEMENT_X || std::abs(dy - result.dy) > AGREEMENT_Y)){ ambiguous = true; } } @@ -184,50 +199,54 @@ static OverworldMatchResult match_landmarks(const ImageRGB32& frame, const std:: OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ // Same cropped reference emblems and local search windows as production. OverworldMatchResult fine = match_landmarks(frame, - {{"Mart", - "PokemonBDSP/Autostory/PoketchInteraction/Mart.png", - {0.768750000000, 0.044444444444, 0.079166666667, 0.088888888889}, - {598.0 / 960, 0, 356.0 / 960, 212.0 / 540}, - .90, - 0}, - {"Center", - "PokemonBDSP/Autostory/PoketchInteraction/Center.png", - {0.900000000000, 0.748148148148, 0.075000000000, 0.137037037037}, - {724.0 / 960, 264.0 / 540, 236.0 / 960, 276.0 / 540}, - .90, - 0}, - {"School", - "PokemonBDSP/Autostory/PoketchInteraction/School.png", - {0.006250000000, 0.755555555556, 0.097916666667, 0.133333333333}, - {0, 268.0 / 540, 240.0 / 960, 272.0 / 540}, - .90, - 0}}, - 2, 7.0 / 960, 7.0 / 540); + {{"Mart", + "PokemonBDSP/Autostory/PoketchInteraction/Mart.png", + {0.768750000000, 0.044444444444, 0.079166666667, 0.088888888889}, + {598.0 / 960, 0, 356.0 / 960, 212.0 / 540}, + .90, + 0}, + {"Center", + "PokemonBDSP/Autostory/PoketchInteraction/Center.png", + {0.900000000000, 0.748148148148, 0.075000000000, 0.137037037037}, + {724.0 / 960, 264.0 / 540, 236.0 / 960, 276.0 / 540}, + .90, + 0}, + {"School", + "PokemonBDSP/Autostory/PoketchInteraction/School.png", + {0.006250000000, 0.755555555556, 0.097916666667, 0.133333333333}, + {0, 268.0 / 540, 240.0 / 960, 272.0 / 540}, + .90, + 0}}, + 2, + 7.0 / 960, + 7.0 / 540); if (fine.located){ return fine; } // Coarse acquisition searches the whole frame. Agreement and a peak margin // guard against confusing the Mart/Center emblems with another building. OverworldMatchResult coarse = match_landmarks(frame, - {{"Mart", - "PokemonBDSP/Autostory/PoketchInteraction/Mart.png", - {0.768750000000, 0.044444444444, 0.079166666667, 0.088888888889}, - {0, 0, 1, 1}, - .93, - .12}, - {"Center", - "PokemonBDSP/Autostory/PoketchInteraction/Center.png", - {0.900000000000, 0.748148148148, 0.075000000000, 0.137037037037}, - {0, 0, 1, 1}, - .93, - .12}, - {"School", - "PokemonBDSP/Autostory/PoketchInteraction/School.png", - {0.006250000000, 0.755555555556, 0.097916666667, 0.133333333333}, - {0, 0, 1, 1}, - .92, - .40}}, - 2, 7.0 / 960, 7.0 / 540); + {{"Mart", + "PokemonBDSP/Autostory/PoketchInteraction/Mart.png", + {0.768750000000, 0.044444444444, 0.079166666667, 0.088888888889}, + {0, 0, 1, 1}, + .93, + .12}, + {"Center", + "PokemonBDSP/Autostory/PoketchInteraction/Center.png", + {0.900000000000, 0.748148148148, 0.075000000000, 0.137037037037}, + {0, 0, 1, 1}, + .93, + .12}, + {"School", + "PokemonBDSP/Autostory/PoketchInteraction/School.png", + {0.006250000000, 0.755555555556, 0.097916666667, 0.133333333333}, + {0, 0, 1, 1}, + .92, + .40}}, + 2, + 7.0 / 960, + 7.0 / 540); if (!coarse.located){ // Acquisition may expose just one building. School keeps its strong // spatial margin; Mart/Center require a higher score than pair matching. @@ -264,23 +283,27 @@ OverworldMatchResult match_poketch_landmarks(const ImageRGB32& frame){ OverworldMatchResult match_oreburgh_mart(const ImageRGB32& frame){ const ImageFloatBox target{0.502083333333, 0.235185185185, 0.056250000000, 0.079629629630}; auto original = match_landmarks(frame, - {{"Mart emblem", - "PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", - target, - {0, 0, 1, 360.0 / 540}, - .93, - .10}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y); + {{"Mart emblem", + "PokemonBDSP/Autostory/OreburghMart/MartEmblem.png", + target, + {0, 0, 1, 360.0 / 540}, + .93, + .10}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y); // The recorded daytime roof appearance narrowly missed the old template. // Preserve both confidence gates and reject disagreement between variants. auto recorded = match_landmarks(frame, - {{"Mart emblem", - "PokemonBDSP/Autostory/OreburghMart/MartEmblemRecorded.png", - target, - {0, 0, 1, 360.0 / 540}, - .93, - .10}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y); + {{"Mart emblem", + "PokemonBDSP/Autostory/OreburghMart/MartEmblemRecorded.png", + target, + {0, 0, 1, 360.0 / 540}, + .93, + .10}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y); if (original.located && recorded.located && (std::abs(original.dx - recorded.dx) > AGREEMENT_X || std::abs(original.dy - recorded.dy) > AGREEMENT_Y)){ original.located = false; @@ -298,21 +321,27 @@ OverworldMatchResult match_jubilife_center_entrance(const ImageRGB32& frame){ // The emblem is about 5.73% of screen width left of the player at approach: // shift its target right to center the door, preserving the approach height. OverworldMatchResult clear = match_landmarks(frame, - {{"Center entrance", - "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", - {0.462500000000, 0.109259259259, 0.072916666667, 0.111111111111}, - {0, 0, 1, 1}, - .90, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblem.png", + {0.462500000000, 0.109259259259, 0.072916666667, 0.111111111111}, + {0, 0, 1, 1}, + .90, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); OverworldMatchResult shadow = match_landmarks(frame, - {{"Center entrance", - "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", - {0.462500000000, 0.109259259259, 0.072916666667, 0.111111111111}, - {0, 0, 1, 1}, - .90, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Center entrance", + "PokemonBDSP/Autostory/JubilifeCenter/CenterEmblemShadow.png", + {0.462500000000, 0.109259259259, 0.072916666667, 0.111111111111}, + {0, 0, 1, 1}, + .90, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); // Lighting alternatives must identify the same position when both pass. if (clear.located && shadow.located && (std::abs(clear.dx - shadow.dx) > AGREEMENT_X || std::abs(clear.dy - shadow.dy) > AGREEMENT_Y)){ @@ -330,26 +359,29 @@ OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame){ // Path shadows change shape, so validate the corner against four lighting states. // Each alternative still requires the sign and path to agree on position. const char* corners[] = {"PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour0.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", - "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png"}; + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour9.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour12.png", + "PokemonBDSP/Autostory/Route201PotionExit/PathCornerHour17.png"}; OverworldMatchResult best; double best_score = -1; for (const char* corner : corners){ auto candidate = match_landmarks(frame, - {{"Route sign face", - "PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", - {0.292708333333, 0.470370370370, 0.109375, 0.072222222222}, - {0, 0, 1, 1}, - .94, - .20}, - {"Path corner", - corner, - {0.202083333333, 0.648148148148, 0.133333333333, 0.174074074074}, - {0, 0, 1, 1}, - .90, - .10}}, - 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Route sign face", + "PokemonBDSP/Autostory/Route201PotionExit/RouteSignFace.png", + {0.292708333333, 0.470370370370, 0.109375, 0.072222222222}, + {0, 0, 1, 1}, + .94, + .20}, + {"Path corner", + corner, + {0.202083333333, 0.648148148148, 0.133333333333, 0.174074074074}, + {0, 0, 1, 1}, + .90, + .10}}, + 2, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (best.located && candidate.located && (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ best.located = false; @@ -370,19 +402,22 @@ OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame){ OverworldMatchResult match_sandgem_junction(const ImageRGB32& frame){ // Building emblems contain no localized words. Ignore NPCs and door lights. auto result = match_landmarks(frame, - {{"Center emblem", - "PokemonBDSP/Autostory/SandgemJunction/Center.png", - {0.171875, 0.2, 0.058333333333, 0.092592592593}, - {0, 0, 1, 1}, - .90, - .08}, - {"Mart emblem", - "PokemonBDSP/Autostory/SandgemJunction/Mart.png", - {0.726041666667, 0.222222222222, 0.0625, 0.088888888889}, - {0, 0, 1, 1}, - .90, - .08}}, - 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Center emblem", + "PokemonBDSP/Autostory/SandgemJunction/Center.png", + {0.171875, 0.2, 0.058333333333, 0.092592592593}, + {0, 0, 1, 1}, + .90, + .08}, + {"Mart emblem", + "PokemonBDSP/Autostory/SandgemJunction/Mart.png", + {0.726041666667, 0.222222222222, 0.0625, 0.088888888889}, + {0, 0, 1, 1}, + .90, + .08}}, + 2, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (result.located || result.landmarks.size() != 2){ return result; } @@ -411,20 +446,27 @@ OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ // Morning tree shadows need a second corner reference. Each alternative // must still agree with the sign; neither can confirm the destination alone. const char* corners[] = {"PokemonBDSP/Autostory/Route202GrassExit/PathCorner.png", - "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png"}; + "PokemonBDSP/Autostory/Route202GrassExit/PathCornerMorning.png"}; OverworldMatchResult best; double best_score = -1; for (const char* corner : corners){ - auto candidate = match_landmarks( - frame, + auto candidate = match_landmarks(frame, {{"Route 202 sign", - "PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", - {0.59375, 0.194444444444, 0.091666666667, 0.059259259259}, - {0, 0, 1, 1}, - .94, - .20}, - {"Path corner", corner, {0.405208333333, 0.331481481481, 0.1375, 0.062962962963}, {0, 0, 1, 1}, .90, .08}}, - 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + "PokemonBDSP/Autostory/Route202GrassExit/RouteSign.png", + {0.59375, 0.194444444444, 0.091666666667, 0.059259259259}, + {0, 0, 1, 1}, + .94, + .20}, + {"Path corner", + corner, + {0.405208333333, 0.331481481481, 0.1375, 0.062962962963}, + {0, 0, 1, 1}, + .90, + .08}}, + 2, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (best.located && candidate.located && (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ best.located = false; @@ -444,11 +486,13 @@ OverworldMatchResult match_route202_grass_exit(const ImageRGB32& frame){ } // Lighting alternatives describe one physical landmark, not extra votes. // Select one reference per landmark before requiring the two positions to agree. -static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, const std::vector& variants, - const char* first, const char* second, - double tolerance_x = POSITION_TOLERANCE_X, - bool allow_vertical_acquisition = false, - double agreement_x = AGREEMENT_X){ +static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, + const std::vector& variants, + const char* first, + const char* second, + double tolerance_x = POSITION_TOLERANCE_X, + bool allow_vertical_acquisition = false, + double agreement_x = AGREEMENT_X){ auto candidates = match_landmarks(frame, variants, variants.size() + 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); OverworldMatchResult result; @@ -472,7 +516,7 @@ static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, const s for (const auto& candidate : candidates.landmarks){ if (candidate.name == name && candidate.accepted && candidate.score >= chosen.score - 0.02 && (std::abs(candidate.dx - chosen.dx) > AGREEMENT_X || - std::abs(candidate.dy - chosen.dy) > AGREEMENT_Y)){ + std::abs(candidate.dy - chosen.dy) > AGREEMENT_Y)){ chosen.accepted = false; } } @@ -484,7 +528,7 @@ static OverworldMatchResult match_lighting_pair(const ImageRGB32& frame, const s result.dx = (a.dx + b.dx) / 2; result.dy = (a.dy + b.dy) / 2; result.at_target = result.located && std::abs(a.dx - b.dx) <= AGREEMENT_X && std::abs(result.dx) <= tolerance_x && - std::abs(result.dy) <= POSITION_TOLERANCE_Y; + std::abs(result.dy) <= POSITION_TOLERANCE_Y; const double spacing_difference = a.dx - b.dx; // At the street approach height, perspective can change column spacing. // Agreeing vertical offsets may guide an upward correction, but cannot @@ -510,55 +554,55 @@ OverworldMatchResult match_eterna_center_entrance(const ImageRGB32& frame){ for (int appearance = 0; appearance < 5; appearance++){ for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs = {{"Center Pokeball", - "PokemonBDSP/Autostory/EternaCenter/BallTarget.png", - {904.0 / 1920, 144.0 / 1080, 112.0 / 1920, 104.0 / 1080}, - {0, 0, 1, .65}, - .93, - .20}, - {"Right sign", - "PokemonBDSP/Autostory/EternaCenter/RightSignTarget.png", - {1762.0 / 1920, 304.0 / 1080, 128.0 / 1920, 138.0 / 1080}, - {0, 0, 1, .85}, - .93, - .20}, - {"Lower left bush", - "PokemonBDSP/Autostory/EternaCenter/BushTarget.png", - {248.0 / 1920, 854.0 / 1080, 180.0 / 1920, 146.0 / 1080}, - {0, .35, 1, .65}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaCenter/BallTarget.png", + {904.0 / 1920, 144.0 / 1080, 112.0 / 1920, 104.0 / 1080}, + {0, 0, 1, .65}, + .93, + .20}, + {"Right sign", + "PokemonBDSP/Autostory/EternaCenter/RightSignTarget.png", + {1762.0 / 1920, 304.0 / 1080, 128.0 / 1920, 138.0 / 1080}, + {0, 0, 1, .85}, + .93, + .20}, + {"Lower left bush", + "PokemonBDSP/Autostory/EternaCenter/BushTarget.png", + {248.0 / 1920, 854.0 / 1080, 180.0 / 1920, 146.0 / 1080}, + {0, .35, 1, .65}, + .93, + .20}}; if (appearance == 1 || appearance == 2){ configs = {{"Center Pokeball", - appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/BallEvening1.png" - : "PokemonBDSP/Autostory/EternaCenter/BallEvening2.png", - {920.0 / 1920, 154.0 / 1080, 82.0 / 1920, 84.0 / 1080}, - {0, 0, 1, .65}, - .93, - .20}, - {"Right sign", - appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/RightSignEvening1.png" - : "PokemonBDSP/Autostory/EternaCenter/RightSignEvening2.png", - {1770.0 / 1920, 310.0 / 1080, 116.0 / 1920, 63.0 / 1080}, - {0, 0, 1, .85}, - .93, - .20}, - {"Lower left bush", - appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/BushEvening1.png" - : "PokemonBDSP/Autostory/EternaCenter/BushEvening2.png", - {248.0 / 1920, 932.0 / 1080, 180.0 / 1920, 68.0 / 1080}, - {0, .35, 1, .65}, - .93, - .20}}; + appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/BallEvening1.png" + : "PokemonBDSP/Autostory/EternaCenter/BallEvening2.png", + {920.0 / 1920, 154.0 / 1080, 82.0 / 1920, 84.0 / 1080}, + {0, 0, 1, .65}, + .93, + .20}, + {"Right sign", + appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/RightSignEvening1.png" + : "PokemonBDSP/Autostory/EternaCenter/RightSignEvening2.png", + {1770.0 / 1920, 310.0 / 1080, 116.0 / 1920, 63.0 / 1080}, + {0, 0, 1, .85}, + .93, + .20}, + {"Lower left bush", + appearance == 1 ? "PokemonBDSP/Autostory/EternaCenter/BushEvening1.png" + : "PokemonBDSP/Autostory/EternaCenter/BushEvening2.png", + {248.0 / 1920, 932.0 / 1080, 180.0 / 1920, 68.0 / 1080}, + {0, .35, 1, .65}, + .93, + .20}}; } if (appearance >= 3){ // Recorded daylight changes the bush/planter contrast. These // crops retain the original doorway geometry and all three votes. configs[0].path = appearance == 3 ? "PokemonBDSP/Autostory/EternaCenter/BallDaytime1.png" - : "PokemonBDSP/Autostory/EternaCenter/BallDaytime2.png"; + : "PokemonBDSP/Autostory/EternaCenter/BallDaytime2.png"; configs[1].path = appearance == 3 ? "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime1.png" - : "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime2.png"; + : "PokemonBDSP/Autostory/EternaCenter/RightSignDaytime2.png"; configs[2].path = appearance == 3 ? "PokemonBDSP/Autostory/EternaCenter/BushDaytime1.png" - : "PokemonBDSP/Autostory/EternaCenter/BushDaytime2.png"; + : "PokemonBDSP/Autostory/EternaCenter/BushDaytime2.png"; } for (auto& config : configs){ const double w = config.target.width, h = config.target.height; @@ -609,34 +653,34 @@ OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame){ const char* references[][2] = { {"PokemonBDSP/Autostory/Route208Stairs/UpperRock.png", "PokemonBDSP/Autostory/Route208Stairs/LowerRocks.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour0Frame1Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour4Frame1Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour17Frame1Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour11Frame3Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour15Frame1Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour11Frame5Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Hour15Frame3Lower.png"}, }; for (const auto& reference : references){ for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs = {{"Stair-top upper rock", - reference[0], - {170.0 / 1920, 350.0 / 1080, 120.0 / 1920, 120.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}, - {"Stair-top lower rock pair", - reference[1], - {240.0 / 1920, 425.0 / 1080, 130.0 / 1920, 180.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}}; + reference[0], + {170.0 / 1920, 350.0 / 1080, 120.0 / 1920, 120.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}, + {"Stair-top lower rock pair", + reference[1], + {240.0 / 1920, 425.0 / 1080, 130.0 / 1920, 180.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}; for (auto& config : configs){ const double w = config.target.width, h = config.target.height; config.target.width *= scale; @@ -660,48 +704,48 @@ OverworldMatchResult match_route208_stair_top(const ImageRGB32& frame){ } const char* approaches[][2] = { {"PokemonBDSP/Autostory/Route208Stairs/Approach0507Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Approach0507Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Approach0507Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Approach0538Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Approach0538Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Approach0538Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/Approach0549Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/Approach0549Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/Approach0549Lower.png"}, // The eastern cliff pose changes rock perspective and the local shadow // pattern. These real-run appearances guide acquisition only: retain // .92 acquisition NCC/.20 uniqueness and two-rock offset agreement. // Arrival still requires the original .93 strict reference bank. {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive01Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive02Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive03Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive04Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive05Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive06Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive07Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive08Lower.png"}, {"PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Upper.png", - "PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Lower.png"}, + "PokemonBDSP/Autostory/Route208Stairs/ApproachLive09Lower.png"}, }; for (const auto& reference : approaches){ for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ std::vector configs = {{"Stair-top approach upper rock", - reference[0], - {170.0 / 1920, 350.0 / 1080, 120.0 / 1920, 120.0 / 1080}, - {0, 0, .50, .85}, - .92, - .20}, - {"Stair-top approach lower rock pair", - reference[1], - {240.0 / 1920, 425.0 / 1080, 130.0 / 1920, 180.0 / 1080}, - {0, 0, .50, .85}, - .92, - .20}}; + reference[0], + {170.0 / 1920, 350.0 / 1080, 120.0 / 1920, 120.0 / 1080}, + {0, 0, .50, .85}, + .92, + .20}, + {"Stair-top approach lower rock pair", + reference[1], + {240.0 / 1920, 425.0 / 1080, 130.0 / 1920, 180.0 / 1080}, + {0, 0, .50, .85}, + .92, + .20}}; for (auto& config : configs){ const double w = config.target.width, h = config.target.height; config.target.width *= scale; @@ -728,17 +772,17 @@ OverworldMatchResult match_route208_lower_stair_lane(const ImageRGB32& frame){ double best_score = -1; for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs = {{"Lower stair cliff corner", - "PokemonBDSP/Autostory/Route208LowerLane/CliffCorner.png", - {690.0 / 1920, 540.0 / 1080, 110.0 / 1920, 150.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}, - {"Honey tree", - "PokemonBDSP/Autostory/Route208LowerLane/HoneyTree.png", - {1510.0 / 1920, 600.0 / 1080, 100.0 / 1920, 170.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}}; + "PokemonBDSP/Autostory/Route208LowerLane/CliffCorner.png", + {690.0 / 1920, 540.0 / 1080, 110.0 / 1920, 150.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}, + {"Honey tree", + "PokemonBDSP/Autostory/Route208LowerLane/HoneyTree.png", + {1510.0 / 1920, 600.0 / 1080, 100.0 / 1920, 170.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}; for (auto& config : configs){ const double w = config.target.width, h = config.target.height; config.target.width *= scale; @@ -787,65 +831,65 @@ OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame){ static const std::array appearances[] = { {"PokemonBDSP/Autostory/Route209Repel/SignFrame.png", "PokemonBDSP/Autostory/Route209Repel/RockPair.png"}, {"PokemonBDSP/Autostory/Route209Repel/EastSignFrame.png", - "PokemonBDSP/Autostory/Route209Repel/EastRockPair.png"}, + "PokemonBDSP/Autostory/Route209Repel/EastRockPair.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H04F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H04F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H04F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H18F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H18F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H18F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F3.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F3.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F3.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H12F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H12F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F2.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F2.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F2.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F3.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F3.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F3.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F2.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F2.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F2.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F3.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F3.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F3.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H02F4.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F4.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H02F4.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H03F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H03F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H03F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H10F1.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F1.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H10F1.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H11F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H11F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H13F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H13F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H13F5.png"}, {"PokemonBDSP/Autostory/Route209Repel/SignFrame-H14F5.png", - "PokemonBDSP/Autostory/Route209Repel/RockPair-H14F5.png"}, + "PokemonBDSP/Autostory/Route209Repel/RockPair-H14F5.png"}, }; for (double scale : {1.0, .95, 1.05, .90, 1.10}){ std::vector lighting_landmarks(2); for (size_t appearance = 0; appearance < std::size(appearances); appearance++){ std::vector configs = {{"Sign frame", - "PokemonBDSP/Autostory/Route209Repel/SignFrame.png", - {1242.0 / 1920, 580.0 / 1080, 28.0 / 1920, 80.0 / 1080}, - {0, 0, 1, 1}, - .93, - .15}, - {"Rock pair", - "PokemonBDSP/Autostory/Route209Repel/RockPair.png", - {580.0 / 1920, 690.0 / 1080, 155.0 / 1920, 165.0 / 1080}, - {0, 0, 1, 1}, - .93, - .15}}; + "PokemonBDSP/Autostory/Route209Repel/SignFrame.png", + {1242.0 / 1920, 580.0 / 1080, 28.0 / 1920, 80.0 / 1080}, + {0, 0, 1, 1}, + .93, + .15}, + {"Rock pair", + "PokemonBDSP/Autostory/Route209Repel/RockPair.png", + {580.0 / 1920, 690.0 / 1080, 155.0 / 1920, 165.0 / 1080}, + {0, 0, 1, 1}, + .93, + .15}}; for (size_t i = 0; i < configs.size(); i++){ configs[i].path = appearances[appearance][i]; } @@ -872,7 +916,7 @@ OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame){ score = std::min(score, landmark.score); } candidate.at_target = appearance != 1 && candidate.located && std::abs(candidate.dx) <= 1.0 / 128 && - std::abs(candidate.dy) <= POSITION_TOLERANCE_Y; + std::abs(candidate.dy) <= POSITION_TOLERANCE_Y; // East-side appearance is acquisition-only; the marked pose owns arrival. if (appearance == 1 && std::abs(candidate.dx) <= 1.0 / 128 && std::abs(candidate.dy) <= POSITION_TOLERANCE_Y){ @@ -893,7 +937,7 @@ OverworldMatchResult match_route209_repel_point(const ImageRGB32& frame){ const auto& sign = combined.landmarks[0]; const auto& rock = combined.landmarks[1]; combined.located = sign.accepted && rock.accepted && std::abs(sign.dx - rock.dx) <= AGREEMENT_X && - std::abs(sign.dy - rock.dy) <= AGREEMENT_Y; + std::abs(sign.dy - rock.dy) <= AGREEMENT_Y; if (combined.located){ combined.dx = (sign.dx + rock.dx) / 2; combined.dy = (sign.dy + rock.dy) / 2; @@ -909,37 +953,37 @@ OverworldMatchResult match_route208_hannah_departure(const ImageRGB32& frame){ for (int appearance : {0, 865, 930, 965, 978}){ for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs = {{"Field stair foot", - "PokemonBDSP/Autostory/Route208Hannah/StairFoot.png", - {225.0 / 1920, 275.0 / 1080, 250.0 / 1920, 110.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Field sign frame", - "PokemonBDSP/Autostory/Route208Hannah/TreeSign.png", - {1000.0 / 1920, 125.0 / 1080, 100.0 / 1920, 80.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}, - {"Field house sign frame", - "PokemonBDSP/Autostory/Route208Hannah/HouseSign.png", - {1440.0 / 1920, 110.0 / 1080, 110.0 / 1920, 110.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}}; + "PokemonBDSP/Autostory/Route208Hannah/StairFoot.png", + {225.0 / 1920, 275.0 / 1080, 250.0 / 1920, 110.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Field sign frame", + "PokemonBDSP/Autostory/Route208Hannah/TreeSign.png", + {1000.0 / 1920, 125.0 / 1080, 100.0 / 1920, 80.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}, + {"Field house sign frame", + "PokemonBDSP/Autostory/Route208Hannah/HouseSign.png", + {1440.0 / 1920, 110.0 / 1080, 110.0 / 1920, 110.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}; if (appearance){ // Recorded dodges stand farther east, with the stairs clipped. // These agreeing signs guide acquisition only; the successful // departure reference above alone may confirm arrival. configs.erase(configs.begin()); - configs[0].path = appearance == 865 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign865.png" - : appearance == 930 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign930.png" - : appearance == 965 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign965.png" - : "PokemonBDSP/Autostory/Route208Hannah/FieldSign978.png"; - configs[1].path = appearance == 865 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign865.png" - : appearance == 930 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign930.png" - : appearance == 965 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign965.png" - : "PokemonBDSP/Autostory/Route208Hannah/HouseSign978.png"; + configs[0].path = appearance == 865 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign865.png" + : appearance == 930 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign930.png" + : appearance == 965 ? "PokemonBDSP/Autostory/Route208Hannah/FieldSign965.png" + : "PokemonBDSP/Autostory/Route208Hannah/FieldSign978.png"; + configs[1].path = appearance == 865 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign865.png" + : appearance == 930 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign930.png" + : appearance == 965 ? "PokemonBDSP/Autostory/Route208Hannah/HouseSign965.png" + : "PokemonBDSP/Autostory/Route208Hannah/HouseSign978.png"; } for (auto& config : configs){ const double w = config.target.width, h = config.target.height; @@ -982,38 +1026,50 @@ OverworldMatchResult match_hearthome_center_entrance(const ImageRGB32& frame){ const char* awning; double ball_width, ball_height; }; - const Reference references[] = { - {"PokemonBDSP/Autostory/HearthomeCenter/Lighting00Ball.png", - "PokemonBDSP/Autostory/HearthomeCenter/Lighting00Awning.png", 144.0 / 1920, 128.0 / 1080}, + const Reference references[] = {{"PokemonBDSP/Autostory/HearthomeCenter/Lighting00Ball.png", + "PokemonBDSP/Autostory/HearthomeCenter/Lighting00Awning.png", + 144.0 / 1920, + 128.0 / 1080}, {"PokemonBDSP/Autostory/HearthomeCenter/Lighting01Ball.png", - "PokemonBDSP/Autostory/HearthomeCenter/Lighting01Awning.png", 144.0 / 1920, 128.0 / 1080}, + "PokemonBDSP/Autostory/HearthomeCenter/Lighting01Awning.png", + 144.0 / 1920, + 128.0 / 1080}, {"PokemonBDSP/Autostory/HearthomeCenter/Lighting02Ball.png", - "PokemonBDSP/Autostory/HearthomeCenter/Lighting02Awning.png", 144.0 / 1920, 128.0 / 1080}, + "PokemonBDSP/Autostory/HearthomeCenter/Lighting02Awning.png", + 144.0 / 1920, + 128.0 / 1080}, {"PokemonBDSP/Autostory/HearthomeCenter/Lighting03Ball.png", - "PokemonBDSP/Autostory/HearthomeCenter/Lighting03Awning.png", 144.0 / 1920, 128.0 / 1080}, + "PokemonBDSP/Autostory/HearthomeCenter/Lighting03Awning.png", + 144.0 / 1920, + 128.0 / 1080}, {"PokemonBDSP/Autostory/HearthomeCenter/Lighting04Ball.png", - "PokemonBDSP/Autostory/HearthomeCenter/Lighting04Awning.png", 112.0 / 1920, 108.0 / 1080}}; + "PokemonBDSP/Autostory/HearthomeCenter/Lighting04Awning.png", + 112.0 / 1920, + 108.0 / 1080}}; std::vector variants; for (const auto& reference : references){ for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ const double w = reference.ball_width * scale, h = reference.ball_height * scale; const double aw = 272.0 / 1920 * scale, ah = 80.0 / 1080 * scale; variants.push_back({"Hearthome Center roof ball", - reference.ball, - {972.0 / 1920 - w / 2, 208.0 / 1080 - h / 2, w, h}, - {0, 0, 1, .65}, - .95, - .20}); + reference.ball, + {972.0 / 1920 - w / 2, 208.0 / 1080 - h / 2, w, h}, + {0, 0, 1, .65}, + .95, + .20}); variants.push_back({"Hearthome Center orange awning", - reference.awning, - {968.0 / 1920 - aw / 2, 308.0 / 1080 - ah / 2, aw, ah}, - {0, 0, 1, .65}, - .95, - .20}); + reference.awning, + {968.0 / 1920 - aw / 2, 308.0 / 1080 - ah / 2, aw, ah}, + {0, 0, 1, .65}, + .95, + .20}); } } - return match_lighting_pair(frame, variants, "Hearthome Center roof ball", "Hearthome Center orange awning", - 1.0 / 128); + return match_lighting_pair(frame, + variants, + "Hearthome Center roof ball", + "Hearthome Center orange awning", + 1.0 / 128); } OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ // Three observed entry-camera angles. Perspective changes the machine @@ -1024,17 +1080,17 @@ OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ ImageFloatBox a, b; }; const View views[] = {{"InteriorMachine.png", - "InteriorRegister.png", - {660.0 / 1920, 160.0 / 1080, 220.0 / 1920, 185.0 / 1080}, - {1150.0 / 1920, 155.0 / 1080, 140.0 / 1920, 175.0 / 1080}}, - {"EntryRightMachine.png", - "EntryRightRegister.png", - {390.0 / 1920, 160.0 / 1080, 240.0 / 1920, 185.0 / 1080}, - {905.0 / 1920, 155.0 / 1080, 130.0 / 1920, 175.0 / 1080}}, - {"EntryLeftMachine.png", - "EntryLeftRegister.png", - {1045.0 / 1920, 160.0 / 1080, 240.0 / 1920, 185.0 / 1080}, - {1550.0 / 1920, 220.0 / 1080, 155.0 / 1920, 110.0 / 1080}}}; + "InteriorRegister.png", + {660.0 / 1920, 160.0 / 1080, 220.0 / 1920, 185.0 / 1080}, + {1150.0 / 1920, 155.0 / 1080, 140.0 / 1920, 175.0 / 1080}}, + {"EntryRightMachine.png", + "EntryRightRegister.png", + {390.0 / 1920, 160.0 / 1080, 240.0 / 1920, 185.0 / 1080}, + {905.0 / 1920, 155.0 / 1080, 130.0 / 1920, 175.0 / 1080}}, + {"EntryLeftMachine.png", + "EntryLeftRegister.png", + {1045.0 / 1920, 160.0 / 1080, 240.0 / 1920, 185.0 / 1080}, + {1550.0 / 1920, 220.0 / 1080, 155.0 / 1920, 110.0 / 1080}}}; OverworldMatchResult best; double best_score = -1; for (const auto& view : views){ @@ -1051,9 +1107,12 @@ OverworldMatchResult match_hearthome_center_interior(const ImageRGB32& frame){ b.width *= scale; b.height *= scale; auto candidate = match_landmarks(frame, - {{"Center healing machine", machine.c_str(), a, {0, 0, 1, .8}, .93, .20}, - {"Center register", desk.c_str(), b, {0, 0, 1, .8}, .93, .20}}, - 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Center healing machine", machine.c_str(), a, {0, 0, 1, .8}, .93, .20}, + {"Center register", desk.c_str(), b, {0, 0, 1, .8}, .93, .20}}, + 2, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); double score = 1; for (const auto& anchor : candidate.landmarks){ score = std::min(score, anchor.score); @@ -1070,54 +1129,57 @@ OverworldMatchResult match_hearthome_contest_fantina(const ImageRGB32& frame){ // Static sign rim above localized text and roof emblem; Fantina can move. // Hourly captures vary the sign illumination. The roof remains reliable. const char* signs[] = {"PokemonBDSP/Autostory/HearthomeContest/SignRim.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting00Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting01Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting02Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting03Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting04Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting05Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting06Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting07Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting08Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting09Sign.png", - "PokemonBDSP/Autostory/HearthomeContest/RimLighting10Sign.png"}; + "PokemonBDSP/Autostory/HearthomeContest/RimLighting00Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting01Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting02Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting03Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting04Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting05Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting06Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting07Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting08Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting09Sign.png", + "PokemonBDSP/Autostory/HearthomeContest/RimLighting10Sign.png"}; std::vector variants; for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ const double sw = 162.0 / 1920 * scale, sh = 62.0 / 1080 * scale; const double rw = 136.0 / 1920 * scale, rh = 98.0 / 1080 * scale; for (const char* sign : signs){ variants.push_back({"Contest Hall sign frame", - sign, - {595.0 / 1920 - sw / 2, 467.0 / 1080 - sh / 2, sw, sh}, - {0, .15, .75, .65}, - .93, - .15}); + sign, + {595.0 / 1920 - sw / 2, 467.0 / 1080 - sh / 2, sw, sh}, + {0, .15, .75, .65}, + .93, + .15}); } variants.push_back({"Contest Hall roof emblem", - "PokemonBDSP/Autostory/HearthomeContest/RoofEmblem.png", - {940.0 / 1920 - rw / 2, 175.0 / 1080 - rh / 2, rw, rh}, - {.1, 0, .8, .65}, - .93, - .15}); + "PokemonBDSP/Autostory/HearthomeContest/RoofEmblem.png", + {940.0 / 1920 - rw / 2, 175.0 / 1080 - rh / 2, rw, rh}, + {.1, 0, .8, .65}, + .93, + .15}); } return match_lighting_pair(frame, variants, "Contest Hall sign frame", "Contest Hall roof emblem", 1.0 / 128); } // Fixed city landmarks preserve the recorded passage and western stair lane. OverworldMatchResult match_veilstone_city_passage(const ImageRGB32& frame){ auto fine = match_landmarks(frame, - {{"Passage house", - "PokemonBDSP/Autostory/VeilstoneDawn/PassageHouse.png", - {42.0 / 960, 222.0 / 540, 210.0 / 960, 110.0 / 540}, - {0, 0, .55, 1}, - .93, - .12}, - {"Game Corner facade", - "PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", - {592.0 / 960, 227.0 / 540, 230.0 / 960, 110.0 / 540}, - {.45, 0, .55, 1}, - .93, - .12}}, - 2, 1.0 / 128, 1.0 / 108, true); + {{"Passage house", + "PokemonBDSP/Autostory/VeilstoneDawn/PassageHouse.png", + {42.0 / 960, 222.0 / 540, 210.0 / 960, 110.0 / 540}, + {0, 0, .55, 1}, + .93, + .12}, + {"Game Corner facade", + "PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", + {592.0 / 960, 227.0 / 540, 230.0 / 960, 110.0 / 540}, + {.45, 0, .55, 1}, + .93, + .12}}, + 2, + 1.0 / 128, + 1.0 / 108, + true); if (fine.located){ return fine; } @@ -1130,27 +1192,27 @@ OverworldMatchResult match_veilstone_city_passage(const ImageRGB32& frame){ const double hw = 95.0 / 960 * scale, hh = 80.0 / 540 * scale; const double gw = 230.0 / 960 * scale, gh = 110.0 / 540 * scale; variants.push_back({"House window", - "PokemonBDSP/Autostory/VeilstoneDawn/PassageHouseWindow.png", - {(165.0 + 95.0 / 2 - 8) / 960 - hw / 2, (250.0 + 80.0 / 2 + 2) / 540 - hh / 2, hw, hh}, - {0, 0, .55, 1}, - .94, - .15}); + "PokemonBDSP/Autostory/VeilstoneDawn/PassageHouseWindow.png", + {(165.0 + 95.0 / 2 - 8) / 960 - hw / 2, (250.0 + 80.0 / 2 + 2) / 540 - hh / 2, hw, hh}, + {0, 0, .55, 1}, + .94, + .15}); variants.push_back({"Game Corner facade", - "PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", - {(600.0 + 230.0 / 2 - 8) / 960 - gw / 2, (225.0 + 110.0 / 2 + 2) / 540 - gh / 2, gw, gh}, - {.25, 0, .75, 1}, - .93, - .12}); + "PokemonBDSP/Autostory/VeilstoneDawn/PassageGameCorner.png", + {(600.0 + 230.0 / 2 - 8) / 960 - gw / 2, (225.0 + 110.0 / 2 + 2) / 540 - gh / 2, gw, gh}, + {.25, 0, .75, 1}, + .93, + .12}); } // At the east-side roof obstruction, perspective changes the facade. // Calibrate this acquisition-only view to the simultaneously visible house // window. It guides west off that roof corner; fine arrival stays mandatory. variants.push_back({"Game Corner facade", - "PokemonBDSP/Autostory/VeilstoneDawn/GameCornerEastApproach.png", - {644.0 / 960, 228.0 / 540, 240.0 / 960, 100.0 / 540}, - {.25, 0, .75, 1}, - .93, - .12}); + "PokemonBDSP/Autostory/VeilstoneDawn/GameCornerEastApproach.png", + {644.0 / 960, 228.0 / 540, 240.0 / 960, 100.0 / 540}, + {.25, 0, .75, 1}, + .93, + .12}); auto approach = match_lighting_pair(frame, variants, "House window", "Game Corner facade", 1.0 / 128); approach.coarse = true; approach.at_target = false; @@ -1160,47 +1222,50 @@ OverworldMatchResult match_veilstone_dawn_stairs(const ImageRGB32& frame){ // The residential terrace to the east has similar stairs. Require its // distinct west corner together with the western staircase, not NPCs. return match_landmarks(frame, - {{"Western stairs", - "PokemonBDSP/Autostory/VeilstoneDawn/GymStairs.png", - {445.0 / 960, 40.0 / 540, 165.0 / 960, 55.0 / 540}, - {.10, 0, .65, .70}, - .93, - .12}, - {"East terrace corner", - "PokemonBDSP/Autostory/VeilstoneDawn/EastTerraceCorner.png", - {620.0 / 960, 155.0 / 540, 200.0 / 960, 110.0 / 540}, - {.35, 0, .65, .85}, - .93, - .12}}, - 2, 1.0 / 128, 1.0 / 108, true); + {{"Western stairs", + "PokemonBDSP/Autostory/VeilstoneDawn/GymStairs.png", + {445.0 / 960, 40.0 / 540, 165.0 / 960, 55.0 / 540}, + {.10, 0, .65, .70}, + .93, + .12}, + {"East terrace corner", + "PokemonBDSP/Autostory/VeilstoneDawn/EastTerraceCorner.png", + {620.0 / 960, 155.0 / 540, 200.0 / 960, 110.0 / 540}, + {.35, 0, .65, .85}, + .93, + .12}}, + 2, + 1.0 / 128, + 1.0 / 108, + true); } OverworldMatchResult match_hearthome_gym_entrance(const ImageRGB32& frame){ // Wider fixed facade distinguishes the window bays at night. Real hourly // references cover the illuminated windows; cursor, NPCs and text stay out. const char* facades[] = {"PokemonBDSP/Autostory/HearthomeGym/LeftFacade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting00Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting01Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting02Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting03Facade.png", - "PokemonBDSP/Autostory/HearthomeGym/Lighting04Facade.png"}; + "PokemonBDSP/Autostory/HearthomeGym/Lighting00Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting01Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting02Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting03Facade.png", + "PokemonBDSP/Autostory/HearthomeGym/Lighting04Facade.png"}; std::vector variants; for (double scale : {.90, .925, .95, .975, 1.0, 1.025, 1.05, 1.075, 1.10}){ const double aw = 160.0 / 1920 * scale, ah = 84.0 / 1080 * scale; const double fw = 218.0 / 1920 * scale, fh = 222.0 / 1080 * scale; variants.push_back({"Hearthome Gym doorway arch", - "PokemonBDSP/Autostory/HearthomeGym/DoorArch.png", - {966.0 / 1920 - aw / 2, 236.0 / 1080 - ah / 2, aw, ah}, - {.15, 0, .75, .65}, - .93, - .20}); + "PokemonBDSP/Autostory/HearthomeGym/DoorArch.png", + {966.0 / 1920 - aw / 2, 236.0 / 1080 - ah / 2, aw, ah}, + {.15, 0, .75, .65}, + .93, + .20}); for (const char* facade : facades){ variants.push_back({"Hearthome Gym left facade", - facade, - {719.0 / 1920 - fw / 2, 315.0 / 1080 - fh / 2, fw, fh}, - {0, 0, .55, .65}, - .93, - .20}); + facade, + {719.0 / 1920 - fw / 2, 315.0 / 1080 - fh / 2, fw, fh}, + {0, 0, .55, .65}, + .93, + .20}); } } return match_lighting_pair(frame, variants, "Hearthome Gym doorway arch", "Hearthome Gym left facade", 1.0 / 128); @@ -1214,26 +1279,27 @@ OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ for (int appearance : {19, 16}){ for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs = {{"Left gym sign", - appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" - : "PokemonBDSP/Autostory/EternaGym/LeftSign16.png", - {458.0 / 1920, 358.0 / 1080, 140.0 / 1920, 150.0 / 1080}, - {0, 0, .65, .85}, - .93, - .20}, - {"Gym Pokeball", - appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" - : "PokemonBDSP/Autostory/EternaGym/Ball16.png", - {880.0 / 1920, 264.0 / 1080, 150.0 / 1920, 80.0 / 1080}, - {.15, 0, .7, .70}, - .93, - .20}, - {"Right bike post", - appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/BikePost19.png" - : "PokemonBDSP/Autostory/EternaGym/BikePost16.png", - {1468.0 / 1920, 460.0 / 1080, 130.0 / 1920, 178.0 / 1080}, - {.35, .15, .65, .80}, - .93, - .20}}; + appearance == 19 + ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" + : "PokemonBDSP/Autostory/EternaGym/LeftSign16.png", + {458.0 / 1920, 358.0 / 1080, 140.0 / 1920, 150.0 / 1080}, + {0, 0, .65, .85}, + .93, + .20}, + {"Gym Pokeball", + appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" + : "PokemonBDSP/Autostory/EternaGym/Ball16.png", + {880.0 / 1920, 264.0 / 1080, 150.0 / 1920, 80.0 / 1080}, + {.15, 0, .7, .70}, + .93, + .20}, + {"Right bike post", + appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/BikePost19.png" + : "PokemonBDSP/Autostory/EternaGym/BikePost16.png", + {1468.0 / 1920, 460.0 / 1080, 130.0 / 1920, 178.0 / 1080}, + {.35, .15, .65, .80}, + .93, + .20}}; for (auto& config : configs){ const double w = config.target.width, h = config.target.height; config.target.width *= scale; @@ -1253,8 +1319,9 @@ OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ score = std::min(score, landmark.score); } } - if (candidate.located && (!best.located || (best.coarse && !candidate.coarse) || - (best.coarse == candidate.coarse && score > best_score))){ + if (candidate.located && + (!best.located || (best.coarse && !candidate.coarse) || + (best.coarse == candidate.coarse && score > best_score))){ best = candidate; best_score = score; }else if (best.landmarks.empty()){ @@ -1273,24 +1340,24 @@ OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ for (double scale : {.85, .90, 1.0, 1.10, 1.15}){ std::vector configs = { {"Left gym sign", - appearance == 680 ? "PokemonBDSP/Autostory/EternaGym/LeftSignApproach.png" - : appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" - : appearance == 333 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWest.png" - : appearance == 306 ? "PokemonBDSP/Autostory/EternaGym/LeftSignSouthShadow.png" - : appearance == 339 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWestShadow.png" - : "PokemonBDSP/Autostory/EternaGym/LeftSignSouth.png", - {458.0 / 1920, 358.0 / 1080, 140.0 / 1920, 150.0 / 1080}, - {0, 0, 1, .95}, - .93, - .20}, + appearance == 680 ? "PokemonBDSP/Autostory/EternaGym/LeftSignApproach.png" + : appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/LeftSign19.png" + : appearance == 333 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWest.png" + : appearance == 306 ? "PokemonBDSP/Autostory/EternaGym/LeftSignSouthShadow.png" + : appearance == 339 ? "PokemonBDSP/Autostory/EternaGym/LeftSignWestShadow.png" + : "PokemonBDSP/Autostory/EternaGym/LeftSignSouth.png", + {458.0 / 1920, 358.0 / 1080, 140.0 / 1920, 150.0 / 1080}, + {0, 0, 1, .95}, + .93, + .20}, {"Gym Pokeball", - appearance == 680 ? "PokemonBDSP/Autostory/EternaGym/BallApproach.png" - : appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" - : "PokemonBDSP/Autostory/EternaGym/BallWest.png", - {880.0 / 1920, 264.0 / 1080, 150.0 / 1920, 80.0 / 1080}, - {0, 0, 1, .90}, - .93, - .20}}; + appearance == 680 ? "PokemonBDSP/Autostory/EternaGym/BallApproach.png" + : appearance == 19 ? "PokemonBDSP/Autostory/EternaGym/Ball19.png" + : "PokemonBDSP/Autostory/EternaGym/BallWest.png", + {880.0 / 1920, 264.0 / 1080, 150.0 / 1920, 80.0 / 1080}, + {0, 0, 1, .90}, + .93, + .20}}; if (appearance == 680){ configs[0].target.width = 148.0 / 1920; configs[0].target.height = 158.0 / 1080; @@ -1350,21 +1417,21 @@ OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs = { {"Gym approach house door", - perspective == 708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorFarWest.png" - : perspective == 734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorWest.png" - : "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoor.png", - {548.0 / 1920 - .55, 448.0 / 1080, 128.0 / 1920, 156.0 / 1080}, - {0, .15, .75, .70}, - .93, - .20}, + perspective == 708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorFarWest.png" + : perspective == 734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoorWest.png" + : "PokemonBDSP/Autostory/EternaGym/ApproachHouseDoor.png", + {548.0 / 1920 - .55, 448.0 / 1080, 128.0 / 1920, 156.0 / 1080}, + {0, .15, .75, .70}, + .93, + .20}, {"Gym approach house window", - perspective == 708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowFarWest.png" - : perspective == 734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowWest.png" - : "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindow.png", - {718.0 / 1920 - .55, 462.0 / 1080, 132.0 / 1920, 112.0 / 1080}, - {0, .15, .85, .70}, - .93, - .20}}; + perspective == 708 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowFarWest.png" + : perspective == 734 ? "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindowWest.png" + : "PokemonBDSP/Autostory/EternaGym/ApproachHouseWindow.png", + {718.0 / 1920 - .55, 462.0 / 1080, 132.0 / 1920, 112.0 / 1080}, + {0, .15, .85, .70}, + .93, + .20}}; if (perspective == 734){ configs[0].target = {338.0 / 1920 - .55, 450.0 / 1080, 132.0 / 1920, 150.0 / 1080}; configs[1].target = {496.0 / 1920 - .55, 464.0 / 1080, 122.0 / 1920, 102.0 / 1080}; @@ -1392,8 +1459,10 @@ OverworldMatchResult match_eterna_gym_entrance(const ImageRGB32& frame){ } return best; } -double overworld_entry_confidence(const OverworldMatchResult& result, double tolerance_x, double tolerance_y, - double minimum_margin){ +double overworld_entry_confidence(const OverworldMatchResult& result, + double tolerance_x, + double tolerance_y, + double minimum_margin){ double confidence = 0; for (size_t i = 0; i < result.landmarks.size(); i++){ const auto& a = result.landmarks[i]; @@ -1418,23 +1487,23 @@ OverworldMatchResult match_galactic_hq_jupiter(const ImageRGB32& frame){ // Confirm the table-facing chair sides, adjacent table edges, and stairs. // Exclude the NPC-facing chair edges where Buneary can overlap the reference. const std::vector configs{{"LeftChairs", - "PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairTableEdge.png", - {300.0 / 1920, 780.0 / 1080, 156.0 / 1920, 206.0 / 1080}, - {0, .40, .45, .60}, - .93, - .20}, - {"RightChairs", - "PokemonBDSP/Autostory/GalacticHQ/AboveRightChairTableEdge.png", - {1286.0 / 1920, 780.0 / 1080, 154.0 / 1920, 206.0 / 1080}, - {.50, .40, .50, .60}, - .93, - .20}, - {"StairBoundary", - "PokemonBDSP/Autostory/GalacticHQ/AboveStairs.png", - {424.0 / 1920, 310.0 / 1080, 214.0 / 1920, 198.0 / 1080}, - {0, 0, 1, .85}, - .93, - .20}}; + "PokemonBDSP/Autostory/GalacticHQ/AboveLeftChairTableEdge.png", + {300.0 / 1920, 780.0 / 1080, 156.0 / 1920, 206.0 / 1080}, + {0, .40, .45, .60}, + .93, + .20}, + {"RightChairs", + "PokemonBDSP/Autostory/GalacticHQ/AboveRightChairTableEdge.png", + {1286.0 / 1920, 780.0 / 1080, 154.0 / 1920, 206.0 / 1080}, + {.50, .40, .50, .60}, + .93, + .20}, + {"StairBoundary", + "PokemonBDSP/Autostory/GalacticHQ/AboveStairs.png", + {424.0 / 1920, 310.0 / 1080, 214.0 / 1920, 198.0 / 1080}, + {0, 0, 1, .85}, + .93, + .20}}; auto result = match_landmarks(frame, configs, 3, 1.0 / 128, POSITION_TOLERANCE_Y, true); if (!result.located){ // Two agreeing points guide the approach; all three still own arrival. @@ -1480,21 +1549,23 @@ OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame){ // The stronger emblem needs a corroborating sign, with a lower identity // gate for the smaller sign face. Both still must agree on the same pose. auto result = match_lighting_pair(frame, - {{"HQ sign", - "PokemonBDSP/Autostory/GalacticHQ/CutSignStructure.png", - {810.0 / 1920, 500.0 / 1080, 80.0 / 1920, 58.0 / 1080}, - {0, .25, 1, .45}, - .85, - .10, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Center Pokeball", - "PokemonBDSP/Autostory/GalacticHQ/CutCenterBallStructure.png", - {1048.0 / 1920, 1004.0 / 1080, 136.0 / 1920, 66.0 / 1080}, - {0, .65, 1, .35}, - .93, - .20, - LandmarkFeatures::BLUE_FRACTION}}, - "HQ sign", "Center Pokeball", 1.0 / 128); + {{"HQ sign", + "PokemonBDSP/Autostory/GalacticHQ/CutSignStructure.png", + {810.0 / 1920, 500.0 / 1080, 80.0 / 1920, 58.0 / 1080}, + {0, .25, 1, .45}, + .85, + .10, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Center Pokeball", + "PokemonBDSP/Autostory/GalacticHQ/CutCenterBallStructure.png", + {1048.0 / 1920, 1004.0 / 1080, 136.0 / 1920, 66.0 / 1080}, + {0, .65, 1, .35}, + .93, + .20, + LandmarkFeatures::BLUE_FRACTION}}, + "HQ sign", + "Center Pokeball", + 1.0 / 128); if (!result.located && result.landmarks.size() == 2){ const auto& sign = result.landmarks[0]; const auto& ball = result.landmarks[1]; @@ -1519,18 +1590,19 @@ OverworldMatchResult match_galactic_hq_cut(const ImageRGB32& frame){ } OverworldMatchResult match_eterna_cycle_shop_entrance(const ImageRGB32& frame){ - const std::vector configs{{"Shop sign", - "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_entrance-0.png", - {555.0 / 1920, 350.0 / 1080, 100.0 / 1920, 120.0 / 1080}, - {0, 0.1, 0.65, 0.75}, - .93, - .20}, - {"Shop roof braces", - "PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png", - {680.0 / 1920, 145.0 / 1080, 470.0 / 1920, 36.0 / 1080}, - {0, 0, .95, .75}, - .93, - .20}}; + const std::vector configs{ + {"Shop sign", + "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_entrance-0.png", + {555.0 / 1920, 350.0 / 1080, 100.0 / 1920, 120.0 / 1080}, + {0, 0.1, 0.65, 0.75}, + .93, + .20}, + {"Shop roof braces", + "PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png", + {680.0 / 1920, 145.0 / 1080, 470.0 / 1920, 36.0 / 1080}, + {0, 0, .95, .75}, + .93, + .20}}; return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } @@ -1538,33 +1610,34 @@ OverworldMatchResult match_eterna_cycle_shop_interior(const ImageRGB32& frame){ // Entry mat view: fixed wall tools and register, clear of NPCs/Pokemon. // This identifies the room; it is not the counter interaction position. const std::vector configs{{"Shop wall tools", - "PokemonBDSP/Autostory/EternaBikeShop/InteriorWallTools.png", - {920.0 / 1920, 20.0 / 1080, 350.0 / 1920, 150.0 / 1080}, - {.15, 0, .60, .55}, - .93, - .20}, - {"Shop register", - "PokemonBDSP/Autostory/EternaBikeShop/InteriorRegister.png", - {760.0 / 1920, 160.0 / 1080, 120.0 / 1920, 145.0 / 1080}, - {.20, 0, .55, .65}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaBikeShop/InteriorWallTools.png", + {920.0 / 1920, 20.0 / 1080, 350.0 / 1920, 150.0 / 1080}, + {.15, 0, .60, .55}, + .93, + .20}, + {"Shop register", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorRegister.png", + {760.0 / 1920, 160.0 / 1080, 120.0 / 1920, 145.0 / 1080}, + {.20, 0, .55, .65}, + .93, + .20}}; auto result = match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); if (result.located){ return result; } - const std::vector north_configs{{"Shop wall tools", - "PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthWallTools.png", - {928.0 / 1920, 84.0 / 1080, 350.0 / 1920, 150.0 / 1080}, - {.15, 0, .60, .55}, - .93, - .20}, - {"Shop register", - "PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthRegister.png", - {754.0 / 1920, 230.0 / 1080, 120.0 / 1920, 145.0 / 1080}, - {.20, 0, .55, .65}, - .93, - .20}}; + const std::vector north_configs{ + {"Shop wall tools", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthWallTools.png", + {928.0 / 1920, 84.0 / 1080, 350.0 / 1920, 150.0 / 1080}, + {.15, 0, .60, .55}, + .93, + .20}, + {"Shop register", + "PokemonBDSP/Autostory/EternaBikeShop/InteriorNorthRegister.png", + {754.0 / 1920, 230.0 / 1080, 120.0 / 1920, 145.0 / 1080}, + {.20, 0, .55, .65}, + .93, + .20}}; return match_landmarks(frame, north_configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } @@ -1572,17 +1645,17 @@ OverworldMatchResult match_eterna_cycle_shop_reward(const ImageRGB32& frame){ // Reward returns the player outside on the bicycle, at a separate pose. // Reuse the stable roof braces; the nearby post changes appearance/shading. const std::vector configs{{"Shop sign", - "PokemonBDSP/Autostory/EternaBikeShop/reward-sign.png", - {580.0 / 1920, 330.0 / 1080, 100.0 / 1920, 120.0 / 1080}, - {0, .1, .65, .75}, - .93, - .20}, - {"Shop roof braces", - "PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png", - {706.0 / 1920, 128.0 / 1080, 470.0 / 1920, 36.0 / 1080}, - {0, 0, .95, .75}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaBikeShop/reward-sign.png", + {580.0 / 1920, 330.0 / 1080, 100.0 / 1920, 120.0 / 1080}, + {0, .1, .65, .75}, + .93, + .20}, + {"Shop roof braces", + "PokemonBDSP/Autostory/EternaBikeShop/ShopRoofBraces.png", + {706.0 / 1920, 128.0 / 1080, 470.0 / 1920, 36.0 / 1080}, + {0, 0, .95, .75}, + .93, + .20}}; return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } @@ -1590,34 +1663,35 @@ OverworldMatchResult match_eterna_bike_riding_card(const ImageRGB32& frame){ // Fixed UI card: require both the blue illustration and B Close prompt. // Small search boxes tolerate capture offsets without matching other menus. const std::vector configs{{"Blue Bike illustration", - "PokemonBDSP/Autostory/EternaBikeShop/BikeRidingBlueFeature.png", - {104.0 / 1920, 314.0 / 1080, 520.0 / 1920, 450.0 / 1080}, - {.035, .26, .32, .48}, - .93, - .20, - LandmarkFeatures::BLUE_FRACTION}, - {"B Close prompt", - "PokemonBDSP/Autostory/EternaBikeShop/BikeRidingClosePrompt.png", - {1570.0 / 1920, 730.0 / 1080, 160.0 / 1920, 60.0 / 1080}, - {.79, .65, .14, .10}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaBikeShop/BikeRidingBlueFeature.png", + {104.0 / 1920, 314.0 / 1080, 520.0 / 1920, 450.0 / 1080}, + {.035, .26, .32, .48}, + .93, + .20, + LandmarkFeatures::BLUE_FRACTION}, + {"B Close prompt", + "PokemonBDSP/Autostory/EternaBikeShop/BikeRidingClosePrompt.png", + {1570.0 / 1920, 730.0 / 1080, 160.0 / 1920, 60.0 / 1080}, + {.79, .65, .14, .10}, + .93, + .20}}; return match_landmarks(frame, configs, 2, 1.0 / 64, 1.0 / 54, true); } OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame){ - const std::vector configs{{"Red frame", - "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-0.png", - {620.0 / 1920, 65.0 / 1080, 390.0 / 1920, 240.0 / 1080}, - {0.1, 0, 0.65, 0.55}, - .93, - .20}, - {"Counter detail", - "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-1.png", - {1110.0 / 1920, 486.0 / 1080, 86.0 / 1920, 38.0 / 1080}, - {0.3, 0.15, 0.65, 0.65}, - .93, - .20}}; + const std::vector configs{ + {"Red frame", + "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-0.png", + {620.0 / 1920, 65.0 / 1080, 390.0 / 1920, 240.0 / 1080}, + {0.1, 0, 0.65, 0.55}, + .93, + .20}, + {"Counter detail", + "PokemonBDSP/Autostory/EternaBikeShop/eterna_cycle_shop_counter-1.png", + {1110.0 / 1920, 486.0 / 1080, 86.0 / 1920, 38.0 / 1080}, + {0.3, 0.15, 0.65, 0.65}, + .93, + .20}}; return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } @@ -1625,19 +1699,21 @@ OverworldMatchResult match_eterna_cycle_shop_counter(const ImageRGB32& frame){ // The doorway and west window avoid the moving player, NPCs and localized text. OverworldMatchResult match_cycling_road_south_gate_interior(const ImageRGB32& frame){ return match_landmarks(frame, - {{"Gate north doorway", - "PokemonBDSP/Autostory/CyclingRoadSouthGate/NorthDoorway.png", - {630.0 / 1920, 118.0 / 1080, 220.0 / 1920, 270.0 / 1080}, - {.15, 0, .70, .55}, - .93, - .20}, - {"Gate west window", - "PokemonBDSP/Autostory/CyclingRoadSouthGate/WestWindow.png", - {302.0 / 1920, 140.0 / 1080, 157.0 / 1920, 145.0 / 1080}, - {.05, 0, .35, .55}, - .93, - .20}}, - 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y); + {{"Gate north doorway", + "PokemonBDSP/Autostory/CyclingRoadSouthGate/NorthDoorway.png", + {630.0 / 1920, 118.0 / 1080, 220.0 / 1920, 270.0 / 1080}, + {.15, 0, .70, .55}, + .93, + .20}, + {"Gate west window", + "PokemonBDSP/Autostory/CyclingRoadSouthGate/WestWindow.png", + {302.0 / 1920, 140.0 / 1080, 157.0 / 1920, 145.0 / 1080}, + {.05, 0, .35, .55}, + .93, + .20}}, + 2, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y); } OverworldMatchResult match_cycling_road_bridge_approach(const ImageRGB32& frame){ // Use the actual bridge entrance: cyan right rail with a broad metal deck @@ -1646,8 +1722,11 @@ OverworldMatchResult match_cycling_road_bridge_approach(const ImageRGB32& frame) if (!frame){ return best; } - cv::Mat pixels((int)frame.height(), (int)frame.width(), CV_8UC4, const_cast(frame.data()), - frame.bytes_per_row()); + cv::Mat pixels((int)frame.height(), + (int)frame.width(), + CV_8UC4, + const_cast(frame.data()), + frame.bytes_per_row()); cv::Mat small, bgr, hsv, cyan; cv::resize(pixels, small, cv::Size(960, 540), 0, 0, cv::INTER_AREA); cv::cvtColor(small, bgr, cv::COLOR_BGRA2BGR); @@ -1801,17 +1880,17 @@ OverworldMatchResult match_cycling_road_bridge_approach(const ImageRGB32& frame) OverworldMatchResult match_galactic_hq_exterior(const ImageRGB32& frame){ const std::vector configs{{"HQ sign", - "PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-0.png", - {660.0 / 1920, 615.0 / 1080, 118.0 / 1920, 124.0 / 1080}, - {0, 0.2, 0.65, 0.8}, - .93, - .20}, - {"HQ doorway lintel", - "PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-1.png", - {874.0 / 1920, 350.0 / 1080, 172.0 / 1920, 78.0 / 1080}, - {0.2, 0, 0.6, 0.7}, - .93, - .20}}; + "PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-0.png", + {660.0 / 1920, 615.0 / 1080, 118.0 / 1920, 124.0 / 1080}, + {0, 0.2, 0.65, 0.8}, + .93, + .20}, + {"HQ doorway lintel", + "PokemonBDSP/Autostory/GalacticHQ/galactic_hq_exterior-1.png", + {874.0 / 1920, 350.0 / 1080, 172.0 / 1920, 78.0 / 1080}, + {0.2, 0, 0.6, 0.7}, + .93, + .20}}; return match_landmarks(frame, configs, 2, 1.0 / 128, POSITION_TOLERANCE_Y, true); } @@ -1821,7 +1900,7 @@ OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame){ auto result = match_eterna_center_entrance(frame); result.dy -= .070463; result.at_target = result.located && !result.coarse && std::abs(result.dx) <= 1.0 / 128 && - std::abs(result.dy) <= POSITION_TOLERANCE_Y; + std::abs(result.dy) <= POSITION_TOLERANCE_Y; return result; } @@ -1837,24 +1916,26 @@ OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame){ // visible statue regions before allowing those shapes to verify entry. const cv::Mat gray = gray_image(frame); const auto visible = [&](double x, double width){ - const cv::Rect box((int)(x * gray.cols), (int)(458.0 / 1080 * gray.rows), (int)(width * gray.cols), - (int)(350.0 / 1080 * gray.rows)); + const cv::Rect box((int)(x * gray.cols), + (int)(458.0 / 1080 * gray.rows), + (int)(width * gray.cols), + (int)(350.0 / 1080 * gray.rows)); return cv::mean(gray(box))[0] >= 60; }; const bool statues_visible = visible(448.0 / 1920, 146.0 / 1920) && visible(1322.0 / 1920, 154.0 / 1920); for (double scale : {.95, 1.0, 1.05}){ std::vector configs = {{"Left gym statue", - "PokemonBDSP/Autostory/EternaGym/LeftStatue.png", - {448.0 / 1920, 458.0 / 1080, 146.0 / 1920, 350.0 / 1080}, - {.12, .30, .27, .53}, - .93, - .20}, - {"Right gym statue", - "PokemonBDSP/Autostory/EternaGym/RightStatue.png", - {1322.0 / 1920, 458.0 / 1080, 154.0 / 1920, 350.0 / 1080}, - {.60, .30, .28, .53}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaGym/LeftStatue.png", + {448.0 / 1920, 458.0 / 1080, 146.0 / 1920, 350.0 / 1080}, + {.12, .30, .27, .53}, + .93, + .20}, + {"Right gym statue", + "PokemonBDSP/Autostory/EternaGym/RightStatue.png", + {1322.0 / 1920, 458.0 / 1080, 154.0 / 1920, 350.0 / 1080}, + {.60, .30, .28, .53}, + .93, + .20}}; for (auto& config : configs){ const double w = config.target.width, h = config.target.height; config.target.width *= scale; @@ -1884,17 +1965,17 @@ OverworldMatchResult match_eterna_underground_house(const ImageRGB32& frame){ bool best_primary_only = false; for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ std::vector configs{{"Center Pokeball", - "PokemonBDSP/Autostory/EternaUndergroundMan/HouseBall.png", - {332.0 / 1920, 224.0 / 1080, 112.0 / 1920, 104.0 / 1080}, - {0, 0, .65, .7}, - .93, - .20}, - {"House sign", - "PokemonBDSP/Autostory/EternaUndergroundMan/HouseSign.png", - {1240.0 / 1920, 396.0 / 1080, 110.0 / 1920, 106.0 / 1080}, - {.35, 0, .65, .85}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaUndergroundMan/HouseBall.png", + {332.0 / 1920, 224.0 / 1080, 112.0 / 1920, 104.0 / 1080}, + {0, 0, .65, .7}, + .93, + .20}, + {"House sign", + "PokemonBDSP/Autostory/EternaUndergroundMan/HouseSign.png", + {1240.0 / 1920, 396.0 / 1080, 110.0 / 1920, 106.0 / 1080}, + {.35, 0, .65, .85}, + .93, + .20}}; for (auto& config : configs){ const double w = config.target.width, h = config.target.height; config.target.width *= scale; @@ -1935,29 +2016,29 @@ OverworldMatchResult match_eterna_underground_man(const ImageRGB32& frame){ // Reuse fixed furniture, excluding people and the animated TV picture. // The right-window search excludes the identical window above the stool. const std::vector configs{{"Stool", - "PokemonBDSP/Autostory/EternaUndergroundMan/Stool.png", - {380.0 / 1920, 610.0 / 1080, 86.0 / 1920, 70.0 / 1080}, - {0, .3, .65, .5}, - .93, - .20}, - {"Right tree top", - "PokemonBDSP/Autostory/EternaUndergroundMan/TreeTop.png", - {1372.0 / 1920, 320.0 / 1080, 102.0 / 1920, 96.0 / 1080}, - {.5, 0, .4, .7}, - .93, - .20}, - {"Wall sign", - "PokemonBDSP/Autostory/EternaUndergroundMan/WallSign.png", - {642.0 / 1920, 354.0 / 1080, 114.0 / 1920, 70.0 / 1080}, - {.2, 0, .5, .6}, - .93, - .20}, - {"Window beside TV", - "PokemonBDSP/Autostory/EternaUndergroundMan/RightWindow.png", - {906.0 / 1920, 260.0 / 1080, 162.0 / 1920, 160.0 / 1080}, - {.35, 0, .55, .55}, - .93, - .20}}; + "PokemonBDSP/Autostory/EternaUndergroundMan/Stool.png", + {380.0 / 1920, 610.0 / 1080, 86.0 / 1920, 70.0 / 1080}, + {0, .3, .65, .5}, + .93, + .20}, + {"Right tree top", + "PokemonBDSP/Autostory/EternaUndergroundMan/TreeTop.png", + {1372.0 / 1920, 320.0 / 1080, 102.0 / 1920, 96.0 / 1080}, + {.5, 0, .4, .7}, + .93, + .20}, + {"Wall sign", + "PokemonBDSP/Autostory/EternaUndergroundMan/WallSign.png", + {642.0 / 1920, 354.0 / 1080, 114.0 / 1920, 70.0 / 1080}, + {.2, 0, .5, .6}, + .93, + .20}, + {"Window beside TV", + "PokemonBDSP/Autostory/EternaUndergroundMan/RightWindow.png", + {906.0 / 1920, 260.0 / 1080, 162.0 / 1920, 160.0 / 1080}, + {.35, 0, .55, .55}, + .93, + .20}}; auto result = match_landmarks(frame, configs, 4, 1.0 / 128, POSITION_TOLERANCE_Y, true); if (!result.located){ // Three agreeing furniture anchors identify the room during entry and @@ -1976,124 +2057,130 @@ OverworldMatchResult match_jubilife_street_approach(const ImageRGB32& frame){ // screenshot. Targets preserve the original destination height. // Capture-derived references cover lamp, daylight, and moving tree shadows. return match_lighting_pair(frame, - {{"BuildingCorner", - "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour3Frame4.png", - {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, - {0, 0, 1, 1}, - .93, - .08}, - {"BuildingCorner", - "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour14Frame2.png", - {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, - {0, 0, 1, 1}, - .93, - .08}, - {"BuildingCorner", - "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour17Frame4.png", - {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, - {0, 0, 1, 1}, - .93, - .08}, - {"BuildingBase", - "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour15Frame2.png", - {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, - {0, 0, 1, 1}, - .93, - .08}, - {"BuildingBase", - "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour17Frame5.png", - {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, - {0, 0, 1, 1}, - .93, - .08}, - {"BuildingBase", - "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour20Frame5.png", - {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, - {0, 0, 1, 1}, - .93, - .08}}, - "BuildingCorner", "BuildingBase", POSITION_TOLERANCE_X, true); + {{"BuildingCorner", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour3Frame4.png", + {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingCorner", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour14Frame2.png", + {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingCorner", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingCornerHour17Frame4.png", + {0.588541666667, 0.351851851852, 0.088541666667, 0.259259259259}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingBase", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour15Frame2.png", + {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingBase", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour17Frame5.png", + {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, + {0, 0, 1, 1}, + .93, + .08}, + {"BuildingBase", + "PokemonBDSP/Autostory/JubilifeStreet/BuildingBaseHour20Frame5.png", + {0.812500000000, 0.388888888889, 0.078125000000, 0.203703703704}, + {0, 0, 1, 1}, + .93, + .08}}, + "BuildingCorner", + "BuildingBase", + POSITION_TOLERANCE_X, + true); } OverworldMatchResult match_jubilife_plaza_approach(const ImageRGB32& frame){ // Capture-derived references cover lamp, daylight, and moving tree shadows. - auto result = - match_lighting_pair(frame, - { - {"RoofEdge", - "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour2Frame2.png", - {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, - {0, 0, 1, 1}, - .93, - .08}, - {"RoofEdge", - "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame1.png", - {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, - {0, 0, 1, 1}, - .93, - .08}, - {"RoofEdge", - "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame2.png", - {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, - {0, 0, 1, 1}, - .93, - .08}, - {"RoofEdge", - "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame5.png", - {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, - {0, 0, 1, 1}, - .93, - .08}, - {"RoofEdge", - "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour16Frame5.png", - {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, - {0, 0, 1, 1}, - .93, - .08}, - {"RoofEdge", - "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour23Frame2.png", - {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, - {0, 0, 1, 1}, - .93, - .08}, - {"Fence", - "PokemonBDSP/Autostory/JubilifePlaza/FenceHour3Frame1.png", - {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, - {0, 0, 1, 1}, - .93, - .08}, - {"Fence", - "PokemonBDSP/Autostory/JubilifePlaza/FenceHour5Frame4.png", - {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, - {0, 0, 1, 1}, - .93, - .08}, - {"Fence", - "PokemonBDSP/Autostory/JubilifePlaza/FenceHour11Frame3.png", - {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, - {0, 0, 1, 1}, - .93, - .08}, - {"Fence", - "PokemonBDSP/Autostory/JubilifePlaza/FenceHour14Frame1.png", - {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, - {0, 0, 1, 1}, - .93, - .08}, - {"Fence", - "PokemonBDSP/Autostory/JubilifePlaza/FenceHour19Frame1.png", - {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, - {0, 0, 1, 1}, - .93, - .08}, - {"Fence", - "PokemonBDSP/Autostory/JubilifePlaza/FenceHour21Frame4.png", - {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, - {0, 0, 1, 1}, - .93, - .08} // Small screen-relative quantization allowance avoids bouncing across the - // plaza target when one movement step spans both sides of the tolerance. - }, - "RoofEdge", "Fence", POSITION_TOLERANCE_X, false, 1.0 / 32); + auto result = match_lighting_pair(frame, + { + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour2Frame2.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame1.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame2.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour11Frame5.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour16Frame5.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"RoofEdge", + "PokemonBDSP/Autostory/JubilifePlaza/RoofEdgeHour23Frame2.png", + {0.614583333333, 0.203703703704, 0.299479166667, 0.111111111111}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour3Frame1.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour5Frame4.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour11Frame3.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour14Frame1.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour19Frame1.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08}, + {"Fence", + "PokemonBDSP/Autostory/JubilifePlaza/FenceHour21Frame4.png", + {0.044270833333, 0.328703703704, 0.169270833333, 0.106481481481}, + {0, 0, 1, 1}, + .93, + .08} // Small screen-relative quantization allowance avoids bouncing across the + // plaza target when one movement step spans both sides of the tolerance. + }, + "RoofEdge", + "Fence", + POSITION_TOLERANCE_X, + false, + 1.0 / 32); if (!result.located && result.landmarks.size() == 2){ const auto& fence = result.landmarks[1]; // A moving roof shadow must not trigger a blind lateral sweep when the @@ -2120,51 +2207,51 @@ OverworldMatchResult match_route204_sign(const ImageRGB32& frame){ {"Fence corner", "PokemonBDSP/Autostory/Route204Sign/FenceCorner.png", fence_target, {0, 0, 1, 1}, .93, .10}, {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour0Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour0Frame3.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour0Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour8Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour8Frame3.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour8Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour13Frame3.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour13Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour17Frame3.png", sign_target, {0, 0, 1, 1}, .93, .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour17Frame3.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour17Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, {"Route sign", "PokemonBDSP/Autostory/Route204Sign/SignHour2Frame1.png", sign_target, {0, 0, 1, 1}, .93, .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour2Frame1.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour2Frame1.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour10Frame3.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerHour10Frame3.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, {"Fence corner", - "PokemonBDSP/Autostory/Route204Sign/FenceCornerRecordedCloud.png", - fence_target, - {0, 0, 1, 1}, - .93, - .10}, + "PokemonBDSP/Autostory/Route204Sign/FenceCornerRecordedCloud.png", + fence_target, + {0, 0, 1, 1}, + .93, + .10}, }; auto result = match_lighting_pair(frame, variants, "Route sign", "Fence corner"); if (!result.located && result.landmarks.size() == 2){ @@ -2188,29 +2275,32 @@ static OverworldMatchResult match_route205_marker(const ImageRGB32& frame, doubl // grass and water are excluded from the acquisition references. for (int variant : {0, 1, 2, 3, 4, 5, 6}){ for (double scale : {.9, 1.0, 1.1, 1.2}){ - const double w = (variant == 6 ? 188.0 - : variant == 5 ? 184.0 - : variant == 4 ? 220.0 - : variant == 3 ? 211.0 - : variant == 2 ? 208.0 - : variant == 1 ? 206.0 - : 205.0) / - 1920 * scale; + const double w = (variant == 6 ? 188.0 + : variant == 5 ? 184.0 + : variant == 4 ? 220.0 + : variant == 3 ? 211.0 + : variant == 2 ? 208.0 + : variant == 1 ? 206.0 + : 205.0) / + 1920 * scale; const double h = (variant == 6 ? 74.0 : variant >= 3 ? 72.0 : variant ? 80.0 : 82.0) / 1080 * scale; auto candidate = match_landmarks(frame, - {{"Route 205 marker", - variant == 6 ? "PokemonBDSP/Autostory/Route205/BridgeSignMidCloud.png" - : variant == 5 ? "PokemonBDSP/Autostory/Route205/BridgeSignCloud.png" - : variant == 4 ? "PokemonBDSP/Autostory/Route205/BridgeSignLow.png" - : variant == 3 ? "PokemonBDSP/Autostory/Route205/BridgeSignLive.png" - : variant == 2 ? "PokemonBDSP/Autostory/Route205/NorthSignRecorded.png" - : variant == 1 ? "PokemonBDSP/Autostory/Route205/NorthSign.png" - : "PokemonBDSP/Autostory/Route205/BridgeSign.png", - {cx - w / 2, cy - h / 2, w, h}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Route 205 marker", + variant == 6 ? "PokemonBDSP/Autostory/Route205/BridgeSignMidCloud.png" + : variant == 5 ? "PokemonBDSP/Autostory/Route205/BridgeSignCloud.png" + : variant == 4 ? "PokemonBDSP/Autostory/Route205/BridgeSignLow.png" + : variant == 3 ? "PokemonBDSP/Autostory/Route205/BridgeSignLive.png" + : variant == 2 ? "PokemonBDSP/Autostory/Route205/NorthSignRecorded.png" + : variant == 1 ? "PokemonBDSP/Autostory/Route205/NorthSign.png" + : "PokemonBDSP/Autostory/Route205/BridgeSign.png", + {cx - w / 2, cy - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (best.landmarks.empty() || (candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = candidate; @@ -2228,26 +2318,28 @@ OverworldMatchResult match_route205_zackary(const ImageRGB32& frame){ // Include the fixed cliff edge beside both pipes: the repeated blue caps // alone produce a second strong peak in cloud shadow. const double cw = 300.0 / 1920 * scale, ch = 300.0 / 1080 * scale; - auto candidate = - match_landmarks(frame, - {{"Zackary bridge sign", - view == 2 ? "PokemonBDSP/Autostory/Route205/PatrolSignCloud.png" - : view >= 1 ? "PokemonBDSP/Autostory/Route205/PatrolSignLive.png" - : "PokemonBDSP/Autostory/Route205/PatrolSign.png", - {959.5 / 1920 - sw / 2, 67.5 / 1080 - sh / 2, sw, sh}, - {0, 0, 1, 1}, - .93, - .20}, - {"Zackary twin chimneys", - view == 3 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysPartialCloud.png" - : view == 2 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysCloud.png" - : view == 1 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysLive.png" - : "PokemonBDSP/Autostory/Route205/PatrolChimneys.png", - {1210.0 / 1920 - cw / 2, 290.0 / 1080 - ch / 2, cw, ch}, - {0, 0, 1, 1}, - .93, - .20}}, - 2, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + auto candidate = match_landmarks(frame, + {{"Zackary bridge sign", + view == 2 ? "PokemonBDSP/Autostory/Route205/PatrolSignCloud.png" + : view >= 1 ? "PokemonBDSP/Autostory/Route205/PatrolSignLive.png" + : "PokemonBDSP/Autostory/Route205/PatrolSign.png", + {959.5 / 1920 - sw / 2, 67.5 / 1080 - sh / 2, sw, sh}, + {0, 0, 1, 1}, + .93, + .20}, + {"Zackary twin chimneys", + view == 3 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysPartialCloud.png" + : view == 2 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysCloud.png" + : view == 1 ? "PokemonBDSP/Autostory/Route205/PatrolChimneysLive.png" + : "PokemonBDSP/Autostory/Route205/PatrolChimneys.png", + {1210.0 / 1920 - cw / 2, 290.0 / 1080 - ch / 2, cw, ch}, + {0, 0, 1, 1}, + .93, + .20}}, + 2, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); if (best.located && candidate.located && (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ @@ -2270,13 +2362,16 @@ OverworldMatchResult match_route205_kelsey(const ImageRGB32& frame){ for (double scale : {.9, 1.0, 1.1}){ const double w = 56.0 / 1920 * scale, h = 53.0 / 1080 * scale; auto candidate = match_landmarks(frame, - {{"Kelsey west item ball", - "PokemonBDSP/Autostory/Route205/KelseyItem.png", - {280.0 / 1920 - w / 2, 574.5 / 1080 - h / 2, w, h}, - {0, .2, .48, .65}, - .93, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Kelsey west item ball", + "PokemonBDSP/Autostory/Route205/KelseyItem.png", + {280.0 / 1920 - w / 2, 574.5 / 1080 - h / 2, w, h}, + {0, .2, .48, .65}, + .93, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (best.landmarks.empty() || (candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = std::move(candidate); @@ -2295,35 +2390,38 @@ OverworldMatchResult match_route205_bank_tree(const ImageRGB32& frame){ const double tw = 140.0 / 1920 * scale, th = 130.0 / 1080 * scale; const double nw = 120.0 / 1920 * scale, nh = 170.0 / 1080 * scale; auto candidate = match_landmarks(frame, - {{"Bank tree crown", - view == 8 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownWest.png" - : view == 7 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud333.png" - : view == 6 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud76.png" - : view == 5 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud336.png" - : view == 4 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud328.png" - : view == 3 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud80.png" - : view == 2 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud72.png" - : view == 1 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownLive.png" - : "PokemonBDSP/Autostory/Route205/BankTreeCrown.png", - {770.0 / 1920 - tw / 2, 235.0 / 1080 - th / 2, tw, th}, - {0, 0, 1, 1}, - .93, - .20}, - {"Bank blue-hat child", - view == 8 ? "PokemonBDSP/Autostory/Route205/BankBlueHatWest.png" - : view == 7 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud333.png" - : view == 6 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud76.png" - : view == 5 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud336.png" - : view == 4 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud328.png" - : view == 3 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud80.png" - : view == 2 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud72.png" - : view == 1 ? "PokemonBDSP/Autostory/Route205/BankBlueHatLive.png" - : "PokemonBDSP/Autostory/Route205/BankBlueHat.png", - {700.0 / 1920 - nw / 2, 525.0 / 1080 - nh / 2, nw, nh}, - {0, 0, 1, 1}, - .93, - .20}}, - 2, 1.0 / 96, POSITION_TOLERANCE_Y, true); + {{"Bank tree crown", + view == 8 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownWest.png" + : view == 7 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud333.png" + : view == 6 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud76.png" + : view == 5 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud336.png" + : view == 4 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud328.png" + : view == 3 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud80.png" + : view == 2 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownCloud72.png" + : view == 1 ? "PokemonBDSP/Autostory/Route205/BankTreeCrownLive.png" + : "PokemonBDSP/Autostory/Route205/BankTreeCrown.png", + {770.0 / 1920 - tw / 2, 235.0 / 1080 - th / 2, tw, th}, + {0, 0, 1, 1}, + .93, + .20}, + {"Bank blue-hat child", + view == 8 ? "PokemonBDSP/Autostory/Route205/BankBlueHatWest.png" + : view == 7 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud333.png" + : view == 6 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud76.png" + : view == 5 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud336.png" + : view == 4 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud328.png" + : view == 3 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud80.png" + : view == 2 ? "PokemonBDSP/Autostory/Route205/BankBlueHatCloud72.png" + : view == 1 ? "PokemonBDSP/Autostory/Route205/BankBlueHatLive.png" + : "PokemonBDSP/Autostory/Route205/BankBlueHat.png", + {700.0 / 1920 - nw / 2, 525.0 / 1080 - nh / 2, nw, nh}, + {0, 0, 1, 1}, + .93, + .20}}, + 2, + 1.0 / 96, + POSITION_TOLERANCE_Y, + true); const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); if (best.located && candidate.located && (std::abs(best.dx - candidate.dx) > AGREEMENT_X || std::abs(best.dy - candidate.dy) > AGREEMENT_Y)){ @@ -2411,15 +2509,18 @@ OverworldMatchResult match_windworks_sign_approach(const ImageRGB32& frame){ const double w = (view == 2 ? 124.0 : view == 1 ? 144.0 : 121.0) / 1920 * scale; const double h = (view == 1 ? 140.0 : 122.0) / 1080 * scale; auto candidate = match_landmarks(frame, - {{"Windworks approach sign", - view == 2 ? "PokemonBDSP/Autostory/WindworksApproach/SignNight.png" - : view == 1 ? "PokemonBDSP/Autostory/WindworksApproach/SignLow.png" - : "PokemonBDSP/Autostory/WindworksApproach/Sign.png", - {836.5 / 1920 - w / 2, 515.0 / 1080 - h / 2, w, h}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Windworks approach sign", + view == 2 ? "PokemonBDSP/Autostory/WindworksApproach/SignNight.png" + : view == 1 ? "PokemonBDSP/Autostory/WindworksApproach/SignLow.png" + : "PokemonBDSP/Autostory/WindworksApproach/Sign.png", + {836.5 / 1920 - w / 2, 515.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (best.landmarks.empty() || (candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = candidate; @@ -2433,14 +2534,17 @@ OverworldMatchResult match_windworks_sign_approach(const ImageRGB32& frame){ const double w = (high_view ? 86.0 : 106.0) / 1920 * scale; const double h = (high_view ? 72.0 : 94.0) / 1080 * scale; auto candidate = match_landmarks(frame, - {{"Windworks approach sign face", - high_view ? "PokemonBDSP/Autostory/WindworksApproach/SignHighFace.png" - : "PokemonBDSP/Autostory/WindworksApproach/SignMidFace.png", - {838.0 / 1920 - w / 2, 516.0 / 1080 - h / 2, w, h}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Windworks approach sign face", + high_view ? "PokemonBDSP/Autostory/WindworksApproach/SignHighFace.png" + : "PokemonBDSP/Autostory/WindworksApproach/SignMidFace.png", + {838.0 / 1920 - w / 2, 516.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if ((candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = candidate; @@ -2459,13 +2563,16 @@ OverworldMatchResult match_route204_floaroma_turn(const ImageRGB32& frame){ for (double scale_y : {.90, 1.0, 1.10}){ const double w = 140.0 / 1920 * scale_x, h = 57.0 / 1080 * scale_y; auto candidate = match_landmarks(frame, - {{"Northern Route 204 sign", - "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", - {.5 - w / 2, 420.0 / 1080 - h / 2, w, h}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, POSITION_TOLERANCE_X, POSITION_TOLERANCE_Y, true); + {{"Northern Route 204 sign", + "PokemonBDSP/Autostory/Route204Sign/SignHour13Frame3.png", + {.5 - w / 2, 420.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + POSITION_TOLERANCE_X, + POSITION_TOLERANCE_Y, + true); if (best.landmarks.empty() || (candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = candidate; @@ -2505,19 +2612,22 @@ OverworldMatchResult match_floaroma_house_entrance(const ImageRGB32& frame){ // Fixed facade details above/aside the player avoid door/player occlusion. // Both must agree on the pose recorded just outside the central house. return match_landmarks(frame, - {{"House window", - "PokemonBDSP/Autostory/FloaromaHouse/Window.png", - {1060.0 / 1920, 370.0 / 1080, 128.0 / 1920, 106.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}, - {"House gable", - "PokemonBDSP/Autostory/FloaromaHouse/Gable.png", - {796.0 / 1920, 196.0 / 1080, 460.0 / 1920, 154.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}}, - 2, 1.0 / 64, 1.0 / 54, true); + {{"House window", + "PokemonBDSP/Autostory/FloaromaHouse/Window.png", + {1060.0 / 1920, 370.0 / 1080, 128.0 / 1920, 106.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"House gable", + "PokemonBDSP/Autostory/FloaromaHouse/Gable.png", + {796.0 / 1920, 196.0 / 1080, 460.0 / 1920, 154.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}}, + 2, + 1.0 / 64, + 1.0 / 54, + true); } OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame){ OverworldMatchResult best; @@ -2525,13 +2635,16 @@ OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame){ for (double scale : {.90, .95, 1.0, 1.05, 1.10}){ const double w = 105.0 / 1920 * scale, h = 86.0 / 1080 * scale; auto candidate = match_landmarks(frame, - {{"Center emblem", - "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", - {.5 - w / 2, 135.0 / 1080 - h / 2, w, h}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, 1.0 / 64, 1.0 / 36, true); + {{"Center emblem", + "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", + {.5 - w / 2, 135.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + 1.0 / 64, + 1.0 / 36, + true); if (best.landmarks.empty() || (candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = candidate; @@ -2545,13 +2658,16 @@ OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame){ for (double scale_y : {1.15, 1.25, 1.35, 1.45}){ const double w = 105.0 / 1920 * scale_x, h = 86.0 / 1080 * scale_y; auto candidate = match_landmarks(frame, - {{"Center emblem west perspective", - "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", - {.5 - w / 2, 135.0 / 1080 - h / 2, w, h}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, 1.0 / 64, 1.0 / 36, true); + {{"Center emblem west perspective", + "PokemonBDSP/Autostory/FloaromaCenter/Emblem.png", + {.5 - w / 2, 135.0 / 1080 - h / 2, w, h}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + 1.0 / 64, + 1.0 / 36, + true); candidate.coarse = true; candidate.at_target = false; if ((candidate.located && !best.located) || @@ -2564,13 +2680,16 @@ OverworldMatchResult match_floaroma_center_entrance(const ImageRGB32& frame){ // A lower-face reference excludes the tester's burned-in overlay line // and covers the doorway-facing perspective, retaining score/margin gates. auto lower = match_landmarks(frame, - {{"Center emblem lower face", - "PokemonBDSP/Autostory/FloaromaCenter/EmblemLower.png", - {.5 - 52.0 / 1920, 114.0 / 1080, 104.0 / 1920, 65.0 / 1080}, - {0, 0, 1, 1}, - .93, - .20}}, - 1, 1.0 / 64, 1.0 / 36, true); + {{"Center emblem lower face", + "PokemonBDSP/Autostory/FloaromaCenter/EmblemLower.png", + {.5 - 52.0 / 1920, 114.0 / 1080, 104.0 / 1920, 65.0 / 1080}, + {0, 0, 1, 1}, + .93, + .20}}, + 1, + 1.0 / 64, + 1.0 / 36, + true); if ((lower.located && (!best.located || best.coarse)) || (lower.located == best.located && lower.landmarks[0].score > best.landmarks[0].score)){ best = lower; @@ -2581,14 +2700,17 @@ OverworldMatchResult match_route204_north_stairs(const ImageRGB32& frame){ OverworldMatchResult best; // Fixed rails/steps, with sunny and cloud-covered perspectives. for (const char* path : {"PokemonBDSP/Autostory/Route204NorthStairs/Stairs.png", - "PokemonBDSP/Autostory/Route204NorthStairs/StairsCloud.png"}){ + "PokemonBDSP/Autostory/Route204NorthStairs/StairsCloud.png"}){ // Camera pitch changes height and width by different amounts. for (double scale_x : {.90, 1.0, 1.10}){ for (double scale_y : {.80, .90, 1.0, 1.10}){ const double w = 298.0 / 1920 * scale_x, h = 282.0 / 1080 * scale_y; - auto candidate = match_landmarks( - frame, {{"North route stairs", path, {.5 - w / 2, 650.0 / 1080, w, h}, {0, 0, 1, 1}, .93, .10}}, 1, - 1.0 / 64, 1.0 / 54, true); + auto candidate = match_landmarks(frame, + {{"North route stairs", path, {.5 - w / 2, 650.0 / 1080, w, h}, {0, 0, 1, 1}, .93, .10}}, + 1, + 1.0 / 64, + 1.0 / 54, + true); if (best.landmarks.empty() || (candidate.located && !best.located) || (candidate.located == best.located && candidate.landmarks[0].score > best.landmarks[0].score)){ best = candidate; @@ -2600,18 +2722,18 @@ OverworldMatchResult match_route204_north_stairs(const ImageRGB32& frame){ // The lower steps distort as the camera moves north. The rail tops, // grass edge and first steps retain a stable physical upper edge. for (const auto& config : - std::vector{{"North route stairs top", - "PokemonBDSP/Autostory/Route204NorthStairs/Top.png", - {.5 - 149.0 / 1920, 640.0 / 1080, 298.0 / 1920, 70.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}, - {"North route stairs top", - "PokemonBDSP/Autostory/Route204NorthStairs/TopRaised.png", - {.5 - 157.0 / 1920, 640.0 / 1080, 314.0 / 1920, 70.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}}){ + std::vector{{"North route stairs top", + "PokemonBDSP/Autostory/Route204NorthStairs/Top.png", + {.5 - 149.0 / 1920, 640.0 / 1080, 298.0 / 1920, 70.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"North route stairs top", + "PokemonBDSP/Autostory/Route204NorthStairs/TopRaised.png", + {.5 - 157.0 / 1920, 640.0 / 1080, 314.0 / 1920, 70.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}}){ for (double scale_x : {.95, 1.0, 1.05}){ for (double scale_y : {.90, 1.0, 1.10}){ auto variant = config; @@ -2652,54 +2774,54 @@ OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame){ // of sizes, keeping each physical anchor's reference center fixed. for (double scale : {.90, 1.0, 1.10}){ for (const auto& config : - std::vector{{"Cave mouth", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Mouth.png", - mouth, - {0, 0, 1, 1}, - .93, - .10}, - {"Entrance sign", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Sign.png", - sign, - {0, 0, 1, 1}, - .93, - .10}, - {"Cave mouth", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/MouthShadow.png", - mouth, - {0, 0, 1, 1}, - .93, - .10}, - {"Entrance sign", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignShadow.png", - sign, - {0, 0, 1, 1}, - .93, - .10}, - {"Entrance sign", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignPartialShadow.png", - sign, - {0, 0, 1, 1}, - .93, - .10}, - {"Entrance sign", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFace.png", - {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}, - {"Entrance sign", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceLowApproach.png", - {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}, - {"Entrance sign", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceCloud.png", - {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, - {0, 0, 1, 1}, - .93, - .10}}){ + std::vector{{"Cave mouth", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Mouth.png", + mouth, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Sign.png", + sign, + {0, 0, 1, 1}, + .93, + .10}, + {"Cave mouth", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/MouthShadow.png", + mouth, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignShadow.png", + sign, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignPartialShadow.png", + sign, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFace.png", + {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceLowApproach.png", + {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}, + {"Entrance sign", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/SignFaceCloud.png", + {828.0 / 1920, 400.0 / 1080, 64.0 / 1920, 62.0 / 1080}, + {0, 0, 1, 1}, + .93, + .10}}){ auto variant = config; variant.target.width *= scale; variant.target.height *= scale; @@ -2726,13 +2848,16 @@ OverworldMatchResult match_ravaged_path_south_entrance(const ImageRGB32& frame){ // The lass is optional evidence for the southern movement boundary, never // a replacement for the fixed mouth/sign arrival anchors. auto boundary = match_landmarks(frame, - {{"Lass south boundary", - "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Lass.png", - {910.0 / 1920, 788.0 / 1080, 100.0 / 1920, 142.0 / 1080}, - {0, 0, 1, 1}, - .88, - .08}}, - 1, 1.0 / 64, 1.0 / 36, true); + {{"Lass south boundary", + "PokemonBDSP/Autostory/RavagedPathSouthEntrance/Lass.png", + {910.0 / 1920, 788.0 / 1080, 100.0 / 1920, 142.0 / 1080}, + {0, 0, 1, 1}, + .88, + .08}}, + 1, + 1.0 / 64, + 1.0 / 36, + true); if (!boundary.landmarks.empty()){ result.landmarks.push_back(boundary.landmarks[0]); } @@ -2750,55 +2875,57 @@ OverworldMatchResult match_route215_bridge_renewal(const ImageRGB32& frame){ cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); auto result = match_lighting_pair(filtered, - {{"Bridge west edge", - "PokemonBDSP/Autostory/Route215Renewal/BridgeEdge.png", - {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, - {.15, .25, .85, .75}, - .90, - .10}, - {"Landing cliff corner", - "PokemonBDSP/Autostory/Route215Renewal/BridgeCliff.png", - {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, - {.10, .25, .85, .75}, - .90, - .10}, - {"Bridge west edge", - "PokemonBDSP/Autostory/Route215Renewal/BridgeFarEdge.png", - {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, - {.15, .25, .85, .75}, - .90, - .10}, - {"Landing cliff corner", - "PokemonBDSP/Autostory/Route215Renewal/BridgeFarCliff.png", - {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, - {.10, .25, .85, .75}, - .90, - .10}, - {"Bridge west edge", - "PokemonBDSP/Autostory/Route215Renewal/BridgeNightEdge.png", - {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, - {.15, .25, .85, .75}, - .90, - .10}, - {"Landing cliff corner", - "PokemonBDSP/Autostory/Route215Renewal/BridgeNightCliff.png", - {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, - {.10, .25, .85, .75}, - .90, - .10}, - {"Bridge west edge", - "PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarEdge.png", - {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, - {.15, .25, .85, .75}, - .90, - .10}, - {"Landing cliff corner", - "PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarCliff.png", - {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, - {.10, .25, .85, .75}, - .90, - .10}}, - "Bridge west edge", "Landing cliff corner", 1.0 / 128); + {{"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}, + {"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeFarEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeFarCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}, + {"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}, + {"Bridge west edge", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarEdge.png", + {538.0 / 960, 302.0 / 540, 50.0 / 960, 100.0 / 540}, + {.15, .25, .85, .75}, + .90, + .10}, + {"Landing cliff corner", + "PokemonBDSP/Autostory/Route215Renewal/BridgeNightFarCliff.png", + {333.0 / 960, 410.0 / 540, 64.0 / 960, 88.0 / 540}, + {.10, .25, .85, .75}, + .90, + .10}}, + "Bridge west edge", + "Landing cliff corner", + 1.0 / 128); result.at_target = result.located && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 108; return result; } @@ -2814,20 +2941,23 @@ OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame){ cv::Mat pixels(540, 960, CV_8UC4, filtered.data(), filtered.bytes_per_row()); cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); auto result = match_landmarks(filtered, - {{"Road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceRoad.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {.10, .15, .85, .80}, - .92, - .08}, - {"Fence corner", - "PokemonBDSP/Autostory/Route215Renewal/AceFence.png", - {165.0 / 960, 389.0 / 540, 110.0 / 960, 78.0 / 540}, - {.05, .25, .90, .75}, - .90, - .10, - LandmarkFeatures::LOCAL_CONTRAST}}, - 2, 1.0 / 128, 1.0 / 108, true); + {{"Road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceRoad.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {.10, .15, .85, .80}, + .92, + .08}, + {"Fence corner", + "PokemonBDSP/Autostory/Route215Renewal/AceFence.png", + {165.0 / 960, 389.0 / 540, 110.0 / 960, 78.0 / 540}, + {.05, .25, .90, .75}, + .90, + .10, + LandmarkFeatures::LOCAL_CONTRAST}}, + 2, + 1.0 / 128, + 1.0 / 108, + true); if (result.located){ return result; } @@ -2840,107 +2970,111 @@ OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame){ cv::GaussianBlur(smoothed, smoothed, cv::Size(0, 0), .8); cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); auto approach = match_lighting_pair(filtered, - {{"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadGoal.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadNear.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFar.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestFar.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNear.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWest.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNearRain.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Upper road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFarRain.png", - {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, - {0, .25, 1, .30}, - .92, - .08}, - {"Lower road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoadEast.png", - {500.0 / 960, 420.0 / 540, 80.0 / 960, 70.0 / 540}, - {0, .70, 1, .30}, - .93, - .10}, - {"Lower road bend", - "PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoad.png", - {500.0 / 960, 420.0 / 540, 80.0 / 960, 70.0 / 540}, - {0, .70, 1, .30}, - .93, - .10}}, - "Upper road bend", "Lower road bend", 1.0 / 128); + {{"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadGoal.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadNear.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFar.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestFar.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNear.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWest.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadWestNearRain.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Upper road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireRoadFarRain.png", + {352.0 / 960, 180.0 / 540, 60.0 / 960, 80.0 / 540}, + {0, .25, 1, .30}, + .92, + .08}, + {"Lower road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoadEast.png", + {500.0 / 960, 420.0 / 540, 80.0 / 960, 70.0 / 540}, + {0, .70, 1, .30}, + .93, + .10}, + {"Lower road bend", + "PokemonBDSP/Autostory/Route215Renewal/AceAcquireLowerRoad.png", + {500.0 / 960, 420.0 / 540, 80.0 / 960, 70.0 / 540}, + {0, .70, 1, .30}, + .93, + .10}}, + "Upper road bend", + "Lower road bend", + 1.0 / 128); // At the eastern extreme, the original bends leave the screen. The two // corners of the same grass bed remain visible above the clear road. auto east = match_lighting_pair(filtered, - {{"Grass west corner", - "PokemonBDSP/Autostory/Route215Renewal/AceGrassWest.png", - {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, - {0, .20, 1, .40}, - .92, - .10}, - {"Grass east corner", - "PokemonBDSP/Autostory/Route215Renewal/AceGrassEast.png", - {890.0 / 960, 175.0 / 540, 65.0 / 960, 62.0 / 540}, - {0, .20, 1, .40}, - .92, - .10}, - {"Grass west corner", - "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", - {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, - {0, .20, 1, .40}, - .92, - .10}, - {"Grass east corner", - "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEast.png", - {890.0 / 960, 175.0 / 540, 65.0 / 960, 62.0 / 540}, - {0, .20, 1, .40}, - .92, - .10}, - {"Grass east corner", - "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEastShoulder.png", - {850.0 / 960, 145.0 / 540, 105.0 / 960, 80.0 / 540}, - {0, .20, 1, .40}, - .92, - .10}}, - "Grass west corner", "Grass east corner", 1.0 / 128); + {{"Grass west corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassWest.png", + {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass east corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassEast.png", + {890.0 / 960, 175.0 / 540, 65.0 / 960, 62.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass west corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", + {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass east corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEast.png", + {890.0 / 960, 175.0 / 540, 65.0 / 960, 62.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Grass east corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarEastShoulder.png", + {850.0 / 960, 145.0 / 540, 105.0 / 960, 80.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}}, + "Grass west corner", + "Grass east corner", + 1.0 / 128); const auto& west = east.landmarks[0]; const auto& right = east.landmarks[1]; const double east_nearest = std::min(std::abs(west.dx), std::abs(right.dx)); east.located = west.accepted && right.accepted && std::abs(west.dy - right.dy) <= AGREEMENT_Y && - west.dx < -1.0 / 12 && right.dx < -1.0 / 12 && east_nearest > 2 * std::abs(west.dx - right.dx) && - east.dy >= -1.0 / 27 && east.dy <= 1.0 / 9; + west.dx < -1.0 / 12 && right.dx < -1.0 / 12 && east_nearest > 2 * std::abs(west.dx - right.dx) && + east.dy >= -1.0 / 27 && east.dy <= 1.0 / 9; if (east.located){ east.coarse = true; east.at_target = false; @@ -2950,31 +3084,33 @@ OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame){ // road bends are off-screen. Its virtual goal lies beyond the right edge; // this pair guides acquisition only and can never confirm arrival. auto far_east = match_lighting_pair(filtered, - {{"Grass west corner", - "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", - {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, - {0, .20, 1, .40}, - .92, - .10}, - {"Route 215 sign", - "PokemonBDSP/Autostory/Route215Renewal/AceRoute215Sign.png", - {1128.0 / 960, 359.0 / 540, 90.0 / 960, 45.0 / 540}, - {.50, .40, .50, .50}, - .94, - .20}, - {"Route 215 sign", - "PokemonBDSP/Autostory/Route215Renewal/AceRoute215SignRain.png", - {1128.0 / 960, 359.0 / 540, 90.0 / 960, 45.0 / 540}, - {.50, .40, .50, .50}, - .94, - .20}}, - "Grass west corner", "Route 215 sign", 1.0 / 128); + {{"Grass west corner", + "PokemonBDSP/Autostory/Route215Renewal/AceGrassFarWest.png", + {665.0 / 960, 145.0 / 540, 110.0 / 960, 75.0 / 540}, + {0, .20, 1, .40}, + .92, + .10}, + {"Route 215 sign", + "PokemonBDSP/Autostory/Route215Renewal/AceRoute215Sign.png", + {1128.0 / 960, 359.0 / 540, 90.0 / 960, 45.0 / 540}, + {.50, .40, .50, .50}, + .94, + .20}, + {"Route 215 sign", + "PokemonBDSP/Autostory/Route215Renewal/AceRoute215SignRain.png", + {1128.0 / 960, 359.0 / 540, 90.0 / 960, 45.0 / 540}, + {.50, .40, .50, .50}, + .94, + .20}}, + "Grass west corner", + "Route 215 sign", + 1.0 / 128); const auto& corner = far_east.landmarks[0]; const auto& sign = far_east.landmarks[1]; const double far_nearest = std::min(std::abs(corner.dx), std::abs(sign.dx)); far_east.located = corner.accepted && sign.accepted && std::abs(corner.dy - sign.dy) <= AGREEMENT_Y && - corner.dx < -1.0 / 4 && sign.dx < -1.0 / 4 && far_nearest > 2 * std::abs(corner.dx - sign.dx) && - far_east.dy >= -1.0 / 27 && far_east.dy <= 1.0 / 9; + corner.dx < -1.0 / 4 && sign.dx < -1.0 / 4 && far_nearest > 2 * std::abs(corner.dx - sign.dx) && + far_east.dy >= -1.0 / 27 && far_east.dy <= 1.0 / 9; if (far_east.located){ far_east.coarse = true; far_east.at_target = false; @@ -2994,11 +3130,11 @@ OverworldMatchResult match_route215_ace_renewal(const ImageRGB32& frame){ // Expiry can also finish one row south, already near the goal column. // Both accepted bends must agree before correcting that row vertically. const bool vertical_support = std::abs(upper.dx - lower.dx) <= AGREEMENT_X && - std::max(std::abs(upper.dx), std::abs(lower.dx)) <= 1.0 / 12 && - std::abs(upper.dy - lower_plane_dy) <= 1.0 / 108 && upper.dy * lower_plane_dy > 0 && - std::abs(approach.dy) > 1.0 / 54; + std::max(std::abs(upper.dx), std::abs(lower.dx)) <= 1.0 / 12 && + std::abs(upper.dy - lower_plane_dy) <= 1.0 / 108 && upper.dy * lower_plane_dy > 0 && + std::abs(approach.dy) > 1.0 / 54; approach.located = upper.accepted && lower.accepted && std::abs(upper.dy - lower_plane_dy) <= AGREEMENT_Y && - std::abs(approach.dy) <= 1.0 / 9 && (horizontal_support || vertical_support); + std::abs(approach.dy) <= 1.0 / 9 && (horizontal_support || vertical_support); approach.coarse = true; approach.at_target = false; return approach; @@ -3015,31 +3151,33 @@ OverworldMatchResult match_route215_derek_stairs_renewal(const ImageRGB32& frame cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); // Clear landing below the short stairs; neither tree nor player is an anchor. auto result = match_lighting_pair(filtered, - {{"White stair railing", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairsRailing.png", - {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, - {.10, .05, .85, .85}, - .93, - .12}, - {"Lower stair corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairsCorner.png", - {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, - {.10, .20, .85, .80}, - .93, - .12}, - {"White stair railing", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightRailing.png", - {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, - {.10, .05, .85, .85}, - .93, - .12}, - {"Lower stair corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightCorner.png", - {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, - {.10, .20, .85, .80}, - .93, - .12}}, - "White stair railing", "Lower stair corner", 1.0 / 128); + {{"White stair railing", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsRailing.png", + {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, + {.10, .05, .85, .85}, + .93, + .12}, + {"Lower stair corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsCorner.png", + {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, + {.10, .20, .85, .80}, + .93, + .12}, + {"White stair railing", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightRailing.png", + {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, + {.10, .05, .85, .85}, + .93, + .12}, + {"Lower stair corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsNightCorner.png", + {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, + {.10, .20, .85, .80}, + .93, + .12}}, + "White stair railing", + "Lower stair corner", + 1.0 / 128); result.at_target = result.located && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 108; if (result.located){ return result; @@ -3048,25 +3186,28 @@ OverworldMatchResult match_route215_derek_stairs_renewal(const ImageRGB32& frame // different amounts. Use that view only to acquire the horizontal column; // the original close pair above remains the sole arrival check. auto approach = match_landmarks(filtered, - {{"White stair railing", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestRailing.png", - {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, - {.10, .05, .85, .85}, - .93, - .12}, - {"Lower stair corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestCorner.png", - {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, - {.10, .20, .85, .80}, - .93, - .12}}, - 2, 1.0 / 128, 1.0 / 108, true); + {{"White stair railing", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestRailing.png", + {500.0 / 960, 78.0 / 540, 90.0 / 960, 110.0 / 540}, + {.10, .05, .85, .85}, + .93, + .12}, + {"Lower stair corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairsWestCorner.png", + {589.0 / 960, 315.0 / 540, 95.0 / 960, 105.0 / 540}, + {.10, .20, .85, .80}, + .93, + .12}}, + 2, + 1.0 / 128, + 1.0 / 108, + true); const auto& rail = approach.landmarks[0]; const auto& stair = approach.landmarks[1]; const double nearest = std::min(std::abs(rail.dx), std::abs(stair.dx)); approach.located = rail.accepted && stair.accepted && std::abs(rail.dy - stair.dy) <= AGREEMENT_Y && - std::abs((rail.dy + stair.dy) / 2) <= 1.0 / 108 && rail.dx * stair.dx > 0 && - nearest > 1.0 / 64 && nearest > 2 * std::abs(rail.dx - stair.dx); + std::abs((rail.dy + stair.dy) / 2) <= 1.0 / 108 && rail.dx * stair.dx > 0 && nearest > 1.0 / 64 && + nearest > 2 * std::abs(rail.dx - stair.dx); approach.dx = (rail.dx + stair.dx) / 2; approach.dy = (rail.dy + stair.dy) / 2; approach.coarse = true; @@ -3085,120 +3226,121 @@ OverworldMatchResult match_route215_derek_stair_top_renewal(const ImageRGB32& fr cv::cvtColor(smoothed, pixels, cv::COLOR_GRAY2BGRA); // The road row above the stairs is clear west/east, below the tall grass. auto result = match_lighting_pair(filtered, - {{"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrier.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrierEast.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFenceEast.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightBarrier.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastBarrier.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestBarrier.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNorthFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthBarrier.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Stair-top barrier", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastBarrier.png", - {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, - {.05, .15, .90, .85}, - .93, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST} - // Raw-luminance alternatives preserve fence structure when local - // contrast normalization amplifies a passing rain streak. - , - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthGrayFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12}, - {"West fence corner", - "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastGrayFence.png", - {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, - {.05, .15, .90, .85}, - .90, - .12}}, - "Stair-top barrier", "West fence corner", 1.0 / 128); + {{"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopBarrierEast.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFenceEast.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightEastFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNightWestFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopNorthFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Stair-top barrier", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastBarrier.png", + {384.0 / 960, 401.0 / 540, 127.0 / 960, 70.0 / 540}, + {.05, .15, .90, .85}, + .93, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST} // Raw-luminance alternatives preserve fence structure when local + // contrast normalization amplifies a passing rain streak. + , + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastNorthGrayFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12}, + {"West fence corner", + "PokemonBDSP/Autostory/Route215Renewal/DerekStairTopFarEastGrayFence.png", + {274.0 / 960, 275.0 / 540, 69.0 / 960, 125.0 / 540}, + {.05, .15, .90, .85}, + .90, + .12}}, + "Stair-top barrier", + "West fence corner", + 1.0 / 128); result.at_target = result.located && std::abs(result.dx) <= 1.0 / 128 && std::abs(result.dy) <= 1.0 / 108; if (!result.located && result.landmarks.size() == 2){ const auto& barrier = result.landmarks[0]; @@ -3237,118 +3379,118 @@ OverworldMatchResult match_route215_jogger_corner(const ImageRGB32& frame){ // as well as a unique peak and an independently agreeing structural anchor. const std::vector appearances = { {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceGoal.png", - {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceGoal.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceRainWest.png", - {0.535416666667, 0.311111111111, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceRainWest.png", + {0.535416666667, 0.311111111111, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceCloudWest.png", - {0.535416666667, 0.311111111111, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceCloudWest.png", + {0.535416666667, 0.311111111111, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceLane3.png", - {0.535416666667, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceLane3.png", + {0.535416666667, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceMid.png", - {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceMid.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceNear.png", - {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceNear.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachFar.png", - {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachFar.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachMiddle.png", - {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachMiddle.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachNear.png", - {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, - {0, .24, 1, .26}, - .93, - .08, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceApproachNear.png", + {0.534375, 0.312962962963, 32.0 / 960, 42.0 / 540}, + {0, .24, 1, .26}, + .93, + .08, + LandmarkFeatures::LOCAL_CONTRAST}, {"Fence top", - "PokemonBDSP/Autostory/Route215JoggerV2/FenceGrayLane3.png", - {0.535416666667, 0.312962962963, 32.0 / 960, 24.0 / 540}, - {0, .24, 1, .26}, - .95, - .05}, + "PokemonBDSP/Autostory/Route215JoggerV2/FenceGrayLane3.png", + {0.535416666667, 0.312962962963, 32.0 / 960, 24.0 / 540}, + {0, .24, 1, .26}, + .95, + .05}, {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperDayGoal.png", - {422.0 / 960, 164.0 / 540, 80.0 / 960, 25.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperDayGoal.png", + {422.0 / 960, 164.0 / 540, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, // In lane 3 the cap overlaps the lower part of the full road patch. // Upper-bend alternatives keep position evidence clear of the player. {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperGoal.png", - {0.439583333333, 0.303703703704, 80.0 / 960, 25.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperGoal.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperRainWest.png", - {0.439583333333, 0.303703703704, 80.0 / 960, 25.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperRainWest.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperCloudWest.png", - {0.447916666667, 0.307407407407, 80.0 / 960, 25.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadUpperCloudWest.png", + {0.447916666667, 0.307407407407, 80.0 / 960, 25.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadGoal.png", - {0.439583333333, 0.303703703704, 80.0 / 960, 41.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadGoal.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 41.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadRainWest.png", - {0.439583333333, 0.303703703704, 80.0 / 960, 41.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadRainWest.png", + {0.439583333333, 0.303703703704, 80.0 / 960, 41.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, {"Road bend", - "PokemonBDSP/Autostory/Route215JoggerV2/RoadCloudWest.png", - {0.447916666667, 0.307407407407, 80.0 / 960, 41.0 / 540}, - {0, .24, 1, .26}, - .90, - .15}, + "PokemonBDSP/Autostory/Route215JoggerV2/RoadCloudWest.png", + {0.447916666667, 0.307407407407, 80.0 / 960, 41.0 / 540}, + {0, .24, 1, .26}, + .90, + .15}, }; std::vector variants; for (const auto& appearance : appearances){ @@ -3412,79 +3554,79 @@ OverworldMatchResult match_route215_jogger_corner(const ImageRGB32& frame){ // Their x coordinates are acquisition-only and can never confirm arrival. const std::vector> approaches = { {// The observed lane-4 to lane-3 step changes the bridge-foot view. - // Both corners must agree; this pair never confirms arrival. - {"Bridge west corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestNightLane3.png", - {106.0 / 960 - .435625, 297.0 / 540 - 70.0 / 1080, 60.0 / 960, 75.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Bridge east corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastNightLane3.png", - {260.0 / 960 - .435625, 295.0 / 540 - 70.0 / 1080, 65.0 / 960, 75.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}}, + // Both corners must agree; this pair never confirms arrival. + {"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestNightLane3.png", + {106.0 / 960 - .435625, 297.0 / 540 - 70.0 / 1080, 60.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastNightLane3.png", + {260.0 / 960 - .435625, 295.0 / 540 - 70.0 / 1080, 65.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}}, {// When the east fence is off-screen, these two full bridge-foot - // corners establish lane 4. They never authorize arrival. - {"Bridge west corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestCurrent.png", - {106.0 / 960 - .435, 257.0 / 540, 60.0 / 960, 75.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"Bridge east corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastCurrent.png", - {260.0 / 960 - .435, 255.0 / 540, 65.0 / 960, 75.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}}, + // corners establish lane 4. They never authorize arrival. + {"Bridge west corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestCurrent.png", + {106.0 / 960 - .435, 257.0 / 540, 60.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"Bridge east corner", + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastCurrent.png", + {260.0 / 960 - .435, 255.0 / 540, 65.0 / 960, 75.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}}, { {"Bridge west corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeFar.png", - {240.0 / 960 - .61, 246.0 / 540, 64.0 / 960, 60.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeFar.png", + {240.0 / 960 - .61, 246.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, {"Bridge east corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastBridgeFar.png", - {388.0 / 960 - .61, 246.0 / 540, 64.0 / 960, 60.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}}, + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeEastBridgeFar.png", + {388.0 / 960 - .61, 246.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}}, {{"Bridge west corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeMid.png", - {108.0 / 960 - .445, 245.0 / 540, 64.0 / 960, 60.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"South cliff corner", - "PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeMid.png", - {77.0 / 960 - .445, 295.0 / 540, 90.0 / 960, 80.0 / 540}, - {0, .2, 1, .6}, - .97, - .05}}, + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeMid.png", + {108.0 / 960 - .445, 245.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"South cliff corner", + "PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeMid.png", + {77.0 / 960 - .445, 295.0 / 540, 90.0 / 960, 80.0 / 540}, + {0, .2, 1, .6}, + .97, + .05}}, {{"Bridge west corner", - "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeNear.png", - {68.0 / 960 - .405, 245.0 / 540, 64.0 / 960, 60.0 / 540}, - {0, .2, 1, .6}, - .90, - .12, - LandmarkFeatures::LOCAL_CONTRAST}, - {"South cliff corner", - "PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeNear.png", - {37.0 / 960 - .405, 295.0 / 540, 90.0 / 960, 80.0 / 540}, - {0, .2, 1, .6}, - .97, - .05}}}; + "PokemonBDSP/Autostory/Route215JoggerV2/BridgeWestBridgeNear.png", + {68.0 / 960 - .405, 245.0 / 540, 64.0 / 960, 60.0 / 540}, + {0, .2, 1, .6}, + .90, + .12, + LandmarkFeatures::LOCAL_CONTRAST}, + {"South cliff corner", + "PokemonBDSP/Autostory/Route215JoggerV2/SouthCliffBridgeNear.png", + {37.0 / 960 - .405, 295.0 / 540, 90.0 / 960, 80.0 / 540}, + {0, .2, 1, .6}, + .97, + .05}}}; for (const auto& approach : approaches){ auto candidate = match_landmarks(filtered, approach, 2, 1.0 / 128, 1.0 / 54, true); const double score = std::min(candidate.landmarks[0].score, candidate.landmarks[1].score); @@ -3535,8 +3677,9 @@ const std::vector& overworld_matchers(){ {"route215-bridge-renewal", "Route 215 west bridge renewal", match_route215_bridge_renewal}, {"route215-ace-renewal", "Route 215 renewal before Ace Trainers", match_route215_ace_renewal}, {"route215-derek-stairs-renewal", "Route 215 renewal below Derek stairs", match_route215_derek_stairs_renewal}, - {"route215-derek-stair-top-renewal", "Route 215 renewal above Derek stairs", - match_route215_derek_stair_top_renewal}, + {"route215-derek-stair-top-renewal", + "Route 215 renewal above Derek stairs", + match_route215_derek_stair_top_renewal}, {"route208-hannah-departure", "Route 208 field after Hannah", match_route208_hannah_departure}, {"eterna-gym", "Eterna Gym doorway", match_eterna_gym_entrance}, {"galactic-hq-jupiter", "Galactic HQ Jupiter interaction", match_galactic_hq_jupiter}, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h index 36aa0aaf8f..6377da938c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_OverworldMatchers.h @@ -87,8 +87,10 @@ OverworldMatchResult match_eterna_center_departure(const ImageRGB32& frame); OverworldMatchResult match_eterna_gym_statues(const ImageRGB32& frame); // Two unique, agreeing landmarks close to the doorway; this confidence only // authorizes a watched entry attempt, never proves the interior location. -double overworld_entry_confidence(const OverworldMatchResult& result, double tolerance_x = 1.0 / 50, - double tolerance_y = 1.0 / 27, double minimum_margin = .15); +double overworld_entry_confidence(const OverworldMatchResult& result, + double tolerance_x = 1.0 / 50, + double tolerance_y = 1.0 / 27, + double minimum_margin = .15); OverworldMatchResult match_eterna_underground_house(const ImageRGB32& frame); OverworldMatchResult match_eterna_underground_man(const ImageRGB32& frame); OverworldMatchResult match_route201_potion_exit(const ImageRGB32& frame); From 45a206f0d7a9a9b98c0298e5766a5dcf31b18bec Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Thu, 29 Oct 2026 18:23:20 -0500 Subject: [PATCH 38/39] updated segment 04 with the gym puzzle and fight sequences --- .../PokemonBDSP_AutoStory_Segment_04.cpp | 145 +++++++++++------- 1 file changed, 93 insertions(+), 52 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 3803d48255..874d5ab562 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -357,7 +357,7 @@ std::string AutoStory_Checkpoint_0418::start_text() const { return "Inside Pastoria Gym at the entrance, facing north."; } std::string AutoStory_Checkpoint_0418::end_text() const { - return "Inside Pastoria Gym in front of Crasher Wake, facing north, after acquiring the fourth badge and clearing his reward dialogue."; + return "Outside Pastoria Gym at the doorway, facing south, after acquiring the fourth badge and clearing the reward dialogue."; } void AutoStory_Checkpoint_0418::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -376,98 +376,139 @@ void AutoStory_Checkpoint_0418::run_checkpoint(SingleSwitchProgramEnvironment& e Milliseconds delay; }; - // Recorded walking counts; repeat_dpad adds the facing press on each turn. + // Trial: use the supplied counts without an extra facing press. const std::vector sequences[] = { { // Trainer 1 {DPAD_UP, 16, 0ms}, - {DPAD_LEFT, 9, 0ms}, + {DPAD_LEFT, 11, 0ms}, {DPAD_DOWN, 8, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_RIGHT, 6, 0ms}, - {DPAD_UP, 6, 0ms}, - {DPAD_RIGHT, 8, 0ms}, - {DPAD_UP, 6, 0ms}, + {DPAD_DOWN, 1, 2000ms}, + {DPAD_RIGHT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 10, 0ms}, + {DPAD_UP, 7, 0ms}, {DPAD_LEFT, 4, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_UP, 13, 0ms}, + {DPAD_UP, 14, 0ms}, {DPAD_RIGHT, 4, 0ms}, - {DPAD_DOWN, 1, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, {DPAD_LEFT, 4, 0ms}, - {DPAD_DOWN, 4, 0ms}, - {DPAD_RIGHT, 5, 0ms}, - {DPAD_DOWN, 9, 0ms}, - {DPAD_LEFT, 20, 0ms}, + {DPAD_DOWN, 6, 0ms}, + {DPAD_RIGHT, 3, 0ms}, + {DPAD_DOWN, 7, 0ms}, + {DPAD_LEFT, 12, 0ms}, {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 4, 0ms}, + {DPAD_RIGHT, 3, 0ms}, }, { // Trainer 2 {DPAD_RIGHT, 2, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 3, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, + {DPAD_LEFT, 4, 0ms}, {DPAD_DOWN, 11, 0ms}, {DPAD_RIGHT, 6, 0ms}, }, { // Trainer 3 - {DPAD_RIGHT, 8, 0ms}, + {DPAD_RIGHT, 10, 0ms}, {DPAD_UP, 2, 0ms}, }, { // Trainer 4 - {DPAD_UP, 7, 0ms}, - {DPAD_RIGHT, 1, 0ms}, - {DPAD_UP, 13, 0ms}, + {DPAD_UP, 21, 0ms}, {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_DOWN, 1, 0ms}, - {DPAD_RIGHT, 2, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 2, 0ms}, + {DPAD_RIGHT, 5, 0ms}, {DPAD_DOWN, 17, 0ms}, - {DPAD_RIGHT, 2, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_UP, 19, 0ms}, - {DPAD_LEFT, 8, 0ms}, - {DPAD_DOWN, 1, 0ms}, - {DPAD_LEFT, 10, 0ms}, - {DPAD_DOWN, 10, 0ms}, - {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 1, 2000ms}, + {DPAD_UP, 25, 0ms}, + {DPAD_LEFT, 18, 0ms}, + {DPAD_DOWN, 11, 0ms}, + {DPAD_LEFT, 4, 0ms}, {DPAD_UP, 10, 0ms}, }, { - // Crasher Wake approach; the battle is added after the recording ends. + // Crasher Wake approach + {DPAD_UP, 2, 0ms}, + {DPAD_UP, 1, 4000ms}, + {DPAD_DOWN, 4, 0ms}, + {DPAD_RIGHT, 11, 0ms}, + {DPAD_UP, 5, 0ms}, + }, + { + // Return from Crasher Wake {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 2, 0ms}, - {DPAD_DOWN, 6, 0ms}, + {DPAD_LEFT, 3, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 11, 0ms}, + {DPAD_DOWN, 35, 0ms}, + {DPAD_RIGHT, 10, 0ms}, + {DPAD_DOWN, 5, 0ms}, {DPAD_RIGHT, 4, 0ms}, - {DPAD_UP, 1, 0ms}, - {DPAD_RIGHT, 8, 0ms}, - {DPAD_UP, 4, 0ms}, + {DPAD_DOWN, 2, 0ms}, }, }; - DpadState dpad; - dpad.last_dir = DPAD_UP; - for (size_t battle = 0; battle < 5; battle++){ - const bool gym_leader = battle == 4; - const std::string label = - gym_leader ? "Crasher Wake" : "Pastoria Gym trainer " + std::to_string(battle + 1); - stream.log("[Pastoria Gym puzzle] Approaching " + label + "."); - for (const auto& move : sequences[battle]){ - repeat_dpad(context, dpad, move.dir, 80ms, 300ms, move.count, true); - if (move.delay > 0ms){ - pbf_wait(context, move.delay); + for (size_t stage = 0; stage < 6; stage++){ + stream.log("[Pastoria Gym puzzle] Movement stage " + std::to_string(stage + 1) + "."); + if (stage == 5){ + // Observe the supplied return route and stop movement at the Gym exit fade. + BlackScreenWatcher exit_black(COLOR_RED); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + for (const auto& move : sequences[stage]){ + // repeat_dpad(ctx, dpad, move.dir, 80ms, 300ms, move.count, true); + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + if (move.delay > 0ms){ + pbf_wait(ctx, move.delay); + } + } + } + pbf_wait(ctx, 5000ms); + }, + {{exit_black}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Pastoria Gym return: Exit transition did not start.", + stream); + } + BlackScreenWatcher outside(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{outside}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Pastoria Gym return: Exit transition did not finish.", + stream); + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + break; + } + for (const auto& move : sequences[stage]){ + // repeat_dpad(context, dpad, move.dir, 80ms, 300ms, move.count, true); + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay > 0ms){ + pbf_wait(context, move.delay); + } } } context.wait_for_all_requests(); - // Replace recorded battle/menu inputs with the existing slot-based handler. + const bool gym_leader = stage == 4; + const std::string label = + gym_leader ? "Crasher Wake" : "Pastoria Gym trainer " + std::to_string(stage + 1); pbf_press_button(context, BUTTON_A, 150ms, 300ms); fight_trainer_using_moves(stream, context, 1, label, gym_leader); clear_post_battle_dialogue(stream, context, BUTTON_B, gym_leader ? 6000ms : 3000ms); } stream.log( - "[Pastoria Gym puzzle] Crasher Wake defeated and reward dialogue cleared; remaining inside the Gym.", + "[Pastoria Gym puzzle] Reward dialogue cleared; outside the Gym doorway, ready for 05.01.", COLOR_GREEN); }, true); From 381c055ac8f00f2a26d9de4c913f291ce3828e0f Mon Sep 17 00:00:00 2001 From: Developer-Butters Date: Fri, 30 Oct 2026 13:34:52 -0500 Subject: [PATCH 39/39] Revise BDSP AutoStory checkpoints and organize segment layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate checkpoints in segments 04–09, update dropdowns, and remove obsolete checkpoints. Organize helpers, named navigation blocks, segment wrappers, and checkpoint wrappers consistently. Keep pathing in navigation blocks while checkpoints sequence routes and handle saves and retries. Split tutorial 00.04 into catch and post-catch checkpoints, renumber the final checkpoint, and remove the redundant diagnostic checkbox. Migrate saved selections and clearly mark missing pathing. --- .../AutoStory/PokemonBDSP_AutoStory.cpp | 241 +-- .../AutoStory/PokemonBDSP_AutoStory.h | 4 +- .../AutoStory/PokemonBDSP_AutoStoryTools.cpp | 39 +- .../AutoStory/PokemonBDSP_AutoStoryTools.h | 31 +- .../PokemonBDSP_AutoStory_Segment_00.cpp | 247 ++- .../PokemonBDSP_AutoStory_Segment_00.h | 76 +- .../PokemonBDSP_AutoStory_Segment_01.cpp | 24 +- .../PokemonBDSP_AutoStory_Segment_01.h | 6 +- .../PokemonBDSP_AutoStory_Segment_02.cpp | 68 +- .../PokemonBDSP_AutoStory_Segment_03.cpp | 120 +- .../PokemonBDSP_AutoStory_Segment_04.cpp | 1155 +++++--------- .../PokemonBDSP_AutoStory_Segment_04.h | 192 --- .../PokemonBDSP_AutoStory_Segment_05.cpp | 1272 +++++++--------- .../PokemonBDSP_AutoStory_Segment_05.h | 181 --- .../PokemonBDSP_AutoStory_Segment_06.cpp | 581 +++---- .../PokemonBDSP_AutoStory_Segment_06.h | 105 -- .../PokemonBDSP_AutoStory_Segment_07.cpp | 982 ++++-------- .../PokemonBDSP_AutoStory_Segment_07.h | 180 --- .../PokemonBDSP_AutoStory_Segment_08.cpp | 1350 +++++------------ .../PokemonBDSP_AutoStory_Segment_08.h | 282 ---- .../PokemonBDSP_AutoStory_Segment_09.cpp | 809 +++++----- .../PokemonBDSP_AutoStory_Segment_09.h | 83 - ...PokemonBDSP_AutoStory_Segment_00_tools.cpp | 122 +- 23 files changed, 2813 insertions(+), 5337 deletions(-) diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp index f4b9f1676b..67fce06504 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.cpp @@ -3,6 +3,10 @@ #include #include +#include "Common/Cpp/Json/JsonObject.h" +#include "Common/Cpp/Json/JsonValue.h" + +#include "CommonFramework/Exceptions/FatalProgramException.h" #include "CommonFramework/Globals.h" #include "CommonFramework/Notifications/ProgramNotifications.h" @@ -65,6 +69,7 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); + checkpoint_list.emplace_back(std::make_unique()); //Segment 01 checkpoint_list.emplace_back(std::make_unique()); @@ -94,19 +99,6 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); // Segment 05 checkpoint_list.emplace_back(std::make_unique()); @@ -114,31 +106,12 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); // Segment 06 checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); // Segment 07 checkpoint_list.emplace_back(std::make_unique()); @@ -148,17 +121,6 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); // Segment 08 checkpoint_list.emplace_back(std::make_unique()); @@ -170,23 +132,6 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); // Segment 09 checkpoint_list.emplace_back(std::make_unique()); @@ -198,11 +143,6 @@ static std::vector> make_checkpoint_list() checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); - checkpoint_list.emplace_back(std::make_unique()); //checkpoint_list.emplace_back(std::make_unique()); @@ -254,9 +194,12 @@ static const std::vector>& ALL_CHECKPOINTS // Segment database // --------------------------------------------------------------------------- -// Section databases retain global index slugs. Never execute across the -// tutorial/main-story boundary: the starter must be returned from HOME first. -static constexpr size_t TUTORIAL_CHECKPOINT_COUNT = 6; +// Preserve Tutorial and Segments 01-03 numeric selections. Revised Segments +// 04-09 use explicit slugs, and obsolete selections require reselection. +// Keep Tutorial and Main Story separate for the manual HOME transfer. +static constexpr size_t TUTORIAL_CHECKPOINT_COUNT = 7; +// Keep existing Main Story option IDs stable after splitting the tutorial. +static constexpr size_t LEGACY_MAIN_CHECKPOINT_START = 6; static const StringSelectDatabase& section_database(bool checkpoints, bool tutorial){ static const StringSelectDatabase tutorial_segments = [] { StringSelectDatabase db; @@ -273,15 +216,23 @@ static const StringSelectDatabase& section_database(bool checkpoints, bool tutor static const StringSelectDatabase tutorial_checkpoints = [] { StringSelectDatabase db; for (size_t i = 0; i < TUTORIAL_CHECKPOINT_COUNT; i++){ - db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + const std::string name = ALL_CHECKPOINTS()[i]->name(); + db.add_entry(StringSelectEntry(name.substr(0, 5), name)); } return db; }(); static const StringSelectDatabase main_checkpoints = [] { StringSelectDatabase db; + size_t legacy_index = LEGACY_MAIN_CHECKPOINT_START; for (size_t i = TUTORIAL_CHECKPOINT_COUNT; i < ALL_CHECKPOINTS().size(); i++){ - db.add_entry(StringSelectEntry(std::to_string(i), ALL_CHECKPOINTS()[i]->name())); + const std::string name = ALL_CHECKPOINTS()[i]->name(); + if (name.compare(0, 3, "04.") >= 0 && name.compare(0, 3, "09.") <= 0){ + db.add_entry(StringSelectEntry(name.substr(0, 5), name)); + }else{ + db.add_entry(StringSelectEntry(std::to_string(legacy_index++), name)); + } } + db.add_entry(StringSelectEntry("checkpoint-reselect", "Select a revised checkpoint (old selection)")); return db; }(); return checkpoints ? (tutorial ? tutorial_checkpoints : main_checkpoints) @@ -338,14 +289,6 @@ BDSPAutoStory::~BDSPAutoStory(){ BDSPAutoStory::BDSPAutoStory() : SingleSwitchProgramInstance() // DIAGNOSTIC ONLY: Remove before pushing to production. - , DIAGNOSTIC_POST_CATCH_ONLY( - "Diagnostic only: Run 00.04 after catch through 00.05
" - "Start at the saved position immediately after catching the second Pokemon. " - "Applies only in advanced Tutorial mode when Start Checkpoint is 00.04. Runs its post-catch part, then 00.05. Other starting checkpoints run normally. " - "Remove before pushing to production.", - LockMode::LOCK_WHILE_RUNNING, - false) - // DIAGNOSTIC ONLY: Remove before pushing to production. , DIAGNOSTIC_FAKE_CHECKPOINT_SAVE( "Diagnostic only: Fake checkpoint saves
" "Use the fake save helper instead of normal checkpoint saving. " @@ -368,11 +311,11 @@ BDSPAutoStory::BDSPAutoStory() , START_CHECKPOINT_TUTORIAL("Start Checkpoint:", section_database(true, true), LockMode::LOCK_WHILE_RUNNING, - "0") + "00.00") , END_CHECKPOINT_TUTORIAL("End Checkpoint:", section_database(true, true), LockMode::UNLOCK_WHILE_RUNNING, - "5") + "00.06") , START_DESCRIPTION_SEGMENT("Start:
", "") , END_DESCRIPTION_SEGMENT("End:
", "") @@ -388,7 +331,7 @@ BDSPAutoStory::BDSPAutoStory() , END_CHECKPOINT_MAINSTORY("End Checkpoint (Main):", section_database(true, false), LockMode::UNLOCK_WHILE_RUNNING, - std::to_string(ALL_CHECKPOINTS().size() - 1)) + section_database(true, false)[ALL_CHECKPOINTS().size() - TUTORIAL_CHECKPOINT_COUNT - 1].slug) , START_SEGMENT("Start Segment:", section_database(false, true), LockMode::LOCK_WHILE_RUNNING, "0") @@ -523,7 +466,6 @@ BDSPAutoStory::BDSPAutoStory() // Options // ----------------------------------------------------------------------- - PA_ADD_OPTION(DIAGNOSTIC_POST_CATCH_ONLY); PA_ADD_OPTION(DIAGNOSTIC_FAKE_CHECKPOINT_SAVE); PA_ADD_OPTION(STORY_SECTION); PA_ADD_OPTION(MAINSTORY_NOTE); @@ -576,6 +518,74 @@ BDSPAutoStory::BDSPAutoStory() END_CHECKPOINT_MAINSTORY.add_listener(*this); on_config_value_changed(nullptr); } +std::string BDSPAutoStory::check_validity() const { + const std::string error = SingleSwitchProgramInstance::check_validity(); + if (!error.empty()){ + return error; + } + if (ENABLE_ADVANCED_MODE && STORY_SECTION == StorySection::MAIN_STORY && + (START_CHECKPOINT_MAINSTORY.slug() == "checkpoint-reselect" || + END_CHECKPOINT_MAINSTORY.slug() == "checkpoint-reselect")){ + return "Checkpoints in Segments 04 through 09 have been consolidated. Select the revised start and end " + "checkpoints that match your in-game save before starting."; + } + return {}; +} + +void BDSPAutoStory::load_json(const JsonValue& json){ + SingleSwitchProgramInstance::load_json(json); + const JsonObject* object = json.to_object(); + if (!object){ + return; + } + const char* keys[] = {"START_CHECKPOINT_MAINSTORY", "END_CHECKPOINT_MAINSTORY"}; + StringSelectOption* selections[] = {&START_CHECKPOINT_MAINSTORY, &END_CHECKPOINT_MAINSTORY}; + for (size_t selection = 0; selection < 2; selection++){ + const std::string* slug = object->get_string(keys[selection]); + if (!slug){ + continue; + } + if (*slug == "07.17" || *slug == "06-reselect" || *slug == "07-reselect"){ + selections[selection]->set_by_slug("checkpoint-reselect"); + continue; + } + for (size_t old_index = 22; old_index <= 125; old_index++){ + if (*slug == std::to_string(old_index)){ + selections[selection]->set_by_slug("checkpoint-reselect"); + break; + } + } + } + const char* tutorial_keys[] = {"START_CHECKPOINT_TUTORIAL", "END_CHECKPOINT_TUTORIAL"}; + StringSelectOption* tutorial_selections[] = {&START_CHECKPOINT_TUTORIAL, &END_CHECKPOINT_TUTORIAL}; + for (size_t selection = 0; selection < 2; selection++){ + const std::string* slug = object->get_string(tutorial_keys[selection]); + if (!slug){ + continue; + } + for (size_t old_index = 0; old_index <= 5; old_index++){ + if (*slug == std::to_string(old_index)){ + size_t revised_index = old_index; + if (old_index == 5){ + revised_index = 6; + }else if (old_index == 4 && selection == 1){ + // Old 00.04 included both the catch and post-catch route. + revised_index = 5; + } + tutorial_selections[selection]->set_by_slug("00.0" + std::to_string(revised_index)); + break; + } + } + } + // Preserve the intent of a saved post-catch diagnostic configuration. + const std::string* old_tutorial_start = object->get_string("START_CHECKPOINT_TUTORIAL"); + if (object->get_boolean_default("DIAGNOSTIC_POST_CATCH_ONLY") && old_tutorial_start && *old_tutorial_start == "4"){ + START_CHECKPOINT_TUTORIAL.set_by_slug("00.05"); + END_CHECKPOINT_TUTORIAL.set_by_slug("00.06"); + } + on_config_value_changed(nullptr); +} + void BDSPAutoStory::update_ui_state(){ const bool advanced = ENABLE_ADVANCED_MODE; const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; @@ -610,14 +620,20 @@ void BDSPAutoStory::on_config_value_changed(void*){ if (ENABLE_ADVANCED_MODE){ const auto& checkpoints = ALL_CHECKPOINTS(); - size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); + size_t start = tutorial ? START_CHECKPOINT_TUTORIAL.index() + : START_CHECKPOINT_MAINSTORY.index() + TUTORIAL_CHECKPOINT_COUNT; + size_t end = tutorial ? END_CHECKPOINT_TUTORIAL.index() + : END_CHECKPOINT_MAINSTORY.index() + TUTORIAL_CHECKPOINT_COUNT; if (start < checkpoints.size()){ START_DESCRIPTION_CHECKPOINT.set_text("Start: " + checkpoints[start]->start_text()); + }else{ + START_DESCRIPTION_CHECKPOINT.set_text("Select a revised checkpoint matching your in-game save."); } if (end < checkpoints.size()){ END_DESCRIPTION_CHECKPOINT.set_text("End: " + checkpoints[end]->end_text()); + }else{ + END_DESCRIPTION_CHECKPOINT.set_text("Select a revised ending checkpoint."); } }else{ @@ -641,6 +657,10 @@ void BDSPAutoStory::on_config_value_changed(void*){ // --------------------------------------------------------------------------- void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + const std::string error = check_validity(); + if (!error.empty()){ + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, error); + } AutoStoryStats& stats = env.current_stats(); AutoStoryOptions options{Language::English, @@ -656,30 +676,41 @@ void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerCo }}, DIAGNOSTIC_FAKE_CHECKPOINT_SAVE ? CheckpointSaveMode::FAKE : CheckpointSaveMode::REAL}; + stats.starter_choice = options.starter_choice; + switch (options.starter_choice){ + case StarterChoice::TURTWIG: + stats.move_pp_capacity = {15, 15, 10, 25}; + break; + case StarterChoice::CHIMCHAR: + stats.move_pp_capacity = {15, 15, 10, 30}; + break; + case StarterChoice::PIPLUP: + stats.move_pp_capacity = {20, 10, 15, 35}; + break; + } + // A direct main-story start requires full HP/PP, as described in the wiki. + // After startup, only confirmed healing or a successful save reload refills it. + stats.move_pp_remaining = stats.move_pp_capacity; + const bool fake_saves = options.checkpoint_save_mode == CheckpointSaveMode::FAKE; if (fake_saves){ options.checkpoint_retry_scope = CheckpointRetryScope::DEFER_TO_SELECTED_RANGE; } + bool tutorial_completed = false; auto run_selected_range = [&](size_t attempt){ if (attempt > 0){ env.log("[Fake saves] Reloaded initial save; restarting the entire selected range from its start."); } - // DIAGNOSTIC ONLY: Remove this branch before pushing to production. - if (DIAGNOSTIC_POST_CATCH_ONLY && ENABLE_ADVANCED_MODE && STORY_SECTION == StorySection::TUTORIAL && - START_CHECKPOINT_TUTORIAL.slug() == "4"){ - env.log("Diagnostic only: Running checkpoint 00.04 after catch."); - diagnostic_checkpoint_0004_after_catch(env, context, options, stats); - env.log("Diagnostic continuation: Running checkpoint 00.05."); - AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); - return; - } + const bool tutorial = STORY_SECTION == StorySection::TUTORIAL; const auto& segments = ALL_SEGMENTS(); if (ENABLE_ADVANCED_MODE){ - size_t start = std::stoul((tutorial ? START_CHECKPOINT_TUTORIAL : START_CHECKPOINT_MAINSTORY).slug()); - size_t end = std::stoul((tutorial ? END_CHECKPOINT_TUTORIAL : END_CHECKPOINT_MAINSTORY).slug()); + size_t start = tutorial ? START_CHECKPOINT_TUTORIAL.index() + : START_CHECKPOINT_MAINSTORY.index() + TUTORIAL_CHECKPOINT_COUNT; + size_t end = tutorial ? END_CHECKPOINT_TUTORIAL.index() + : END_CHECKPOINT_MAINSTORY.index() + TUTORIAL_CHECKPOINT_COUNT; env.log("Starting AutoStory from checkpoint " + std::to_string(start) + " to " + std::to_string(end)); @@ -688,6 +719,9 @@ void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerCo for (size_t i = start; i <= end && i < checkpoints.size(); i++){ env.log("Running Checkpoint: " + std::to_string(i)); checkpoints[i]->run_checkpoint(env, context, options, stats); + if (tutorial && i + 1 == TUTORIAL_CHECKPOINT_COUNT){ + tutorial_completed = true; + } } }else{ @@ -697,6 +731,9 @@ void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerCo for (size_t i = start; i <= end && i < segments.size(); i++){ env.log("Running Segment: " + std::to_string(i)); segments[i]->run_segment(env, context, options, stats); + if (tutorial && i == 0){ + tutorial_completed = true; + } } } }; @@ -713,7 +750,19 @@ void BDSPAutoStory::program(SingleSwitchProgramEnvironment& env, ProControllerCo run_selected_range(0); } - send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); + std::string completion_message; + if (tutorial_completed){ + context.wait_for_all_requests(); + completion_message = "Tutorial complete. Deposit your starter in the Jubilife Pokemon Center PC, " + "save and close BDSP, then transfer it through Pokemon HOME. Return your fully evolved " + "level-100 starter to the first party slot with the wiki moveset and full HP and PP " + "before starting Main Story."; + MAINSTORY_NOTE.set_text(completion_message); + MAINSTORY_NOTE.set_visibility(ConfigOptionState::ENABLED); + env.log_to_ui(completion_message, COLOR_GREEN); + env.console.log(completion_message, COLOR_GREEN); + } + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH, completion_message); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h index 541918b1c0..ef9a50d0d2 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory.h @@ -38,6 +38,8 @@ class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::L ~BDSPAutoStory(); BDSPAutoStory(); + virtual std::string check_validity() const override; + virtual void load_json(const JsonValue& json) override; virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; @@ -46,8 +48,6 @@ class BDSPAutoStory : public SingleSwitchProgramInstance, public ConfigOption::L void update_ui_state(); private: - // DIAGNOSTIC ONLY: Remove before pushing to production. - BooleanCheckBoxOption DIAGNOSTIC_POST_CATCH_ONLY; // DIAGNOSTIC ONLY: Remove before pushing to production. BooleanCheckBoxOption DIAGNOSTIC_FAKE_CHECKPOINT_SAVE; EnumDropdownOption STORY_SECTION; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp index 62de9bebf9..a34f52973d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.cpp @@ -120,6 +120,30 @@ bool save_game(SingleSwitchProgramEnvironment& env, ProControllerContext& contex pbf_mash_button(context, BUTTON_B, 1000ms); } + // BlackScreenOverWatcher can finish while the destination is still + // fading in. Drain movement and wait before sending the menu input. + context.wait_for_all_requests(); + BlackScreenWatcher destination_visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 1000ms); + if (wait_until(env.console, context, 15s, {{destination_visible}}) != 0){ + throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, + "Checkpoint destination did not become visible before saving.", + env.console); + } + pbf_wait(context, 1000ms); + context.wait_for_all_requests(); + + OverworldWatcher ready_to_save; + if (wait_until(env.console, context, 15s, {{ready_to_save}}) != 0){ + throw OperationFailedExceptionWithScreenshot(ErrorReportMode::SEND_ERROR_REPORT, + "Checkpoint destination did not return to the overworld before saving.", + env.console); + } + // Reuse existing feedback-based save: opens menu with MenuWatcher // + respects GameSettings::OVERWORLD_TO_MENU_DELAY0 internally. save_game(env.console, context); @@ -199,6 +223,7 @@ void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, bool save, CheckpointSaveMode save_mode, CheckpointRetryScope retry_scope){ + const auto pp_at_checkpoint = stats.move_pp_remaining; static constexpr size_t MAX_ATTEMPTS = 20; for (size_t i = 0;; i++){ @@ -234,6 +259,8 @@ void checkpoint_reattempt_loop(SingleSwitchProgramEnvironment& env, "Failed to reset game from HOME during checkpoint retry.", env.console); } + // Only a real game reload restores the corresponding PP balance. + stats.move_pp_remaining = pp_at_checkpoint; }else{ env.console.log("Reloading from backup save before retry...", COLOR_ORANGE); reload_backup_save(env, context); @@ -595,7 +622,10 @@ bool buy_items(VideoStream& stream, } -bool heal_pokemon(VideoStream& stream, ProControllerContext& context, const std::string& label){ +bool heal_pokemon(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + AutoStoryStats* stats){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -623,6 +653,11 @@ bool heal_pokemon(VideoStream& stream, ProControllerContext& context, const std: return false; } + context.wait_for_all_requests(); + if (stats){ + stats->move_pp_remaining = stats->move_pp_capacity; + stream.log("[Move PP] " + label + ": restored the selected starter's PP budgets."); + } return true; } @@ -720,10 +755,10 @@ bool fly_to(VideoStream& stream, ProControllerContext& context, FlyPoint place){ case FlyPoint::CelesticTown: pbf_move_left_joystick(context, {-1, +1}, 4000ms, 500ms); break; - case FlyPoint::HearthomeCity: case FlyPoint::OreburghCity: pbf_move_left_joystick(context, {-1, -1}, 4000ms, 500ms); break; + case FlyPoint::HearthomeCity: case FlyPoint::PastoriaCity: case FlyPoint::PokemonLeagueLower: case FlyPoint::PokemonLeagueUpper: diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h index 7d511bea85..b8f4f32279 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h @@ -40,6 +40,12 @@ namespace PokemonBDSP { // Stats // --------------------------------------------------------------------------- +enum class StarterChoice { + TURTWIG, + CHIMCHAR, + PIPLUP, +}; + struct AutoStoryStats : public StatsTracker { AutoStoryStats() : m_checkpoint(m_stats["Checkpoint"]) @@ -53,6 +59,10 @@ struct AutoStoryStats : public StatsTracker { std::atomic& m_segment; std::atomic& m_reset; bool m_first_run = true; + // Main-story slots: contact, non-contact, spread, high-PP backup. + StarterChoice starter_choice = StarterChoice::CHIMCHAR; + std::array move_pp_capacity{15, 15, 10, 30}; + std::array move_pp_remaining = move_pp_capacity; }; @@ -60,12 +70,6 @@ struct AutoStoryStats : public StatsTracker { // Options passed to every segment/checkpoint // --------------------------------------------------------------------------- -enum class StarterChoice { - TURTWIG, - CHIMCHAR, - PIPLUP, -}; - enum class HelpMode { NO_OUTSIDE_HELP, OUTSIDE_HELP, @@ -272,6 +276,16 @@ void fight_trainer_using_moves(VideoStream& stream, const std::string& label, bool post_battle_dialogue_cleanup); +// Use the selected starter's slot-role priorities and retain PP across battles. +// Slots 1/2/3/4 are contact/non-contact/spread/backup. Exhausted slots are +// skipped only within this battle's allowed priorities. One confirmed selection +// reserves one PP; this budget does not replace actual-PP OCR or model Pressure. +void fight_trainer_using_moves(VideoStream& stream, + ProControllerContext& context, + AutoStoryStats& stats, + const std::string& label, + bool post_battle_dialogue_cleanup); + // A full Bag screenshot with the desired item highlighted. Paths are relative // to RESOURCE_PATH(); quantities and selection borders are excluded. struct BagItemTemplate { @@ -372,7 +386,10 @@ void use_HM(VideoStream& stream, ProControllerContext& context, const std::strin // Walk to the nurse, heal the party, and exit the Pokemon Center. // `label` names the center for log messages (e.g. "Veilstone"). // Returns false if the exit black screen was not detected. -bool heal_pokemon(VideoStream& stream, ProControllerContext& context, const std::string& label); +bool heal_pokemon(VideoStream& stream, + ProControllerContext& context, + const std::string& label, + AutoStoryStats* stats = nullptr); enum class FlyPoint { CanalavCity, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp index 6b68b60593..42ef5e0d5f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.cpp @@ -1118,16 +1118,19 @@ tutorial_part_4_navigation(SingleSwitchProgramEnvironment& env, VideoStream& str // Segment / Checkpoint classes // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- + std::string AutoStory_Segment_00::name() const { - return "00: Intro to Poketech"; + return "00: Intro to Poketch"; } std::string AutoStory_Segment_00::start_text() const { return "Start: Game launched, Standing in the player's room after tv stops playing"; } std::string AutoStory_Segment_00::end_text() const { - return "End: Starter Pokemon chosen, received, and Pokedex acquired, standing in front of the PC."; + return "End: Poketch received; standing at the Jubilife Pokemon Center PC, ready to deposit the starter for the HOME transfer."; } - void AutoStory_Segment_00::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1143,11 +1146,16 @@ void AutoStory_Segment_00::run_segment(SingleSwitchProgramEnvironment& env, AutoStory_Checkpoint_0003().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0004().run_checkpoint(env, context, options, stats); AutoStory_Checkpoint_0005().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0006().run_checkpoint(env, context, options, stats); context.wait_for_all_requests(); env.console.log("End Segment " + name(), COLOR_GREEN); } +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + std::string AutoStory_Checkpoint_0000::name() const { return "00.00 - Verify Settings"; } @@ -1161,13 +1169,36 @@ void AutoStory_Checkpoint_0000::run_checkpoint(SingleSwitchProgramEnvironment& e ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const { + checkpoint_0000(env, context, options, stats); +} + +void checkpoint_0000(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions /*options*/, + AutoStoryStats& /*stats*/){ // Before any route movement or checkpoint save: fail visibly if // options cannot be read, rather than navigate with unknown settings. verify_tutorial_settings(env.console, context); context.wait_for_all_requests(); } -static void checkpoint_0001(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0001::name() const { + return "00.01 - After TV"; +} +std::string AutoStory_Checkpoint_0001::start_text() const { + return "Game launched, Standing in the player's room after tv stops playing"; +} +std::string AutoStory_Checkpoint_0001::end_text() const { + return "Standing outside Lake Verity before picking starter."; +} +void AutoStory_Checkpoint_0001::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0001(env, context, options, stats); +} + +void checkpoint_0001(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1182,7 +1213,24 @@ static void checkpoint_0001(SingleSwitchProgramEnvironment& env, }, true); } -static void checkpoint_0002(SingleSwitchProgramEnvironment& env, + +std::string AutoStory_Checkpoint_0002::name() const { + return "00.02 - Outside Verity before starter"; +} +std::string AutoStory_Checkpoint_0002::start_text() const { + return AutoStory_Checkpoint_0001().end_text(); +} +std::string AutoStory_Checkpoint_0002::end_text() const { + return "Standing outside Lake Verity after picking starter."; +} +void AutoStory_Checkpoint_0002::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0002(env, context, options, stats); +} + +void checkpoint_0002(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1211,7 +1259,24 @@ static void checkpoint_0002(SingleSwitchProgramEnvironment& env, }, true); } -static void checkpoint_0003(SingleSwitchProgramEnvironment& env, + +std::string AutoStory_Checkpoint_0003::name() const { + return "00.03 - Outside Verity after starter"; +} +std::string AutoStory_Checkpoint_0003::start_text() const { + return AutoStory_Checkpoint_0002().end_text(); +} +std::string AutoStory_Checkpoint_0003::end_text() const { + return "Standing in front of The professor after recieving the Pokedex"; +} +void AutoStory_Checkpoint_0003::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0003(env, context, options, stats); +} + +void checkpoint_0003(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1226,7 +1291,24 @@ static void checkpoint_0003(SingleSwitchProgramEnvironment& env, }, true); } -static void checkpoint_0004(SingleSwitchProgramEnvironment& env, + +std::string AutoStory_Checkpoint_0004::name() const { + return "00.04 - After Pokedex"; +} +std::string AutoStory_Checkpoint_0004::start_text() const { + return AutoStory_Checkpoint_0003().end_text(); +} +std::string AutoStory_Checkpoint_0004::end_text() const { + return "On Route 202 after catching the second Pokemon, with the catch sequence finished."; +} +void AutoStory_Checkpoint_0004::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0004(env, context, options, stats); +} + +void checkpoint_0004(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1240,36 +1322,25 @@ static void checkpoint_0004(SingleSwitchProgramEnvironment& env, tutorial_part_3_navigation(env, env.console, context); }, true); +} - // Save and advance progress at the catch boundary. Retries of this - // section must start from the caught-Pokemon save, not the old route. - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - tutorial_part_3_after_catch_navigation(env, env.console, context); - }, - true); +std::string AutoStory_Checkpoint_0005::name() const { + return "00.05 - Route 202 after catch to Jubilife"; +} +std::string AutoStory_Checkpoint_0005::start_text() const { + return AutoStory_Checkpoint_0004().end_text(); } -// DIAGNOSTIC ONLY: Remove before pushing to production. -void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0005::end_text() const { + return "In Jubilife after speaking to Dawn, ready to deliver the Town Map to Barry."; +} +void AutoStory_Checkpoint_0005::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - tutorial_part_3_after_catch_navigation(env, env.console, context); - }, - true); + AutoStoryStats& stats) const { + checkpoint_0005(env, context, options, stats); } -static void checkpoint_0005(SingleSwitchProgramEnvironment& env, +void checkpoint_0005(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1279,113 +1350,41 @@ static void checkpoint_0005(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - // Segment 00-004: Poketech acquisition - tutorial_part_4_navigation(env, env.console, context); + tutorial_part_3_after_catch_navigation(env, env.console, context); }, true); } -// ======================== -// Checkpoint 0001 Wrapper -// ======================== -std::string AutoStory_Checkpoint_0001::name() const { - return "00.01 - After TV"; -} -std::string AutoStory_Checkpoint_0001::start_text() const { - return "Game launched, Standing in the player's room after tv stops playing"; -} -std::string AutoStory_Checkpoint_0001::end_text() const { - return "Standing outside Lake Verity before picking starter."; -} - -void AutoStory_Checkpoint_0001::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0001(env, context, options, stats); -} - -// ======================== -// Checkpoint 0002 Wrapper -// ======================== - -std::string AutoStory_Checkpoint_0002::name() const { - return "00.02 - Outside Verity before starter"; -} -std::string AutoStory_Checkpoint_0002::start_text() const { - return AutoStory_Checkpoint_0001().end_text(); -} -std::string AutoStory_Checkpoint_0002::end_text() const { - return "Standing outside Lake Verity after picking starter."; -} - -void AutoStory_Checkpoint_0002::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0002(env, context, options, stats); -} - - -// ======================== -// Checkpoint 0003 Wrapper -// ======================== - -std::string AutoStory_Checkpoint_0003::name() const { - return "00.03 - Outside Verity after starter"; -} -std::string AutoStory_Checkpoint_0003::start_text() const { - return AutoStory_Checkpoint_0002().end_text(); -} -std::string AutoStory_Checkpoint_0003::end_text() const { - return "Standing in front of The professor after recieving the Pokedex"; -} - -void AutoStory_Checkpoint_0003::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0003(env, context, options, stats); -} -// ======================== -// Checkpoint 0004 Wrapper -// ======================== - -std::string AutoStory_Checkpoint_0004::name() const { - return "00.04 - After Pokedex"; +std::string AutoStory_Checkpoint_0006::name() const { + return "00.06 - Jubilife to Poketch"; } -std::string AutoStory_Checkpoint_0004::start_text() const { - return AutoStory_Checkpoint_0003().end_text(); +std::string AutoStory_Checkpoint_0006::start_text() const { + return AutoStory_Checkpoint_0005().end_text(); } -std::string AutoStory_Checkpoint_0004::end_text() const { - return "Standing in in jubilife after speaking to dawn. Next: give town map to barry"; +std::string AutoStory_Checkpoint_0006::end_text() const { + return "Standing at the Jubilife Pokemon Center PC with the Poketch received, ready to deposit the starter."; } - -void AutoStory_Checkpoint_0004::run_checkpoint(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0006::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const { - checkpoint_0004(env, context, options, stats); -} -// ======================== -// Checkpoint 0005 Wrapper -// ======================== - -std::string AutoStory_Checkpoint_0005::name() const { - return "00.05 - Jubilife After Dawn"; -} -std::string AutoStory_Checkpoint_0005::start_text() const { - return AutoStory_Checkpoint_0004().end_text(); -} -std::string AutoStory_Checkpoint_0005::end_text() const { - return "Standing in front of PC. Recieved the Poketech. Ready to deposit starter"; + checkpoint_0006(env, context, options, stats); } -void AutoStory_Checkpoint_0005::run_checkpoint(SingleSwitchProgramEnvironment& env, +void checkpoint_0006(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0005(env, context, options, stats); + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + // Segment 00-004: Poketech acquisition + tutorial_part_4_navigation(env, env.console, context); + }, + true); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h index 681d09caa8..12f6a84a18 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_00.h @@ -15,13 +15,6 @@ namespace NintendoSwitch { namespace PokemonBDSP { -// DIAGNOSTIC ONLY: Remove before pushing to production. -// Starts at the post-catch save and runs only the rest of checkpoint 00.04. -void diagnostic_checkpoint_0004_after_catch(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - class AutoStory_Segment_00 : public AutoStory_Segment { public: virtual std::string name() const override; @@ -43,6 +36,10 @@ class AutoStory_Checkpoint_0000 : public AutoStory_Checkpoint { AutoStoryOptions options, AutoStoryStats& stats) const override; }; +void checkpoint_0000(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint { public: @@ -54,6 +51,10 @@ class AutoStory_Checkpoint_0001 : public AutoStory_Checkpoint { AutoStoryOptions options, AutoStoryStats& stats) const override; }; +void checkpoint_0001(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint { public: @@ -65,6 +66,10 @@ class AutoStory_Checkpoint_0002 : public AutoStory_Checkpoint { AutoStoryOptions options, AutoStoryStats& stats) const override; }; +void checkpoint_0002(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint { public: @@ -76,6 +81,10 @@ class AutoStory_Checkpoint_0003 : public AutoStory_Checkpoint { AutoStoryOptions options, AutoStoryStats& stats) const override; }; +void checkpoint_0003(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint { public: @@ -87,58 +96,41 @@ class AutoStory_Checkpoint_0004 : public AutoStory_Checkpoint { AutoStoryOptions options, AutoStoryStats& stats) const override; }; +void checkpoint_0004(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats); class AutoStory_Checkpoint_0005 : public AutoStory_Checkpoint { public: virtual std::string name() const override; virtual std::string start_text() const override; virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const override; }; - -// start: game launched, in front of the TV. -// end: Standing outside lake varety before starter -//Segment 00-00 begins -void checkpoint_000(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats); - -// start: Standing outside lake varety before starter -// end: Standing outside lake varety after starter battle -//Segment 00-001 begins -void checkpoint_001(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats); - -// start: Standing outside lake varety after starter battle -// end: recieved Pokedex, standing in front of the professor -//Segment 00-002 begins -void checkpoint_002(SingleSwitchProgramEnvironment& env, +void checkpoint_0005(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, + AutoStoryOptions options, AutoStoryStats& stats); -// start: recieved Pokedex, standing in front of the professor -// end: Standing in the entrance to Jubilife City after speaking to dawn -//Segment 00-003 begins -void checkpoint_003(SingleSwitchProgramEnvironment& env, +class AutoStory_Checkpoint_0006 : public AutoStory_Checkpoint { +public: + virtual std::string name() const override; + virtual std::string start_text() const override; + virtual std::string end_text() const override; + virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const override; +}; +void checkpoint_0006(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - EventNotificationOption& notif_status_update, + AutoStoryOptions options, AutoStoryStats& stats); -// start: Standing in the entrance to Jubilife City after speaking to dawn -// end: Standing in the Pokemon Center ready to deposit pokemon after recieving poketech. -//Segment 00-004 begins -void checkpoint_004(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - EventNotificationOption& notif_status_update, - AutoStoryStats& stats); } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp index 03d1aa16e2..f18f8fc403 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.cpp @@ -202,7 +202,7 @@ static std::chrono::milliseconds walk_until_doorway(VideoStream& stream, // --------------------------------------------------------------------------- void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ + ProControllerContext& context, AutoStoryStats& stats){ // Wait for game to fully accept input after save/overworld return pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -221,7 +221,7 @@ void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, wait_for_dialogue(stream, context, "Phase 1 (Barry 1 fight dialog)"); pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); pbf_mash_button(context, BUTTON_B, 5000ms); context.wait_for_all_requests(); pbf_move_left_joystick(context, {1.000000, -0.000000}, 2761ms, 0ms); @@ -229,7 +229,7 @@ void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, walk_through_grass(stream, context, 1, 0, 5, "right through grass"); wait_for_dialogue(stream, context, "Dallas fight dialog)"); pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); //fighting Dallas + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); //fighting Dallas context.wait_for_all_requests(); walk_through_grass(stream, context, 1, 0, 5, "right through grass"); pbf_move_left_joystick(context, {1.000000, -0.000000}, 600ms, 0ms); @@ -238,7 +238,7 @@ void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, //function addresses that edge case if (try_detect_dialogue(stream, context, "Sebastian fight")){ pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); //fighting Sebastian + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); //fighting Sebastian context.wait_for_all_requests(); }else{ stream.log("Sebastian dialogue never appeared. Skipping fight.", COLOR_ORANGE); @@ -249,7 +249,7 @@ void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, context.wait_for_all_requests(); enter_cave_with_down_right_search(stream, context, "jubilife_to_oreburgh_gate", 10000ms); } -void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ pbf_wait(context, 2000ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 879ms, 0ms); wait_for_dialogue(stream, context, "HM Rock Smash Dialog"); @@ -261,13 +261,13 @@ void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, walk_through_grass(stream, context, 1, 0, 1, "right through gate"); } pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); //fighting Curtis + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); //fighting Curtis walk_through_grass(stream, context, 1, 0, 10, "right through gate"); while (!try_detect_dialogue(stream, context, "Diana fight", 1, 3000ms)){ walk_through_grass(stream, context, 1, 0, 1, "right through gate"); } pbf_mash_button(context, BUTTON_B, 5000ms); - Barry_1_fight(env, stream, context); //fighting Diana + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); //fighting Diana // Walk right until the gate exit transition (white screen, or black screen without a battle menu). // Wild battles are escaped without using up the budget. Throws on failure so the // checkpoint is retried. @@ -1601,7 +1601,7 @@ void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stre // Navigates the player through the first Gym in Oreburgh City and completes the Gym battle. // --------------------------------------------------------------------------- -void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ //Upon entering the Gym. // The entry camera sequence lasts about 7.5 seconds in the reference @@ -1636,7 +1636,7 @@ void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro wait_for_dialogue(stream, context, "Phase 3 (Roark fight dialog)"); pbf_mash_button(context, BUTTON_B, 5000ms); //Barry 1 battle logic can be used here since the moves don't matter this early. - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Roark", false); context.wait_for_all_requests(); wait_for_dialogue(stream, context, "Phase 4 (Roark post battle dialog)"); @@ -1735,8 +1735,8 @@ void checkpoint_0101(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - jubilife_to_oreburgh_gate(env, env.console, context); - oreburgh_gate_nav(env, env.console, context); + jubilife_to_oreburgh_gate(env, env.console, context, stats); + oreburgh_gate_nav(env, env.console, context, stats); }, true); } @@ -1817,7 +1817,7 @@ void checkpoint_0103(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ //#. Oreburgh Gym navigation and battle - gym_Puzzle_01(env, env.console, context); + gym_Puzzle_01(env, env.console, context, stats); }, true); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h index a9cd8d6a76..dee25efb45 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_01.h @@ -38,9 +38,9 @@ struct MineRouteTestOptions { bool reset_on_failure = false; }; -void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); +void jubilife_to_oreburgh_gate(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats); -void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); +void oreburgh_gate_nav(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats); void oreburgh_gate_to_mine(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); @@ -48,7 +48,7 @@ void oreburgh_mine_nav(VideoStream& stream, ProControllerContext& context); void oreburgh_mine_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); -void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context); +void gym_Puzzle_01(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats); // Starts inside the mine at an entrance position; stops outside after // talking to Roark and using Escape Rope. Does not reset on failure. diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp index bca31cf01c..ea85cc755f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_02.cpp @@ -855,7 +855,7 @@ static bool align_to_route204_sign(VideoStream& stream, ProControllerContext& co static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ + ProControllerContext& context, AutoStoryStats& stats){ context.wait_for_all_requests(); // The cave's brown corridor and black map void distinguish an // indoor return from a battle fade or an outdoor scene. All ROIs @@ -876,7 +876,7 @@ static void enter_ravaged_path_from_route204(SingleSwitchProgramEnvironment& env auto handle_trainer = [&] { stream.log("[Ravaged Path approach] Lass Samantha triggered; completing battle before continuing."); wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); }; Route204ArrivalTracker arrival; @@ -1137,7 +1137,7 @@ static void exit_ravaged_path_to_route204(VideoStream& stream, ProControllerCont context.wait_for_all_requests(); } -void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ pbf_move_left_joystick(context, {1.000000, -0.000000}, 550ms, 0ms); pbf_wait(context, 1046ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 2000ms, 0ms); @@ -1256,7 +1256,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, align_to_overworld_landmarks(stream, context, match_jubilife_plaza_approach, "Jubilife plaza alignment", true); approach_galactic_dialogue(stream, context); wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); pbf_wait(context, 2000ms); clear_post_battle_dialogue(stream, context); @@ -1280,7 +1280,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, // entered the building before exterior alignment even started. // Repel is inactive here; no expiry search is needed before the Center. align_and_enter_jubilife_center(stream, context); - if (!heal_pokemon(stream, context, "Jubilife")){ + if (!heal_pokemon(stream, context, "Jubilife", &stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Jubilife Center: Healing/exit did not complete.", stream); @@ -1296,7 +1296,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, recover_route204_female_trainer(stream, context); } wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); // Resume encounter protection after the Center and the following battle. if (!activate_repel(stream, context, 6)){ @@ -1322,7 +1322,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, // An early locator interruption still has to complete combat; // it must never fall through into overworld movement mid-battle. wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); // Keep the recently activated Repel through Ravaged Path. // Reacquire west of the sign after Tyler before moving north; @@ -1334,7 +1334,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, break; } wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); } if (!northbound_aligned){ @@ -1342,7 +1342,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, "Route 204 north approach: Trainer interruptions exhausted.", stream); } - enter_ravaged_path_from_route204(env, stream, context); + enter_ravaged_path_from_route204(env, stream, context, stats); //cave navigation // Ravaged Path -> Floaroma -> Valley Windworks -> Mars. @@ -1356,7 +1356,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, auto battle = [&](const std::string& label){ stream.log("[Segment 02 battle] " + label); wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); }; // Break the blocking rock using the existing HM confirmation helper. @@ -1396,7 +1396,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, } stream.log("[Segment 02 battle] Aroma Lady Teighler: move slot 2."); wait_for_trainer_battle_menu(stream, context); - fight_trainer_using_move_slot(stream, context, 2, "Aroma Lady Teighler"); + fight_trainer_using_moves(stream, context, stats, "Aroma Lady Teighler", false); clear_post_battle_dialogue(stream, context); // Omit the Awakening. The grass north of Teighler can trigger @@ -1477,7 +1477,7 @@ void roark_to_floaroma(SingleSwitchProgramEnvironment& env, VideoStream& stream, stream.log("[Segment 02] Floaroma house exit established; ready for town checkpoint.", COLOR_GREEN); } -static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms){ pbf_move_left_joystick(context, {x, y}, duration, 0ms); pbf_wait(context, settle); @@ -1535,7 +1535,7 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s auto battle = [&](const std::string& label){ stream.log("[Segment 02 battle] " + label); wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); }; auto talk = [&](const std::string& label, Button response = BUTTON_B){ @@ -1559,7 +1559,7 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s } transition(0, 1, 950ms, "Floaroma Pokemon Center entrance"); // heal_pokemon includes a watched exit; do not exit a second time. - if (!heal_pokemon(stream, context, "Floaroma")){ + if (!heal_pokemon(stream, context, "Floaroma", &stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Floaroma Center: Healing/exit did not complete.", stream); @@ -1588,7 +1588,7 @@ static void floaroma_to_mars(SingleSwitchProgramEnvironment& env, VideoStream& s pbf_press_button(context, BUTTON_A, 150ms, 500ms); stream.log("[Segment 02 battle] Floaroma Meadow first Galactic grunt."); wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); // The second grunt starts another battle automatically. Do not // wait for quiet overworld or send movement between the fights. battle("Floaroma Meadow second Galactic grunt"); @@ -1907,7 +1907,7 @@ static void align_route205_patrol_position(VideoStream& stream, stream); } -void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ auto move = [&](double x, double y, Milliseconds duration, Milliseconds settle = 250ms){ pbf_move_left_joystick(context, {x, y}, duration, 0ms); pbf_wait(context, settle); @@ -1916,7 +1916,7 @@ void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& str auto battle = [&](const std::string& label){ stream.log("[Route 205 battle] " + label); wait_for_trainer_battle_menu(stream, context); - Barry_1_fight(env, stream, context); + fight_trainer_using_moves(stream, context, stats, "Route trainer", false); clear_post_battle_dialogue(stream, context); }; auto patrol_battle = [&](const std::string& label, @@ -2065,7 +2065,7 @@ void mars_to_eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& str } // Cheryl controls her own Pokemon. Select move slot 2 visually, // then A advances target selection and battle text, as in the recording. -void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ (void)env; // Second recorded run, beginning at the northern Route 205 sign. pbf_move_left_joystick(context, {0.000000, 1.000000}, 1466ms, 0ms); @@ -2087,7 +2087,9 @@ void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro stream.log("[Eterna Forest battle] " + label); wait_for_trainer_battle_menu(stream, context); // Ordinary trainers return after the confirmed battle-exit transition. - fight_trainer_using_moves(stream, context, 2, label, false); + fight_trainer_using_moves(stream, context, stats, label, false); + // Cheryl restores the party after each completed partner battle. + stats.move_pp_remaining = stats.move_pp_capacity; }; encounter("Lass Briana and Bug Catcher Jack", [&](ProControllerContext& context){ pbf_move_left_joystick(context, {1.000000, -0.000000}, 579ms, 0ms); @@ -2168,7 +2170,7 @@ void eterna_forest(SingleSwitchProgramEnvironment& env, VideoStream& stream, Pro } void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, VideoStream& stream, - ProControllerContext& context){ + ProControllerContext& context, AutoStoryStats& stats){ (void)env; // Clear Repel on the open street before locator corrections. // Reuse the helper's horizontal wiggles and decline renewal. @@ -2190,7 +2192,7 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, stream); } context.wait_for_all_requests(); - if (!heal_pokemon(stream, context, "Eterna")){ + if (!heal_pokemon(stream, context, "Eterna", &stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Center exit was not confirmed.", stream); @@ -2419,7 +2421,7 @@ void eterna_forest_exit_to_gym(SingleSwitchProgramEnvironment& env, } stream.log("[Eterna City] Watched Explorer Kit/Cynthia events and verified gym entry complete.", COLOR_GREEN); } -static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context){ +static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ //Upon entering the Gym. @@ -2537,7 +2539,7 @@ static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stre pbf_press_button(context, BUTTON_A, 150ms, 300ms); wait_for_trainer_battle_menu(stream, context); - fight_trainer_using_moves(stream, context, 2, "Lass Caroline", false); + fight_trainer_using_moves(stream, context, stats, "Lass Caroline", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. @@ -2551,7 +2553,7 @@ static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stre pbf_press_button(context, BUTTON_A, 150ms, 300ms); wait_for_trainer_battle_menu(stream, context); - fight_trainer_using_moves(stream, context, 2, "Aroma Lady Jenna", false); + fight_trainer_using_moves(stream, context, stats, "Aroma Lady Jenna", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue. @@ -2565,7 +2567,7 @@ static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stre pbf_press_button(context, BUTTON_A, 150ms, 300ms); wait_for_trainer_battle_menu(stream, context); - fight_trainer_using_moves(stream, context, 2, "Aroma Lady Angela", false); + fight_trainer_using_moves(stream, context, stats, "Aroma Lady Angela", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2579,7 +2581,7 @@ static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stre pbf_press_button(context, BUTTON_A, 150ms, 300ms); wait_for_trainer_battle_menu(stream, context); - fight_trainer_using_moves(stream, context, 2, "Beauty Lindsay", false); + fight_trainer_using_moves(stream, context, stats, "Beauty Lindsay", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2593,7 +2595,7 @@ static void gym_Puzzle_02(SingleSwitchProgramEnvironment& env, VideoStream& stre pbf_press_button(context, BUTTON_A, 150ms, 300ms); wait_for_trainer_battle_menu(stream, context); - fight_trainer_using_moves(stream, context, 2, "Gardenia", true); + fight_trainer_using_moves(stream, context, stats, "Gardenia", true); // The shared battle handler already advances reward dialogue // while confirming the overworld. Do not require the badge @@ -2677,7 +2679,7 @@ void checkpoint_0201(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - roark_to_floaroma(env, env.console, context); + roark_to_floaroma(env, env.console, context, stats); }, true); @@ -2701,7 +2703,7 @@ void AutoStory_checkpoint_0202::run_checkpoint(SingleSwitchProgramEnvironment& e options, stats, [&](size_t /*attempt*/){ - floaroma_to_mars(env, env.console, context); + floaroma_to_mars(env, env.console, context, stats); }, true); } @@ -2734,7 +2736,7 @@ void checkpoint_0203(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - mars_to_eterna_forest(env, env.console, context); + mars_to_eterna_forest(env, env.console, context, stats); }, true); @@ -2769,7 +2771,7 @@ void checkpoint_0204(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 02 gameplay logic - eterna_forest(env, env.console, context); + eterna_forest(env, env.console, context, stats); }, true); @@ -2804,7 +2806,7 @@ void checkpoint_0205(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ // TODO: implement Segment 02 gameplay logic - eterna_forest_exit_to_gym(env, env.console, context); + eterna_forest_exit_to_gym(env, env.console, context, stats); }, true); } @@ -2838,7 +2840,7 @@ void checkpoint_0206(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ //#. Eterna Gym navigation and battle - gym_Puzzle_02(env, env.console, context); + gym_Puzzle_02(env, env.console, context, stats); }, true); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp index 2cdad75774..42315d92fb 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_03.cpp @@ -101,7 +101,7 @@ static void segment03_confirm_location(VideoStream& stream, // Watch the approach itself, cancelling queued movement as soon as the // trainer speaks or a battle starts. The existing battle engine owns combat. static void segment03_expected_encounter(VideoStream& stream, - ProControllerContext& context, + ProControllerContext& context, AutoStoryStats& stats, const std::string& label, const std::function& approach, Milliseconds encounter_wait = 45000ms, @@ -129,7 +129,7 @@ static void segment03_expected_encounter(VideoStream& stream, stream); } stream.log("[" + label + "] Encounter detected; route movement cancelled."); - fight_trainer_using_moves(stream, context, 2, label, false); + fight_trainer_using_moves(stream, context, stats, label, false); } static void segment03_wait_image(VideoStream& stream, @@ -228,7 +228,7 @@ static void segment03_align_lower_stair_lane(VideoStream& stream, ProControllerC } static bool segment03_cycling_move(VideoStream& stream, - ProControllerContext& context, + ProControllerContext& context, AutoStoryStats& stats, double x, double y, Milliseconds duration, @@ -247,7 +247,7 @@ static bool segment03_cycling_move(VideoStream& stream, }, {{black}, {white}, {battle}}); if (result == 2){ - fight_trainer_using_moves(stream, context, 2, label + " encounter", false); + fight_trainer_using_moves(stream, context, stats, label + " encounter", false); return false; } if (result < 0){ @@ -392,7 +392,7 @@ static bool segment03_align_cycle_start(VideoStream& stream, ProControllerContex } -static void segment03_cycle_road_battles(VideoStream& stream, ProControllerContext& context, bool encounter_pending){ +static void segment03_cycle_road_battles(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, bool encounter_pending){ size_t completed = 0; auto watch = [&](bool initial){ context.wait_for_all_requests(); @@ -424,7 +424,7 @@ static void segment03_cycle_road_battles(VideoStream& stream, ProControllerConte "Cycling Road: Expected at least three battles before the south gate.", stream); } - fight_trainer_using_moves(stream, context, 2, "Cycling Road encounter " + std::to_string(completed + 1), false); + fight_trainer_using_moves(stream, context, stats, "Cycling Road encounter " + std::to_string(completed + 1), false); ++completed; stream.log("[Cycling Road] Completed battles: " + std::to_string(completed) + "/4."); } @@ -485,9 +485,9 @@ static void segment03_doorway(VideoStream& stream, segment03_finish_transition(stream, context, label); } -void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ +void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ auto fight = [&](const std::string& label, bool post_battle_dialogue_cleanup = false){ - fight_trainer_using_moves(stream, context, 2, label, post_battle_dialogue_cleanup); + fight_trainer_using_moves(stream, context, stats, label, post_battle_dialogue_cleanup); }; pbf_move_left_joystick(context, {-1.000000, -0.000000}, 1060ms, 0ms); pbf_wait(context, 225ms); @@ -499,7 +499,7 @@ void eterna_gym_to_jupiter(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 237ms); pbf_move_left_joystick(context, {0.000000, 1.000000}, 1140ms, 0ms); pbf_wait(context, 2621ms); - if (!heal_pokemon(stream, context, "Eterna before Galactic HQ")){ + if (!heal_pokemon(stream, context, "Eterna before Galactic HQ", &stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Center exit was not confirmed.", stream); @@ -824,7 +824,7 @@ static void segment03_enter_cycle_shop(VideoStream& stream, ProControllerContext stream.log("[Eterna bicycle shop] Interior confirmed; outdoor alignment stopped.", COLOR_GREEN); } -void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ +void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ // Continue from 03.01 outside HQ, directly in front of the Cut bush. use_HM(stream, context, "Galactic HQ exit Cut"); clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); @@ -843,7 +843,7 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ false, true); segment03_doorway(stream, context, true, 4000ms, "Eterna Center entrance after Jupiter"); - if (!heal_pokemon(stream, context, "Eterna after Jupiter")){ + if (!heal_pokemon(stream, context, "Eterna after Jupiter", &stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Eterna Center exit not confirmed.", stream); @@ -892,9 +892,9 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ true, match_eterna_bike_riding_card); segment03_register_bike(stream, context); - segment03_cycling_move(stream, context, -1, 0, 733ms, 316ms, "Cycling Road approach"); - segment03_cycling_move(stream, context, 0, -1, 3280ms, 3000ms, "Cycling Road north gate entrance", true); - segment03_cycling_move(stream, context, 0, -1, 1789ms, 3000ms, "Cycling Road north gate exit", true); + segment03_cycling_move(stream, context, stats, -1, 0, 733ms, 316ms, "Cycling Road approach"); + segment03_cycling_move(stream, context, stats, 0, -1, 3280ms, 3000ms, "Cycling Road north gate entrance", true); + segment03_cycling_move(stream, context, stats, 0, -1, 1789ms, 3000ms, "Cycling Road north gate exit", true); // Fade completion can precede restored player control. Settle and // clear the steps before acquiring the bridge, not the cliff wall. pbf_wait(context, 2000ms); @@ -923,27 +923,27 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ stream); } const bool cycling_encounter = clearance >= 2 || segment03_align_cycle_start(stream, context); - segment03_cycle_road_battles(stream, context, cycling_encounter); + segment03_cycle_road_battles(stream, context, stats, cycling_encounter); // Any approach movement may already enter the south gate. Preserve // that transition instead of requesting two more doorway crossings. bool south_gate_entered = - segment03_cycling_move(stream, context, -1, 0, 129ms, 1499ms, "Cycling Road south gate approach"); + segment03_cycling_move(stream, context, stats, -1, 0, 129ms, 1499ms, "Cycling Road south gate approach"); if (!south_gate_entered){ south_gate_entered = - segment03_cycling_move(stream, context, -1, 0, 361ms, 3345ms, "Cycling Road south gate approach"); + segment03_cycling_move(stream, context, stats, -1, 0, 361ms, 3345ms, "Cycling Road south gate approach"); } if (!south_gate_entered){ south_gate_entered = - segment03_cycling_move(stream, context, 0, -1, 472ms, 2574ms, "Cycling Road south gate approach"); + segment03_cycling_move(stream, context, stats, 0, -1, 472ms, 2574ms, "Cycling Road south gate approach"); } if (!south_gate_entered){ - segment03_cycling_move(stream, context, 0, -1, 1977ms, 1544ms, "Cycling Road south gate entrance", true); + segment03_cycling_move(stream, context, stats, 0, -1, 1977ms, 1544ms, "Cycling Road south gate entrance", true); } segment03_confirm_location(stream, context, match_cycling_road_south_gate_interior, "Cycling Road south gate interior"); - segment03_cycling_move(stream, context, 0, -1, 1977ms, 1544ms, "Cycling Road south gate exit", true); + segment03_cycling_move(stream, context, stats, 0, -1, 1977ms, 1544ms, "Cycling Road south gate exit", true); OverworldWatcher south_gate_overworld; if (wait_until(stream, context, 10s, {{south_gate_overworld}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, @@ -1012,18 +1012,18 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ stream.log("[Route 207 Dawn] Dialogue cleared; continuing to Camper Anthony.", COLOR_GREEN); // restof206.txt begins after Dawn. Recorded battle/menu inputs are // replaced by the encounter watchers and existing move-2 battle engine. - segment03_expected_encounter(stream, context, "Route 207 Camper Anthony", [](ProControllerContext& ctx){ + segment03_expected_encounter(stream, context, stats, "Route 207 Camper Anthony", [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {0, -1}, 1013ms, 1667ms); pbf_move_left_joystick(ctx, {1, 0}, 3309ms, 1011ms); }); // The recorded approach triggers Lauren next without further steering. - segment03_expected_encounter(stream, context, "Route 207 Picnicker Lauren", [](ProControllerContext&){ + segment03_expected_encounter(stream, context, stats, "Route 207 Picnicker Lauren", [](ProControllerContext&){ }); // Justin can be dodged. After the recorded approach, wait three seconds // for an encounter; otherwise keep the remaining Coronet route. segment03_expected_encounter( stream, - context, + context, stats, "Route 207 Hiker Justin", [](ProControllerContext& ctx){ pbf_move_left_joystick(ctx, {1, 0}, 994ms, 508ms); @@ -1034,15 +1034,15 @@ void jupiter_to_coronet(VideoStream& stream, ProControllerContext& context){ false); // Keep the recorded route after Justin; replace its final blind doorway // excerpt with the same watched down/right search used at Oreburgh Gate. - bool coronet_entered = segment03_cycling_move(stream, context, 1, 0, 942ms, 443ms, "Mount Coronet approach"); + bool coronet_entered = segment03_cycling_move(stream, context, stats, 1, 0, 942ms, 443ms, "Mount Coronet approach"); if (!coronet_entered){ - coronet_entered = segment03_cycling_move(stream, context, 0, -1, 776ms, 119ms, "Mount Coronet approach"); + coronet_entered = segment03_cycling_move(stream, context, stats, 0, -1, 776ms, 119ms, "Mount Coronet approach"); } if (!coronet_entered){ - coronet_entered = segment03_cycling_move(stream, context, 1, 0, 627ms, 147ms, "Mount Coronet approach"); + coronet_entered = segment03_cycling_move(stream, context, stats, 1, 0, 627ms, 147ms, "Mount Coronet approach"); } if (!coronet_entered){ - coronet_entered = segment03_cycling_move(stream, context, 0, 1, 725ms, 1008ms, "Mount Coronet approach"); + coronet_entered = segment03_cycling_move(stream, context, stats, 0, 1, 725ms, 1008ms, "Mount Coronet approach"); } if (!coronet_entered){ enter_cave_with_down_right_search(stream, context, "Mount Coronet west entrance"); @@ -1125,7 +1125,7 @@ void coronet1(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 2038ms); context.wait_for_all_requests(); } -void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ +void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ // The expiry routine starts west. Step east first to leave room for // its west/east pairs, then clear the old Repel before route movement. context.wait_for_all_requests(); @@ -1203,7 +1203,7 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ continue; } } - fight_trainer_using_moves(stream, context, 2, "Route 208 Hiker", false); + fight_trainer_using_moves(stream, context, stats, "Route 208 Hiker", false); if (++bridge_battles > 2){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Route 208 bridge: Unexpected additional Hiker battle.", @@ -1288,21 +1288,21 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ // inside the gatehouse. The existing watcher cancels at black/white, // waits for GONE500ms, then settles for2s before the next input. bool hearthome_gate_entered = - segment03_cycling_move(stream, context, 1, 0, 3500ms, 443ms, "Hearthome west gate approach"); + segment03_cycling_move(stream, context, stats, 1, 0, 3500ms, 443ms, "Hearthome west gate approach"); if (!hearthome_gate_entered){ hearthome_gate_entered = - segment03_cycling_move(stream, context, 0, 1, 250ms, 405ms, "Hearthome west gate doorway alignment"); + segment03_cycling_move(stream, context, stats, 0, 1, 250ms, 405ms, "Hearthome west gate doorway alignment"); } if (!hearthome_gate_entered){ hearthome_gate_entered = - segment03_cycling_move(stream, context, 1, 0, 600ms, 1617ms, "Hearthome west gate entrance", true); + segment03_cycling_move(stream, context, stats, 1, 0, 600ms, 1617ms, "Hearthome west gate entrance", true); } if (!hearthome_gate_entered){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome west gate: Interior transition not confirmed.", stream); } - if (!segment03_cycling_move(stream, context, 1, 0, 1800ms, 1588ms, "Hearthome west gate exit", true)){ + if (!segment03_cycling_move(stream, context, stats, 1, 0, 1800ms, 1588ms, "Hearthome west gate exit", true)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome west gate: Exterior transition not confirmed.", stream); @@ -1313,7 +1313,7 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ "Hearthome west gate: Overworld not ready after exit.", stream); } - if (segment03_cycling_move(stream, context, 1, 0, 2200ms, 6189ms, "Hearthome Buneary approach")){ + if (segment03_cycling_move(stream, context, stats, 1, 0, 2200ms, 6189ms, "Hearthome Buneary approach")){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Buneary approach: Unexpected extra transition.", stream); @@ -1405,7 +1405,7 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 1500ms); context.wait_for_all_requests(); segment03_confirm_location(stream, context, match_hearthome_center_interior, "Hearthome Center interior"); - if (!heal_pokemon(stream, context, "Hearthome")){ + if (!heal_pokemon(stream, context, "Hearthome", &stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Center exit not confirmed.", stream); @@ -1469,7 +1469,7 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ pbf_wait(context, 2000ms); clear_post_battle_dialogue(stream, context, BUTTON_B); // Keep Hall entry separate from Fantina's outdoor conversation. - segment03_cycling_move(stream, context, 0, 1, 1179ms, 4268ms, "Hearthome Contest Hall entrance", true); + segment03_cycling_move(stream, context, stats, 0, 1, 1179ms, 4268ms, "Hearthome Contest Hall entrance", true); pbf_press_button(context, BUTTON_A, 181ms, 219ms); context.wait_for_all_requests(); ShortDialogWatcher contest_dialogue; @@ -1501,7 +1501,7 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ } // Own the exit before handing off the checkpoint. The transition watcher // cancels southward movement at the fade, leaving the fixed outdoor spawn. - if (!segment03_cycling_move(stream, context, 0, -1, 903ms, 1455ms, "Hearthome Contest Hall exit", true)){ + if (!segment03_cycling_move(stream, context, stats, 0, -1, 903ms, 1455ms, "Hearthome Contest Hall exit", true)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "Hearthome Contest Hall: Exit transition not confirmed.", stream); @@ -1518,7 +1518,7 @@ void coronet_to_heartholme(VideoStream& stream, ProControllerContext& context){ // End 03.04 / start 03.05: just outside the Hall, south-facing, Repel off. } -void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context){ +void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ OverworldWatcher hall_start; if (wait_until(stream, context, 10s, {{hall_start}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, @@ -1534,7 +1534,7 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context pbf_move_left_joystick(context, {-1.000000, -0.000000}, 2068ms, 0ms); pbf_wait(context, 456ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1065ms, 0ms); - fight_trainer_using_moves(stream, context, std::vector{2, 1}, "Barry in Hearthome", true); + fight_trainer_using_moves(stream, context, stats, "Barry in Hearthome", true); // butters' recorded rival-to-Lost Tower route. pbf_move_left_joystick(context, {0.000000, -1.000000}, 1088ms, 0ms); pbf_wait(context, 95ms); @@ -1712,7 +1712,7 @@ void heartholme_to_lost_tower(VideoStream& stream, ProControllerContext& context context.wait_for_all_requests(); stream.log("[Lost Tower handoff] Outside doorway, facing south; checkpoint complete.", COLOR_GREEN); } -void lost_tower(VideoStream& stream, ProControllerContext& context){ +void lost_tower(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ // Checkpoint 03.06 starts outside the doorway, facing south, with Repel active. // Turn north, then enter using the same single-step timing as Segment 02. context.wait_for_all_requests(); @@ -1939,7 +1939,7 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ if (biker_challenged){ stream.log("[Lost Tower biker] Dialogue detected after " + std::to_string(biker_up_completed) + " up presses; handling the trainer battle."); - fight_trainer_using_moves(stream, context, std::vector{2}, "Lost Tower biker", false); + fight_trainer_using_moves(stream, context, stats, "Lost Tower biker", false); clear_post_battle_dialogue(stream, context, BUTTON_B); // The trainer conversation turns the player toward the biker. @@ -2062,7 +2062,7 @@ void lost_tower(VideoStream& stream, ProControllerContext& context){ stream.log("[Lost Tower] Reward dialogue cleared; Escape Rope exit settled.", COLOR_GREEN); } -void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context){ +void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ // Recorded Tower-to-Gym path; shared helpers handle prompts and battles. pbf_move_left_joystick(context, {0.000000, -1.000000}, 336ms, 0ms); pbf_wait(context, 590ms); @@ -2151,7 +2151,7 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont "Route 215 Calvin: Trainer encounter did not start.", stream); } - fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ruin Maniac Calvin", false); + fight_trainer_using_moves(stream, context, stats, "Route 215 Ruin Maniac Calvin", false); // Renew in the clear area after Calvin, before the next walking strip. force_repel_expiry(stream, context, RepelRenewalChoice::YES, false, true); // Cover at most one unfinished 500ms westward expiry pulse. @@ -2207,7 +2207,7 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont // The recorded failure is the jogger's trainer introduction. // Complete it with the shared move-list handler, then measure // the current lane again; the trainer may have approached us. - fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 jogger", false); + fight_trainer_using_moves(stream, context, stats, "Route 215 jogger", false); context.wait_for_all_requests(); fresh_after = current_time(); last_frame_time = WallClock::min(); @@ -2370,7 +2370,7 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont "Route 215 Derek: Trainer encounter did not start.", stream); } - fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Black Belt Derek", false); + fight_trainer_using_moves(stream, context, stats, "Route 215 Black Belt Derek", false); pbf_move_left_joystick(context, {0.000000, -1.000000}, 1929ms, 0ms); pbf_wait(context, 5ms); pbf_move_left_joystick(context, {1.000000, -0.000000}, 2195ms, 0ms); @@ -2443,8 +2443,8 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont stream); } // Dennis and Maya are separate consecutive battles. - fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ace Trainer Dennis", false); - fight_trainer_using_moves(stream, context, std::vector{2}, "Route 215 Ace Trainer Maya", false); + fight_trainer_using_moves(stream, context, stats, "Route 215 Ace Trainer Dennis", false); + fight_trainer_using_moves(stream, context, stats, "Route 215 Ace Trainer Maya", false); pbf_move_left_joystick(context, {1.000000, -0.000000}, 10884ms, 0ms); pbf_wait(context, 48ms); pbf_move_left_joystick(context, {0.000000, -1.000000}, 2314ms, 0ms); @@ -2532,7 +2532,7 @@ void lost_tower_to_veilstone_gym(VideoStream& stream, ProControllerContext& cont context.wait_for_all_requests(); } -static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ +static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ //Upon entering the Gym. @@ -2667,7 +2667,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 01 (Black Belt Rafael). pbf_press_button(context, BUTTON_A, 150ms, 300ms); - fight_trainer_using_moves(stream, context, 2, "Black Belt Rafael", false); + fight_trainer_using_moves(stream, context, stats, "Black Belt Rafael", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2683,7 +2683,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 02 (Black Belt Jeffrey). pbf_press_button(context, BUTTON_A, 150ms, 300ms); - fight_trainer_using_moves(stream, context, 2, "Black Belt Jeffrey", false); + fight_trainer_using_moves(stream, context, stats, "Black Belt Jeffrey", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2699,7 +2699,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 03 (Black Belt Darren). pbf_press_button(context, BUTTON_A, 150ms, 300ms); - fight_trainer_using_moves(stream, context, 2, "Black Belt Darren", false); + fight_trainer_using_moves(stream, context, stats, "Black Belt Darren", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2715,7 +2715,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate Trainer battle 04 (Black Belt Colby). pbf_press_button(context, BUTTON_A, 150ms, 300ms); - fight_trainer_using_moves(stream, context, 1, "Black Belt Colby", false); + fight_trainer_using_moves(stream, context, stats, "Black Belt Colby", false); pbf_mash_button(context, BUTTON_B, 2500ms); //Battle exit dialogue @@ -2731,7 +2731,7 @@ static void gym_Puzzle_03(VideoStream& stream, ProControllerContext& context){ //Button presses to initiate the Gym battle. pbf_press_button(context, BUTTON_A, 150ms, 300ms); - fight_trainer_using_moves(stream, context, 1, "Maylene", true); + fight_trainer_using_moves(stream, context, stats, "Maylene", true); // Clear badge and reward dialogue before handing off inside the Gym. // Match Gardenia's quiet period so gaps between rewards do not end cleanup. @@ -2800,7 +2800,7 @@ void Checkpoint_0301(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. - eterna_gym_to_jupiter(env.console, context); + eterna_gym_to_jupiter(env.console, context, stats); }, true); @@ -2845,7 +2845,7 @@ void Checkpoint_0302(SingleSwitchProgramEnvironment& env, pbf_wait(context, 5000ms); context.wait_for_all_requests(); // Recorded route with feedback battle handling. - jupiter_to_coronet(env.console, context); + jupiter_to_coronet(env.console, context, stats); }, true); @@ -2913,7 +2913,7 @@ void Checkpoint_0304(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ // Recorded route with feedback battle handling. - coronet_to_heartholme(env.console, context); + coronet_to_heartholme(env.console, context, stats); }, true); @@ -2957,7 +2957,7 @@ void Checkpoint_0305(SingleSwitchProgramEnvironment& env, pbf_wait(context, 5000ms); context.wait_for_all_requests(); // Recorded route with feedback battle handling. - heartholme_to_lost_tower(env.console, context); + heartholme_to_lost_tower(env.console, context, stats); // Stop here. Checkpoint 03.06 saves outside and owns the Tower visit. }, @@ -3003,8 +3003,8 @@ void Checkpoint_0306(SingleSwitchProgramEnvironment& env, pbf_wait(context, 5000ms); context.wait_for_all_requests(); // Save/retry begins outside the Tower, at the end of 03.05. - lost_tower(env.console, context); - lost_tower_to_veilstone_gym(env.console, context); + lost_tower(env.console, context, stats); + lost_tower_to_veilstone_gym(env.console, context, stats); }, true); } @@ -3038,7 +3038,7 @@ void Checkpoint_0307(SingleSwitchProgramEnvironment& env, stats, [&](size_t /*attempt*/){ //#. Veilstone Gym navigation and battle - gym_Puzzle_03(env.console, context); + gym_Puzzle_03(env.console, context, stats); }, true); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp index 874d5ab562..32a903f7f1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.cpp @@ -33,486 +33,9 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - -std::string AutoStory_Segment_04::name() const { - return "04: Third Badge to Fourth Badge"; -} -std::string AutoStory_Segment_04::start_text() const { - return "Start: Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; -} -std::string AutoStory_Segment_04::end_text() const { - return "End: Fourth Gym Badge acquired (Pastoria City)."; -} - - -void AutoStory_Segment_04::run_segment(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0401().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0402().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0403().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0404().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0405().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0406().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0407().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0408().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0409().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0410().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0411().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0412().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0413().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0414().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0415().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0416().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0417().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0418().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} - - -std::string AutoStory_Checkpoint_0401::name() const { - return "04.01 - leave_gym"; -} -std::string AutoStory_Checkpoint_0401::start_text() const { - return "Inside Veilstone Gym in front of Maylene, facing north, after acquiring the third badge and clearing her reward dialogue."; -} -std::string AutoStory_Checkpoint_0401::end_text() const { - return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; -} -void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0401(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0402::name() const { - return "04.02 - go_to_veilstone_pokemon_center"; -} -std::string AutoStory_Checkpoint_0402::start_text() const { - return "Outside Veilstone Gym, before clearing Dawn's post-Gym conversation."; -} -std::string AutoStory_Checkpoint_0402::end_text() const { - return "Entered Veilstone City Pokemon Center."; -} -void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0402(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0403::name() const { - return "04.03 - heal_at_pokemon_center (Veilstone)"; -} -std::string AutoStory_Checkpoint_0403::start_text() const { - return "Inside Veilstone City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0403::end_text() const { - return "Healed and exited Veilstone City Pokemon Center."; -} -void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0403(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0404::name() const { - return "04.04 - go_to_galactic_warehouse"; -} -std::string AutoStory_Checkpoint_0404::start_text() const { - return "Outside Veilstone City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0404::end_text() const { - return "Reached Galactic Warehouse, battle started."; -} -void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0404(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0405::name() const { - return "04.05 - handle_battle (veilstone_galactic_grunts_1)"; -} -std::string AutoStory_Checkpoint_0405::start_text() const { - return "In battle against Galactic Grunt at Veilstone."; -} -std::string AutoStory_Checkpoint_0405::end_text() const { - return "Defeated Galactic Grunt at Veilstone."; -} -void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0405(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0406::name() const { - return "04.06 - enter_galactic_warehouse"; -} -std::string AutoStory_Checkpoint_0406::start_text() const { - return "Outside Galactic Warehouse."; -} -std::string AutoStory_Checkpoint_0406::end_text() const { - return "Entered Galactic Warehouse."; -} -void AutoStory_Checkpoint_0406::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0406(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0407::name() const { - return "04.07 - exit_galactic_warehouse"; -} -std::string AutoStory_Checkpoint_0407::start_text() const { - return "Inside Galactic Warehouse."; -} -std::string AutoStory_Checkpoint_0407::end_text() const { - return "Exited Galactic Warehouse with TM Fly."; -} -void AutoStory_Checkpoint_0407::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0407(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0408::name() const { - return "04.08 - go_to_route_214"; -} -std::string AutoStory_Checkpoint_0408::start_text() const { - return "Outside Galactic Warehouse."; -} -std::string AutoStory_Checkpoint_0408::end_text() const { - return "Arrived at Route 214, battle started."; -} -void AutoStory_Checkpoint_0408::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0408(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0409::name() const { - return "04.09 - handle_battle (route_214_psychic_abigail)"; -} -std::string AutoStory_Checkpoint_0409::start_text() const { - return "In battle against Psychic Abigail on Route 214."; -} -std::string AutoStory_Checkpoint_0409::end_text() const { - return "Defeated Psychic Abigail on Route 214."; -} -void AutoStory_Checkpoint_0409::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0409(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0410::name() const { - return "04.10 - walk_through_route_214"; -} -std::string AutoStory_Checkpoint_0410::start_text() const { - return "On Route 214 after Psychic Abigail."; -} -std::string AutoStory_Checkpoint_0410::end_text() const { - return "Walked through Route 214."; -} -void AutoStory_Checkpoint_0410::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0410(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0411::name() const { - return "04.11 - pass_by_valor_lakefront"; -} -std::string AutoStory_Checkpoint_0411::start_text() const { - return "End of Route 214."; -} -std::string AutoStory_Checkpoint_0411::end_text() const { - return "Passed Valor Lakefront, entered building."; -} -void AutoStory_Checkpoint_0411::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0411(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0412::name() const { - return "04.12 - go_through_building_route_213"; -} -std::string AutoStory_Checkpoint_0412::start_text() const { - return "Inside building between Route 214 and Route 213."; -} -std::string AutoStory_Checkpoint_0412::end_text() const { - return "Exited building onto Route 213."; -} -void AutoStory_Checkpoint_0412::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0412(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0413::name() const { - return "04.13 - go_through_route_213"; -} -std::string AutoStory_Checkpoint_0413::start_text() const { - return "On Route 213."; -} -std::string AutoStory_Checkpoint_0413::end_text() const { - return "Walked through Route 213, entered east Pastoria building."; -} -void AutoStory_Checkpoint_0413::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0413(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0414::name() const { - return "04.14 - go_through_house_east_pastoria"; -} -std::string AutoStory_Checkpoint_0414::start_text() const { - return "Inside east Pastoria building."; -} -std::string AutoStory_Checkpoint_0414::end_text() const { - return "Exited east Pastoria building into Pastoria City."; -} -void AutoStory_Checkpoint_0414::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0414(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0415::name() const { - return "04.15 - go_through_pastoria"; -} -std::string AutoStory_Checkpoint_0415::start_text() const { - return "In Pastoria City."; -} -std::string AutoStory_Checkpoint_0415::end_text() const { - return "Entered Pastoria City Pokemon Center."; -} -void AutoStory_Checkpoint_0415::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0415(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0416::name() const { - return "04.16 - heal_at_pokemon_center (Pastoria)"; -} -std::string AutoStory_Checkpoint_0416::start_text() const { - return "Inside Pastoria City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0416::end_text() const { - return "Healed and exited Pastoria City Pokemon Center."; -} -void AutoStory_Checkpoint_0416::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0416(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0417::name() const { - return "04.17 - go_to_pastoria_gym"; -} -std::string AutoStory_Checkpoint_0417::start_text() const { - return "Outside Pastoria City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0417::end_text() const { - return "Inside Pastoria Gym at the entrance, facing north."; -} -void AutoStory_Checkpoint_0417::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0417(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0418::name() const { - return "04.18 - Pastoria Gym Puzzle"; -} -std::string AutoStory_Checkpoint_0418::start_text() const { - return "Inside Pastoria Gym at the entrance, facing north."; -} -std::string AutoStory_Checkpoint_0418::end_text() const { - return "Outside Pastoria Gym at the doorway, facing south, after acquiring the fourth badge and clearing the reward dialogue."; -} -void AutoStory_Checkpoint_0418::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - VideoStream& stream = env.console; - struct Move { - DpadPosition dir; - int count; - Milliseconds delay; - }; - - // Trial: use the supplied counts without an extra facing press. - const std::vector sequences[] = { - { - // Trainer 1 - {DPAD_UP, 16, 0ms}, - {DPAD_LEFT, 11, 0ms}, - {DPAD_DOWN, 8, 0ms}, - {DPAD_DOWN, 1, 2000ms}, - {DPAD_RIGHT, 5, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_RIGHT, 10, 0ms}, - {DPAD_UP, 7, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_UP, 14, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_RIGHT, 1, 2000ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_DOWN, 6, 0ms}, - {DPAD_RIGHT, 3, 0ms}, - {DPAD_DOWN, 7, 0ms}, - {DPAD_LEFT, 12, 0ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 3, 0ms}, - }, - { - // Trainer 2 - {DPAD_RIGHT, 2, 0ms}, - {DPAD_RIGHT, 1, 2000ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_DOWN, 11, 0ms}, - {DPAD_RIGHT, 6, 0ms}, - }, - { - // Trainer 3 - {DPAD_RIGHT, 10, 0ms}, - {DPAD_UP, 2, 0ms}, - }, - { - // Trainer 4 - {DPAD_UP, 21, 0ms}, - {DPAD_LEFT, 1, 0ms}, - {DPAD_LEFT, 1, 2000ms}, - {DPAD_DOWN, 2, 0ms}, - {DPAD_RIGHT, 5, 0ms}, - {DPAD_DOWN, 17, 0ms}, - {DPAD_DOWN, 1, 2000ms}, - {DPAD_UP, 25, 0ms}, - {DPAD_LEFT, 18, 0ms}, - {DPAD_DOWN, 11, 0ms}, - {DPAD_LEFT, 4, 0ms}, - {DPAD_UP, 10, 0ms}, - }, - { - // Crasher Wake approach - {DPAD_UP, 2, 0ms}, - {DPAD_UP, 1, 4000ms}, - {DPAD_DOWN, 4, 0ms}, - {DPAD_RIGHT, 11, 0ms}, - {DPAD_UP, 5, 0ms}, - }, - { - // Return from Crasher Wake - {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 3, 0ms}, - {DPAD_UP, 3, 0ms}, - {DPAD_LEFT, 11, 0ms}, - {DPAD_DOWN, 35, 0ms}, - {DPAD_RIGHT, 10, 0ms}, - {DPAD_DOWN, 5, 0ms}, - {DPAD_RIGHT, 4, 0ms}, - {DPAD_DOWN, 2, 0ms}, - }, - }; - - for (size_t stage = 0; stage < 6; stage++){ - stream.log("[Pastoria Gym puzzle] Movement stage " + std::to_string(stage + 1) + "."); - if (stage == 5){ - // Observe the supplied return route and stop movement at the Gym exit fade. - BlackScreenWatcher exit_black(COLOR_RED); - if (run_until(stream, - context, - [&](ProControllerContext& ctx){ - for (const auto& move : sequences[stage]){ - // repeat_dpad(ctx, dpad, move.dir, 80ms, 300ms, move.count, true); - for (int press = 0; press < move.count; press++){ - pbf_press_dpad(ctx, move.dir, 80ms, 300ms); - if (move.delay > 0ms){ - pbf_wait(ctx, move.delay); - } - } - } - pbf_wait(ctx, 5000ms); - }, - {{exit_black}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Pastoria Gym return: Exit transition did not start.", - stream); - } - BlackScreenWatcher outside(COLOR_RED, - {0.1, 0.1, 0.8, 0.8}, - 100, - 10, - BlackScreenWatcher::FinderType::GONE, - 500ms); - if (wait_until(stream, context, 30s, {{outside}}) != 0){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "Pastoria Gym return: Exit transition did not finish.", - stream); - } - pbf_wait(context, 500ms); - context.wait_for_all_requests(); - break; - } - for (const auto& move : sequences[stage]){ - // repeat_dpad(context, dpad, move.dir, 80ms, 300ms, move.count, true); - for (int press = 0; press < move.count; press++){ - pbf_press_dpad(context, move.dir, 80ms, 300ms); - if (move.delay > 0ms){ - pbf_wait(context, move.delay); - } - } - } - context.wait_for_all_requests(); - - const bool gym_leader = stage == 4; - const std::string label = - gym_leader ? "Crasher Wake" : "Pastoria Gym trainer " + std::to_string(stage + 1); - pbf_press_button(context, BUTTON_A, 150ms, 300ms); - fight_trainer_using_moves(stream, context, 1, label, gym_leader); - clear_post_battle_dialogue(stream, context, BUTTON_B, gym_leader ? 6000ms : 3000ms); - } - stream.log( - "[Pastoria Gym puzzle] Reward dialogue cleared; outside the Gym doorway, ready for 05.01.", - COLOR_GREEN); - }, - true); -} +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ @@ -531,56 +54,22 @@ move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& con } } - -static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ - context.wait_for_all_requests(); - stream.log("Battling " + trainerid + "...", COLOR_GREEN); - BattleMenuWatcher battle_menu(BattleType::TRAINER); - int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); - if (ret_menu < 0){ - stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); - return false; - } - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 500ms); - - if (trainerid == "veilstone_galactic_grunts_1" or trainerid == "route_213_tuber_chelsea" or - trainerid == "route_213_tuber_jared" or trainerid == "route_213_beauty_cindy"){ - // Move the battle-move cursor up. - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - }else if (trainerid == "route_214_collector_brady" or trainerid == "route_214_collector_douglas"){ - // Move the battle-move cursor down. - pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - }else if (trainerid == "route_214_psychic_abigail" or trainerid == "route_214_psychic_mitchell"){ - // Keep the current battle-move selection. - } - context.wait_for_all_requests(); - - { - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; - } - } - - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - if (trainerid == "route_213_tuber_chelsea" or trainerid == "route_213_tuber_jared" or +static bool handle_battle(VideoStream& stream, + ProControllerContext& context, + const std::string& trainerid, + AutoStoryStats& stats){ + fight_trainer_using_moves(stream, context, stats, trainerid, true); + if (trainerid == "route_213_tuber_chelsea" || trainerid == "route_213_tuber_jared" || trainerid == "route_213_beauty_cindy"){ move_back_after_unexpected_battle(stream, context, trainerid); } - stream.log("handle_battle_" + trainerid + ": transition confirmed."); return true; } +// --------------------------------------------------------------------------- +// Named route navigation blocks +// --------------------------------------------------------------------------- + static bool leave_gym(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -657,8 +146,8 @@ static bool go_to_veilstone_pokemon_center(VideoStream& stream, ProControllerCon return true; } -static bool heal_at_pokemon_center(VideoStream& stream, ProControllerContext& context){ - return heal_pokemon(stream, context, "Veilstone"); +static bool heal_at_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ + return heal_pokemon(stream, context, "Veilstone", &stats); } static bool go_to_galactic_warehouse(VideoStream& stream, ProControllerContext& context){ @@ -741,6 +230,7 @@ static bool exit_galactic_warehouse(VideoStream& stream, ProControllerContext& c {{getting_fly_TM}}); if (ret < 0){ stream.log("enter_galactic_warehouse_getting_fly_TM: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("enter_galactic_warehouse_getting_fly_TM: getting TM Fly not detected!"); @@ -839,7 +329,8 @@ static bool go_to_route_214(VideoStream& stream, ProControllerContext& context){ return true; } -static bool walk_through_route_214(VideoStream& stream, ProControllerContext& context){ +static bool +walk_through_route_214(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ context.wait_for_all_requests(); stream.log("walk_through_route_214: Continuing on the first part of route 214"); pbf_wait(context, 3000ms); @@ -850,7 +341,9 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co dpad.last_dir = DPAD_RIGHT; repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 3, true); - activate_repel(stream, context); + if (!activate_repel(stream, context)){ + return false; + } { MarkDetector battle_dialogue_1(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); @@ -873,7 +366,7 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co stream.log("walk_through_route_214_collector_brady: black screen not detected!", COLOR_RED); return false; }; - handle_battle(stream, context, "route_214_collector_brady"); + handle_battle(stream, context, "route_214_collector_brady", stats); } } context.wait_for_all_requests(); @@ -911,7 +404,7 @@ static bool walk_through_route_214(VideoStream& stream, ProControllerContext& co stream.log("walk_through_route_214_psychic_mitchell: black screen not detected!", COLOR_RED); return false; }; - handle_battle(stream, context, "route_214_psychic_mitchell"); + handle_battle(stream, context, "route_214_psychic_mitchell", stats); } } @@ -1019,7 +512,8 @@ static bool go_through_building_route_213(VideoStream& stream, ProControllerCont return true; } -static bool go_through_route_213(VideoStream& stream, ProControllerContext& context){ +static bool +go_through_route_213(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ context.wait_for_all_requests(); stream.log("Going through route 213"); pbf_wait(context, 2000ms); @@ -1051,7 +545,7 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont return false; }else if (ret == 0){ stream.log("go_through_route_213_tuber_chelsea: Starting battle."); - handle_battle(stream, context, "route_213_tuber_chelsea"); + handle_battle(stream, context, "route_213_tuber_chelsea", stats); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } @@ -1082,7 +576,7 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont return false; }else if (ret == 0){ stream.log("go_through_route_213_tuber_jared: Starting battle."); - handle_battle(stream, context, "route_213_tuber_jared"); + handle_battle(stream, context, "route_213_tuber_jared", stats); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } @@ -1115,7 +609,7 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont }else if (ret == 0){ context.wait_for_all_requests(); stream.log("go_through_route_213_beauty_cindy: Starting battle."); - handle_battle(stream, context, "route_213_beauty_cindy"); + handle_battle(stream, context, "route_213_beauty_cindy", stats); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } @@ -1126,7 +620,9 @@ static bool go_through_route_213(VideoStream& stream, ProControllerContext& cont pbf_move_left_joystick(context, {+1, 0}, 800ms, 100ms); // 5 pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 8 - activate_repel(stream, context); + if (!activate_repel(stream, context)){ + return false; + } context.wait_for_all_requests(); pbf_wait(context, 1000ms); @@ -1243,159 +739,211 @@ static bool go_to_pastoria_gym(VideoStream& stream, ProControllerContext& contex return true; } -void checkpoint_0401(SingleSwitchProgramEnvironment& env, +// Start inside Pastoria Gym at its entrance; finish outside after Wake rewards. +static void pastoria_gym_puzzle_and_wake(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!leave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "leave_gym: transition not detected.", - env.console); - } + VideoStream& stream = env.console; + struct Move { + DpadPosition dir; + int count; + Milliseconds delay; + }; + + // Trial: use the supplied counts without an extra facing press. + const std::vector sequences[] = { + { + // Trainer 1 + {DPAD_UP, 16, 0ms}, + {DPAD_LEFT, 11, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_DOWN, 1, 2000ms}, + {DPAD_RIGHT, 5, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_RIGHT, 10, 0ms}, + {DPAD_UP, 7, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_UP, 14, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 6, 0ms}, + {DPAD_RIGHT, 3, 0ms}, + {DPAD_DOWN, 7, 0ms}, + {DPAD_LEFT, 12, 0ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 3, 0ms}, }, - true); -} + { + // Trainer 2 + {DPAD_RIGHT, 2, 0ms}, + {DPAD_RIGHT, 1, 2000ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_DOWN, 11, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + }, + { + // Trainer 3 + {DPAD_RIGHT, 10, 0ms}, + {DPAD_UP, 2, 0ms}, + }, + { + // Trainer 4 + {DPAD_UP, 21, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 2000ms}, + {DPAD_DOWN, 2, 0ms}, + {DPAD_RIGHT, 5, 0ms}, + {DPAD_DOWN, 17, 0ms}, + {DPAD_DOWN, 1, 2000ms}, + {DPAD_UP, 25, 0ms}, + {DPAD_LEFT, 18, 0ms}, + {DPAD_DOWN, 11, 0ms}, + {DPAD_LEFT, 4, 0ms}, + {DPAD_UP, 10, 0ms}, + }, + { + // Crasher Wake approach + {DPAD_UP, 2, 0ms}, + {DPAD_UP, 1, 4000ms}, + {DPAD_DOWN, 4, 0ms}, + {DPAD_RIGHT, 11, 0ms}, + {DPAD_UP, 5, 0ms}, + }, + { + // Return from Crasher Wake + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 3, 0ms}, + {DPAD_UP, 3, 0ms}, + {DPAD_LEFT, 11, 0ms}, + {DPAD_DOWN, 35, 0ms}, + {DPAD_RIGHT, 10, 0ms}, + {DPAD_DOWN, 5, 0ms}, + {DPAD_RIGHT, 4, 0ms}, + {DPAD_DOWN, 2, 0ms}, + }, + }; -void checkpoint_0402(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_veilstone_pokemon_center(env.console, context)){ + for (size_t stage = 0; stage < 6; stage++){ + stream.log("[Pastoria Gym puzzle] Movement stage " + std::to_string(stage + 1) + "."); + if (stage == 5){ + // Observe the supplied return route and stop movement at the Gym exit fade. + BlackScreenWatcher exit_black(COLOR_RED); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + for (const auto& move : sequences[stage]){ + // repeat_dpad(ctx, dpad, move.dir, 80ms, 300ms, move.count, true); + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + if (move.delay > 0ms){ + pbf_wait(ctx, move.delay); + } + } + } + pbf_wait(ctx, 5000ms); + }, + {{exit_black}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_veilstone_pokemon_center: transition not detected.", - env.console); + "Pastoria Gym return: Exit transition did not start.", + stream); } - }, - false); -} - -void checkpoint_0403(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_at_pokemon_center(env.console, context)){ + BlackScreenWatcher outside(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{outside}}) != 0){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_pokemon_center: transition not detected.", - env.console); + "Pastoria Gym return: Exit transition did not finish.", + stream); } - }, - false); + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + // Let any automatic exit dialogue finish before 05.01 saves or opens the map. + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + break; + } + for (const auto& move : sequences[stage]){ + // repeat_dpad(context, dpad, move.dir, 80ms, 300ms, move.count, true); + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay > 0ms){ + pbf_wait(context, move.delay); + } + } + } + context.wait_for_all_requests(); + + const bool gym_leader = stage == 4; + const std::string label = + gym_leader ? "Crasher Wake" : "Pastoria Gym trainer " + std::to_string(stage + 1); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + fight_trainer_using_moves(stream, context, stats, label, gym_leader); + clear_post_battle_dialogue(stream, context, BUTTON_B, gym_leader ? 6000ms : 3000ms); + } + stream.log("[Pastoria Gym puzzle] Reward dialogue cleared; outside the Gym doorway, ready for 05.01.", + COLOR_GREEN); } -void checkpoint_0404(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_galactic_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_galactic_warehouse: transition not detected.", - env.console); - } - }, - false); +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_04::name() const { + return "04: Veilstone to Pastoria"; } -void checkpoint_0405(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "handle_battle_veilstone_galactic_grunts_1: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Segment_04::start_text() const { + return "Start: Inside Veilstone Gym in front of Maylene, facing north, with third-badge rewards and dialogue finished."; } -void checkpoint_0406(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!enter_galactic_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_galactic_warehouse: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Segment_04::end_text() const { + return "Outside Pastoria Gym at the doorway, facing south, with fourth-badge rewards and dialogue finished."; } -void checkpoint_0407(SingleSwitchProgramEnvironment& env, +void AutoStory_Segment_04::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!exit_galactic_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "exit_galactic_warehouse: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + AutoStory_Checkpoint_0401().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0402().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0403().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0404().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0405().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Checkpoint_0401::name() const { + return "04.01 - Veilstone recovery"; +} + +std::string AutoStory_Checkpoint_0401::start_text() const { + return "Inside Veilstone Gym in front of Maylene, facing north, with third-badge rewards and dialogue finished."; } -void checkpoint_0408(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0401::end_text() const { + return "Healed and outside Veilstone Pokemon Center, with Dawn's post-Gym dialogue finished."; +} + +void AutoStory_Checkpoint_0401::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_route_214: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0401(env, context, options, stats); } -void checkpoint_0409(SingleSwitchProgramEnvironment& env, +void checkpoint_0401(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1405,35 +953,52 @@ void checkpoint_0409(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!handle_battle(env.console, context, "route_214_psychic_abigail")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "handle_battle_route_214_psychic_abigail: transition not detected.", - env.console); + { + if (!leave_gym(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "leave_gym: transition not detected.", + env.console); + } + } + { + if (!go_to_veilstone_pokemon_center(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_veilstone_pokemon_center: transition not detected.", + env.console); + } } + { + if (!heal_at_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_pokemon_center: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0402::name() const { + return "04.02 - Galactic grunts and Fly"; +} + +std::string AutoStory_Checkpoint_0402::start_text() const { + return "Healed and outside Veilstone Pokemon Center, with Dawn's post-Gym dialogue finished."; +} + +std::string AutoStory_Checkpoint_0402::end_text() const { + return "Outside Galactic Warehouse with Fly obtained and dialogue cleared."; } -void checkpoint_0410(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0402::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!walk_through_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "walk_through_route_214: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0402(env, context, options, stats); } -void checkpoint_0411(SingleSwitchProgramEnvironment& env, +void checkpoint_0402(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1443,35 +1008,59 @@ void checkpoint_0411(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!pass_by_valor_lakefront(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "pass_by_valor_lakefront: transition not detected.", - env.console); + { + if (!go_to_galactic_warehouse(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_galactic_warehouse: transition not detected.", + env.console); + } } + { + if (!handle_battle(env.console, context, "veilstone_galactic_grunts_1", stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "handle_battle_veilstone_galactic_grunts_1: transition not detected.", + env.console); + } + } + { + if (!enter_galactic_warehouse(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_galactic_warehouse: transition not detected.", + env.console); + } + } + { + if (!exit_galactic_warehouse(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "exit_galactic_warehouse: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0412(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0403::name() const { + return "04.03 - Route 214 and Valor Lakefront"; +} + +std::string AutoStory_Checkpoint_0403::start_text() const { + return "Outside Galactic Warehouse with Fly obtained and dialogue cleared."; +} + +std::string AutoStory_Checkpoint_0403::end_text() const { + return "Outside the Valor Lakefront south gate on Route 213."; +} + +void AutoStory_Checkpoint_0403::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_building_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_building_route_213: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0403(env, context, options, stats); } -void checkpoint_0413(SingleSwitchProgramEnvironment& env, +void checkpoint_0403(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1481,35 +1070,66 @@ void checkpoint_0413(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_through_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_route_213: transition not detected.", - env.console); + { + if (!go_to_route_214(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_route_214: transition not detected.", + env.console); + } + } + { + if (!handle_battle(env.console, context, "route_214_psychic_abigail", stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "handle_battle_route_214_psychic_abigail: transition not detected.", + env.console); + } } + { + if (!walk_through_route_214(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "walk_through_route_214: transition not detected.", + env.console); + } + } + { + if (!pass_by_valor_lakefront(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "pass_by_valor_lakefront: transition not detected.", + env.console); + } + } + { + if (!go_through_building_route_213(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_building_route_213: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0404::name() const { + return "04.04 - Route 213 to Pastoria"; +} + +std::string AutoStory_Checkpoint_0404::start_text() const { + return "Outside the Valor Lakefront south gate on Route 213."; } -void checkpoint_0414(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0404::end_text() const { + return "Healed and outside Pastoria Pokemon Center at the doorway."; +} + +void AutoStory_Checkpoint_0404::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_house_east_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_east_pastoria: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0404(env, context, options, stats); } -void checkpoint_0415(SingleSwitchProgramEnvironment& env, +void checkpoint_0404(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1519,35 +1139,59 @@ void checkpoint_0415(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_through_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_pastoria: transition not detected.", - env.console); + { + if (!go_through_route_213(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_213: transition not detected.", + env.console); + } + } + { + if (!go_through_house_east_pastoria(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_east_pastoria: transition not detected.", + env.console); + } } + { + if (!go_through_pastoria(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_pastoria: transition not detected.", + env.console); + } + } + { + if (!heal_at_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_pokemon_center: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0405::name() const { + return "04.05 - Pastoria Gym and Wake"; } -void checkpoint_0416(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0405::start_text() const { + return "Healed and outside Pastoria Pokemon Center at the doorway."; +} + +std::string AutoStory_Checkpoint_0405::end_text() const { + return "Outside Pastoria Gym at the doorway, facing south, with fourth-badge rewards and dialogue finished."; +} + +void AutoStory_Checkpoint_0405::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_at_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_pokemon_center: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0405(env, context, options, stats); } -void checkpoint_0417(SingleSwitchProgramEnvironment& env, +void checkpoint_0405(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1557,16 +1201,19 @@ void checkpoint_0417(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_to_pastoria_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_pastoria_gym: transition not detected.", - env.console); + { + if (!go_to_pastoria_gym(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_pastoria_gym: transition not detected.", + env.console); + } } + pastoria_gym_puzzle_and_wake(env, context, stats); + context.wait_for_all_requests(); }, - false); + true); } - } } } \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h index 6b35d624b9..6ddc235338 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_04.h @@ -101,198 +101,6 @@ void checkpoint_0405(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_0406 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0406(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0407 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0407(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0408 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0408(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0409 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0409(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0410 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0410(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0411 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0411(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0412 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0412(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0413 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0413(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0414 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0414(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0415 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0415(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0416 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0416(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0417 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0417(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - -class AutoStory_Checkpoint_0418 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp index a232e233a5..07b5b841dd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.cpp @@ -19,8 +19,12 @@ #include "PokemonBDSP/Inference/Battles/PokemonBDSP_BattleMenuDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" +#include "PokemonBDSP/Programs/AutoStory/Detect/PokemonBDSP_AutoStory_OverworldDetector.h" #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" + +#include "PokemonBDSP_AutoStory_Segment_02.h" #include "PokemonBDSP_AutoStory_Segment_05.h" @@ -28,320 +32,9 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - -std::string AutoStory_Segment_05::name() const { - return "05: Fourth Badge to Fifth Badge"; -} -std::string AutoStory_Segment_05::start_text() const { - return "Start: Fourth Gym Badge acquired (Pastoria City)."; -} -std::string AutoStory_Segment_05::end_text() const { - return "End: Fifth Gym Badge acquired (Veilstone City)."; -} - -void AutoStory_Segment_05::run_segment(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0501().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0502().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0503().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0504().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0505().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0506().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0507().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0508().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0509().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0510().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0511().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0512().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0513().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0514().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0515().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0516().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0517().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} - - -std::string AutoStory_Checkpoint_0501::name() const { - return "05.01 - go_to_pastoria_pokemon_center"; -} -std::string AutoStory_Checkpoint_0501::start_text() const { - return "Fourth Gym Badge acquired (Pastoria City)."; -} -std::string AutoStory_Checkpoint_0501::end_text() const { - return "Entered Pastoria City Pokemon Center."; -} -void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0501(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0502::name() const { - return "05.02 - exit_pastoria_pokemon_center"; -} -std::string AutoStory_Checkpoint_0502::start_text() const { - return "Inside Pastoria City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0502::end_text() const { - return "Healed and exited Pastoria City Pokemon Center."; -} -void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0502(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0503::name() const { - return "05.03 - pastoria_city_drama"; -} -std::string AutoStory_Checkpoint_0503::start_text() const { - return "Outside Pastoria City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0503::end_text() const { - return "Pastoria City drama complete, exited east building."; -} -void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0503(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0504::name() const { - return "05.04 - go_through_house_east_pastoria"; -} -std::string AutoStory_Checkpoint_0504::start_text() const { - return "Inside east Pastoria building."; -} -std::string AutoStory_Checkpoint_0504::end_text() const { - return "Exited east Pastoria building onto Route 213."; -} -void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0504(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0505::name() const { - return "05.05 - go_through_route_213_chasing"; -} -std::string AutoStory_Checkpoint_0505::start_text() const { - return "On Route 213 chasing Galactic Grunts."; -} -std::string AutoStory_Checkpoint_0505::end_text() const { - return "Entered east Route 213 building."; -} -void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0505(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0506::name() const { - return "05.06 - go_through_building_route_213"; -} -std::string AutoStory_Checkpoint_0506::start_text() const { - return "Inside building on Route 213."; -} -std::string AutoStory_Checkpoint_0506::end_text() const { - return "Exited building onto Route 214."; -} -void AutoStory_Checkpoint_0506::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0506(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0507::name() const { - return "05.07 - go_through_route_214"; -} -std::string AutoStory_Checkpoint_0507::start_text() const { - return "On Route 214 chasing Galactic Grunts."; -} -std::string AutoStory_Checkpoint_0507::end_text() const { - return "Flew to Solaceon Town."; -} -void AutoStory_Checkpoint_0507::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0507(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0508::name() const { - return "05.08 - go_inside_solaceon_pokemon_center"; -} -std::string AutoStory_Checkpoint_0508::start_text() const { - return "In Solaceon Town."; -} -std::string AutoStory_Checkpoint_0508::end_text() const { - return "Entered Solaceon Town Pokemon Center."; -} -void AutoStory_Checkpoint_0508::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0508(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0509::name() const { - return "05.09 - heal_at_solaceon_pokemon_center"; -} -std::string AutoStory_Checkpoint_0509::start_text() const { - return "Inside Solaceon Town Pokemon Center."; -} -std::string AutoStory_Checkpoint_0509::end_text() const { - return "Healed and exited Solaceon Town Pokemon Center."; -} -void AutoStory_Checkpoint_0509::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0509(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0510::name() const { - return "05.10 - go_to_the_psyduck_route_210"; -} -std::string AutoStory_Checkpoint_0510::start_text() const { - return "Outside Solaceon Town Pokemon Center."; -} -std::string AutoStory_Checkpoint_0510::end_text() const { - return "Reached Celestic Town Pokemon Center via Route 210."; -} -void AutoStory_Checkpoint_0510::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0510(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0511::name() const { - return "05.11 - heal_at_celestic_pokemon_center"; -} -std::string AutoStory_Checkpoint_0511::start_text() const { - return "Inside Celestic Town Pokemon Center."; -} -std::string AutoStory_Checkpoint_0511::end_text() const { - return "Healed and exited Celestic Town Pokemon Center."; -} -void AutoStory_Checkpoint_0511::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0511(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0512::name() const { - return "05.12 - celestic_town_drama"; -} -std::string AutoStory_Checkpoint_0512::start_text() const { - return "In Celestic Town."; -} -std::string AutoStory_Checkpoint_0512::end_text() const { - return "Entered Celestic Town cave."; -} -void AutoStory_Checkpoint_0512::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0512(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0513::name() const { - return "05.13 - discover_the_paintings"; -} -std::string AutoStory_Checkpoint_0513::start_text() const { - return "Inside Celestic Town cave."; -} -std::string AutoStory_Checkpoint_0513::end_text() const { - return "Discovered paintings and exited cave."; -} -void AutoStory_Checkpoint_0513::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0513(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0514::name() const { - return "05.14 - talk_to_cyrus_and_fly_hearthome"; -} -std::string AutoStory_Checkpoint_0514::start_text() const { - return "Outside Celestic Town cave."; -} -std::string AutoStory_Checkpoint_0514::end_text() const { - return "Entered Hearthome City Pokemon Center."; -} -void AutoStory_Checkpoint_0514::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0514(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0515::name() const { - return "05.15 - heal_at_hearthome_pokemon_center"; -} -std::string AutoStory_Checkpoint_0515::start_text() const { - return "Inside Hearthome City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0515::end_text() const { - return "Healed and exited Hearthome City Pokemon Center."; -} -void AutoStory_Checkpoint_0515::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0515(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0516::name() const { - return "05.16 - get_into_hearthome_gym"; -} -std::string AutoStory_Checkpoint_0516::start_text() const { - return "Outside Hearthome City gym."; -} -std::string AutoStory_Checkpoint_0516::end_text() const { - return "Fifth Gym Badge acquired (Hearthome City)."; -} -void AutoStory_Checkpoint_0516::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0516(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0517::name() const { - return "05.17 - Hearthome Gym Puzzle"; -} -std::string AutoStory_Checkpoint_0517::start_text() const { - return "Outside Hearthome City gym."; -} -std::string AutoStory_Checkpoint_0517::end_text() const { - return "Fifth Gym Badge acquired (Hearthome City)."; -} -void AutoStory_Checkpoint_0517::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0517(env, context, options, stats); -} +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ @@ -365,52 +58,9 @@ move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& con } } -static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ - context.wait_for_all_requests(); - stream.log("Battling " + trainerid + "...", COLOR_GREEN); - BattleMenuWatcher battle_menu(BattleType::TRAINER); - int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); - if (ret_menu < 0){ - stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); - return false; - } - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 500ms); - - if (trainerid == "pastoria_clint" or trainerid == "route_213_tuber_chelsea" or - trainerid == "route_213_tuber_jared" or trainerid == "route_213_beauty_cindy" or - trainerid == "route_210_veteran_brian"){ - // Move the battle-move cursor up. - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - - }else if (trainerid == "route_214_galactic_grunt" or trainerid == "route_210_rancher_marco" or - trainerid == "route_210_breeder_amber" or trainerid == "route_210_blackbelt_adam"){ - // Move the battle-move cursor down. - pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - - }else if (trainerid == "route_210_ace_alyssa" or trainerid == "route_210_birdkeeper_brianna" or - trainerid == "celestic_town_galactic_grunt"){ - // Keep the current battle-move selection. - } - context.wait_for_all_requests(); - - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; - } - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_" + trainerid + ": transition confirmed."); - return true; -} +// --------------------------------------------------------------------------- +// Named route navigation blocks +// --------------------------------------------------------------------------- static bool leave_gym(VideoStream& stream, ProControllerContext& context){ DpadState dpad; @@ -443,39 +93,53 @@ static bool leave_gym(VideoStream& stream, ProControllerContext& context){ return true; } +// Starts at 04.18's completed Gym exit. Fly within Pastoria to use the +// Pokemon Center's fixed arrival position instead of an unanchored city walk. static bool go_to_pastoria_pokemon_center(VideoStream& stream, ProControllerContext& context){ - DpadState dpad; context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - stream.log("go_to_pastoria_pokemon_center: going to pokemon center"); + stream.log("[Pastoria Center] Flying from outside the Gym to the city's Pokemon Center."); + if (!fly_to(stream, context, FlyPoint::CurrentCity)){ + stream.log("[Pastoria Center] Flight to the current city's Pokemon Center failed.", COLOR_RED); + return false; + } - pbf_move_left_joystick(context, {+1, 0}, 900ms, 100ms); // 6 - pbf_move_left_joystick(context, {0, +1}, 2400ms, 100ms); // 12+ - pbf_move_left_joystick(context, {-1, 0}, 800ms, 100ms); // 3+ - dpad.last_dir = DPAD_LEFT; - repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 7, true); + BlackScreenWatcher landed(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{landed}}) != 0){ + stream.log("[Pastoria Center] Flight transition did not finish.", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret1 = run_until(stream, + // The Fly arrival is directly south of the Center entrance. + BlackScreenWatcher entry_black(COLOR_RED); + if (run_until(stream, context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 20000ms, 100ms); - }, - {{black_screen}}); - if (ret1 < 0){ - stream.log("go_to_pastoria_pokemon_centert: Didn't detect transition!", COLOR_RED); + [](ProControllerContext& ctx){ + pbf_move_left_joystick(ctx, {0, +1}, 5000ms, 0ms); + pbf_wait(ctx, 1000ms); + }, + {{entry_black}}) != 0){ + stream.log("[Pastoria Center] Entrance transition did not start.", COLOR_RED); return false; } - stream.log("go_to_pastoria_pokemon_center: transition confirmed."); + BlackScreenWatcher inside(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 100, 10, BlackScreenWatcher::FinderType::GONE, 500ms); + if (wait_until(stream, context, 30s, {{inside}}) != 0){ + stream.log("[Pastoria Center] Entrance transition did not finish.", COLOR_RED); + return false; + } + pbf_wait(context, 500ms); + context.wait_for_all_requests(); + stream.log("[Pastoria Center] Entered after the completed Gym return.", COLOR_GREEN); return true; } -static bool exit_pastoria_pokemon_center(VideoStream& stream, ProControllerContext& context){ - return heal_pokemon(stream, context, "Pastoria"); +static bool exit_pastoria_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ + return heal_pokemon(stream, context, "Pastoria", &stats); } -static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& context){ +static bool +pastoria_city_drama(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -501,10 +165,11 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte {{talk_to_grunt_1}}); if (ret < 0){ stream.log("pastoria_city_drama_talk_to_grunt_1: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("pastoria_city_drama_talk_to_grunt_1: talking to grunt!"); - pbf_mash_button(context, BUTTON_B, 8000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); }; } @@ -526,6 +191,7 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte {{talk_to_grunt_2}}); if (ret < 0){ stream.log("pastoria_city_drama_talk_to_grunt_2: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("pastoria_city_drama_talk_to_grunt_2: talking to grunt!"); @@ -542,7 +208,7 @@ static bool pastoria_city_drama(VideoStream& stream, ProControllerContext& conte return false; } stream.log("pastoria_city_drama_talk_to_clint: battling Clint."); - handle_battle(stream, context, "pastoria_clint"); + fight_trainer_using_moves(stream, context, stats, "pastoria_clint", true); pbf_mash_button(context, BUTTON_B, 3000ms); }; } @@ -590,7 +256,8 @@ static bool go_through_house_east_pastoria(VideoStream& stream, ProControllerCon return true; } -static bool go_through_route_213_chasing(VideoStream& stream, ProControllerContext& context){ +static bool +go_through_route_213_chasing(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -598,7 +265,9 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte stream.log("go_through_route_213_chasing: continuing the drama"); pbf_move_left_joystick(context, {+1, 0}, 1600ms, 100ms); // 12 - activate_repel(stream, context); + if (!activate_repel(stream, context)){ + return false; + } context.wait_for_all_requests(); pbf_wait(context, 1000ms); pbf_move_left_joystick(context, {+1, 0}, 3600ms, 100ms); // 18+ @@ -627,9 +296,15 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte }else if (ret == 0){ context.wait_for_all_requests(); stream.log("go_through_route_213_beauty_cindy: Starting battle."); - handle_battle(stream, context, "route_213_beauty_cindy"); + fight_trainer_using_moves(stream, context, stats, "route_213_beauty_cindy", true); context.wait_for_all_requests(); pbf_wait(context, 3000ms); + + // Cindy approaches from the west and blocks the next westward leg. + // Step south into the gap above the cliff before walking around it. + dpad.last_dir = DPAD_LEFT; + repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1, true); + context.wait_for_all_requests(); } } context.wait_for_all_requests(); @@ -648,21 +323,37 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); + // Rock Smash uses the same short dialogue box as an NPC conversation. + ShortDialogPromptDetector unexpected_choice(stream.overlay(), {0.70, 0.60, 0.20, 0.20}, COLOR_RED); ShortDialogWatcher talk_to_grunt(COLOR_BLUE); ret = run_until(stream, context, [](ProControllerContext& context){ - ssf_press_left_joystick(context, {+1, 0}, 0ms, 5000ms); - ssf_mash1_button(context, BUTTON_A, 5000ms); + // Finish approaching and facing east before interacting once. + pbf_move_left_joystick(context, {+1, 0}, 5000ms, 100ms); + pbf_press_button(context, BUTTON_A, 80ms, 1000ms); }, - {{talk_to_grunt}}); + {{unexpected_choice}, {talk_to_grunt}}); if (ret < 0){ stream.log("go_through_route_213_chasing_talk_to_grunt: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ - stream.log("go_through_route_213_chasing_talk_to_grunt: talking to grunt!"); - pbf_mash_button(context, BUTTON_B, 6000ms); - }; + stream.log("[Route 213 grunt] Unexpected choice prompt; stopping the approach.", COLOR_RED); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + return false; + } + + // The blank left edge of the NPC nameplate is independent of language. + // A Rock Smash prompt has no nameplate, even if its arrow appears late. + BlackScreenWatcher npc_nameplate(COLOR_BLUE, {0.21, 0.765, 0.01, 0.02}); + if (wait_until(stream, context, 1000ms, {{npc_nameplate}}) != 0){ + stream.log("[Route 213 grunt] NPC nameplate not detected; refusing this dialogue.", COLOR_RED); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); + return false; + } + stream.log("[Route 213 grunt] NPC dialogue detected at the grunt approach."); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -670,22 +361,7 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte pbf_move_left_joystick(context, {0, +1}, 400ms, 100ms); // 2+ context.wait_for_all_requests(); - ShortDialogWatcher repel_dialog(COLOR_RED); - ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 2000ms, 100ms); - }, - {{repel_dialog}}); - if (ret == 0){ - pbf_mash_button(context, BUTTON_B, 3000ms); - }else if (ret < 0){ - stream.log("go_through_route_213: Didn't detect repel dialog box!", COLOR_RED); - return false; - } + force_repel_expiry(stream, context, RepelRenewalChoice::NO, false, true); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -716,7 +392,7 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte return false; }else if (ret == 0){ stream.log("go_through_route_213_tuber_jared: Starting battle."); - handle_battle(stream, context, "route_213_tuber_jared"); + fight_trainer_using_moves(stream, context, stats, "route_213_tuber_jared", true); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } @@ -748,7 +424,7 @@ static bool go_through_route_213_chasing(VideoStream& stream, ProControllerConte stream.log("go_through_route_213_tuber_chelsea: Didn't detect battle starting!"); }else if (ret == 0){ stream.log("go_through_route_213_tuber_chelsea: Starting battle."); - handle_battle(stream, context, "route_213_tuber_chelsea"); + fight_trainer_using_moves(stream, context, stats, "route_213_tuber_chelsea", true); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } @@ -790,7 +466,8 @@ static bool go_through_building_route_213(VideoStream& stream, ProControllerCont return true; } -static bool go_through_route_214(VideoStream& stream, ProControllerContext& context){ +static bool +go_through_route_214(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -816,10 +493,11 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont {{talk_to_grunt}}); if (ret < 0){ stream.log("go_through_route_214_talk_to_grunt: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("go_through_route_214_talk_to_grunt: talking to grunt!"); - pbf_mash_button(context, BUTTON_B, 6000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); }; context.wait_for_all_requests(); @@ -839,6 +517,7 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont {{talk_to_grunt_2}}); if (ret < 0){ stream.log("go_through_route_214_talk_to_grunt_2: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("go_through_route_214_talk_to_grunt_2: talking to grunt!"); @@ -855,7 +534,7 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont return false; }else if (ret == 0){ stream.log("go_through_route_214_galactic_grunt: Starting battle."); - handle_battle(stream, context, "route_214_galactic_grunt"); + fight_trainer_using_moves(stream, context, stats, "route_214_galactic_grunt", true); context.wait_for_all_requests(); pbf_wait(context, 5000ms); } @@ -876,16 +555,19 @@ static bool go_through_route_214(VideoStream& stream, ProControllerContext& cont {{talk_to_cynthia}}); if (ret < 0){ stream.log("go_through_route_214_talk_to_cynthia: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("go_through_route_214_talk_to_cynthia: talking to Cynthia!"); - pbf_mash_button(context, BUTTON_A, 12000ms); + clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); }; context.wait_for_all_requests(); pbf_wait(context, 4000ms); - fly_to(stream, context, FlyPoint::SolaceonTown); + if (!fly_to(stream, context, FlyPoint::SolaceonTown)){ + return false; + } return true; } @@ -912,14 +594,14 @@ static bool go_inside_solaceon_pokemon_center(VideoStream& stream, ProController return true; } -static bool heal_at_solaceon_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool heal_at_solaceon_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ stream.log("heal_at_solaceon_pokemon_center: healing pokemon"); context.wait_for_all_requests(); - heal_pokemon(stream, context, "Solaceon"); - return true; + return heal_pokemon(stream, context, "Solaceon", &stats); } -static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContext& context){ +static bool +go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -935,7 +617,9 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 1); context.wait_for_all_requests(); pbf_wait(context, 1000ms); - activate_repel(stream, context); + if (!activate_repel(stream, context)){ + return false; + } context.wait_for_all_requests(); repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); @@ -966,7 +650,7 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex stream.log("go_to_the_psyduck_route_210_rancher_marco: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_210_rancher_marco"); + fight_trainer_using_moves(stream, context, stats, "route_210_rancher_marco", true); } } } @@ -979,6 +663,7 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex { MarkDetector battle_dialogue(stream.overlay(), {0.1, 0.0, 0.8, 0.9}); ShortDialogWatcher battle_dialogue_2(COLOR_BLUE); + BattleMenuWatcher amber_menu(BattleType::TRAINER, COLOR_GREEN); int ret = run_until(stream, context, @@ -992,25 +677,26 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); pbf_mash_button(context, BUTTON_A, 200000ms); }, - {{battle_dialogue}, {battle_dialogue_2}}); + {{battle_dialogue}, {battle_dialogue_2}, {amber_menu}}); if (ret >= 0){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); stream.log("go_to_the_psyduck_route_210_breeder_amber: marker detected!", COLOR_GREEN); - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("go_to_the_psyduck_route_210_breeder_amber: black screen not detected!", COLOR_RED); - return false; - }else if (ret == 0){ - handle_battle(stream, context, "route_210_breeder_amber"); + if (ret != 2){ + // Confirm the command menu, not a black fade that could be battle exit. + BattleMenuWatcher ready(BattleType::TRAINER, COLOR_GREEN); + if (run_until(stream, + context, + [](ProControllerContext& ctx){ + pbf_mash_button(ctx, BUTTON_A, 30000ms); + }, + {{ready}}) != 0){ + stream.log("Route 210 Amber: Battle command menu not detected.", COLOR_RED); + return false; + } } + fight_trainer_using_moves(stream, context, stats, "route_210_breeder_amber", true); } } @@ -1030,11 +716,18 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex {{talk_to_psyducks}}); if (ret_psyducks < 0){ stream.log("go_to_the_psyduck_route_210_talk_to_psyducks: dialog box not detected!", COLOR_RED); + return false; } if (ret_psyducks == 0){ stream.log("go_to_the_psyduck_route_210_talk_to_psyducks: talking to Psyducks!"); - pbf_mash_button(context, BUTTON_A, 28000ms); - }; + // Finish the medicine prompts, then wait through the departure animation. + // Its dialogue-free gap does not mean Cynthia's follow-up is finished. + mash_until_dialogue_ends(stream, context, BUTTON_A, 60s); + stream.log("[Route 210] Waiting for Cynthia after the Psyducks leave."); + wait_for_dialogue(stream, context, "Route 210 - Cynthia after Psyducks", 30s); + clear_post_battle_dialogue(stream, context, BUTTON_A, 6000ms); + stream.log("[Route 210] Cynthia follow-up cleared; resuming movement and Repel setup."); + } context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1044,27 +737,14 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex repeat_dpad(context, dpad, DPAD_RIGHT, 80ms, 300ms, 3); context.wait_for_all_requests(); - activate_repel(stream, context); + if (!activate_repel(stream, context)){ + return false; + } pbf_move_left_joystick(context, {0, +1}, 8000ms, 100ms); // 29+ tall grass repeat_dpad(context, dpad, DPAD_LEFT, 80ms, 300ms, 1); - { - ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, +1}, 2600ms, 100ms); - }, - {{repel_dialog}}); - if (ret == 0){ - stream.log("go_to_the_psyduck_route_210: Reactivating repel.", COLOR_GREEN); - pbf_mash_button(context, BUTTON_A, 3000ms); - }else if (ret < 0){ - stream.log("go_to_the_psyduck_route_210: Didn't detect repel dialog box!", COLOR_RED); - return false; - } - } + force_repel_expiry(stream, context, RepelRenewalChoice::YES, true, true); context.wait_for_all_requests(); pbf_move_left_joystick(context, {0, +1}, 1000ms, 100ms); // 5+ @@ -1104,7 +784,7 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex stream.log("go_to_the_psyduck_route_210_ace_alyssa: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_210_ace_alyssa"); + fight_trainer_using_moves(stream, context, stats, "route_210_ace_alyssa", true); } } } @@ -1141,7 +821,7 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex stream.log("go_to_the_psyduck_route_210_ace_ernest: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_210_ace_ernest"); + fight_trainer_using_moves(stream, context, stats, "route_210_ace_ernest", true); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_210_ace_ernest"); @@ -1176,32 +856,16 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex stream.log("go_to_the_psyduck_route_210_blackbelt_adam: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_210_blackbelt_adam"); + fight_trainer_using_moves(stream, context, stats, "route_210_blackbelt_adam", true); } } } context.wait_for_all_requests(); - { - ShortDialogWatcher repel_dialog(COLOR_RED); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); // 1+ - pbf_move_left_joystick(context, {-1, 0}, 1000ms, 100ms); - pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); - }, - {{repel_dialog}}); - if (ret == 0){ - stream.log("go_to_the_psyduck_route_210: Not reactivating repel.", COLOR_GREEN); - pbf_mash_button(context, BUTTON_B, 3000ms); - }else if (ret < 0){ - stream.log("go_to_the_psyduck_route_210: Didn't detect repel dialog box!", COLOR_RED); - return false; - } - } + pbf_move_left_joystick(context, {0, -1}, 200ms, 100ms); + pbf_move_left_joystick(context, {+1, 0}, 200ms, 100ms); + force_repel_expiry(stream, context, RepelRenewalChoice::NO, false, true); context.wait_for_all_requests(); pbf_move_left_joystick(context, {+1, 0}, 1000ms, 100ms); @@ -1235,7 +899,7 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex stream.log("go_to_the_psyduck_route_210_birdkeeper_brianna: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_210_birdkeeper_brianna"); + fight_trainer_using_moves(stream, context, stats, "route_210_birdkeeper_brianna", true); } } } @@ -1269,7 +933,7 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex stream.log("go_to_the_psyduck_route_210_veteran_brian: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_210_veteran_brian"); + fight_trainer_using_moves(stream, context, stats, "route_210_veteran_brian", true); } } } @@ -1299,14 +963,14 @@ static bool go_to_the_psyduck_route_210(VideoStream& stream, ProControllerContex return true; } -static bool heal_at_celestic_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool heal_at_celestic_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ stream.log("heal_at_celestic_pokemon_center: healing pokemon"); context.wait_for_all_requests(); - heal_pokemon(stream, context, "Celestic"); - return true; + return heal_pokemon(stream, context, "Celestic", &stats); } -static bool celestic_town_drama(VideoStream& stream, ProControllerContext& context){ +static bool +celestic_town_drama(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1326,10 +990,11 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte {{talk_to_cynthias_grandma}}); if (ret < 0){ stream.log("celestic_town_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("celestic_town_talk_to_cynthias_grandma: talking to Cynthia's grandma!"); - pbf_mash_button(context, BUTTON_B, 4000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); }; } @@ -1352,6 +1017,7 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte {{talk_to_the_grunt}}); if (ret < 0){ stream.log("celestic_town_talk_to_the_grunt: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("celestic_town_talk_to_the_grunt: talking to the grunt!"); @@ -1368,8 +1034,8 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte return false; } stream.log("celestic_town_talk_to_the_grunt: starting the battle."); - handle_battle(stream, context, "celestic_town_galactic_grunt"); - pbf_mash_button(context, BUTTON_B, 17000ms); + fight_trainer_using_moves(stream, context, stats, "celestic_town_galactic_grunt", true); + clear_post_battle_dialogue(stream, context, BUTTON_B, 4000ms); }; } @@ -1394,7 +1060,6 @@ static bool celestic_town_drama(VideoStream& stream, ProControllerContext& conte return true; } - static bool discover_the_paintings(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); @@ -1421,10 +1086,11 @@ static bool discover_the_paintings(VideoStream& stream, ProControllerContext& co {{talk_to_cynthias_grandma}}); if (ret < 0){ stream.log("discover_the_paintings_talk_to_cynthias_grandma: dialog box not detected!", COLOR_RED); + return false; } if (ret == 0){ stream.log("discover_the_paintings_talk_to_cynthias_grandma: talking to the grandma!"); - pbf_mash_button(context, BUTTON_B, 20000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); }; } context.wait_for_all_requests(); @@ -1456,20 +1122,17 @@ static bool discover_the_paintings(VideoStream& stream, ProControllerContext& co } static bool talk_to_cyrus_and_fly_hearthome(VideoStream& stream, ProControllerContext& context){ + // The cave exit can start dialogue before the checkpoint can save. pbf_press_button(context, BUTTON_L, 80ms, 300ms); pbf_press_button(context, BUTTON_L, 80ms, 300ms); context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - stream.log("talk_to_cyrus_and_fly_hearthome: continuing the drama."); - wait_for_dialogue(stream, context, "Cyrus", 10000ms); - pbf_mash_button(context, BUTTON_B, 10000ms); - - context.wait_for_all_requests(); - pbf_wait(context, 3000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); - fly_to(stream, context, FlyPoint::HearthomeCity); + stream.log("[After Celestic cave] Flying to Hearthome after Cyrus's conversation."); + if (!fly_to(stream, context, FlyPoint::HearthomeCity)){ + return false; + } context.wait_for_all_requests(); pbf_wait(context, 3000ms); @@ -1491,12 +1154,10 @@ static bool talk_to_cyrus_and_fly_hearthome(VideoStream& stream, ProControllerCo return true; } - -static bool heal_at_hearthome_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool heal_at_hearthome_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ stream.log("heal_at_hearthome_pokemon_center: healing pokemon"); context.wait_for_all_requests(); - heal_pokemon(stream, context, "Hearthome"); - return true; + return heal_pokemon(stream, context, "Hearthome", &stats); } static bool get_into_hearthome_gym(VideoStream& stream, ProControllerContext& context){ @@ -1527,159 +1188,241 @@ static bool get_into_hearthome_gym(VideoStream& stream, ProControllerContext& co return true; } -void checkpoint_0501(SingleSwitchProgramEnvironment& env, +// Start inside Hearthome Gym at its entrance; finish outside with Cynthia dialogue cleared. +static void hearthome_gym_puzzle_and_cynthia(SingleSwitchProgramEnvironment& env, ProControllerContext& context, - AutoStoryOptions options, AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_pastoria_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_pastoria_pokemon_center: transition not detected.", - env.console); + VideoStream& stream = env.console; + struct Move { + DpadPosition dir; + int count; + Milliseconds delay; + }; + + // Trial: use the supplied counts without an extra facing press. + const Move sequence1[] = { + {DPAD_UP, 7, 0ms}, + {DPAD_UP, 1, 5000ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_UP, 8, 0ms}, + {DPAD_UP, 1, 3500ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 5000ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_UP, 1, 0ms}, + {DPAD_UP, 1, 3500ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 5000ms}, + {DPAD_LEFT, 6, 0ms}, + {DPAD_UP, 8, 0ms}, + {DPAD_UP, 1, 3500ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_UP, 9, 0ms}, + {DPAD_LEFT, 1, 0ms}, + {DPAD_LEFT, 1, 5000ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_UP, 8, 0ms}, + {DPAD_UP, 1, 3500ms}, + {DPAD_UP, 6, 0ms}, + }; + + stream.log("[Hearthome Gym puzzle] Approaching Fantina."); + for (const auto& move : sequence1){ + // repeat_dpad(context, dpad, move.dir, 80ms, 300ms, move.count, true); + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay > 0ms){ + pbf_wait(context, move.delay); + context.wait_for_all_requests(); + BlackScreenWatcher visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Gym: Room/lift transition did not finish.", + stream); + } + OverworldWatcher ready; + if (wait_until(stream, context, 15s, {{ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Gym: Overworld not ready after the recorded room/lift wait.", + stream); + } } - }, - true); -} + } + } + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 150ms, 300ms); + fight_trainer_using_moves(stream, context, stats, "Fantina", true); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); + + const Move sequence2[] = { + {DPAD_DOWN, 6, 0ms}, + {DPAD_DOWN, 1, 3500ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_LEFT, 6, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_DOWN, 1, 5000ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_DOWN, 1, 3500ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 6, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_DOWN, 1, 5000ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_DOWN, 1, 3500ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 10, 0ms}, + {DPAD_RIGHT, 1, 0ms}, + {DPAD_RIGHT, 1, 5000ms}, + {DPAD_LEFT, 2, 0ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_RIGHT, 2, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_DOWN, 1, 3500ms}, + {DPAD_DOWN, 8, 0ms}, + {DPAD_LEFT, 6, 0ms}, + {DPAD_DOWN, 1, 0ms}, + {DPAD_DOWN, 1, 5000ms}, + {DPAD_DOWN, 8, 0ms}, + }; -void checkpoint_0502(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!exit_pastoria_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "exit_pastoria_pokemon_center: transition not detected.", - env.console); + stream.log("[Hearthome Gym puzzle] Returning from Fantina."); + for (size_t move_index = 0; move_index + 1 < sizeof(sequence2) / sizeof(sequence2[0]); move_index++){ + const auto& move = sequence2[move_index]; + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(context, move.dir, 80ms, 300ms); + if (move.delay > 0ms){ + pbf_wait(context, move.delay); + context.wait_for_all_requests(); + BlackScreenWatcher visible(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 15s, {{visible}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Gym: Room/lift transition did not finish.", + stream); + } + OverworldWatcher ready; + if (wait_until(stream, context, 15s, {{ready}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Gym return: Overworld not ready after the recorded room/lift wait.", + stream); + } } - }, - false); + } + } + context.wait_for_all_requests(); + BlackScreenWatcher exit_black(COLOR_RED); + if (run_until(stream, + context, + [&](ProControllerContext& ctx){ + const auto& move = sequence2[sizeof(sequence2) / sizeof(sequence2[0]) - 1]; + for (int press = 0; press < move.count; press++){ + pbf_press_dpad(ctx, move.dir, 80ms, 300ms); + } + pbf_wait(ctx, 5000ms); + }, + {{exit_black}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Gym return: Exit transition did not start.", + stream); + } + BlackScreenWatcher outside(COLOR_RED, + {0.1, 0.1, 0.8, 0.8}, + 100, + 10, + BlackScreenWatcher::FinderType::GONE, + 500ms); + if (wait_until(stream, context, 30s, {{outside}}) != 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "Hearthome Gym return: Exit transition did not finish.", + stream); + } + context.wait_for_all_requests(); + wait_for_dialogue(stream, context, "Cynthia after Hearthome Gym", 30000ms); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); + context.wait_for_all_requests(); + stream.log( + "[Hearthome Gym puzzle] Fantina defeated; outside the Gym with Cynthia's dialogue finished.", + COLOR_GREEN); } -void checkpoint_0503(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!pastoria_city_drama(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "pastoria_city_drama: transition not detected.", - env.console); - } - }, - false); +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_05::name() const { + return "05: Pastoria to Hearthome"; } -void checkpoint_0504(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_house_east_pastoria(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_east_pastoria: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Segment_05::start_text() const { + return "Start: Outside Pastoria Gym at the doorway, facing south, with fourth-badge rewards and dialogue finished."; } -void checkpoint_0505(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_route_213_chasing(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_route_213_chasing: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Segment_05::end_text() const { + return "Outside Hearthome Gym at the post-Cynthia position, with her dialogue finished and the fifth badge acquired."; } -void checkpoint_0506(SingleSwitchProgramEnvironment& env, +void AutoStory_Segment_05::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_building_route_213(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_building_route_213: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + AutoStory_Checkpoint_0501().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0502().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0503().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0504().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0505().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); } -void checkpoint_0507(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_route_214(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_route_214: transition not detected.", - env.console); - } - }, - false); +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Checkpoint_0501::name() const { + return "05.01 - Pastoria recovery"; } -void checkpoint_0508(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0501::start_text() const { + return "Outside Pastoria Gym at the doorway, facing south, with fourth-badge rewards and dialogue finished."; +} + +std::string AutoStory_Checkpoint_0501::end_text() const { + return "Healed and outside Pastoria Pokemon Center at the doorway."; +} + +void AutoStory_Checkpoint_0501::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_inside_solaceon_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_inside_solaceon_pokemon_center: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0501(env, context, options, stats); } -void checkpoint_0509(SingleSwitchProgramEnvironment& env, +void checkpoint_0501(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1689,35 +1432,45 @@ void checkpoint_0509(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_at_solaceon_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_solaceon_pokemon_center: transition not detected.", - env.console); + { + if (!go_to_pastoria_pokemon_center(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_pastoria_pokemon_center: transition not detected.", + env.console); + } + } + { + if (!exit_pastoria_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "exit_pastoria_pokemon_center: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0502::name() const { + return "05.02 - Galactic chase and Solaceon"; +} + +std::string AutoStory_Checkpoint_0502::start_text() const { + return "Healed and outside Pastoria Pokemon Center at the doorway."; } -void checkpoint_0510(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0502::end_text() const { + return "Healed and outside Solaceon Pokemon Center after the Galactic chase and Cynthia dialogue."; +} + +void AutoStory_Checkpoint_0502::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_the_psyduck_route_210(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_the_psyduck_route_210: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0502(env, context, options, stats); } -void checkpoint_0511(SingleSwitchProgramEnvironment& env, +void checkpoint_0502(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1727,35 +1480,80 @@ void checkpoint_0511(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_at_celestic_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_celestic_pokemon_center: transition not detected.", - env.console); + { + if (!pastoria_city_drama(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "pastoria_city_drama: transition not detected.", + env.console); + } + } + { + if (!go_through_house_east_pastoria(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_east_pastoria: transition not detected.", + env.console); + } + } + { + if (!go_through_route_213_chasing(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_213_chasing: transition not detected.", + env.console); + } + } + { + if (!go_through_building_route_213(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_building_route_213: transition not detected.", + env.console); + } + } + { + if (!go_through_route_214(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_route_214: transition not detected.", + env.console); + } } + { + if (!go_inside_solaceon_pokemon_center(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_inside_solaceon_pokemon_center: transition not detected.", + env.console); + } + } + { + if (!heal_at_solaceon_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_solaceon_pokemon_center: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0503::name() const { + return "05.03 - Route 210 to Celestic"; +} + +std::string AutoStory_Checkpoint_0503::start_text() const { + return "Healed and outside Solaceon Pokemon Center after the Galactic chase and Cynthia dialogue."; +} + +std::string AutoStory_Checkpoint_0503::end_text() const { + return "Healed and outside Celestic Pokemon Center after the Psyducks and Cynthia follow-up."; } -void checkpoint_0512(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0503::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!celestic_town_drama(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "celestic_town_drama: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0503(env, context, options, stats); } -void checkpoint_0513(SingleSwitchProgramEnvironment& env, +void checkpoint_0503(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1765,35 +1563,45 @@ void checkpoint_0513(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!discover_the_paintings(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "discover_the_paintings: transition not detected.", - env.console); + { + if (!go_to_the_psyduck_route_210(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_the_psyduck_route_210: transition not detected.", + env.console); + } } + { + if (!heal_at_celestic_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_celestic_pokemon_center: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0504::name() const { + return "05.04 - Celestic story and Hearthome"; } -void checkpoint_0514(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0504::start_text() const { + return "Healed and outside Celestic Pokemon Center after the Psyducks and Cynthia follow-up."; +} + +std::string AutoStory_Checkpoint_0504::end_text() const { + return "Healed and outside Hearthome Pokemon Center after the paintings and Cyrus dialogue."; +} + +void AutoStory_Checkpoint_0504::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "talk_to_cyrus_and_fly_hearthome: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0504(env, context, options, stats); } -void checkpoint_0515(SingleSwitchProgramEnvironment& env, +void checkpoint_0504(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1803,35 +1611,59 @@ void checkpoint_0515(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_at_hearthome_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_hearthome_pokemon_center: transition not detected.", - env.console); + { + if (!celestic_town_drama(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "celestic_town_drama: transition not detected.", + env.console); + } + } + { + if (!discover_the_paintings(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "discover_the_paintings: transition not detected.", + env.console); + } } + { + if (!talk_to_cyrus_and_fly_hearthome(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_to_cyrus_and_fly_hearthome: transition not detected.", + env.console); + } + } + { + if (!heal_at_hearthome_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_hearthome_pokemon_center: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0505::name() const { + return "05.05 - Hearthome Gym and Cynthia"; +} + +std::string AutoStory_Checkpoint_0505::start_text() const { + return "Healed and outside Hearthome Pokemon Center after the paintings and Cyrus dialogue."; +} + +std::string AutoStory_Checkpoint_0505::end_text() const { + return "Outside Hearthome Gym at the post-Cynthia position, with her dialogue finished and the fifth badge acquired."; } -void checkpoint_0516(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0505::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!get_into_hearthome_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_into_hearthome_gym: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0505(env, context, options, stats); } -void checkpoint_0517(SingleSwitchProgramEnvironment& env, +void checkpoint_0505(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1841,13 +1673,17 @@ void checkpoint_0517(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!get_into_hearthome_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_into_hearthome_gym: transition not detected.", - env.console); + { + if (!get_into_hearthome_gym(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_into_hearthome_gym: transition not detected.", + env.console); + } } + hearthome_gym_puzzle_and_cynthia(env, context, stats); + context.wait_for_all_requests(); }, - false); + true); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h index 9c24bad367..b12991e75f 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_05.h @@ -101,187 +101,6 @@ void checkpoint_0505(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_0506 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0506(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0507 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0507(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0508 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0508(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0509 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0509(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0510 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0510(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0511 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0511(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0512 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0512(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0513 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0513(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0514 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0514(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0515 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0515(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0516 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0516(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0517 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0517(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp index 176f79a152..b42238de15 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.cpp @@ -5,6 +5,7 @@ * */ +#include "CommonFramework/Exceptions/FatalProgramException.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" @@ -21,6 +22,7 @@ #include "PokemonBDSP/Inference/PokemonBDSP_MarkFinder.h" #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" #include "PokemonBDSP_AutoStory_Segment_06.h" @@ -28,219 +30,9 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - -std::string AutoStory_Segment_06::name() const { - return "06: Fifth Badge to Sixth Badge"; -} -std::string AutoStory_Segment_06::start_text() const { - return "Start: Fifth Gym Badge acquired (Veilstone City)."; -} -std::string AutoStory_Segment_06::end_text() const { - return "End: Sixth Gym Badge acquired (Canalave City)."; -} - -void AutoStory_Segment_06::run_segment(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0601().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0602().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0603().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0604().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0605().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0606().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0607().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0608().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0609().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0610().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0611().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} - - -std::string AutoStory_Checkpoint_0601::name() const { - return "06.01 - cynthia_and_fly_to_jubilife"; -} -std::string AutoStory_Checkpoint_0601::start_text() const { - return "Fifth Gym Badge acquired (Hearthome City)."; -} -std::string AutoStory_Checkpoint_0601::end_text() const { - return "Flew to Jubilife City."; -} -void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0601(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0602::name() const { - return "06.02 - enter_jubilife_pokemon_center"; -} -std::string AutoStory_Checkpoint_0602::start_text() const { - return "In Jubilife City."; -} -std::string AutoStory_Checkpoint_0602::end_text() const { - return "Entered Jubilife City Pokemon Center."; -} -void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0602(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0603::name() const { - return "06.03 - heal_and_exit_jubilife_pokemon_center"; -} -std::string AutoStory_Checkpoint_0603::start_text() const { - return "Inside Jubilife City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0603::end_text() const { - return "Healed and exited Jubilife City Pokemon Center."; -} -void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0603(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0604::name() const { - return "06.04 - move_to_jubilife_west_exit"; -} -std::string AutoStory_Checkpoint_0604::start_text() const { - return "Outside Jubilife City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0604::end_text() const { - return "Entered west exit building of Jubilife City."; -} -void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0604(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0605::name() const { - return "06.05 - go_through_jubilife_west_exit"; -} -std::string AutoStory_Checkpoint_0605::start_text() const { - return "Inside Jubilife City west exit building."; -} -std::string AutoStory_Checkpoint_0605::end_text() const { - return "Exited onto Route 218."; -} -void AutoStory_Checkpoint_0605::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0605(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0606::name() const { - return "06.06 - go_through_route_218"; -} -std::string AutoStory_Checkpoint_0606::start_text() const { - return "On Route 218."; -} -std::string AutoStory_Checkpoint_0606::end_text() const { - return "Entered building at end of Route 218."; -} -void AutoStory_Checkpoint_0606::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0606(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0607::name() const { - return "06.07 - go_through_building_route_218"; -} -std::string AutoStory_Checkpoint_0607::start_text() const { - return "Inside building at end of Route 218."; -} -std::string AutoStory_Checkpoint_0607::end_text() const { - return "Exited building into Canalave City."; -} -void AutoStory_Checkpoint_0607::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0607(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0608::name() const { - return "06.08 - go_to_clint_and_pokemon_center"; -} -std::string AutoStory_Checkpoint_0608::start_text() const { - return "In Canalave City."; -} -std::string AutoStory_Checkpoint_0608::end_text() const { - return "Battled Clint and entered Canalave City Pokemon Center."; -} -void AutoStory_Checkpoint_0608::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0608(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0609::name() const { - return "06.09 - heal_and_exit_canalave_pokemon_center"; -} -std::string AutoStory_Checkpoint_0609::start_text() const { - return "Inside Canalave City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0609::end_text() const { - return "Healed and exited Canalave City Pokemon Center."; -} -void AutoStory_Checkpoint_0609::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0609(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0610::name() const { - return "06.10 - go_to_canalave_gym"; -} -std::string AutoStory_Checkpoint_0610::start_text() const { - return "Outside Canalave City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0610::end_text() const { - return "Sixth Gym Badge acquired (Canalave City)."; -} -void AutoStory_Checkpoint_0610::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0610(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0611::name() const { - return "06.11 - Canalave Gym Puzzle"; -} -std::string AutoStory_Checkpoint_0611::start_text() const { - return "Outside Canalave City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0611::end_text() const { - return "Sixth Gym Badge acquired (Canalave City)."; -} -void AutoStory_Checkpoint_0611::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0611(env, context, options, stats); -} - +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ @@ -255,44 +47,8 @@ move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& con } } -static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ - context.wait_for_all_requests(); - stream.log("Battling " + trainerid + "...", COLOR_GREEN); - BattleMenuWatcher battle_menu(BattleType::TRAINER); - int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); - if (ret_menu < 0){ - stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); - return false; - } - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 500ms); - - if (trainerid == "route_218_sailor_skyler"){ - // Move the battle-move cursor up. - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - }else if (trainerid == "canalave_clint"){ - // Keep the current battle-move selection. - } - context.wait_for_all_requests(); - - { - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; - } - } - - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_" + trainerid + ": transition confirmed."); +static bool handle_battle(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, const std::string& trainerid){ + fight_trainer_using_moves(stream, context, stats, trainerid, true); return true; } @@ -305,36 +61,9 @@ static bool test(VideoStream& stream, ProControllerContext& context){ return true; } -static bool cynthia_and_fly_to_jubilife(VideoStream& stream, ProControllerContext& context){ - context.wait_for_all_requests(); - pbf_wait(context, 2000ms); - context.wait_for_all_requests(); - stream.log("cynthia_and_fly_to_jubilife: talking to Cynthia and then flying to Jubilife."); - - /* - { - ShortDialogWatcher talk_to_cynthia(COLOR_BLUE); - int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_cynthia}}); - if (ret < 0){ - stream.log("cynthia_and_fly_to_jubilife: dialog box not detected!", COLOR_RED); - return false; - } else if (ret == 0) { - stream.log("cynthia_and_fly_to_jubilife: dialog box detected.", COLOR_GREEN); - pbf_mash_button(context, BUTTON_A, 12000ms); - context.wait_for_all_requests(); - open_menu(stream, context, MenuCursorPosition::MAP, 8); - fly_to(stream, context, FlyPoint::JubilifeCity); - } - }*/ - // Temporary, will be removed when the gym exit is done - - context.wait_for_all_requests(); - fly_to(stream, context, FlyPoint::JubilifeCity); - - // End of temporary addition - - return true; -} +// --------------------------------------------------------------------------- +// Named route navigation blocks +// --------------------------------------------------------------------------- static bool enter_jubilife_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); @@ -358,12 +87,17 @@ static bool enter_jubilife_pokemon_center(VideoStream& stream, ProControllerCont return true; } -static bool heal_and_exit_jubilife_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool heal_and_exit_jubilife_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("heal_and_exit_jubilife_pokemon_center: healing and exiting..."); - heal_pokemon(stream, context, "Jubilife city"); + if (!heal_pokemon(stream, context, "Jubilife city", &stats)){ + return false; + } + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } @@ -419,7 +153,7 @@ static bool go_through_jubilife_west_exit(VideoStream& stream, ProControllerCont return true; } -static bool go_through_route_218(VideoStream& stream, ProControllerContext& context){ +static bool go_through_route_218(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -465,7 +199,7 @@ static bool go_through_route_218(VideoStream& stream, ProControllerContext& cont stream.log("go_through_route_218_sailor_skyler: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_218_sailor_skyler"); + handle_battle(stream, context, stats, "route_218_sailor_skyler"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_218_sailor_skyler"); @@ -533,10 +267,13 @@ static bool go_through_building_route_218(VideoStream& stream, ProControllerCont stream.log("go_through_building_route_218: transition detected.", COLOR_GREEN); } } + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } -static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -599,7 +336,7 @@ static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerCon stream.log("go_to_clint_and_pokemon_center_clint: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "canalave_clint"); + handle_battle(stream, context, stats, "canalave_clint"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); } @@ -632,12 +369,17 @@ static bool go_to_clint_and_pokemon_center(VideoStream& stream, ProControllerCon return true; } -static bool heal_and_exit_canalave_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool heal_and_exit_canalave_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("heal_and_exit_canalave_pokemon_center: healing and exiting..."); - heal_pokemon(stream, context, "Canalave city"); + if (!heal_pokemon(stream, context, "Canalave city", &stats)){ + return false; + } + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } @@ -669,33 +411,75 @@ static bool go_to_canalave_gym(VideoStream& stream, ProControllerContext& contex } stream.log("go_to_canalave_gym: entered gym", COLOR_GREEN); }; - return true; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + // The old 06.11 repeated Gym entry; it did not solve the puzzle. + // Stop here until the actual puzzle and Byron route are available. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "Canalave Gym entrance reached. The puzzle and Byron battle are not implemented; " + "Segment 06 is incomplete and Segment 07 must not start.", + stream); } +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- -void checkpoint_0601(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Segment_06::name() const { + return "06: Hearthome to Canalave"; +} + +std::string AutoStory_Segment_06::start_text() const { + return "Start: Outside Hearthome Gym at the post-Cynthia position, with her dialogue finished and the fifth badge acquired."; +} + +std::string AutoStory_Segment_06::end_text() const { + return "Target: Sixth Gym Badge acquired (Canalave City). The Canalave Gym puzzle and Byron battle are not implemented yet."; +} + +void AutoStory_Segment_06::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - /*if (!test(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, "test: transition not detected.", env.console); - }*/ - if (!cynthia_and_fly_to_jubilife(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "cynthia_and_fly_to_jubilife: transition not detected.", - env.console); - } - }, - true); + AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_0601().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0602().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0603().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0604().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); } -void checkpoint_0602(SingleSwitchProgramEnvironment& env, +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Checkpoint_0601::name() const { + return "06.01 - Hearthome to Jubilife"; +} + +std::string AutoStory_Checkpoint_0601::start_text() const { + return "Outside Hearthome Gym at the post-Cynthia position, with her dialogue finished and the fifth badge acquired."; +} + +std::string AutoStory_Checkpoint_0601::end_text() const { + return "Healed and outside Jubilife City Pokemon Center at the doorway, facing south."; +} + +void AutoStory_Checkpoint_0601::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0601(env, context, options, stats); +} + +void checkpoint_0601(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -705,35 +489,45 @@ void checkpoint_0602(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ + if (!fly_to(env.console, context, FlyPoint::JubilifeCity)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "06.01: Flight to Jubilife did not complete.", + env.console); + } if (!enter_jubilife_pokemon_center(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_jubilife_pokemon_center: transition not detected.", + "06.01: Jubilife Center entry did not complete.", + env.console); + } + if (!heal_and_exit_jubilife_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "06.01: Jubilife healing and exit did not complete.", env.console); } }, - false); + true); } -void checkpoint_0603(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0602::name() const { + return "06.02 - Jubilife to Canalave"; +} + +std::string AutoStory_Checkpoint_0602::start_text() const { + return "Outside Jubilife City Pokemon Center at the doorway, facing south."; +} + +std::string AutoStory_Checkpoint_0602::end_text() const { + return "Outside Canalave City east gate, facing west, after the scientist conversation."; +} + +void AutoStory_Checkpoint_0602::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit_jubilife_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_jubilife_pokemon_center: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0602(env, context, options, stats); } -void checkpoint_0604(SingleSwitchProgramEnvironment& env, +void checkpoint_0602(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -745,90 +539,48 @@ void checkpoint_0604(SingleSwitchProgramEnvironment& env, [&](size_t /*attempt*/){ if (!move_to_jubilife_west_exit(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "move_to_jubilife_west_exit: transition not detected.", + "06.02: Jubilife west gate entry did not complete.", env.console); } - }, - false); -} - -void checkpoint_0605(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!go_through_jubilife_west_exit(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_jubilife_west_exit: transition not detected.", + "06.02: Jubilife west gate exit did not complete.", env.console); } - }, - false); -} - -void checkpoint_0606(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_route_218(env.console, context)){ + if (!go_through_route_218(env.console, context, stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_route_218: transition not detected.", + "06.02: Route 218 crossing did not complete.", env.console); } - }, - false); -} - -void checkpoint_0607(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!go_through_building_route_218(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_building_route_218: transition not detected.", + "06.02: Canalave gate crossing and scientist conversation did not complete.", env.console); } }, - false); + true); +} + +std::string AutoStory_Checkpoint_0603::name() const { + return "06.03 - Canalave rival battle and healing"; +} + +std::string AutoStory_Checkpoint_0603::start_text() const { + return "Outside Canalave City east gate, facing west, after the scientist conversation."; +} + +std::string AutoStory_Checkpoint_0603::end_text() const { + return "Rival defeated; healed and outside Canalave City Pokemon Center at the doorway, facing south."; } -void checkpoint_0608(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0603::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_clint_and_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_clint_and_pokemon_center: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0603(env, context, options, stats); } -void checkpoint_0609(SingleSwitchProgramEnvironment& env, +void checkpoint_0603(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -838,35 +590,40 @@ void checkpoint_0609(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_and_exit_canalave_pokemon_center(env.console, context)){ + if (!go_to_clint_and_pokemon_center(env.console, context, stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_canalave_pokemon_center: transition not detected.", + "06.03: Canalave rival battle and Center entry did not complete.", + env.console); + } + if (!heal_and_exit_canalave_pokemon_center(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "06.03: Canalave healing and exit did not complete.", env.console); } }, - false); + true); +} + +std::string AutoStory_Checkpoint_0604::name() const { + return "06.04 - Canalave Gym (puzzle pending)"; } -void checkpoint_0610(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0604::start_text() const { + return "Outside Canalave City Pokemon Center at the doorway, facing south, with full HP and PP."; +} + +std::string AutoStory_Checkpoint_0604::end_text() const { + return "Current implementation stops inside Canalave Gym at the entrance. Puzzle, Byron battle, and reward dialogue are pending."; +} + +void AutoStory_Checkpoint_0604::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_canalave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_canalave_gym: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0604(env, context, options, stats); } -void checkpoint_0611(SingleSwitchProgramEnvironment& env, +void checkpoint_0604(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -878,11 +635,11 @@ void checkpoint_0611(SingleSwitchProgramEnvironment& env, [&](size_t /*attempt*/){ if (!go_to_canalave_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_canalave_gym: transition not detected.", + "06.04: Canalave Gym entry did not complete.", env.console); } }, - false); + true); } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h index 4c99db317b..ad4838c3d4 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_06.h @@ -86,111 +86,6 @@ void checkpoint_0604(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_0605 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0605(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0606 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0606(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0607 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0607(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0608 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0608(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0609 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0609(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0610 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0610(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0611 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0611(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp index cc96681cd3..2c227c79ee 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.cpp @@ -5,6 +5,7 @@ * */ +#include "CommonFramework/Exceptions/FatalProgramException.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" @@ -22,6 +23,7 @@ #include "PokemonBDSP/Inference/PokemonBDSP_TransitionWatchers.h" #include "PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStoryTools.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Battle.h" +#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.h" #include "PokemonBDSP_AutoStory_Segment_07.h" @@ -29,354 +31,9 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - -std::string AutoStory_Segment_07::name() const { - return "07: Sixth Badge to Seventh Badge"; -} -std::string AutoStory_Segment_07::start_text() const { - return "Start: Sixth Gym Badge acquired (Canalave City)."; -} -std::string AutoStory_Segment_07::end_text() const { - return "End: Seventh Gym Badge acquired (Snowpoint City)."; -} - -void AutoStory_Segment_07::run_segment(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0701().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0702().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0703().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0704().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0705().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0706().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0707().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0708().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0709().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0710().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0711().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0712().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0713().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0714().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0715().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0716().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0717().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0718().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0719().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} - - -std::string AutoStory_Checkpoint_0701::name() const { - return "07.01 - leave_gym"; -} -std::string AutoStory_Checkpoint_0701::start_text() const { - return "Inside Canalave City gym after sixth badge."; -} -std::string AutoStory_Checkpoint_0701::end_text() const { - return "Exited Canalave City gym."; -} -void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0701(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0702::name() const { - return "07.02 - go_to_the_library"; -} -std::string AutoStory_Checkpoint_0702::start_text() const { - return "Outside Canalave City gym."; -} -std::string AutoStory_Checkpoint_0702::end_text() const { - return "Entered Canalave City library."; -} -void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0702(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0703::name() const { - return "07.03 - drama_at_the_library"; -} -std::string AutoStory_Checkpoint_0703::start_text() const { - return "Inside Canalave City library."; -} -std::string AutoStory_Checkpoint_0703::end_text() const { - return "Library drama complete, exited library."; -} -void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0703(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0704::name() const { - return "07.04 - talk_fly_and_enter_mount_coronet"; -} -std::string AutoStory_Checkpoint_0704::start_text() const { - return "Outside Canalave City library."; -} -std::string AutoStory_Checkpoint_0704::end_text() const { - return "Entered Mt. Coronet."; -} -void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0704(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0705::name() const { - return "07.05 - go_through_mount_coronet"; -} -std::string AutoStory_Checkpoint_0705::start_text() const { - return "Inside Mt. Coronet."; -} -std::string AutoStory_Checkpoint_0705::end_text() const { - return "Exited Mt. Coronet."; -} -void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0705(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0706::name() const { - return "07.06 - go_through_route_216"; -} -std::string AutoStory_Checkpoint_0706::start_text() const { - return "On Route 216."; -} -std::string AutoStory_Checkpoint_0706::end_text() const { - return "Entered small chalet on Route 216."; -} -void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0706(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0707::name() const { - return "07.07 - heal_small_house_route_216"; -} -std::string AutoStory_Checkpoint_0707::start_text() const { - return "Inside small chalet on Route 216."; -} -std::string AutoStory_Checkpoint_0707::end_text() const { - return "Rested in chalet and exited."; -} -void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0707(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0708::name() const { - return "07.08 - continue_through_route_216"; -} -std::string AutoStory_Checkpoint_0708::start_text() const { - return "Outside chalet on Route 216."; -} -std::string AutoStory_Checkpoint_0708::end_text() const { - return "Entered Veilstone City Pokemon Center."; -} -void AutoStory_Checkpoint_0708::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0708(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0709::name() const { - return "07.09 - heal_and_exit_veilstone_pokemon_center"; -} -std::string AutoStory_Checkpoint_0709::start_text() const { - return "Inside Veilstone City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0709::end_text() const { - return "Healed and exited Veilstone City Pokemon Center."; -} -void AutoStory_Checkpoint_0709::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0709(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0710::name() const { - return "07.10 - go_to_house_south_veilstone"; -} -std::string AutoStory_Checkpoint_0710::start_text() const { - return "Outside Veilstone City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0710::end_text() const { - return "Entered house south of Veilstone City."; -} -void AutoStory_Checkpoint_0710::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0710(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0711::name() const { - return "07.11 - go_through_house_south_veilstone"; -} -std::string AutoStory_Checkpoint_0711::start_text() const { - return "Inside house south of Veilstone City."; -} -std::string AutoStory_Checkpoint_0711::end_text() const { - return "Exited house onto Route 214."; -} -void AutoStory_Checkpoint_0711::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0711(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0712::name() const { - return "07.12 - walk_through_route_214"; -} -std::string AutoStory_Checkpoint_0712::start_text() const { - return "On Route 214 heading south."; -} -std::string AutoStory_Checkpoint_0712::end_text() const { - return "Entered Valor Lakefront."; -} -void AutoStory_Checkpoint_0712::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0712(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0713::name() const { - return "07.13 - go_through_lake_valor"; -} -std::string AutoStory_Checkpoint_0713::start_text() const { - return "At Valor Lakefront."; -} -std::string AutoStory_Checkpoint_0713::end_text() const { - return "Flew to Twinleaf Town and entered own house."; -} -void AutoStory_Checkpoint_0713::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0713(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0714::name() const { - return "07.14 - heal_at_home"; -} -std::string AutoStory_Checkpoint_0714::start_text() const { - return "Inside own house in Twinleaf Town."; -} -std::string AutoStory_Checkpoint_0714::end_text() const { - return "Healed at home and exited."; -} -void AutoStory_Checkpoint_0714::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0714(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0715::name() const { - return "07.15 - go_to_lake_verity"; -} -std::string AutoStory_Checkpoint_0715::start_text() const { - return "Outside own house in Twinleaf Town."; -} -std::string AutoStory_Checkpoint_0715::end_text() const { - return "Entered Verity Lakefront."; -} -void AutoStory_Checkpoint_0715::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0715(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0716::name() const { - return "07.16 - commander_mars_and_snowpoint"; -} -std::string AutoStory_Checkpoint_0716::start_text() const { - return "At Lake Verity."; -} -std::string AutoStory_Checkpoint_0716::end_text() const { - return "Defeated Commander Mars and entered Snowpoint City Pokemon Center."; -} -void AutoStory_Checkpoint_0716::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0716(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0717::name() const { - return "07.17 - heal_at_snowpoint_pokemon_center"; -} -std::string AutoStory_Checkpoint_0717::start_text() const { - return "Inside Snowpoint City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0717::end_text() const { - return "Healed and exited Snowpoint City Pokemon Center."; -} -void AutoStory_Checkpoint_0717::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0717(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0718::name() const { - return "07.18 - go_to_snowpoint_gym"; -} -std::string AutoStory_Checkpoint_0718::start_text() const { - return "Outside Snowpoint City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0718::end_text() const { - return "Seventh Gym Badge acquired (Snowpoint City)."; -} -void AutoStory_Checkpoint_0718::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0718(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0719::name() const { - return "07.19 - Snowpoint Gym Battle"; -} -std::string AutoStory_Checkpoint_0719::start_text() const { - return "Outside Snowpoint City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0719::end_text() const { - return "Seventh Gym Badge acquired (Snowpoint City)."; -} -void AutoStory_Checkpoint_0719::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0719(env, context, options, stats); -} +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ @@ -395,54 +52,15 @@ move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& con } } -static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ - context.wait_for_all_requests(); - stream.log("Battling " + trainerid + "...", COLOR_GREEN); - BattleMenuWatcher battle_menu(BattleType::TRAINER); - int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); - if (ret_menu < 0){ - stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); - return false; - } - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 500ms); - - if (trainerid == "route_216_ace_maria" or trainerid == "route_216_skier_edward" or - trainerid == "route_217_skier_lexie" or trainerid == "route_217_blackbelt_luke" or - trainerid == "route_217_ace_olivia" or trainerid == "verity_lakefront_grunt_1" or - trainerid == "verity_lakefront_grunt_3"){ - // Move the battle-move cursor down. - pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - }else if (trainerid == "route_217_ace_dalton"){ - // Move the battle-move cursor up. - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - }else if (trainerid == "verity_lakefront_grunt_2" or trainerid == "verity_lakefront_grunt_4" or - trainerid == "verity_lakefront_commander_mars"){ - // Keep the current battle-move selection. - } - context.wait_for_all_requests(); - - { - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; - } - } - - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_" + trainerid + ": transition confirmed."); +static bool handle_battle(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, const std::string& trainerid){ + fight_trainer_using_moves(stream, context, stats, trainerid, true); return true; } +// --------------------------------------------------------------------------- +// Named route navigation blocks +// --------------------------------------------------------------------------- + static bool leave_gym(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -635,36 +253,43 @@ static bool drama_at_the_library(VideoStream& stream, ProControllerContext& cont } stream.log("drama_at_the_library: moved out of the library", COLOR_GREEN); }; - return true; -} - -static bool talk_fly_and_enter_mount_coronet(VideoStream& stream, ProControllerContext& context){ - DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); - stream.log("talk_fly_and_enter_mount_coronet: talking, flying and becoming a speleologist..."); + stream.log("drama_at_the_library: finishing the Rowan follow-up outside the library..."); { ShortDialogWatcher talk_to_rowan(COLOR_BLUE); int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_rowan}}); if (ret < 0){ - stream.log("talk_fly_and_enter_mount_coronet_talk_to_rowan: dialog box not detected!", COLOR_RED); + stream.log("drama_at_the_library_talk_to_rowan: dialog box not detected!", COLOR_RED); return false; }else if (ret == 0){ - stream.log("talk_fly_and_enter_mount_coronet_talk_to_rowan: dialog box detected.", COLOR_GREEN); + stream.log("drama_at_the_library_talk_to_rowan: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 21000ms); context.wait_for_all_requests(); } } + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); + context.wait_for_all_requests(); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + return true; +} + +static bool fly_and_enter_mount_coronet(VideoStream& stream, ProControllerContext& context){ + DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); - fly_to(stream, context, FlyPoint::CelesticTown); + if (!fly_to(stream, context, FlyPoint::CelesticTown)){ + return false; + } context.wait_for_all_requests(); pbf_wait(context, 2000ms); - stream.log("talk_fly_and_enter_mount_coronet: going towards Mt. Coronet..."); + stream.log("fly_and_enter_mount_coronet: going towards Mt. Coronet..."); pbf_move_left_joystick(context, {-1, 0}, 2000ms, 100ms); // 10+ pbf_move_left_joystick(context, {0, -1}, 800ms, 100ms); // 3+ @@ -694,10 +319,10 @@ static bool talk_fly_and_enter_mount_coronet(VideoStream& stream, ProControllerC }, {{black_screen}}); if (ret < 0){ - stream.log("talk_fly_and_enter_mount_coronet: black screen not detected!", COLOR_RED); + stream.log("fly_and_enter_mount_coronet: black screen not detected!", COLOR_RED); return false; } - stream.log("talk_fly_and_enter_mount_coronet: entered Mt. Coronet", COLOR_GREEN); + stream.log("fly_and_enter_mount_coronet: entered Mt. Coronet", COLOR_GREEN); }; return true; } @@ -803,7 +428,7 @@ static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& return true; } -static bool go_through_route_216(VideoStream& stream, ProControllerContext& context){ +static bool go_through_route_216(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -857,7 +482,7 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont stream.log("go_through_route_216_ace_maria: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_216_ace_maria"); + handle_battle(stream, context, stats, "route_216_ace_maria"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_216_ace_maria"); @@ -892,7 +517,7 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont stream.log("go_through_route_216_skier_edward: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_216_skier_edward"); + handle_battle(stream, context, stats, "route_216_skier_edward"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_216_skier_edward"); @@ -922,7 +547,7 @@ static bool go_through_route_216(VideoStream& stream, ProControllerContext& cont return true; } -static bool heal_small_house_route_216(VideoStream& stream, ProControllerContext& context){ +static bool heal_small_house_route_216(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -989,10 +614,15 @@ static bool heal_small_house_route_216(VideoStream& stream, ProControllerContext stream.log("heal_small_house_route_216: exited chalet", COLOR_GREEN); }; + context.wait_for_all_requests(); + stats.move_pp_remaining = stats.move_pp_capacity; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } -static bool continue_through_route_216(VideoStream& stream, ProControllerContext& context){ +static bool continue_through_route_216(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1053,7 +683,7 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext stream.log("continue_through_route_216_ace_dalton: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_217_ace_dalton"); + handle_battle(stream, context, stats, "route_217_ace_dalton"); } } } @@ -1108,7 +738,7 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext stream.log("continue_through_route_216_skier_lexie: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_217_skier_lexie"); + handle_battle(stream, context, stats, "route_217_skier_lexie"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_217_skier_lexie"); @@ -1151,7 +781,7 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext stream.log("continue_through_route_216_blackbelt_luke: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_217_blackbelt_luke"); + handle_battle(stream, context, stats, "route_217_blackbelt_luke"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_217_blackbelt_luke"); @@ -1190,7 +820,7 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext stream.log("continue_through_route_216_ace_olivia: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_217_ace_olivia"); + handle_battle(stream, context, stats, "route_217_ace_olivia"); } } } @@ -1212,8 +842,10 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext context.wait_for_all_requests(); pbf_wait(context, 2000ms); - fly_to(stream, context, FlyPoint::VeilstoneCity); - + if (!fly_to(stream, context, FlyPoint::VeilstoneCity)){ + return false; + } + context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1235,16 +867,20 @@ static bool continue_through_route_216(VideoStream& stream, ProControllerContext return true; } -static bool heal_and_exit_veilstone_pokemon_center(VideoStream& stream, ProControllerContext& context){ +static bool heal_and_exit_veilstone_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("heal_and_exit_veilstone_pokemon_center: healing and exiting..."); - heal_pokemon(stream, context, "Veilstone city"); + if (!heal_pokemon(stream, context, "Veilstone city", &stats)){ + return false; + } + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } - static bool go_to_house_south_veilstone(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); @@ -1432,7 +1068,6 @@ static bool go_through_lake_valor(VideoStream& stream, ProControllerContext& con dpad.last_dir = DPAD_LEFT; repeat_dpad(context, dpad, DPAD_DOWN, 80ms, 300ms, 3); - // @GumGum: uncomment this when you've tested your part and made sure that it works. This is for the activate repel running. { ShortDialogWatcher repel_dialog(COLOR_RED); @@ -1460,12 +1095,20 @@ static bool go_through_lake_valor(VideoStream& stream, ProControllerContext& con pbf_move_left_joystick(context, {-1, 0}, 2400ms, 100ms); // 12+ - // @GumGum: Here comes the rest of the code - - - // And this comes after having defeated the commander and having exited the cave + // TODO(07.04): Record the cave approach, Galactic battles, Saturn battle, + // reward/dialogue cleanup, and return to the outdoor cave exit. + // return_to_twinleaf_and_heal() preserves the continuation after this gap. + context.wait_for_all_requests(); + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "07.04 incomplete: Lake Valor cave approach, Saturn battle, and cave exit need recorded pathing. " + "Stopped before the missing route; later checkpoints have not run.", + stream); +} - fly_to(stream, context, FlyPoint::TwinleafTown); +static bool return_to_twinleaf_and_heal(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ + if (!fly_to(stream, context, FlyPoint::TwinleafTown)){ + return false; + } { BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); @@ -1482,10 +1125,7 @@ static bool go_through_lake_valor(VideoStream& stream, ProControllerContext& con stream.log("go_through_lake_valor: back into own's house", COLOR_GREEN); } - return true; -} -static bool heal_at_home(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1535,6 +1175,11 @@ static bool heal_at_home(VideoStream& stream, ProControllerContext& context){ stream.log("heal_at_home: leaving own's house", COLOR_GREEN); } + context.wait_for_all_requests(); + stats.move_pp_remaining = stats.move_pp_capacity; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } @@ -1570,21 +1215,21 @@ static bool go_to_lake_verity(VideoStream& stream, ProControllerContext& context return true; } -static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerContext& context){ +static bool commander_mars_at_verity(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 3000ms); context.wait_for_all_requests(); - stream.log("commander_mars_and_snowpoint: Dealing with Commander Mars first and then heading to Snowpoint City..."); + stream.log("commander_mars_at_verity: Completing the Lake Verity battles and Mars dialogue..."); /*{ ShortDialogWatcher rowan_talking(COLOR_BLUE); int ret = wait_until(stream, context, std::chrono::seconds(30), {{talk_to_clint}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint_rowan_talking: dialog box not detected!", COLOR_RED); + stream.log("commander_mars_at_verity_rowan_talking: dialog box not detected!", COLOR_RED); return false; } else if (ret == 0) { - stream.log("commander_mars_and_snowpoint_rowan_talking: dialog box detected.", COLOR_GREEN); + stream.log("commander_mars_at_verity_rowan_talking: dialog box detected.", COLOR_GREEN); pbf_mash_button(context, BUTTON_B, 4000ms); context.wait_for_all_requests(); } @@ -1615,10 +1260,10 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint_grunt_1: black screen not detected!", COLOR_RED); + stream.log("commander_mars_at_verity_grunt_1: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "verity_lakefront_grunt_1"); + handle_battle(stream, context, stats, "verity_lakefront_grunt_1"); } } } @@ -1635,10 +1280,10 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); + stream.log("commander_mars_at_verity_grunt_4: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "verity_lakefront_grunt_2"); + handle_battle(stream, context, stats, "verity_lakefront_grunt_2"); } } @@ -1674,10 +1319,10 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint_grunt_3: black screen not detected!", COLOR_RED); + stream.log("commander_mars_at_verity_grunt_3: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "verity_lakefront_grunt_3"); + handle_battle(stream, context, stats, "verity_lakefront_grunt_3"); } } } @@ -1694,10 +1339,10 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint_grunt_4: black screen not detected!", COLOR_RED); + stream.log("commander_mars_at_verity_grunt_4: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "verity_lakefront_grunt_4"); + handle_battle(stream, context, stats, "verity_lakefront_grunt_4"); } } @@ -1718,10 +1363,10 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{commander_mars_fight}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint: Didn't detect Commander Mars dialog box!", COLOR_RED); + stream.log("commander_mars_at_verity: Didn't detect Commander Mars dialog box!", COLOR_RED); return false; }else if (ret == 0){ - stream.log("commander_mars_and_snowpoint: Battling Commander Mars...", COLOR_GREEN); + stream.log("commander_mars_at_verity: Battling Commander Mars...", COLOR_GREEN); context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1733,10 +1378,10 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint_commander_mars: black screen not detected!", COLOR_RED); + stream.log("commander_mars_at_verity_commander_mars: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "verity_lakefront_commander_mars"); + handle_battle(stream, context, stats, "verity_lakefront_commander_mars"); } } } @@ -1748,7 +1393,18 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); pbf_wait(context, 1000ms); - fly_to(stream, context, FlyPoint::SnowpointCity); + clear_post_battle_dialogue(stream, context, BUTTON_B, 6000ms); + context.wait_for_all_requests(); + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + return true; +} + +static bool fly_to_snowpoint_and_heal(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ + if (!fly_to(stream, context, FlyPoint::SnowpointCity)){ + return false; + } context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1762,23 +1418,24 @@ static bool commander_mars_and_snowpoint(VideoStream& stream, ProControllerConte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); + stream.log("fly_to_snowpoint_and_heal: black screen not detected!", COLOR_RED); return false; } - stream.log("commander_mars_and_snowpoint: entered Snowpoint City Pokemon Center", COLOR_GREEN); + stream.log("fly_to_snowpoint_and_heal: entered Snowpoint City Pokemon Center", COLOR_GREEN); } - return true; -} -static bool heal_at_snowpoint_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 3000ms); context.wait_for_all_requests(); stream.log("heal_at_snowpoint_pokemon_center: Healing at Snowpoint City Pokemon center and exiting..."); - heal_pokemon(stream, context, "Snowpoint City"); - + if (!heal_pokemon(stream, context, "Snowpoint City", &stats)){ + return false; + } + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); return true; } @@ -1804,15 +1461,82 @@ static bool go_to_snowpoint_gym(VideoStream& stream, ProControllerContext& conte }, {{black_screen}}); if (ret < 0){ - stream.log("commander_mars_and_snowpoint: black screen not detected!", COLOR_RED); + stream.log("go_to_snowpoint_gym: black screen not detected!", COLOR_RED); return false; } - stream.log("commander_mars_and_snowpoint: entered Snowpoint City Pokemon Center", COLOR_GREEN); + stream.log("go_to_snowpoint_gym: entered Snowpoint Gym", COLOR_GREEN); } - return true; + context.wait_for_all_requests(); + pbf_wait(context, 2000ms); + context.wait_for_all_requests(); + // TODO(07.07): Record the puzzle, trainers, Candice battle, + // rewards, and return route to the inside entrance for Segment 08. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "07.07 incomplete: Snowpoint Gym entrance reached. Puzzle, Candice battle, " + "rewards, and return path need recorded pathing. Segment 08 must not start.", + stream); } +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_07::name() const { + return "07: Canalave to Snowpoint"; +} + +std::string AutoStory_Segment_07::start_text() const { + return "Start: Inside Canalave Gym at the entrance, after Byron, with sixth-badge rewards and dialogue finished."; +} + +std::string AutoStory_Segment_07::end_text() const { + return "Target: Seventh badge and inside Snowpoint Gym at the entrance, with rewards finished. Lake Valor and Snowpoint Gym pathing are pending recording."; +} + +void AutoStory_Segment_07::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + + AutoStory_Checkpoint_0701().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0702().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0703().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0704().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0705().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0706().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0707().run_checkpoint(env, context, options, stats); + + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); +} + +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Checkpoint_0701::name() const { + return "07.01 - Canalave library events"; +} + +std::string AutoStory_Checkpoint_0701::start_text() const { + return "Inside Canalave Gym at the entrance, after Byron, with sixth-badge rewards and dialogue finished."; +} + +std::string AutoStory_Checkpoint_0701::end_text() const { + return "Outside Canalave library after the meeting and Rowan follow-up, with dialogue finished."; +} + +void AutoStory_Checkpoint_0701::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0701(env, context, options, stats); +} void checkpoint_0701(SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -1826,71 +1550,43 @@ void checkpoint_0701(SingleSwitchProgramEnvironment& env, [&](size_t /*attempt*/){ if (!leave_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "leave_gym: transition not detected.", + "07.01: leave_gym did not complete.", env.console); } - }, - true); -} - -void checkpoint_0702(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!go_to_the_library(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_the_library: transition not detected.", + "07.01: go_to_the_library did not complete.", env.console); } - }, - false); -} - -void checkpoint_0703(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!drama_at_the_library(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "drama_at_the_library: transition not detected.", + "07.01: drama_at_the_library did not complete.", env.console); } }, - false); + true); } -void checkpoint_0704(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0702::name() const { + return "07.02 - Canalave to Route 216"; +} + +std::string AutoStory_Checkpoint_0702::start_text() const { + return "Outside Canalave library after the meeting and Rowan follow-up, with dialogue finished."; +} + +std::string AutoStory_Checkpoint_0702::end_text() const { + return "Healed and outside the Route 216 rest house at the doorway."; +} + +void AutoStory_Checkpoint_0702::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!talk_fly_and_enter_mount_coronet(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "talk_fly_and_enter_mount_coronet: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0702(env, context, options, stats); } -void checkpoint_0705(SingleSwitchProgramEnvironment& env, +void checkpoint_0702(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1900,54 +1596,50 @@ void checkpoint_0705(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ + if (!fly_and_enter_mount_coronet(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "07.02: fly_and_enter_mount_coronet did not complete.", + env.console); + } if (!go_through_mount_coronet(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_mount_coronet: transition not detected.", + "07.02: go_through_mount_coronet did not complete.", env.console); } - }, - false); -} - -void checkpoint_0706(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_route_216(env.console, context)){ + if (!go_through_route_216(env.console, context, stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_route_216: transition not detected.", + "07.02: go_through_route_216 did not complete.", + env.console); + } + if (!heal_small_house_route_216(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "07.02: heal_small_house_route_216 did not complete.", env.console); } }, - false); + true); } -void checkpoint_0707(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0703::name() const { + return "07.03 - Route 217 and Snowpoint"; +} + +std::string AutoStory_Checkpoint_0703::start_text() const { + return "Healed and outside the Route 216 rest house at the doorway."; +} + +std::string AutoStory_Checkpoint_0703::end_text() const { + return "Healed and outside Veilstone Pokemon Center after the first Snowpoint visit and Rock Climb pickup."; +} + +void AutoStory_Checkpoint_0703::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_small_house_route_216(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_small_house_route_216: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0703(env, context, options, stats); } -void checkpoint_0708(SingleSwitchProgramEnvironment& env, +void checkpoint_0703(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1957,35 +1649,40 @@ void checkpoint_0708(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!continue_through_route_216(env.console, context)){ + if (!continue_through_route_216(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "07.03: continue_through_route_216 did not complete.", + env.console); + } + if (!heal_and_exit_veilstone_pokemon_center(env.console, context, stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "continue_through_route_216: transition not detected.", + "07.03: heal_and_exit_veilstone_pokemon_center did not complete.", env.console); } }, - false); + true); +} + +std::string AutoStory_Checkpoint_0704::name() const { + return "07.04 - Lake Valor (path pending)"; } -void checkpoint_0709(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0704::start_text() const { + return "Healed and outside Veilstone Pokemon Center after the first Snowpoint visit and Rock Climb pickup."; +} + +std::string AutoStory_Checkpoint_0704::end_text() const { + return "Target: Saturn defeated; healed and outside the Twinleaf home. Currently stops at the unrecorded Lake Valor cave approach."; +} + +void AutoStory_Checkpoint_0704::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_at_snowpoint_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_snowpoint_pokemon_center: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0704(env, context, options, stats); } -void checkpoint_0710(SingleSwitchProgramEnvironment& env, +void checkpoint_0704(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1997,90 +1694,53 @@ void checkpoint_0710(SingleSwitchProgramEnvironment& env, [&](size_t /*attempt*/){ if (!go_to_house_south_veilstone(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_house_south_veilstone: transition not detected.", + "07.04: go_to_house_south_veilstone did not complete.", env.console); } - }, - false); -} - -void checkpoint_0711(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!go_through_house_south_veilstone(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_south_veilstone: transition not detected.", + "07.04: go_through_house_south_veilstone did not complete.", env.console); } - }, - false); -} - -void checkpoint_0712(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!walk_through_route_214(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "walk_through_route_214: transition not detected.", + "07.04: walk_through_route_214 did not complete.", env.console); } - }, - false); -} - -void checkpoint_0713(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ if (!go_through_lake_valor(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_lake_valor: transition not detected.", + "07.04: go_through_lake_valor did not complete.", + env.console); + } + if (!return_to_twinleaf_and_heal(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "07.04: return_to_twinleaf_and_heal did not complete.", env.console); } }, - false); + true); } -void checkpoint_0714(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0705::name() const { + return "07.05 - Lake Verity and Mars"; +} + +std::string AutoStory_Checkpoint_0705::start_text() const { + return "Outside the Twinleaf home, healed, after completing Lake Valor and Saturn."; +} + +std::string AutoStory_Checkpoint_0705::end_text() const { + return "At Lake Verity after Mars, with follow-up dialogue finished and outdoor control restored."; +} + +void AutoStory_Checkpoint_0705::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_at_home(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_home: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0705(env, context, options, stats); } -void checkpoint_0715(SingleSwitchProgramEnvironment& env, +void checkpoint_0705(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2092,33 +1752,38 @@ void checkpoint_0715(SingleSwitchProgramEnvironment& env, [&](size_t /*attempt*/){ if (!go_to_lake_verity(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_lake_verity: transition not detected.", + "07.05: go_to_lake_verity did not complete.", + env.console); + } + if (!commander_mars_at_verity(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "07.05: commander_mars_at_verity did not complete.", env.console); } }, - false); + true); } -void checkpoint_0716(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0706::name() const { + return "07.06 - Return to Snowpoint"; +} + +std::string AutoStory_Checkpoint_0706::start_text() const { + return "At Lake Verity after Mars, with follow-up dialogue finished and outdoor control restored."; +} + +std::string AutoStory_Checkpoint_0706::end_text() const { + return "Healed and outside Snowpoint Pokemon Center at the doorway."; +} + +void AutoStory_Checkpoint_0706::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!commander_mars_and_snowpoint(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "commander_mars_and_snowpoint: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0706(env, context, options, stats); } -void checkpoint_0717(SingleSwitchProgramEnvironment& env, +void checkpoint_0706(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2128,35 +1793,35 @@ void checkpoint_0717(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_at_snowpoint_pokemon_center(env.console, context)){ + if (!fly_to_snowpoint_and_heal(env.console, context, stats)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_snowpoint_pokemon_center: transition not detected.", + "07.06: fly_to_snowpoint_and_heal did not complete.", env.console); } }, - false); + true); +} + +std::string AutoStory_Checkpoint_0707::name() const { + return "07.07 - Snowpoint Gym (path pending)"; +} + +std::string AutoStory_Checkpoint_0707::start_text() const { + return "Healed and outside Snowpoint Pokemon Center at the doorway."; } -void checkpoint_0718(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0707::end_text() const { + return "Target: Seventh badge, rewards finished, inside Snowpoint Gym at the entrance. Currently stops at the unrecorded Gym puzzle."; +} + +void AutoStory_Checkpoint_0707::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_snowpoint_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_snowpoint_gym: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0707(env, context, options, stats); } -void checkpoint_0719(SingleSwitchProgramEnvironment& env, +void checkpoint_0707(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2168,14 +1833,13 @@ void checkpoint_0719(SingleSwitchProgramEnvironment& env, [&](size_t /*attempt*/){ if (!go_to_snowpoint_gym(env.console, context)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_snowpoint_gym: transition not detected.", + "07.07: go_to_snowpoint_gym did not complete.", env.console); } }, - false); + true); } - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h index e69a7f863c..f4ab8fe8c9 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_07.h @@ -131,186 +131,6 @@ void checkpoint_0707(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_0708 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0708(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0709 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0709(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0710 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0710(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0711 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0711(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0712 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0712(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0713 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0713(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0714 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0714(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0715 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0715(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0716 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0716(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0717 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0717(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0718 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0718(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0719 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0719(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp index e1f6138fb4..57d3ceaa33 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.cpp @@ -5,6 +5,7 @@ * */ +#include "CommonFramework/Exceptions/FatalProgramException.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" @@ -32,9 +33,8 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - // --------------------------------------------------------------------------- -// Legendary encounter (from Routes/Legendary_Segment.cpp) +// Helpers // --------------------------------------------------------------------------- static void approach_legendary(VideoStream& /*stream*/, ProControllerContext& context){ @@ -77,74 +77,6 @@ static void exit_legendary_encounter(VideoStream& /*stream*/, ProControllerConte pbf_wait(context, 2000ms); } - -// --------------------------------------------------------------------------- -// Segment / Checkpoint classes -// --------------------------------------------------------------------------- - -std::string AutoStory_Segment_08::name() const { - return "08: Seventh Badge to Eighth Badge"; -} -std::string AutoStory_Segment_08::start_text() const { - return "Start: Seventh Gym Badge acquired (Snowpoint City)."; -} -std::string AutoStory_Segment_08::end_text() const { - return "End: Eighth Gym Badge acquired (Sunyshore City)."; -} - -void AutoStory_Segment_08::run_segment(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0801().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0802().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0803().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0804().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0805().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0806().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0807().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0808().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0809().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0810().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0811().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0812().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0813().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0814().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0815().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0816().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0817().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0818().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0819().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0820().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0821().run_checkpoint(env, context, options, stats); - - /*AutoStory_Checkpoint_021().run_checkpoint(env, context, options, stats); - checkpoint_021_legendary(env, context, options, stats);*/ - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} - - -std::string AutoStory_Checkpoint_021::name() const { - return "021 - " + AutoStory_Segment_08().name(); -} -std::string AutoStory_Checkpoint_021::start_text() const { - return "Seventh Gym Badge acquired (Snowpoint City)."; -} -std::string AutoStory_Checkpoint_021::end_text() const { - return "Eighth Gym Badge acquired (Sunyshore City)."; -} - - -// ------------------------------------------------------------------ Functions logic --------------------------------------------------------- // - - static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ stream.log("Finishing battle with " + trainerid, COLOR_GREEN); @@ -160,54 +92,19 @@ move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& con } } -static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ - context.wait_for_all_requests(); - stream.log("Battling " + trainerid + "...", COLOR_GREEN); - BattleMenuWatcher battle_menu(BattleType::TRAINER); - int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); - if (ret_menu < 0){ - stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); - return false; - } - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 500ms); - - if (trainerid == "veilstone_warehouse_warehouse_grunt_2" or trainerid == "galactic_hq_grunt_4" or - trainerid == "galactic_hq_grunt_5"){ - // Move the battle-move cursor down. - pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - }else if (trainerid == "galactic_hq_grunt_1" or trainerid == "galactic_hq_double_grunts_1" or - trainerid == "route_222_sailor_luther"){ - // Move the battle-move cursor up. - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - }else if (trainerid == "veilstone_warehouse_warehouse_grunt_1" or trainerid == "galactic_hq_scientist_fredrick" or - trainerid == "galactic_hq_grunt_2" or trainerid == "galactic_hq_grunt_3" or trainerid == "galactic_hq_cyrus" or - trainerid == "galactic_hq_commander_saturn" or trainerid == "route_207_hiker_justin"){ - // Keep the current battle-move selection. - } - context.wait_for_all_requests(); - - { - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; - } - } - - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_" + trainerid + ": transition confirmed."); +static bool handle_battle(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, const std::string& trainerid){ + fight_trainer_using_moves(stream, context, stats, trainerid, true); return true; } +static bool heal_and_exit(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, const std::string& label){ + return heal_pokemon(stream, context, label, &stats); +} + +// --------------------------------------------------------------------------- +// Named route navigation blocks +// --------------------------------------------------------------------------- + static bool leave_gym(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -259,10 +156,6 @@ static bool heal_at_snowpoint_city_pokemon_center(VideoStream& stream, ProContro return true; } -static bool heal_and_exit(VideoStream& stream, ProControllerContext& context, const std::string& label){ - return heal_pokemon(stream, context, label); -} - static bool go_to_lake_acuity(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); @@ -354,7 +247,6 @@ static bool talk_to_clint_then_go_veilstone(VideoStream& stream, ProControllerCo return true; } - static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContext& context){ DpadState dpad; context.wait_for_all_requests(); @@ -462,7 +354,7 @@ static bool get_key_and_enter_warehouse(VideoStream& stream, ProControllerContex return true; } -static bool go_through_warehouse(VideoStream& stream, ProControllerContext& context){ +static bool go_through_warehouse(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -541,7 +433,7 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont stream.log("veilstone_warehouse_warehouse_grunt_1: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "veilstone_warehouse_warehouse_grunt_1"); + handle_battle(stream, context, stats, "veilstone_warehouse_warehouse_grunt_1"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "veilstone_warehouse_warehouse_grunt_1"); @@ -577,7 +469,7 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont stream.log("veilstone_warehouse_warehouse_grunt_2: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "veilstone_warehouse_warehouse_grunt_2"); + handle_battle(stream, context, stats, "veilstone_warehouse_warehouse_grunt_2"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "veilstone_warehouse_warehouse_grunt_2"); @@ -660,7 +552,7 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont stream.log("galactic_hq_grunt_1: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_grunt_1"); + handle_battle(stream, context, stats, "galactic_hq_grunt_1"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "galactic_hq_grunt_1"); @@ -799,7 +691,7 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont stream.log("galactic_hq_scientist_fredrick: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_scientist_fredrick"); + handle_battle(stream, context, stats, "galactic_hq_scientist_fredrick"); } } } @@ -921,7 +813,6 @@ static bool go_through_warehouse(VideoStream& stream, ProControllerContext& cont return true; } - static bool heal_at_veilstone_pokemon_center(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -989,7 +880,7 @@ static bool go_to_galactic_hq(VideoStream& stream, ProControllerContext& context return true; } -static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, AutoStoryOptions options){ // ------- This has returned one error and I couldn't reproduce it. It was somewhere at the other comment I left behind ----------- // DpadState dpad; @@ -1078,7 +969,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_grunt_2"); + handle_battle(stream, context, stats, "galactic_hq_grunt_2"); grunt_2_done = true; } } @@ -1127,7 +1018,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("galactic_hq_grunt_2: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_grunt_2"); + handle_battle(stream, context, stats, "galactic_hq_grunt_2"); } } } @@ -1150,7 +1041,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co }else if (ret == 0){ context.wait_for_all_requests(); - handle_battle(stream, context, "galactic_hq_grunt_3"); + handle_battle(stream, context, stats, "galactic_hq_grunt_3"); context.wait_for_all_requests(); pbf_wait(context, 2000ms); } @@ -1232,7 +1123,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("galactic_hq_grunt_4: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_grunt_4"); + handle_battle(stream, context, stats, "galactic_hq_grunt_4"); } } } @@ -1249,7 +1140,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("galactic_hq_grunt_5: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_grunt_5"); + handle_battle(stream, context, stats, "galactic_hq_grunt_5"); } } }; @@ -1349,7 +1240,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("galactic_hq_cyrus: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_cyrus"); + handle_battle(stream, context, stats, "galactic_hq_cyrus"); } } } @@ -1440,7 +1331,7 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co stream.log("galactic_hq_commander_saturn: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "galactic_hq_commander_saturn"); + handle_battle(stream, context, stats, "galactic_hq_commander_saturn"); } } } @@ -1566,7 +1457,6 @@ static bool go_through_galactic_hq(VideoStream& stream, ProControllerContext& co return true; } - static bool fly_and_heal_at_oreburgh(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1596,7 +1486,7 @@ static bool fly_and_heal_at_oreburgh(VideoStream& stream, ProControllerContext& return true; } -static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1647,7 +1537,7 @@ static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& conte stream.log("go_to_mount_coronet, route_207_hiker_justin: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_207_hiker_justin"); + handle_battle(stream, context, stats, "route_207_hiker_justin"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_207_hiker_justin"); @@ -1676,16 +1566,19 @@ static bool go_to_mount_coronet(VideoStream& stream, ProControllerContext& conte return true; } - -static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +static bool go_through_mount_coronet(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options, bool summit_events = false){ context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); stream.log("go_through_mount_coronet: Going through Mount Coronet..."); - // @GumGum: Here the code, you can add more of these if you need them, but remember to adapt the numbering. - - return true; + // TODO(08.05/08.06): Record the ascent and a stable pre-boss position, + // then the summit battles, story dialogue, and legendary approach/exit. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + summit_events + ? "08.06 incomplete: Spear Pillar battles, story and legendary route need recording." + : "08.05 incomplete: Coronet ascent and the pre-boss handoff need recording.", + stream); } static bool fly_and_heal_at_veilstone(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ @@ -1784,7 +1677,7 @@ go_through_house_south_veilstone(VideoStream& stream, ProControllerContext& cont return true; } -static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -1910,7 +1803,7 @@ static bool go_to_house_west_sunyshore(VideoStream& stream, ProControllerContext stream.log("go_to_house_west_sunyshore_route_222_sailor_luther: black screen not detected!", COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_222_sailor_luther"); + handle_battle(stream, context, stats, "route_222_sailor_luther"); } } } @@ -2159,12 +2052,84 @@ static bool go_to_sunyshore_gym(VideoStream& stream, ProControllerContext& conte stream.log("go_to_sunyshore_gym: entered the gym.", COLOR_GREEN); } - return true; + // TODO(08.09): Record the puzzle, trainers, Volkner, rewards and return + // to the inside entrance for Segment 09. The old second entry was not a puzzle. + context.wait_for_all_requests(); + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "08.09 incomplete: Sunyshore Gym entered. Puzzle, Volkner battle, rewards and return need recording.", + stream); +} + +// Retained box-legendary inputs; approach and exit positions require the summit recording. +static void recorded_box_legendary_encounter(SingleSwitchProgramEnvironment& env, + ProControllerContext& context){ + approach_legendary(env.console, context); + enter_legendary_battle(env.console, context); + weaken_legendary(env.console, context); + throw_pokeball_at_legendary(env.console, context); + confirm_legendary_catch(env.console, context); + exit_legendary_encounter(env.console, context); +} + +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_08::name() const { + return "08: Snowpoint to Sunyshore"; +} + +std::string AutoStory_Segment_08::start_text() const { + return "Start: Inside Snowpoint Gym at the entrance, with seventh-badge rewards and dialogue finished."; +} + +std::string AutoStory_Segment_08::end_text() const { + return "Target: Inside Sunyshore Gym entrance with eighth-badge rewards finished. Currently stops at the missing puzzle."; +} + +void AutoStory_Segment_08::run_segment(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + AutoStory_Checkpoint_0801().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0802().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0803().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0804().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0805().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0806().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0807().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0808().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0809().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); } +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Checkpoint_0801::name() const { + return "08.01 - Snowpoint recovery"; +} + +std::string AutoStory_Checkpoint_0801::start_text() const { + return "Inside Snowpoint Gym at the entrance, with seventh-badge rewards and dialogue finished."; +} -// ------------------------------------------------------------------ Checkpoints logic --------------------------------------------------------- // +std::string AutoStory_Checkpoint_0801::end_text() const { + return "Healed and outside Snowpoint Pokemon Center at the doorway."; +} +void AutoStory_Checkpoint_0801::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0801(env, context, options, stats); +} void checkpoint_0801(SingleSwitchProgramEnvironment& env, ProControllerContext& context, @@ -2176,15 +2141,51 @@ void checkpoint_0801(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!leave_gym(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "leave_gym: transition not detected.", - env.console); + { + if (!leave_gym(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "leave_gym: transition not detected.", + env.console); + } + } + { + if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_snowpoint_city_pokemon_center: transition not detected.", + env.console); + } + } + { + if (!heal_and_exit(env.console, context, stats, "Snowpoint City")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_snowpoint_city: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, true); } +std::string AutoStory_Checkpoint_0802::name() const { + return "08.02 - Lake Acuity and Veilstone"; +} + +std::string AutoStory_Checkpoint_0802::start_text() const { + return "Healed and outside Snowpoint Pokemon Center at the doorway."; +} + +std::string AutoStory_Checkpoint_0802::end_text() const { + return "Healed and outside Veilstone Pokemon Center after the Lake Acuity events."; +} + +void AutoStory_Checkpoint_0802::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0802(env, context, options, stats); +} + void checkpoint_0802(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -2195,13 +2196,49 @@ void checkpoint_0802(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_at_snowpoint_city_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_snowpoint_city_pokemon_center: transition not detected.", - env.console); + { + if (!go_to_lake_acuity(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_lake_acuity: transition not detected.", + env.console); + } + } + { + if (!talk_to_clint_then_go_veilstone(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_to_clint_then_go_veilstone: transition not detected.", + env.console); + } } + { + if (!heal_and_exit(env.console, context, stats, "Veilstone City")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_veilstone_city: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0803::name() const { + return "08.03 - Galactic Warehouse"; +} + +std::string AutoStory_Checkpoint_0803::start_text() const { + return "Healed and outside Veilstone Pokemon Center after the Lake Acuity events."; +} + +std::string AutoStory_Checkpoint_0803::end_text() const { + return "Healed and outside Veilstone Pokemon Center with the Galactic Key obtained."; +} + +void AutoStory_Checkpoint_0803::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0803(env, context, options, stats); } void checkpoint_0803(SingleSwitchProgramEnvironment& env, @@ -2214,13 +2251,56 @@ void checkpoint_0803(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Snowpoint City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_snowpoint_city: transition not detected.", - env.console); + { + if (!get_key_and_enter_warehouse(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_key_and_enter_warehouse: transition not detected.", + env.console); + } + } + { + if (!go_through_warehouse(env.console, context, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_warehouse: transition not detected.", + env.console); + } + } + { + if (!heal_at_veilstone_pokemon_center(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_at_veilstone_pokemon_center: transition not detected.", + env.console); + } + } + { + if (!heal_and_exit(env.console, context, stats, "Veilstone City")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_veilstone_city: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); +} + +std::string AutoStory_Checkpoint_0804::name() const { + return "08.04 - Galactic HQ and Oreburgh"; +} + +std::string AutoStory_Checkpoint_0804::start_text() const { + return "Healed and outside Veilstone Pokemon Center with the Galactic Key obtained."; +} + +std::string AutoStory_Checkpoint_0804::end_text() const { + return "Healed and outside Oreburgh Pokemon Center after the Galactic HQ events."; +} + +void AutoStory_Checkpoint_0804::run_checkpoint(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats) const { + checkpoint_0804(env, context, options, stats); } void checkpoint_0804(SingleSwitchProgramEnvironment& env, @@ -2233,35 +2313,59 @@ void checkpoint_0804(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_to_lake_acuity(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_lake_acuity: transition not detected.", - env.console); + { + if (!go_to_galactic_hq(env.console, context)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_galactic_hq: transition not detected.", + env.console); + } + } + { + if (!go_through_galactic_hq(env.console, context, stats, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_galactic_hq: transition not detected.", + env.console); + } + } + { + if (!fly_and_heal_at_oreburgh(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "fly_and_heal_at_oreburgh: transition not detected.", + env.console); + } } + { + if (!heal_and_exit(env.console, context, stats, "Oreburgh City")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_oreburgh_city: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0805(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0805::name() const { + return "08.05 - Coronet ascent (path pending)"; +} + +std::string AutoStory_Checkpoint_0805::start_text() const { + return "Healed and outside Oreburgh Pokemon Center after the Galactic HQ events."; +} + +std::string AutoStory_Checkpoint_0805::end_text() const { + return "Target: Stable pre-boss position at Spear Pillar. Currently stops at the missing Coronet ascent."; +} + +void AutoStory_Checkpoint_0805::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!talk_to_clint_then_go_veilstone(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "talk_to_clint_then_go_veilstone: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0805(env, context, options, stats); } -void checkpoint_0806(SingleSwitchProgramEnvironment& env, +void checkpoint_0805(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2271,111 +2375,45 @@ void checkpoint_0806(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_veilstone_city: transition not detected.", - env.console); + { + if (!go_to_mount_coronet(env.console, context, stats, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_mount_coronet: transition not detected.", + env.console); + } } - }, - false); -} - -void checkpoint_0807(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!get_key_and_enter_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_key_and_enter_warehouse: transition not detected.", - env.console); + { + if (!go_through_mount_coronet(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_mount_coronet: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0808(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_warehouse(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_warehouse: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0806::name() const { + return "08.06 - Spear Pillar (path pending)"; } -void checkpoint_0809(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_at_veilstone_pokemon_center(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_at_veilstone_pokemon_center: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0806::start_text() const { + return "At the recorded pre-boss Spear Pillar position; exact position is pending recording."; } -void checkpoint_0810(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_veilstone_city: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0806::end_text() const { + return "Target: Summit story and legendary encounter finished; healed outside Veilstone Center. Summit pathing is missing."; } -void checkpoint_0811(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0806::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_galactic_hq(env.console, context)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_galactic_hq: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0806(env, context, options, stats); } -void checkpoint_0812(SingleSwitchProgramEnvironment& env, +void checkpoint_0806(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2385,92 +2423,53 @@ void checkpoint_0812(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_through_galactic_hq(env.console, context, options)){ + // Same existing helper, now split at the proposed summit boundary. + if (!go_through_mount_coronet(env.console, context, options, true)){ OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_galactic_hq: transition not detected.", - env.console); + "08.06: Summit story did not complete.", env.console); + } + // Retain the former encounter sequence for review with the summit recording. + // Capture/skip behavior must be verified before this missing route is enabled. + recorded_box_legendary_encounter(env, context); + { + if (!fly_and_heal_at_veilstone(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "fly_and_heal_at_veilstone: transition not detected.", + env.console); + } + } + { + if (!heal_and_exit(env.console, context, stats, "Veilstone City")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_veilstone_city: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0813(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!fly_and_heal_at_oreburgh(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "fly_and_heal_at_oreburgh: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0807::name() const { + return "08.07 - Route 222 to Sunyshore"; } -void checkpoint_0814(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Oreburgh City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_oreburgh_city: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0807::start_text() const { + return "Healed and outside Veilstone Pokemon Center after completing the summit story."; } -void checkpoint_0815(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_mount_coronet(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_mount_coronet: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0807::end_text() const { + return "In Sunyshore after the west gate and arrival dialogue."; } -void checkpoint_0816(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0807::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_mount_coronet(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_mount_coronet: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0807(env, context, options, stats); } -void checkpoint_0817(SingleSwitchProgramEnvironment& env, +void checkpoint_0807(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2480,92 +2479,59 @@ void checkpoint_0817(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!fly_and_heal_at_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "fly_and_heal_at_veilstone: transition not detected.", - env.console); + { + if (!go_to_house_south_veilstone(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_house_south_veilstone: transition not detected.", + env.console); + } + } + { + if (!go_through_house_south_veilstone(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_south_veilstone: transition not detected.", + env.console); + } + } + { + if (!go_to_house_west_sunyshore(env.console, context, stats, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_house_west_sunyshore: transition not detected.", + env.console); + } } + { + if (!go_through_house_west_sunyshore(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_house_west_sunyshore: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0818(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Veilstone City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_veilstone_city: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0808::name() const { + return "08.08 - Volkner at the lighthouse"; } -void checkpoint_0819(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_house_south_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_house_south_veilstone: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0808::start_text() const { + return "In Sunyshore after the west gate and arrival dialogue."; } -void checkpoint_0820(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_house_south_veilstone(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_south_veilstone: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0808::end_text() const { + return "Outside Sunyshore lighthouse after Volkner dialogue."; } -void checkpoint_0821(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0808::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_house_west_sunyshore(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_house_west_sunyshore: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0808(env, context, options, stats); } -void checkpoint_0822(SingleSwitchProgramEnvironment& env, +void checkpoint_0808(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2575,73 +2541,45 @@ void checkpoint_0822(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_through_house_west_sunyshore(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_house_west_sunyshore: transition not detected.", - env.console); + { + if (!go_to_sunyshore_lighthouse(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_sunyshore_lighthouse: transition not detected.", + env.console); + } + } + { + if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "talk_to_volkner_at_the_lighthouse: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0823(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_sunyshore_lighthouse(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_sunyshore_lighthouse: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0809::name() const { + return "08.09 - Sunyshore Gym (path pending)"; } -void checkpoint_0824(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!talk_to_volkner_at_the_lighthouse(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "talk_to_volkner_at_the_lighthouse: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0809::start_text() const { + return "Outside Sunyshore lighthouse after Volkner dialogue."; } -void checkpoint_0825(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0809::end_text() const { + return "Target: Inside Sunyshore Gym entrance with eighth-badge rewards finished. Currently stops at the missing puzzle."; +} + +void AutoStory_Checkpoint_0809::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_sunyshore_gym: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0809(env, context, options, stats); } -void checkpoint_0826(SingleSwitchProgramEnvironment& env, +void checkpoint_0809(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -2651,476 +2589,18 @@ void checkpoint_0826(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!go_to_sunyshore_gym(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_sunyshore_gym: transition not detected.", - env.console); + { + if (!go_to_sunyshore_gym(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_sunyshore_gym: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); } - -std::string AutoStory_Checkpoint_0801::name() const { - return "08.01 - Gym badge acquired"; -} -std::string AutoStory_Checkpoint_0801::start_text() const { - return "Seventh Gym Badge acquired (Snowpoint City)."; -} -std::string AutoStory_Checkpoint_0801::end_text() const { - return "Leave Snowpoint Gym."; -} -void AutoStory_Checkpoint_0801::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0801(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0802::name() const { - return "08.02 - Left Snowpoint Gym"; -} -std::string AutoStory_Checkpoint_0802::start_text() const { - return "Left Snowpoint Gym."; -} -std::string AutoStory_Checkpoint_0802::end_text() const { - return "Arrived at Snowpoint City Pokémon Center."; -} -void AutoStory_Checkpoint_0802::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0802(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0803::name() const { - return "08.03 - Arrived at Snowpoint Pokemon Center"; -} -std::string AutoStory_Checkpoint_0803::start_text() const { - return "Arrived at Snowpoint City Pokémon Center."; -} -std::string AutoStory_Checkpoint_0803::end_text() const { - return "Healed at Snowpoint City Pokemon Center."; -} -void AutoStory_Checkpoint_0803::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0803(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0804::name() const { - return "08.04 - Healed at Snowpoint City Pokemon Center"; -} -std::string AutoStory_Checkpoint_0804::start_text() const { - return "Healed at Snowpoint City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0804::end_text() const { - return "Entered Lake Acuity Lakefront."; -} -void AutoStory_Checkpoint_0804::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0804(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0805::name() const { - return "08.05 - Entered Acuity Lakefront"; -} -std::string AutoStory_Checkpoint_0805::start_text() const { - return "Entered Lake Acuity Lakefront."; -} -std::string AutoStory_Checkpoint_0805::end_text() const { - return "Arrived at Veilstone City Pokemon Center."; -} -void AutoStory_Checkpoint_0805::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0805(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0806::name() const { - return "08.06 - Arrived at Veilstone Pokemon Center"; -} -std::string AutoStory_Checkpoint_0806::start_text() const { - return "Arrived at Veilstone City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0806::end_text() const { - return "Healed at Veilstone City Pokemon Center."; -} -void AutoStory_Checkpoint_0806::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0806(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0807::name() const { - return "08.07 - Healed at Veilstone Pokemon Center"; -} -std::string AutoStory_Checkpoint_0807::start_text() const { - return "Healed at Veilstone City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0807::end_text() const { - return "Entered Team Galactic Warehouse."; -} -void AutoStory_Checkpoint_0807::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0807(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0808::name() const { - return "08.08 - Entered Team Galactic Warehouse"; -} -std::string AutoStory_Checkpoint_0808::start_text() const { - return "Entered Team Galactic Warehouse."; -} -std::string AutoStory_Checkpoint_0808::end_text() const { - return "Got out of the warehouse with the key."; -} -void AutoStory_Checkpoint_0808::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0808(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0809::name() const { - return "08.09 - Fly back to Veilstone Pokemon Center for healing"; -} -std::string AutoStory_Checkpoint_0809::start_text() const { - return "Got out of the warehouse with the key."; -} -std::string AutoStory_Checkpoint_0809::end_text() const { - return "Entered Veilstone Pokemon Center for healing."; -} -void AutoStory_Checkpoint_0809::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0809(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0810::name() const { - return "08.10 - Heal Pokemon"; -} -std::string AutoStory_Checkpoint_0810::start_text() const { - return "Entered Veilstone Pokemon Center for healing."; -} -std::string AutoStory_Checkpoint_0810::end_text() const { - return "Exited Veilstone Pokemon Center."; -} -void AutoStory_Checkpoint_0810::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0810(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0811::name() const { - return "08.11 - Go to Galactic HQ"; -} -std::string AutoStory_Checkpoint_0811::start_text() const { - return "Exited Veilstone Pokemon Center."; -} -std::string AutoStory_Checkpoint_0811::end_text() const { - return "Entered Galactic HQ."; -} -void AutoStory_Checkpoint_0811::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0811(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0812::name() const { - return "08.12 - Deal with Cyrus at the Galactic HQ"; -} -std::string AutoStory_Checkpoint_0812::start_text() const { - return "Entered Galactic HQ."; -} -std::string AutoStory_Checkpoint_0812::end_text() const { - return "Dealt with Cyrus and left the Galactic HQ."; -} -void AutoStory_Checkpoint_0812::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0812(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0813::name() const { - return "08.13 - Fly to Oreburgh City"; -} -std::string AutoStory_Checkpoint_0813::start_text() const { - return "Dealt with Cyrus and left the Galactic HQ."; -} -std::string AutoStory_Checkpoint_0813::end_text() const { - return "Flew to Oreburgh city and entered Pokemon Center."; -} -void AutoStory_Checkpoint_0813::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0813(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0814::name() const { - return "08.14 - Heal Pokemon at Oreburgh City Pokemon Center"; -} -std::string AutoStory_Checkpoint_0814::start_text() const { - return "Flew to Oreburgh City and entered Pokemon Center."; -} -std::string AutoStory_Checkpoint_0814::end_text() const { - return "Healed Pokemon at Oreburgh City Pokemon Center."; -} -void AutoStory_Checkpoint_0814::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0814(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0815::name() const { - return "08.15 - Go from Oreburgh City to Mount Coronet entrance"; -} -std::string AutoStory_Checkpoint_0815::start_text() const { - return "Healed Pokemon at Oreburgh City Pokemon Center."; -} -std::string AutoStory_Checkpoint_0815::end_text() const { - return "Entered Mount Coronet."; -} -void AutoStory_Checkpoint_0815::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0815(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0816::name() const { - return "08.16 - Go through Mt. Coronet to deal with Cyrus and Dialga/Palkia"; -} -std::string AutoStory_Checkpoint_0816::start_text() const { - return "Entered Mount Coronet."; -} -std::string AutoStory_Checkpoint_0816::end_text() const { - return "Exited Mt. Coronet, having dealt with Cyrus."; -} -void AutoStory_Checkpoint_0816::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0816(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0817::name() const { - return "08.17 - Fly to Veilstone City"; -} -std::string AutoStory_Checkpoint_0817::start_text() const { - return "Exited Mt. Coronet, having dealt with Cyrus."; -} -std::string AutoStory_Checkpoint_0817::end_text() const { - return "Entered the Veilstone Pokemon Center."; -} -void AutoStory_Checkpoint_0817::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0817(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0818::name() const { - return "08.18 - Heal and exit"; -} -std::string AutoStory_Checkpoint_0818::start_text() const { - return "Entered the Veilstone Pokemon Center."; -} -std::string AutoStory_Checkpoint_0818::end_text() const { - return "Exited the Veilstone Pokemon Center."; -} -void AutoStory_Checkpoint_0818::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0818(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0819::name() const { - return "08.19 - Go to house South of Veilstone City"; -} -std::string AutoStory_Checkpoint_0819::start_text() const { - return "Exited the Veilstone Pokemon Center."; -} -std::string AutoStory_Checkpoint_0819::end_text() const { - return "Arrived at the house South of Veilstone City."; -} -void AutoStory_Checkpoint_0819::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0819(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0820::name() const { - return "08.20 - Go through the house South of Veilstone City"; -} -std::string AutoStory_Checkpoint_0820::start_text() const { - return "Arrived at the house South of Veilstone City."; -} -std::string AutoStory_Checkpoint_0820::end_text() const { - return "Exited the house South of Veilstone City."; -} -void AutoStory_Checkpoint_0820::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0820(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0821::name() const { - return "08.21 - Go through routes 214 and 222 to the house West of Sunyshore City"; -} -std::string AutoStory_Checkpoint_0821::start_text() const { - return "Exited house South of Veilstone City."; -} -std::string AutoStory_Checkpoint_0821::end_text() const { - return "Entered the house West of Sunyshore City."; -} -void AutoStory_Checkpoint_0821::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0821(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0822::name() const { - return "08.22 - Go through the house West of Sunyshore City"; -} -std::string AutoStory_Checkpoint_0822::start_text() const { - return "Entered the house West of Sunyshore City."; -} -std::string AutoStory_Checkpoint_0822::end_text() const { - return "Entered Sunyshore City."; -} -void AutoStory_Checkpoint_0822::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0822(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0823::name() const { - return "08.23 - Go to Sunyshore City's lighthouse"; -} -std::string AutoStory_Checkpoint_0823::start_text() const { - return "Entered Sunyshore City."; -} -std::string AutoStory_Checkpoint_0823::end_text() const { - return "Entered Sunyshore City's lighthouse."; -} -void AutoStory_Checkpoint_0823::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0823(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0824::name() const { - return "08.24 - Talk to Volkner at the lighthouse"; -} -std::string AutoStory_Checkpoint_0824::start_text() const { - return "Entered Sunyshore City's lighthouse."; -} -std::string AutoStory_Checkpoint_0824::end_text() const { - return "Exited Sunyshore City's lighthouse."; -} -void AutoStory_Checkpoint_0824::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0824(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0825::name() const { - return "08.25 - Go to the Sunyshore City gym"; -} -std::string AutoStory_Checkpoint_0825::start_text() const { - return "Exited Sunyshore City's lighthouse."; -} -std::string AutoStory_Checkpoint_0825::end_text() const { - return "Entered Sunyshore City's gym."; -} -void AutoStory_Checkpoint_0825::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0825(env, context, options, stats); -} - -std::string AutoStory_Checkpoint_0826::name() const { - return "08.26 - Sunnyshore Gym Puzzle"; -} -std::string AutoStory_Checkpoint_0826::start_text() const { - return "Exited Sunyshore City's lighthouse."; -} -std::string AutoStory_Checkpoint_0826::end_text() const { - return "Entered Sunyshore City's gym."; -} -void AutoStory_Checkpoint_0826::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0826(env, context, options, stats); -} - -void AutoStory_Checkpoint_021::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_021(env, context, options, stats); -} - - -void checkpoint_021(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - // TODO: implement Segment 08 gameplay logic (seventh badge area) - }, - true); -} - -void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - approach_legendary(env.console, context); - enter_legendary_battle(env.console, context); - weaken_legendary(env.console, context); - throw_pokeball_at_legendary(env.console, context); - confirm_legendary_catch(env.console, context); - exit_legendary_encounter(env.console, context); - }, - true); -} - - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h index 36aff87cb0..b35b46b514 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_08.h @@ -161,288 +161,6 @@ void checkpoint_0809(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); -class AutoStory_Checkpoint_0810 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0810(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0811 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0811(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0812 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0812(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0813 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0813(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0814 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0814(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0815 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0815(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0816 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0816(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0817 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0817(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0818 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0818(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0819 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0819(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0820 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0820(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0821 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0821(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0822 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0822(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0823 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0823(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0824 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0824(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0825 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0825(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0826 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0826(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - -class AutoStory_Checkpoint_021 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; - -// start: seventh badge acquired -// end: legendary encounter (Spear Pillar / Mt. Coronet) -void checkpoint_021(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -// start: legendary encounter -// end: eighth Gym Badge acquired (Sunyshore City) -void checkpoint_021_legendary(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp index 3452998b48..a86c0d36db 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.cpp @@ -5,6 +5,7 @@ * */ +#include "CommonFramework/Exceptions/FatalProgramException.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h" #include "CommonFramework/Globals.h" @@ -32,44 +33,9 @@ namespace PokemonAutomation { namespace NintendoSwitch { namespace PokemonBDSP { - -std::string AutoStory_Segment_09::name() const { - return "09: Eighth Badge to Champion"; -} -std::string AutoStory_Segment_09::start_text() const { - return "Start: Eighth Gym Badge acquired (Sunyshore City)."; -} -std::string AutoStory_Segment_09::end_text() const { - return "End: Elite Four and Champion defeated, credits roll."; -} - -void AutoStory_Segment_09::run_segment(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - stats.m_segment++; - env.update_stats(); - context.wait_for_all_requests(); - env.console.log("Start Segment " + name(), COLOR_ORANGE); - - AutoStory_Checkpoint_0901().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0902().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0903().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0904().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0905().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0906().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0907().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0908().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0909().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0910().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0911().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0912().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0913().run_checkpoint(env, context, options, stats); - AutoStory_Checkpoint_0914().run_checkpoint(env, context, options, stats); - - context.wait_for_all_requests(); - env.console.log("End Segment " + name(), COLOR_GREEN); -} +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- static void move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ @@ -88,57 +54,28 @@ move_back_after_unexpected_battle(VideoStream& stream, ProControllerContext& con } } -static bool handle_battle(VideoStream& stream, ProControllerContext& context, const std::string& trainerid){ - context.wait_for_all_requests(); - stream.log("Battling " + trainerid + "...", COLOR_GREEN); - BattleMenuWatcher battle_menu(BattleType::TRAINER); - int ret_menu = wait_until(stream, context, std::chrono::seconds(30), {{battle_menu}}); - if (ret_menu < 0){ - stream.log("handle_battle_" + trainerid + ": battle menu not detected!", COLOR_RED); - return false; - } - pbf_wait(context, 500ms); - pbf_press_button(context, BUTTON_A, 80ms, 300ms); - pbf_wait(context, 500ms); - - if (trainerid == "victory_road_ace_mariah" or trainerid == "victory_road_blackbelt_miles" or - trainerid == "victory_road_dragontamer_andre" or trainerid == "victory_road_veteran_edgar"){ - // Move the battle-move cursor down. - pbf_press_dpad(context, DPAD_DOWN, 280ms, 200ms); - }else if (trainerid == "route_223_swimmer_miranda" or trainerid == "route_223_swimmer_oscar" or - trainerid == "route_223_swimmer_crystal" or trainerid == "route_223_swimmer_gabrielle" or - trainerid == "route_223_swimmer_troy" or trainerid == "victory_road_ace_sydney" or - trainerid == "victory_road_psychic_valencia"){ - // Move the battle-move cursor up. - pbf_press_dpad(context, DPAD_UP, 280ms, 200ms); - }else if (trainerid == "victory_road_ace_omar" or trainerid == "victory_road_veteran_clayton" or - trainerid == "victory_road_dragontamer_clinton"){ - // Keep the current battle-move selection. - } - context.wait_for_all_requests(); - - { - BlackScreenOverWatcher black_screen(COLOR_RED, {0.1, 0.1, 0.8, 0.8}); - int ret = run_until(stream, - context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_A, 200000ms); - }, - {{black_screen}}); - if (ret < 0){ - stream.log("handle_battle_" + trainerid + ": black screen not detected!", COLOR_RED); - return false; - } - } - - context.wait_for_all_requests(); - pbf_mash_button(context, BUTTON_B, 2000ms); - stream.log("handle_battle_" + trainerid + ": transition confirmed."); +static bool handle_battle(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, const std::string& trainerid){ + fight_trainer_using_moves(stream, context, stats, trainerid, true); return true; } +static bool heal_and_exit(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, const std::string& label){ + return heal_pokemon(stream, context, label, &stats); +} + +// --------------------------------------------------------------------------- +// Named route navigation blocks +// --------------------------------------------------------------------------- + static bool -enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options, bool start_inside_gym = true){ + if (start_inside_gym){ + // TODO(09.01): Record the watched Gym exit before the retained outdoor flight. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.01 incomplete: Sunyshore Gym exit path needs recording before flying to the Center.", + stream); + } + context.wait_for_all_requests(); pbf_wait(context, 2000ms); context.wait_for_all_requests(); @@ -168,7 +105,7 @@ enter_sunyshore_pokemon_center(VideoStream& stream, ProControllerContext& contex } static bool -go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -250,7 +187,7 @@ go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& con COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_223_swimmer_miranda"); + handle_battle(stream, context, stats, "route_223_swimmer_miranda"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_223_swimmer_miranda"); @@ -287,7 +224,7 @@ go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& con COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_223_swimmer_oscar"); + handle_battle(stream, context, stats, "route_223_swimmer_oscar"); } } } @@ -322,7 +259,7 @@ go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& con COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_223_swimmer_crystal"); + handle_battle(stream, context, stats, "route_223_swimmer_crystal"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_223_swimmer_crystal"); @@ -409,7 +346,7 @@ go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& con COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_223_swimmer_gabrielle"); + handle_battle(stream, context, stats, "route_223_swimmer_gabrielle"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_223_swimmer_gabrielle"); @@ -454,7 +391,7 @@ go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& con COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "route_223_swimmer_troy"); + handle_battle(stream, context, stats, "route_223_swimmer_troy"); context.wait_for_all_requests(); pbf_wait(context, 3000ms); move_back_after_unexpected_battle(stream, context, "route_223_swimmer_troy"); @@ -519,10 +456,6 @@ go_to_victory_road_pokemon_center(VideoStream& stream, ProControllerContext& con return true; } -static bool heal_and_exit(VideoStream& stream, ProControllerContext& context, const std::string& label){ - return heal_pokemon(stream, context, label); -} - static bool enter_victory_road_cave(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); @@ -551,7 +484,7 @@ static bool enter_victory_road_cave(VideoStream& stream, ProControllerContext& c return true; } -static bool go_through_victory_road_cave(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +static bool go_through_victory_road_cave(VideoStream& stream, ProControllerContext& context, AutoStoryStats& stats, AutoStoryOptions options){ DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 2000ms); @@ -604,7 +537,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_ace_mariah"); + handle_battle(stream, context, stats, "victory_road_ace_mariah"); } } } @@ -698,7 +631,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_ace_omar"); + handle_battle(stream, context, stats, "victory_road_ace_omar"); } } } @@ -755,7 +688,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_ace_sydney"); + handle_battle(stream, context, stats, "victory_road_ace_sydney"); } } } @@ -795,7 +728,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_veteran_clayton"); + handle_battle(stream, context, stats, "victory_road_veteran_clayton"); } } } @@ -887,7 +820,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_blackbelt_miles"); + handle_battle(stream, context, stats, "victory_road_blackbelt_miles"); } } } @@ -950,7 +883,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_psychic_valencia"); + handle_battle(stream, context, stats, "victory_road_psychic_valencia"); } } } @@ -1023,7 +956,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_dragontamer_andre"); + handle_battle(stream, context, stats, "victory_road_dragontamer_andre"); } } } @@ -1110,7 +1043,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_veteran_edgar"); + handle_battle(stream, context, stats, "victory_road_veteran_edgar"); } } } @@ -1155,7 +1088,7 @@ static bool go_through_victory_road_cave(VideoStream& stream, ProControllerConte COLOR_RED); return false; }else if (ret == 0){ - handle_battle(stream, context, "victory_road_dragontamer_clinton"); + handle_battle(stream, context, stats, "victory_road_dragontamer_clinton"); } } } @@ -1225,7 +1158,15 @@ static bool enter_elite_4_pokemon_center(VideoStream& stream, ProControllerConte return true; } -static bool ready_in_front_of_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ +static bool ready_in_front_of_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats, bool before_rival = true){ + if (before_rival){ + // TODO(09.04): Record the rival encounter and return to the healing position. + // The retained nurse/shop route below starts after that missing encounter. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.04 incomplete: League rival battle and recovery path need recording before final preparation.", + stream); + } + DpadState dpad; context.wait_for_all_requests(); pbf_wait(context, 6000ms); @@ -1265,6 +1206,7 @@ static bool ready_in_front_of_elite_4(VideoStream& stream, ProControllerContext& wait_for_dialogue(stream, context, "Elite 4 Pokemon Center - Pokemon healing"); mash_until_dialogue_ends(stream, context, BUTTON_B); context.wait_for_all_requests(); + stats.move_pp_remaining = stats.move_pp_capacity; pbf_wait(context, 1000ms); pbf_move_left_joystick(context, {+1, 0}, 3200ms, 100ms); // 16+ @@ -1285,7 +1227,10 @@ get_into_the_elite_4_entrance(VideoStream& stream, ProControllerContext& context context.wait_for_all_requests(); stream.log("get_into_the_elite_4_entrance: entered the Elite 4..."); - return true; + // TODO(09.04): Record this route and its verified ending position. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.04 League admission and the first-room handoff need recorded pathing; checkpoint is incomplete.", + stream); } static bool deal_with_aaron_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ @@ -1294,7 +1239,10 @@ static bool deal_with_aaron_elite_4(VideoStream& stream, ProControllerContext& c context.wait_for_all_requests(); stream.log("deal_with_aaron_elite_4: battling Aaron of the Elite 4 and passing the gate..."); - return true; + // TODO(09.05): Record this route and its verified ending position. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.05 Aaron battle and next-room handoff need recorded pathing; checkpoint is incomplete.", + stream); } static bool deal_with_bertha_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ @@ -1303,7 +1251,10 @@ static bool deal_with_bertha_elite_4(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); stream.log("deal_with_bertha_elite_4: battling Bertha of the Elite 4 and passing the gate..."); - return true; + // TODO(09.06): Record this route and its verified ending position. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.06 Bertha battle and next-room handoff need recorded pathing; checkpoint is incomplete.", + stream); } static bool deal_with_flint_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ @@ -1312,7 +1263,10 @@ static bool deal_with_flint_elite_4(VideoStream& stream, ProControllerContext& c context.wait_for_all_requests(); stream.log("deal_with_flint_elite_4: battling Flint of the Elite 4 and passing the gate..."); - return true; + // TODO(09.07): Record this route and its verified ending position. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.07 Flint battle and next-room handoff need recorded pathing; checkpoint is incomplete.", + stream); } static bool deal_with_lucian_elite_4(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ @@ -1321,7 +1275,10 @@ static bool deal_with_lucian_elite_4(VideoStream& stream, ProControllerContext& context.wait_for_all_requests(); stream.log("deal_with_lucian_elite_4: battling Lucian of the Elite 4 and passing the gate..."); - return true; + // TODO(09.08): Record this route and its verified ending position. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.08 Lucian battle and Champion-room handoff need recorded pathing; checkpoint is incomplete.", + stream); } static bool deal_with_cynthia_and_finish(VideoStream& stream, ProControllerContext& context, AutoStoryOptions options){ @@ -1330,124 +1287,73 @@ static bool deal_with_cynthia_and_finish(VideoStream& stream, ProControllerConte context.wait_for_all_requests(); stream.log("deal_with_cynthia_and_finish: battling Cynthia and finishing the game..."); - return true; + // TODO(09.09): Record this route and its verified ending position. + throw FatalProgramException(ErrorReportMode::NO_ERROR_REPORT, + "09.09 Cynthia battle, Hall of Fame and final completion need recorded pathing; checkpoint is incomplete.", + stream); } -void checkpoint_0901(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!enter_sunyshore_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_sunyshore_pokemon_center: transition not detected.", - env.console); - } - }, - true); +// --------------------------------------------------------------------------- +// Segment wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Segment_09::name() const { + return "09: Sunyshore to Champion"; } -void checkpoint_0902(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Sunyshore City")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_1: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Segment_09::start_text() const { + return "Start: Inside Sunyshore Gym at the entrance, with eighth-badge rewards and dialogue finished."; } -void checkpoint_0903(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_to_victory_road_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_to_victory_road_pokemon_center: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Segment_09::end_text() const { + return "Target: Cynthia defeated and Hall of Fame completion confirmed. Battle and ending pathing are missing."; } -void checkpoint_0904(SingleSwitchProgramEnvironment& env, +void AutoStory_Segment_09::run_segment(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!heal_and_exit(env.console, context, "Victory Road")){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "heal_and_exit_2: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + stats.m_segment++; + env.update_stats(); + context.wait_for_all_requests(); + env.console.log("Start Segment " + name(), COLOR_ORANGE); + AutoStory_Checkpoint_0901().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0902().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0903().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0904().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0905().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0906().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0907().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0908().run_checkpoint(env, context, options, stats); + AutoStory_Checkpoint_0909().run_checkpoint(env, context, options, stats); + context.wait_for_all_requests(); + env.console.log("End Segment " + name(), COLOR_GREEN); } -void checkpoint_0905(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!enter_victory_road_cave(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_victory_road_cave: transition not detected.", - env.console); - } - }, - false); +// --------------------------------------------------------------------------- +// Checkpoint wrappers +// --------------------------------------------------------------------------- + +std::string AutoStory_Checkpoint_0901::name() const { + return "09.01 - Sunyshore recovery (exit pending)"; } -void checkpoint_0906(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0901::start_text() const { + return "Inside Sunyshore Gym at the entrance, with eighth-badge rewards and dialogue finished."; +} + +std::string AutoStory_Checkpoint_0901::end_text() const { + return "Target: Healed and outside Sunyshore Pokemon Center. Gym exit pathing is missing."; +} + +void AutoStory_Checkpoint_0901::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!go_through_victory_road_cave(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "go_through_victory_road_cave: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0901(env, context, options, stats); } -void checkpoint_0907(SingleSwitchProgramEnvironment& env, +void checkpoint_0901(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1457,35 +1363,45 @@ void checkpoint_0907(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!enter_elite_4_pokemon_center(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "enter_elite_4_pokemon_center: transition not detected.", - env.console); + { + if (!enter_sunyshore_pokemon_center(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_sunyshore_pokemon_center: transition not detected.", + env.console); + } + } + { + if (!heal_and_exit(env.console, context, stats, "Sunyshore City")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_1: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0908(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0902::name() const { + return "09.02 - Route 223 and Victory Road"; +} + +std::string AutoStory_Checkpoint_0902::start_text() const { + return "Healed and outside Sunyshore Pokemon Center at the doorway."; +} + +std::string AutoStory_Checkpoint_0902::end_text() const { + return "Healed and outside Victory Road Pokemon Center after Jasmine and Route 223."; +} + +void AutoStory_Checkpoint_0902::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!ready_in_front_of_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "ready_in_front_of_elite_4: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0902(env, context, options, stats); } -void checkpoint_0909(SingleSwitchProgramEnvironment& env, +void checkpoint_0902(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1495,54 +1411,45 @@ void checkpoint_0909(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!get_into_the_elite_4_entrance(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "get_into_the_elite_4_entrance: transition not detected.", - env.console); + { + if (!go_to_victory_road_pokemon_center(env.console, context, stats, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_to_victory_road_pokemon_center: transition not detected.", + env.console); + } + } + { + if (!heal_and_exit(env.console, context, stats, "Victory Road")){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "heal_and_exit_2: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, true); } -void checkpoint_0910(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!deal_with_aaron_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_aaron_elite_4: transition not detected.", - env.console); - } - }, - false); +std::string AutoStory_Checkpoint_0903::name() const { + return "09.03 - Victory Road traversal"; } -void checkpoint_0911(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0903::start_text() const { + return "Healed and outside Victory Road Pokemon Center at the doorway."; +} + +std::string AutoStory_Checkpoint_0903::end_text() const { + return "Inside the Pokemon League building at its arrival position."; +} + +void AutoStory_Checkpoint_0903::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!deal_with_bertha_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_bertha_elite_4: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0903(env, context, options, stats); } -void checkpoint_0912(SingleSwitchProgramEnvironment& env, +void checkpoint_0903(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1552,35 +1459,52 @@ void checkpoint_0912(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!deal_with_flint_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_flint_elite_4: transition not detected.", - env.console); + { + if (!enter_victory_road_cave(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_victory_road_cave: transition not detected.", + env.console); + } + } + { + if (!go_through_victory_road_cave(env.console, context, stats, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "go_through_victory_road_cave: transition not detected.", + env.console); + } + } + { + if (!enter_elite_4_pokemon_center(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "enter_elite_4_pokemon_center: transition not detected.", + env.console); + } } + context.wait_for_all_requests(); }, - false); + true); } -void checkpoint_0913(SingleSwitchProgramEnvironment& env, +std::string AutoStory_Checkpoint_0904::name() const { + return "09.04 - League preparation (path pending)"; +} + +std::string AutoStory_Checkpoint_0904::start_text() const { + return "Inside the Pokemon League building at its arrival position."; +} + +std::string AutoStory_Checkpoint_0904::end_text() const { + return "Target: Rival defeated, final healing and supplies confirmed, ready for admission. Rival and admission pathing are missing."; +} + +void AutoStory_Checkpoint_0904::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats){ - checkpoint_reattempt_loop( - env, - context, - options, - stats, - [&](size_t /*attempt*/){ - if (!deal_with_lucian_elite_4(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_lucian_elite_4: transition not detected.", - env.console); - } - }, - false); + AutoStoryStats& stats) const { + checkpoint_0904(env, context, options, stats); } -void checkpoint_0914(SingleSwitchProgramEnvironment& env, +void checkpoint_0904(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats){ @@ -1590,105 +1514,78 @@ void checkpoint_0914(SingleSwitchProgramEnvironment& env, options, stats, [&](size_t /*attempt*/){ - if (!deal_with_cynthia_and_finish(env.console, context, options)){ - OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, - "deal_with_cynthia_and_finish: transition not detected.", - env.console); + { + if (!ready_in_front_of_elite_4(env.console, context, options, stats)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "ready_in_front_of_elite_4: transition not detected.", + env.console); + } } + { + if (!get_into_the_elite_4_entrance(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "get_into_the_elite_4_entrance: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); }, - false); + true); } -std::string AutoStory_Checkpoint_0901::name() const { - return "09.01 - Leave the gym and fly to the Sunyshore Pokemon Center."; -} -std::string AutoStory_Checkpoint_0901::start_text() const { - return "Eighth Gym Badge acquired (Sunyshore City)."; -} -std::string AutoStory_Checkpoint_0901::end_text() const { - return "Enter the Sunyshore Pokemon Center."; -} -void AutoStory_Checkpoint_0901::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0901(env, context, options, stats); +std::string AutoStory_Checkpoint_0905::name() const { + return "09.05 - Aaron (path pending)"; } -std::string AutoStory_Checkpoint_0902::name() const { - return "09.02 - Heal the Pokemon at the Sunyshore Pokemon Center."; -} -std::string AutoStory_Checkpoint_0902::start_text() const { - return "Entered the Sunyshore Pokemon Center."; -} -std::string AutoStory_Checkpoint_0902::end_text() const { - return "Exit the Sunyshore Pokemon Center."; -} -void AutoStory_Checkpoint_0902::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0902(env, context, options, stats); +std::string AutoStory_Checkpoint_0905::start_text() const { + return "At the recorded Aaron room entry position; admission and position are pending recording."; } - -std::string AutoStory_Checkpoint_0903::name() const { - return "09.03 - Start at the Sunyshore Pokemon Center and go to the Victory Road Pokemon Center."; -} -std::string AutoStory_Checkpoint_0903::start_text() const { - return "Exited the Sunyshore Pokemon Center."; -} -std::string AutoStory_Checkpoint_0903::end_text() const { - return "Enter the Victory Road Pokemon Center."; -} -void AutoStory_Checkpoint_0903::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0903(env, context, options, stats); +std::string AutoStory_Checkpoint_0905::end_text() const { + return "Target: Aaron defeated, dialogue cleared, at the next room entry. Battle and handoff pathing are missing."; } -std::string AutoStory_Checkpoint_0904::name() const { - return "09.04 - Heal at the Victory Road Pokemon Center."; -} -std::string AutoStory_Checkpoint_0904::start_text() const { - return "Entered Victory Road Pokemon Center."; -} -std::string AutoStory_Checkpoint_0904::end_text() const { - return "Exit Victory Road Pokemon Center."; -} -void AutoStory_Checkpoint_0904::run_checkpoint(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0905::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const { - checkpoint_0904(env, context, options, stats); + checkpoint_0905(env, context, options, stats); } -std::string AutoStory_Checkpoint_0905::name() const { - return "09.05 - Enter the Victory Road cave."; -} -std::string AutoStory_Checkpoint_0905::start_text() const { - return "Exited Victory Road Pokemon Center."; -} -std::string AutoStory_Checkpoint_0905::end_text() const { - return "Enter the Victory Road cave."; -} -void AutoStory_Checkpoint_0905::run_checkpoint(SingleSwitchProgramEnvironment& env, +void checkpoint_0905(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0905(env, context, options, stats); + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + { + if (!deal_with_aaron_elite_4(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_aaron_elite_4: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); + }, + true); } std::string AutoStory_Checkpoint_0906::name() const { - return "09.06 - Go through the Victory Road cave."; + return "09.06 - Bertha (path pending)"; } + std::string AutoStory_Checkpoint_0906::start_text() const { - return "Entered Victory Road cave."; + return "At the recorded Bertha room entry position, after Aaron; exact position is pending recording."; } + std::string AutoStory_Checkpoint_0906::end_text() const { - return "Exit Victory Road cave."; + return "Target: Bertha defeated, dialogue cleared, at the next room entry. Battle and handoff pathing are missing."; } + void AutoStory_Checkpoint_0906::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1696,15 +1593,40 @@ void AutoStory_Checkpoint_0906::run_checkpoint(SingleSwitchProgramEnvironment& e checkpoint_0906(env, context, options, stats); } +void checkpoint_0906(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + { + if (!deal_with_bertha_elite_4(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_bertha_elite_4: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); + }, + true); +} + std::string AutoStory_Checkpoint_0907::name() const { - return "09.07 - Go through the end of Victory Road into the Elite 4 building"; + return "09.07 - Flint (path pending)"; } + std::string AutoStory_Checkpoint_0907::start_text() const { - return "Exited Victory Road cave."; + return "At the recorded Flint room entry position, after Bertha; exact position is pending recording."; } + std::string AutoStory_Checkpoint_0907::end_text() const { - return "Enter the Elite 4 building."; + return "Target: Flint defeated, dialogue cleared, at the next room entry. Battle and handoff pathing are missing."; } + void AutoStory_Checkpoint_0907::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1712,15 +1634,40 @@ void AutoStory_Checkpoint_0907::run_checkpoint(SingleSwitchProgramEnvironment& e checkpoint_0907(env, context, options, stats); } +void checkpoint_0907(SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AutoStoryOptions options, + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + { + if (!deal_with_flint_elite_4(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_flint_elite_4: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); + }, + true); +} + std::string AutoStory_Checkpoint_0908::name() const { - return "09.08 - Enter the Elite 4 building and get ready to enter."; + return "09.08 - Lucian (path pending)"; } + std::string AutoStory_Checkpoint_0908::start_text() const { - return "Entered the Elite 4 building."; + return "At the recorded Lucian room entry position, after Flint; exact position is pending recording."; } + std::string AutoStory_Checkpoint_0908::end_text() const { - return "Ready in front of the Elite 4."; + return "Target: Lucian defeated, dialogue cleared, at the Champion room entry. Battle and handoff pathing are missing."; } + void AutoStory_Checkpoint_0908::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, @@ -1728,103 +1675,69 @@ void AutoStory_Checkpoint_0908::run_checkpoint(SingleSwitchProgramEnvironment& e checkpoint_0908(env, context, options, stats); } -std::string AutoStory_Checkpoint_0909::name() const { - return "09.09 - Enter the first room of the Elite 4."; -} -std::string AutoStory_Checkpoint_0909::start_text() const { - return "Readied in front of the Elite 4."; -} -std::string AutoStory_Checkpoint_0909::end_text() const { - return "Enter the Elite 4's first room."; -} -void AutoStory_Checkpoint_0909::run_checkpoint(SingleSwitchProgramEnvironment& env, +void checkpoint_0908(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0909(env, context, options, stats); + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + { + if (!deal_with_lucian_elite_4(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_lucian_elite_4: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); + }, + true); } -std::string AutoStory_Checkpoint_0910::name() const { - return "09.10 - Deal with Aaron and go to the second room."; -} -std::string AutoStory_Checkpoint_0910::start_text() const { - return "Entered the Elite 4's first room."; -} -std::string AutoStory_Checkpoint_0910::end_text() const { - return "Deal with Aaron and enter the second room."; -} -void AutoStory_Checkpoint_0910::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0910(env, context, options, stats); +std::string AutoStory_Checkpoint_0909::name() const { + return "09.09 - Cynthia and Hall of Fame (path pending)"; } -std::string AutoStory_Checkpoint_0911::name() const { - return "09.11 - Deal with Bertha and go to the third room."; -} -std::string AutoStory_Checkpoint_0911::start_text() const { - return "Dealt with Aaron and entered the second room."; -} -std::string AutoStory_Checkpoint_0911::end_text() const { - return "Deal with Bertha and enter the third room."; -} -void AutoStory_Checkpoint_0911::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0911(env, context, options, stats); +std::string AutoStory_Checkpoint_0909::start_text() const { + return "At the recorded Champion room entry position, after Lucian; exact position is pending recording."; } -std::string AutoStory_Checkpoint_0912::name() const { - return "09.12 - Deal with Flint and go to the fourth room."; -} -std::string AutoStory_Checkpoint_0912::start_text() const { - return "Dealt with Bertha and entered the third room."; -} -std::string AutoStory_Checkpoint_0912::end_text() const { - return "Deal with Flint and enter the fourth room."; -} -void AutoStory_Checkpoint_0912::run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0912(env, context, options, stats); +std::string AutoStory_Checkpoint_0909::end_text() const { + return "Target: Cynthia defeated and Hall of Fame completion confirmed. Battle and ending pathing are missing."; } -std::string AutoStory_Checkpoint_0913::name() const { - return "09.13 - Deal with Lucian and go to the last room."; -} -std::string AutoStory_Checkpoint_0913::start_text() const { - return "Dealt with Flint and entered the fourth room."; -} -std::string AutoStory_Checkpoint_0913::end_text() const { - return "Deal with Lucian and enter the last room."; -} -void AutoStory_Checkpoint_0913::run_checkpoint(SingleSwitchProgramEnvironment& env, +void AutoStory_Checkpoint_0909::run_checkpoint(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, AutoStoryStats& stats) const { - checkpoint_0913(env, context, options, stats); + checkpoint_0909(env, context, options, stats); } -std::string AutoStory_Checkpoint_0914::name() const { - return "09.14 - Deal with Cynthia and finish the game."; -} -std::string AutoStory_Checkpoint_0914::start_text() const { - return "Dealt with Lucian and entered the last room."; -} -std::string AutoStory_Checkpoint_0914::end_text() const { - return "Deal with Cynthia and finish the game."; -} -void AutoStory_Checkpoint_0914::run_checkpoint(SingleSwitchProgramEnvironment& env, +void checkpoint_0909(SingleSwitchProgramEnvironment& env, ProControllerContext& context, AutoStoryOptions options, - AutoStoryStats& stats) const { - checkpoint_0914(env, context, options, stats); + AutoStoryStats& stats){ + checkpoint_reattempt_loop( + env, + context, + options, + stats, + [&](size_t /*attempt*/){ + { + if (!deal_with_cynthia_and_finish(env.console, context, options)){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + "deal_with_cynthia_and_finish: transition not detected.", + env.console); + } + } + context.wait_for_all_requests(); + }, + true); } - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h index a9674f13ce..2a91b50690 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/StorySegments/PokemonBDSP_AutoStory_Segment_09.h @@ -101,7 +101,6 @@ void checkpoint_0905(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); - class AutoStory_Checkpoint_0906 : public AutoStory_Checkpoint { public: virtual std::string name() const override; @@ -117,7 +116,6 @@ void checkpoint_0906(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); - class AutoStory_Checkpoint_0907 : public AutoStory_Checkpoint { public: virtual std::string name() const override; @@ -133,7 +131,6 @@ void checkpoint_0907(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); - class AutoStory_Checkpoint_0908 : public AutoStory_Checkpoint { public: virtual std::string name() const override; @@ -149,7 +146,6 @@ void checkpoint_0908(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); - class AutoStory_Checkpoint_0909 : public AutoStory_Checkpoint { public: virtual std::string name() const override; @@ -165,85 +161,6 @@ void checkpoint_0909(SingleSwitchProgramEnvironment& env, AutoStoryOptions options, AutoStoryStats& stats); - -class AutoStory_Checkpoint_0910 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0910(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - -class AutoStory_Checkpoint_0911 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0911(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - -class AutoStory_Checkpoint_0912 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0912(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0913 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0913(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - -class AutoStory_Checkpoint_0914 : public AutoStory_Checkpoint { -public: - virtual std::string name() const override; - virtual std::string start_text() const override; - virtual std::string end_text() const override; - virtual void run_checkpoint(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats) const override; -}; -void checkpoint_0914(SingleSwitchProgramEnvironment& env, - ProControllerContext& context, - AutoStoryOptions options, - AutoStoryStats& stats); - - } } } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp index c7843b41cb..786bd5fee0 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Segment_00_tools.cpp @@ -1061,7 +1061,8 @@ static void fight_using_move_list(VideoStream& stream, const std::vector& move_slots, const std::string& label, bool confirm_default_target, - bool post_battle_dialogue_cleanup){ + bool post_battle_dialogue_cleanup, + std::array* remaining_pp = nullptr){ if (move_slots.empty() || std::any_of(move_slots.begin(), move_slots.end(), [](size_t slot){ return slot < 1 || slot > 4; })){ @@ -1111,27 +1112,65 @@ static void fight_using_move_list(VideoStream& stream, // ------------------------------------------------- // Battle menu detected. // ------------------------------------------------- - case 0: + case 0: { battle_menu_seen = true; + size_t selected_move = move_slots[move_index]; + if (remaining_pp){ + selected_move = 0; + for (size_t slot : move_slots){ + if ((*remaining_pp)[slot - 1] > 0){ + selected_move = slot; + break; + } + } + if (selected_move == 0){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Allowed move PP budgets exhausted; heal before continuing.", + stream); + } + } stream.log("[AutoStory] Battle menu detected.", COLOR_BLUE); - stream.log("[" + label + "] Move list position " + std::to_string(move_index + 1) + ": use move " + - std::to_string(move_slots[move_index]) + "."); + if (remaining_pp){ + stream.log("[" + label + "] Using available move slot " + std::to_string(selected_move) + + " from this battle's priority list."); + }else{ + stream.log("[" + label + "] Move list position " + std::to_string(move_index + 1) + ": use move " + + std::to_string(selected_move) + "."); + } select_battle_move_slot(stream, context, battle_type, - move_slots[move_index], + selected_move, label, confirm_default_target, current_move); - current_move = move_slots[move_index]; + // A disabled move leaves the Fight list open and consumes no PP. + // The single-slot policy has no legal alternative: retry the checkpoint + // rather than repeatedly charging an input the game rejected. + if (remaining_pp && move_slots.size() == 1){ + context.wait_for_all_requests(); + const auto frame = stream.video().snapshot(); + if (frame && detect_battle_move_menu(frame).move_count){ + OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, + label + ": Move selection was rejected; PP budget unchanged. Retrying from the saved checkpoint.", + stream); + } + } + current_move = selected_move; + if (remaining_pp){ + --(*remaining_pp)[selected_move - 1]; + stream.log("[Move PP] Slot " + std::to_string(selected_move) + ": " + + std::to_string((*remaining_pp)[selected_move - 1]) + " remaining in the current budget."); + } // Advance once per move selection; retain the final entry. - if (move_index + 1 < move_slots.size()){ + if (!remaining_pp && move_index + 1 < move_slots.size()){ move_index++; } break; + } // ------------------------------------------------- // End battle detected. @@ -1579,6 +1618,75 @@ void fight_trainer_using_moves(VideoStream& stream, fight_trainer_using_moves(stream, context, std::vector{move}, label, post_battle_dialogue_cleanup); } +void fight_trainer_using_moves(VideoStream& stream, + ProControllerContext& context, + AutoStoryStats& stats, + const std::string& label, + bool post_battle_dialogue_cleanup){ + // Preserve the list-based selection/target/exit handling. These priorities + // refer to the wiki's standardized slot roles, not move-name OCR. + std::vector priorities{1, 2, 4, 3}; + const bool grass_gym = label == "Gardenia" || label == "Lass Caroline" || + label == "Aroma Lady Jenna" || label == "Aroma Lady Angela" || + label == "Beauty Lindsay" || label == "Aroma Lady Teighler"; + const bool rock_battle = label == "Roark" || label.find("Hiker") != std::string::npos || + label.find("hiker_") != std::string::npos; + const bool water_battle = label == "Crasher Wake" || label.find("Pastoria Gym trainer") != std::string::npos || + label.find("tuber_") != std::string::npos || label.find("swimmer_") != std::string::npos; + const bool flying_battle = label.find("Bird") != std::string::npos || + label.find("birdkeeper_") != std::string::npos || label.find("Cycling Road") != std::string::npos; + const bool fighting_battle = label.find("Black Belt") != std::string::npos || + label.find("blackbelt_") != std::string::npos; + + switch (stats.starter_choice){ + case StarterChoice::TURTWIG: + if (label == "Fantina" || flying_battle){ + priorities = {1, 2, 4}; + }else if (rock_battle || water_battle){ + priorities = {2, 4, 1, 3}; + }else if (label == "Maylene"){ + priorities = {3, 1, 2, 4}; + } + break; + case StarterChoice::CHIMCHAR: + if (label == "Fantina" || label.find("psychic_") != std::string::npos || + label.find("Psychics ") != std::string::npos){ + // Psychic/Ghost parties rule out Fighting slots; Levitate users also + // rule out Earthquake. Do not spend a different slot on exhaustion. + priorities = {2}; + }else if (grass_gym || fighting_battle || label == "Maylene" || + label == "Route 215 Ruin Maniac Calvin" || + label == "Bug Catchers Donald and Phillip" || label == "victory_road_ace_sydney"){ + priorities = {2, 1, 4}; + }else if (flying_battle){ + priorities = {1, 4, 2}; + }else if (water_battle || rock_battle){ + priorities = {1, 4, 3, 2}; + } + break; + case StarterChoice::PIPLUP: + if (grass_gym || flying_battle || label.find("dragontamer_") != std::string::npos || + label == "victory_road_ace_sydney"){ + priorities = {2, 1, 4, 3}; + }else if (rock_battle || label == "Maylene" || label == "Fantina" || + label.find(" and ") != std::string::npos || + label == "veilstone_galactic_grunts_1" || label == "Route 215 Ruin Maniac Calvin"){ + priorities = {3, 2, 1, 4}; + }else if (water_battle || fighting_battle){ + priorities = {1, 4, 2, 3}; + } + break; + } + fight_using_move_list(stream, + context, + BattleType::TRAINER, + priorities, + label, + true, + post_battle_dialogue_cleanup, + &stats.move_pp_remaining); +} + void fight_trainer_using_move_slot(VideoStream& stream, ProControllerContext& context, size_t move_slot,