diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events.h index f3917b1c0c..388b396eae 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events.h @@ -11,8 +11,9 @@ * the executor turns that into a failed step. * * They are in files by area: _Events_Lake (00.00 to 00.03), _Sandgem - * (00.04), _Jubilife (00.05, 01.01, 01.06 to 01.08), _Oreburgh (01.02 to - * 01.05) and _Route204 (01.09 to 01.11). + * (00.04), _Jubilife (00.05, 01.01), _Route203 (01.02), _Oreburgh (01.03 + * and 01.04) and _Route204 (01.09's Rock Smash, 01.11). 01.05 to 01.10 + * are planned walks in the story table. * */ diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Jubilife.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Jubilife.cpp index c8ede10192..c1dc95e0dd 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Jubilife.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Jubilife.cpp @@ -1,10 +1,10 @@ -/* BDSP AutoStory - Events: Jubilife City (00.05, 01.01, 01.07) +/* BDSP AutoStory - Events: Jubilife City (00.05, 01.01) * * From: https://github.com/PokemonAutomation/ * * The Jubilife beats of 00.05 the story table cannot plan (up the steps, - * Barry's Town Map, the talk to a wandering clown), and segment 01's walks - * out of the Center to Barry and up to Rowan's scene. + * Barry's Town Map, the talk to a wandering clown), and segment 01's walk + * out of the Center to Barry. * */ @@ -209,37 +209,6 @@ void east_to_barry(VideoStream& stream, ProControllerContext& context){ } } -// 01.07: west along Jubilife's long road to the crossroads. -void west_to_the_crossroads(VideoStream& stream, ProControllerContext& context){ - // Route 203's row 18 carries on as Jubilife's long road. From the first - // tile the crossroads with the street north to Route 204 is 14 tiles - // west; the street is about 4 tiles wide, so a stick hold does (by hand, - // 2026-10-31: 2 x 1120 ms landed mid-street; 7 D-pad taps stopped below - // a building, and 18 went on to the road down to Route 202, r230b). - route_turn(stream, context, Direction::WEST); - pbf_move_left_joystick(context, {-1, 0}, route_open_hold(Direction::WEST, 14), 300ms); - context.wait_for_all_requests(); -} - -// 01.07: north up the street until Rowan's scene starts. -void north_to_rowan(VideoStream& stream, ProControllerContext& context){ - // North up the street: Rowan's scene starts a few tiles up (by hand, a - // 6 s walk). It opens with a cutscene, not a dialog box, so the walk - // reads as stopped; wait for his first line. The green-haired man walks - // about the crossroads and may stand in our way: wait and walk again. - bool scene = false; - for (size_t tries = 0; tries < 3 && !scene; tries++){ - const WalkResult up = walk_dir(stream, context, Direction::NORTH, 6000ms, "north up the street to Route 204"); - scene = up.stop == WalkStop::DIALOG || dialogue_opens(stream, context, 8000ms); - if (!scene){ - pbf_wait(context, 2500ms); - } - } - if (!scene){ - fail_step(stream, "Jubilife: Rowan's scene did not start up the street north."); - } -} - } @@ -249,8 +218,6 @@ void add_jubilife_events(StoryEvents& events){ events.emplace("talk_to_wanderer", talk_to_wanderer); events.emplace("jubilife_center_to_street", console_event(jubilife_center_to_street)); events.emplace("east_to_barry", console_event(east_to_barry)); - events.emplace("west_to_the_crossroads", console_event(west_to_the_crossroads)); - events.emplace("north_to_rowan", console_event(north_to_rowan)); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Oreburgh.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Oreburgh.cpp index 163993a003..7628d3ce8c 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Oreburgh.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Oreburgh.cpp @@ -1,10 +1,9 @@ -/* BDSP AutoStory - Events: Oreburgh (01.03 to 01.05) +/* BDSP AutoStory - Events: Oreburgh (01.03 to 01.04) * * From: https://github.com/PokemonAutomation/ * - * Segment 01's walks in Oreburgh: the mine and Roark, the Gym, and back - * out of the Oreburgh Gate, as they ran on the console (2026-10-29 to - * 10-31). + * Segment 01's walks in Oreburgh: the mine and Roark, and the Gym, as + * they ran on the console (2026-10-29 to 10-31). * */ @@ -21,7 +20,6 @@ #include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h" #include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_StoryMaps.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Indoors.h" -#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Location.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_PlannedRoute.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_RouteWalk.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Walk.h" @@ -504,42 +502,6 @@ void gym_until_roark_beaten(VideoStream& stream, ProControllerContext& context){ } } -// 01.05: out of the gym onto its doorstep. -void out_of_the_gym(VideoStream& stream, ProControllerContext& context){ - // 01.04 ends in front of Roark, just after his battle: its save at - // 01.05's start is right after the win (2026-10-31, at the user's - // request), so a failure later never has to beat him again. - // No trip back to the Oreburgh Center (2026-10-31, the user): talk_or_fight() - // gives a Potion after Roark when HP is low, and Jubilife's Center is on - // the way. Out of the gym onto its doorstep, which is on the Center door's - // row: west along it reaches the stairs' cliff. - if (!dpad_out_of_building(stream, context, 40, "out of the gym")){ - fail_step(stream, "Oreburgh Gym: did not leave by its door."); - } -} - -// 01.05: up to the platform by the Oreburgh Gate and into it (Barry bumps into us there). -void into_oreburgh_gate(VideoStream& stream, ProControllerContext& context){ - // From the gym's doorstep, as walked by hand on 2026-10-29. - walk_dir(stream, context, Direction::WEST, 20000ms, "west to the stairs' cliff"); - walk_dir(stream, context, Direction::NORTH, 10000ms, "north along the cliff"); - hold_tiles(stream, context, Direction::EAST, 2); - walk_dir(stream, context, Direction::NORTH, 5000ms, "north up the stairs"); - walk_dir(stream, context, Direction::WEST, 6000ms, "west along the platform"); - hold_tiles(stream, context, Direction::SOUTH, 1); - // Barry runs into us on the platform ("*thud*") the first time; his lines - // end with no battle. - west_until_transition(stream, context, "Into Oreburgh Gate", 8); -} - -// 01.05: west through the Gate until Route 203's banner shows. -void out_of_oreburgh_gate(VideoStream& stream, ProControllerContext& context){ - // The cave's fade out of the Gate does not always read as a transition - // (r89), so the walk west ends on Route 203's location banner, which - // shows for a few seconds as we step out. 01.06 starts there, from a save. - west_until_transition(stream, context, "West out of Oreburgh Gate", 8, location_banner_showing); -} - } @@ -551,9 +513,6 @@ void add_oreburgh_events(StoryEvents& events){ events.emplace("oreburgh_center_heal", console_event(oreburgh_center_heal)); events.emplace("out_of_the_center", console_event(out_of_the_center)); events.emplace("gym_until_roark_beaten", console_event(gym_until_roark_beaten)); - events.emplace("out_of_the_gym", console_event(out_of_the_gym)); - events.emplace("into_oreburgh_gate", console_event(into_oreburgh_gate)); - events.emplace("out_of_oreburgh_gate", console_event(out_of_oreburgh_gate)); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route203.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route203.cpp index 6c3b272bab..6b15500f78 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route203.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route203.cpp @@ -1,10 +1,9 @@ -/* BDSP AutoStory - Events: Route 203 (01.02, 01.06) +/* BDSP AutoStory - Events: Route 203 (01.02) * * From: https://github.com/PokemonAutomation/ * * Segment 01's walks along Route 203: east to the Oreburgh Gate and through - * it to Oreburgh, and back west to Jubilife, as they ran on the console - * (2026-10-29 to 10-31). + * it to Oreburgh, as they ran on the console (2026-10-29 to 10-31). * */ @@ -18,16 +17,12 @@ #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonBDSP/Inference/PokemonBDSP_DialogDetector.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_StoryMaps.h" -#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Location.h" -#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_PlannedRoute.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_RouteWalk.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Walk.h" #include "PokemonBDSP_AutoStory_Talks.h" #include "PokemonBDSP_AutoStory_Walks.h" #include "PokemonBDSP_AutoStory_WildBattle.h" #include "PokemonBDSP_AutoStory_Events.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -188,51 +183,11 @@ void oreburgh_welcome(VideoStream& stream, ProControllerContext& context){ context.wait_for_all_requests(); } -// 01.06: Route 203 on its grid to row 18, below the girl. -Pokemon::Outcome route_203_to_row_18(StoryExecutor& executor, Pokemon::RunState& state, const Pokemon::StoryStep&){ - VideoStream& stream = executor.stream(); - ProControllerContext& context = executor.context(); - // On the grid (Maps/Route203.json, from the decomp, - // its east stretch checked by hand): south from the Gate down the sand, - // west along the sand's bottom row and on under the girl's row, which - // runs straight to Jubilife as row 18. Trainers the map does not list - // are fought where they stop us. - RouteTrainers trainers{{}, [&](const std::string& who){ - talk_or_fight(stream, context, "Route 203: " + who); - }}; - const std::vector at = walk_route(stream, context, run_from_wild_battle, route203_maps(), map_region("Route203", "gate_exit"), trainers, RouteWalkPlan{on_route203_west_line, "Route 203 to row 18", {}, route_plan_costs()}); - if (at.size() != 1){ - fail_step(stream, "Route 203: the walk to row 18 did not end on a known tile."); - } - return Pokemon::outcome_ok(Pokemon::tile_pose("Route203", at)); -} - -// 01.06: west along row 18 into Jubilife, counting tiles. -Pokemon::Outcome row_18_into_jubilife(StoryExecutor& executor, Pokemon::RunState& state, const Pokemon::StoryStep&){ - VideoStream& stream = executor.stream(); - ProControllerContext& context = executor.context(); - const std::vector at = Pokemon::tiles_of(state.pose); - if (at.size() != 1){ - fail_step(stream, "Route 203: row 18's walk needs the one tile the walk to it ended on."); - } - // Row 18 has no wall before Jubilife, so count the tiles: from column - // `x` it is x + 1 taps to Jubilife's first tile (by hand from column 31: - // 32 taps, Jubilife's banner on the last one). 01.07 starts there. - dpad_tiles(stream, context, DPAD_LEFT, (size_t)at[0].x + 1, "west along row 18 into Jubilife"); - context.wait_for_all_requests(); - if (!location_banner_showing(*stream.video().snapshot().frame)){ - fail_step(stream, "Route 203: no Jubilife banner after counting the tiles west."); - } - return Pokemon::outcome_ok(); -} - } void add_route203_events(StoryEvents& events){ events.emplace("route_203_east_to_the_gate", route_203_east_to_the_gate); - events.emplace("route_203_to_row_18", route_203_to_row_18); - events.emplace("row_18_into_jubilife", row_18_into_jubilife); events.emplace("the_hiker", console_event(the_hiker)); events.emplace("hidden_moves_card", console_event(hidden_moves_card)); events.emplace("gate_east_to_oreburgh", console_event(gate_east_to_oreburgh)); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route204.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route204.cpp index d17300478a..157c99cbcc 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route204.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Events_Route204.cpp @@ -1,27 +1,18 @@ -/* BDSP AutoStory - Events: Route 204 to Floaroma (01.08 to 01.11) +/* BDSP AutoStory - Events: Route 204 to Floaroma (01.09, 01.11) * * From: https://github.com/PokemonAutomation/ * - * Segment 01's walks up Route 204 South and through the Ravaged Path, up Route 204 North into - * Floaroma, and to the old couple in its flower field, as they ran on the - * console (2026-10-31). + * The Ravaged Path's Rock Smash, and segment 01's walks to the old couple + * in Floaroma's flower field, as they ran on the console (2026-10-31). * */ -#include #include "CommonFramework/ImageTools/ImageBoxes.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/Inference/PokemonBDSP_SelectionArrow.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_Maps.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_StoryMaps.h" -#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Location.h" -#include "PokemonBDSP/Programs/AutoStory/Data/PokemonBDSP_AutoStory_PlanCosts.h" -#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_PlannedRoute.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_RouteWalk.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Walk.h" #include "PokemonBDSP_AutoStory_Talks.h" @@ -68,139 +59,16 @@ bool answer_rock_smash_prompt(VideoStream& stream, ProControllerContext& context return false; } -// 01.08: north out of Jubilife onto Route 204. -void north_out_of_jubilife(VideoStream& stream, ProControllerContext& context){ - // The street's column 14 carries on as the route's; the location banner - // names Route 204 as we cross. A D-pad walk keeps the column; it may - // stop a tile or two past the border, which the entry set allows for. - const WalkResult out = walk_until_dpad(stream, context, run_from_wild_battle, {0, 1}, 8000ms, "north out of Jubilife", {.arrived = location_banner_showing}); - if (out.stop != WalkStop::ARRIVED){ - fail_step(stream, "Jubilife: no Route 204 banner walking north up the street."); +// 01.09: Rock Smash on the rock east of us, from the tile the route ends on +// facing it. Only the prompt's Yes/No box tells the rock from someone in +// the way (r237a: the walker took one for a person and said No). +void smash_the_rock(VideoStream& stream, ProControllerContext& context){ + route_turn(stream, context, Direction::EAST); + if (!talk_with_nudges(stream, context, run_from_wild_battle, Direction::EAST, "the rock east of us", 2000ms)){ + fail_step(stream, "Ravaged Path: nothing answered facing the rock east of us."); } - context.wait_for(1500ms); -} - -// 01.08: Route 204 South on its grid to below the Ravaged Path's door. -void route_204_south_to_the_door(VideoStream& stream, ProControllerContext& context){ - // On the grid decoded from the game's data (Maps/Route204South.json): - // a tile west onto the tall grass column that runs up to the cliff, - // north up it, then west along the cliff to the signboard beside the - // door (the offline plan: 3 legs). Other trainers' sight lines are kept - // out of; town_plan_costs() lets the one-tile step land exactly. Lass Sarah's east line crosses - // the street, so she may stop us for a battle on the way; a Youngster - // who is not a trainer wanders in front of the door, and the walker - // waits for him. - { - RouteTrainers trainers{route204_south_trainer_sights(), [&](const std::string& who){ talk_or_fight(stream, context, who); }}; - const std::vector at = walk_route(stream, context, run_from_wild_battle, route204_south_maps(), - map_region("Route204South", "jubilife_entry"), trainers, RouteWalkPlan{[](const Pokemon::TilePos& pos){ return pos == map_point("Route204South", "below_door"); }, "Route 204 South to the Ravaged Path", route204_south_tiles_to_avoid(), town_plan_costs()}); - if (at.size() != 1 || !(at[0] == map_point("Route204South", "below_door"))){ - fail_step(stream, "Route 204: the walk did not end below the Ravaged Path's door."); - } - } -} - -// 01.08: north through the door. -void into_the_ravaged_path(VideoStream& stream, ProControllerContext& context){ - if (!dpad_through_door_north(stream, context, "Ravaged Path door")){ - fail_step(stream, "Route 204: no dark screen walking north into the Ravaged Path."); - } - context.wait_for(2000ms); -} - -// 01.09: from the tile above the way out, one tap south out of the cave. -void out_of_the_ravaged_path(VideoStream& stream, ProControllerContext& context){ - // One tap onto the way out, then wait for Route 204's banner: a timed - // walk south read the fade as nothing and walked on to the cliff, past - // cave_exit where 01.10 starts (r81). Not brightness: outside can be - // as dark as the cave at night. - pbf_press_dpad(context, DPAD_DOWN, 160ms, 0ms); - context.wait_for_all_requests(); - bool out = false; - const auto deadline = current_time() + 8000ms; - while (!out && current_time() < deadline){ - context.wait_for(200ms); - const VideoSnapshot frame = stream.video().snapshot(); - out = frame && location_banner_showing(*frame.frame); - } - if (!out){ - fail_step(stream, "Ravaged Path: no location banner after one tap south from a tile west of the corridor's end."); - } -} - -// 01.09: through the Ravaged Path, smashing its rocks. -void through_the_ravaged_path(VideoStream& stream, ProControllerContext& context){ - // On the traced grid (Maps/RavagedPath.json): from the - // south door up the stairs to the tile west of one of the two rocks - // across the lower corridor, smash it, then east and south to the - // corridor's east end; the way out is a tile west of it, south. - { - // Blocked short of a wall in here, it is a rock the plan counted on - // being gone (r237a: the walker took one for a person and said No). - auto in_the_way = [&](const std::string& who){ - if (!answer_rock_smash_prompt(stream, context, who)){ - talk_or_fight(stream, context, who); - } - }; - RouteTrainers no_trainers{{}, in_the_way}; - std::vector at{map_point("RavagedPath", "entrance")}; - const StoryWorld rocks_in = ravaged_path_maps(); - at = walk_route(stream, context, run_from_wild_battle, rocks_in, at, no_trainers, RouteWalkPlan{[&](const Pokemon::TilePos& pos){ - const auto& before = map_region("RavagedPath", "before_rocks"); - return std::find(before.begin(), before.end(), pos) != before.end(); - }, "To a rock across the corridor", {}, route_plan_costs()}); - route_turn(stream, context, Direction::EAST); - if (!talk_with_nudges(stream, context, run_from_wild_battle, Direction::EAST, "the rock east of us", 2000ms)){ - fail_step(stream, "Ravaged Path: nothing answered facing the rock east of us."); - } - if (!answer_rock_smash_prompt(stream, context, "the rock east of us")){ - fail_step(stream, "Ravaged Path: no Rock Smash offer facing the rock east of us."); - } - // We may be on either row; the rock east of the other stays, but no - // leg from our row crosses it. - std::vector smashed; - for (const Pokemon::TilePos& pos : map_region("RavagedPath", "before_rocks")){ - smashed.push_back(pos.next(Direction::EAST)); - } - const StoryWorld rocks_out = ravaged_path_maps(smashed); - at = walk_route(stream, context, run_from_wild_battle, rocks_out, at, no_trainers, RouteWalkPlan{[&](const Pokemon::TilePos& pos){ return pos == map_point("RavagedPath", "east_end"); }, "To the corridor's east end", {}, route_plan_costs()}); - dpad_tiles(stream, context, DPAD_LEFT, 1, "west onto the tile above the way out"); - out_of_the_ravaged_path(stream, context); - context.wait_for(2000ms); - } -} - -// 01.10: Route 204 north on the human's line, into Floaroma. -void route_204_to_floaroma(VideoStream& stream, ProControllerContext& context){ - // The line a human walked (Maps/Route204North.json, - // the region floaroma_waypoints): a tile south out of the cave's mouth, west along - // the row under the cliff past the Youngster who wanders there, up the - // stairs, east along the corridor into Aroma Lady Taylor's battle, north - // through the tall grass, west below the flower beds, and north between - // the beds into Floaroma. Whole tiles on the D-pad, corner to corner. - { - RouteTrainers trainers{route204_north_trainer_sights(), [&](const std::string& who){ - const bool doubles = who.find("Twins") != std::string::npos || who.find("someone") != std::string::npos; - talk_or_fight(stream, context, who, TalkExpect::TALK_OR_BATTLE, doubles ? BattleFormat::DOUBLES : BattleFormat::SINGLES); - }}; - std::vector at{map_point("Route204North", "cave_exit")}; - at = walk_waypoints(stream, context, run_from_wild_battle, route204_north_maps(), at, trainers, WaypointWalkPlan{map_region("Route204North", "floaroma_waypoints"), "To the path into Floaroma", waypoint_plan_costs()}); - if (!std::all_of(at.begin(), at.end(), in_floaroma_path)){ - fail_step(stream, "Route 204: the walk did not end in the path into Floaroma."); - } - // North until the town's banner shows: the checkpoint ends right - // there, where the save goes (r247a walked 20 s on and overshot). - const WalkResult in = walk_until_dpad(stream, context, run_from_wild_battle, {0, 1}, 12000ms, "north into Floaroma", {.arrived = location_banner_showing}); - if (in.stop != WalkStop::ARRIVED){ - fail_step(stream, "Route 204: no location banner walking north into Floaroma."); - } - context.wait_for(500ms); - const VideoSnapshot banner = stream.video().snapshot(); - const std::string read = banner ? read_location_banner(*banner.frame) : ""; - if (!location_banner_matches(read, "Floaroma Town")){ - fail_step(stream, "Route 204: the banner read \"" + read + "\", not Floaroma Town."); - } - stream.log("[01.10] In Floaroma Town; the banner read \"" + read + "\".", COLOR_GREEN); + if (!answer_rock_smash_prompt(stream, context, "the rock east of us")){ + fail_step(stream, "Ravaged Path: no Rock Smash offer facing the rock east of us."); } } @@ -237,11 +105,7 @@ void below_the_old_man(VideoStream& stream, ProControllerContext& context){ void add_route204_events(StoryEvents& events){ - events.emplace("north_out_of_jubilife", console_event(north_out_of_jubilife)); - events.emplace("route_204_south_to_the_door", console_event(route_204_south_to_the_door)); - events.emplace("into_the_ravaged_path", console_event(into_the_ravaged_path)); - events.emplace("through_the_ravaged_path", console_event(through_the_ravaged_path)); - events.emplace("route_204_to_floaroma", console_event(route_204_to_floaroma)); + events.emplace("smash_the_rock", console_event(smash_the_rock)); events.emplace("west_8_north_to_the_old_woman", console_event(west_8_north_to_the_old_woman)); events.emplace("below_the_old_man", console_event(below_the_old_man)); } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.cpp index 3d9868ef59..c7a5c8a090 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.cpp @@ -66,6 +66,9 @@ std::optional story_route_costs(const std::string& variant){ if (variant == "robust"){ return RobustPlanCosts(); } + if (variant == "line"){ + return waypoint_plan_costs(); + } return std::nullopt; } @@ -92,6 +95,16 @@ void save_failure_screenshot(VideoStream& stream, size_t attempt){ stream.log("Failure screenshot: " + path); } +// A trainer who saw us on a route: Route 204's Twins battle as a pair +// (01.10), everyone else alone. +void fight_story_trainer(VideoStream& stream, ProControllerContext& context, const std::string& trainer){ + if (trainer.find("Twins") != std::string::npos){ + talk_or_fight(stream, context, trainer, TalkExpect::BATTLE, BattleFormat::DOUBLES); + return; + } + fight_route_trainer(stream, context, trainer); +} + } @@ -103,8 +116,7 @@ void StoryExecutor::begin_attempt(const Pokemon::StoryCheckpoint& data){ m_checkpoint = data.id; // A row starts from its save: after the shoes' row, the save has them. m_can_run = data.id > std::string(RUNNING_SHOES_ROW); - const StoryWorld* south = story_plan_world("Route202", "Route202"); - m_trainers = south ? story_trainers(*south) : std::vector{}; + m_trainers.clear(); } Outcome StoryExecutor::run(RunState& state, const StoryStep& step){ @@ -153,21 +165,18 @@ Pose StoryExecutor::walk_to(const Pose& from, const Pose& goal, const StoryStep& for (const TilePos& pos : Pokemon::tiles_of(goal)){ targets.push_back(story_to_world(*maps, goal.map, pos)); } - // Only Route 202's trainers are known; the ones fought stay off - // m_trainers until the next attempt. - const bool south = maps == story_plan_world("Route202", "Route202"); + // The ones fought stay off m_trainers until the next attempt. + std::vector& sights = m_trainers.try_emplace(maps, story_trainers(*maps)).first->second; RouteTrainers trainers{ - south ? m_trainers : std::vector{}, - [this](const std::string& trainer){ fight_route_trainer(stream(), context(), trainer); } + sights, + [this](const std::string& trainer){ fight_story_trainer(stream(), context(), trainer); } }; const RouteWalkPlan plan{ [&](const TilePos& pos){ return std::find(targets.begin(), targets.end(), pos) != targets.end(); }, label_of(step), {}, *costs, m_can_run ? MoveSpeed::RUN : MoveSpeed::WALK }; const std::vector at = walk_route(stream(), context(), run_from_wild_battle, *maps, start, trainers, plan); - if (south){ - m_trainers = std::move(trainers.sights); - } + sights = std::move(trainers.sights); std::vector local; for (const TilePos& pos : at){ const std::optional tile = story_to_local(*maps, goal.map, pos); @@ -354,11 +363,15 @@ namespace{ // stops it: holding on under Mom's "Oh, Kosmos!" wasted 12 s (r45). The walker's black-screen watch // missed the lab's fade into Sandgem (r44, "dialog"), and settle's samples // 1.3 s apart can fall either side of it, so watch at settle's own darkness. -bool run_into_door(VideoStream& stream, ProControllerContext& context, Direction direction, bool run){ - const DpadPosition dpad = +DpadPosition dpad_toward(Direction direction){ + return direction == Direction::NORTH ? DPAD_UP : direction == Direction::SOUTH ? DPAD_DOWN : direction == Direction::WEST ? DPAD_LEFT : DPAD_RIGHT; +} + +bool run_into_door(VideoStream& stream, ProControllerContext& context, Direction direction, bool run){ + const DpadPosition dpad = dpad_toward(direction); BlackScreenWatcher dark(COLOR_RED, {0.1, 0.1, 0.8, 0.8}, 3 * FADE_DARKNESS, 60); ShortDialogWatcher dialog(COLOR_YELLOW); const int ret = run_until(stream, context, [&](ProControllerContext& context){ @@ -370,10 +383,68 @@ bool run_into_door(VideoStream& stream, ProControllerContext& context, Direction return ret == 0; } +// What the screen showed after a tap toward a door or a cave mouth. +enum class TapLook{ + NOTHING, + DARK, + BANNER, +}; + +// Looks for `map`'s location banner (and, if `dark_too`, the fade) for up +// to `wait`. +TapLook look_after_tap(VideoStream& stream, ProControllerContext& context, const std::string& map, Milliseconds wait, bool dark_too){ + const WallClock deadline = current_time() + wait; + while (true){ + context.wait_for_all_requests(); + const VideoSnapshot screen = stream.video().snapshot(); + if (screen && location_banner_matches(read_location_banner(*screen.frame), map)){ + return TapLook::BANNER; + } + if (dark_too && screen && image_average(*screen.frame).sum() / 3 < FADE_DARKNESS){ + return TapLook::DARK; + } + if (current_time() >= deadline){ + return TapLook::NOTHING; + } + context.wait_for(100ms); + } +} + +// A STEP "to_banner": D-pad taps one at a time toward a door or cave mouth, +// each followed by a look, until the fade (then no more taps) and +// pose_after's map's location banner. A held walk can carry on past a +// fade that goes unseen: out of the Gym it walked on south (r219g), and +// through the Oreburgh Gate's west mouth on west into a pocket by Route +// 203's pond (r75). Taps during the fade are eaten. No banner fails the +// step right there. +Outcome tap_to_banner(VideoStream& stream, ProControllerContext& context, const StoryStep& step, const std::string& checkpoint){ + const std::string map = step.pose_after ? story_banner_name(step.pose_after->map) : ""; + for (size_t tap = 1; tap <= 30; tap++){ + pbf_press_dpad(context, dpad_toward(step.direction), 160ms, 0ms); + TapLook look = look_after_tap(stream, context, map, 1500ms, true); + const bool faded = look == TapLook::DARK; + if (faded){ + look = look_after_tap(stream, context, map, 8000ms, false); + } + if (look == TapLook::BANNER){ + stream.log(std::format("[{}] {}: {}'s banner after {} taps {}.", checkpoint, step.name, map, tap, direction_name(step.direction))); + context.wait_for(1000ms); + return Pokemon::outcome_ok(); + } + if (faded){ + break; + } + } + return Pokemon::outcome_failed("no_banner", map); +} + } Outcome StoryExecutor::step(RunState& state, const StoryStep& step){ route_turn(stream(), context(), step.direction); + if (step.variant == "to_banner"){ + return tap_to_banner(stream(), context(), step, m_checkpoint); + } // A step through a door: walk (or run) on until the screen goes dark, // from next to the door or from anywhere in its line (r43: tapping 8 tiles // down the lab took 7 s), then settle for any scene on arrival (Mom's, diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.h index abad7f441b..d8b57457bc 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryExecutor.h @@ -76,7 +76,8 @@ struct StoryEnv{ // The plan costs a ROUTE step's variant names: "" the route costs, -// "town" town_plan_costs(), "robust" the planner's defaults. Nothing for +// "town" town_plan_costs(), "robust" the planner's defaults, "line" +// waypoint_plan_costs() (a stretch of a line a human walked). Nothing for // another name. std::optional story_route_costs(const std::string& variant); @@ -118,7 +119,7 @@ class StoryExecutor : public Pokemon::StoryStepExecutor{ virtual Pokemon::Outcome new_game(Pokemon::RunState& state, const Pokemon::StoryStep& step) override; // verify_tutorial_settings(): fast text, Autosave off. virtual Pokemon::Outcome options(Pokemon::RunState& state, const Pokemon::StoryStep& step) override; - // Variant: the plan's costs ("" route, "town", "robust"). + // Variant: the plan's costs ("" route, "town", "robust", "line"). virtual Pokemon::Outcome route(Pokemon::RunState& state, const Pokemon::StoryStep& step) override; // Each leg in D-pad taps, turning first. Variant "one_short_ok": a leg // may end a tile short (a door lined up by sight after). Variant @@ -127,7 +128,8 @@ class StoryExecutor : public Pokemon::StoryStepExecutor{ // unless the step gives one. virtual Pokemon::Outcome legs(Pokemon::RunState& state, const Pokemon::StoryStep& step) override; // One D-pad step `direction`, turning first: onto a scene's trigger or - // into a door. + // into a door. Variant "to_banner": one tap into a cave mouth, then + // wait for pose_after's map's location banner. virtual Pokemon::Outcome step(Pokemon::RunState& state, const Pokemon::StoryStep& step) override; // A at what we face; a dialogue must open. virtual Pokemon::Outcome talk(Pokemon::RunState& state, const Pokemon::StoryStep& step) override; @@ -156,9 +158,10 @@ class StoryExecutor : public Pokemon::StoryStepExecutor{ std::string m_checkpoint; // Whether the player has the Running Shoes: the pace routes are timed at. bool m_can_run = false; - // Route 202's trainers who can still see us, in story_plan_world()'s - // world tiles; all of them again at each attempt. - std::vector m_trainers; + // The trainers who can still see us on each story_plan_world() world, + // in its tiles, from its first walk this attempt; all of them again at + // each attempt. + std::map> m_trainers; std::map m_failures; std::map m_run_counts; std::set m_stop_rows; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.cpp index 475acd37e4..3c05fa52b1 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.cpp @@ -16,12 +16,14 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonBDSP{ +using Pokemon::Direction; using Pokemon::TilePos; namespace{ -// The JSON maps' names and the location banners'. +// The JSON maps' names and the location banners'. Both halves of Route +// 204 show "Route 204"; no world holds both. const std::map& banner_names(){ static const std::map names{ {"TwinleafTown", "Twinleaf Town"}, @@ -30,6 +32,11 @@ const std::map& banner_names(){ {"SandgemTown", MAP_SANDGEM_TOWN}, {"Route202", MAP_ROUTE_202}, {"JubilifeCity", MAP_JUBILIFE_CITY}, + {"Route203", MAP_ROUTE_203}, + {"OreburghCity", MAP_OREBURGH_CITY}, + {"Route204South", "Route 204"}, + {"Route204North", "Route 204"}, + {"FloaromaTown", MAP_FLOAROMA_TOWN}, }; return names; } @@ -42,56 +49,177 @@ const std::string& banner_name(const std::string& map){ return map; } +// `maps` (JSON names) in one world, each with its tile (0,0) at the corner +// of its decomp rectangle (devtools/maps/bdsp_maps.py), so maps that meet +// in the game meet here. +StoryWorld joined(const std::vector>& maps){ + StoryWorld world; + for (const auto& [map, corner] : maps){ + world.add(banner_name(map), story_map(map), corner); + } + return world; +} + // TODO(#114 multi-map view): MapData has no joined view of maps yet. // Twinleaf to Route 202 in one world, at the decomp's world tiles (its // cells are 32 tiles square; Route 201's map starts 7 rows into its cell // and Route 202's a column west of its own). const StoryWorld& south_world(){ + static const StoryWorld world = joined({ + {"TwinleafTown", {96, 864}}, + {"Route201", {96, 839}}, + {"VerityLakefront", {64, 832}}, + {"SandgemTown", {160, 832}}, + {"Route202", {159, 800}}, + }); + return world; +} + +// Jubilife without its east column: Route 203's map starts on that column +// (world x 191, its column 0), and two maps may not both hold open ground. +// Jubilife's tiles keep their coordinates. +Pokemon::MapData jubilife_west_of_route_203(){ + const Pokemon::MapData& city = story_map("JubilifeCity"); + Pokemon::MapData cut; + cut.name = city.name; + cut.source = city.source; + cut.tiles = Pokemon::Grid{city.tiles.width - 1, city.tiles.height, {}}; + for (int y = 0; y < cut.tiles.height; y++){ + for (int x = 0; x < cut.tiles.width; x++){ + cut.tiles.cells.push_back(city.tiles.at({x, y})); + } + } + for (const Pokemon::MapObject& object : city.objects){ + if (cut.tiles.contains({object.x, object.y})){ + cut.objects.push_back(object); + } + } + return cut; +} + +// 01.06: Route 203 west into Jubilife (decomp rectangles (191, 740) and +// (128, 736)): Route 203's rows 17..20 are Jubilife's street, rows 21..24. +const StoryWorld& route_203_world(){ static const StoryWorld world = []{ - StoryWorld world; - world.add(banner_name("TwinleafTown"), story_map("TwinleafTown"), {96, 864}); - world.add(banner_name("Route201"), story_map("Route201"), {96, 839}); - world.add(banner_name("VerityLakefront"), story_map("VerityLakefront"), {64, 832}); - world.add(banner_name("SandgemTown"), story_map("SandgemTown"), {160, 832}); - world.add(banner_name("Route202"), story_map("Route202"), {159, 800}); + StoryWorld world = joined({{"Route203", {191, 740}}}); + world.add(MAP_JUBILIFE_CITY, jubilife_west_of_route_203(), {128, 736}); return world; }(); return world; } -// The interiors of 00.01 to 00.04, each its own world. +// 01.08: Jubilife north onto Route 204's south half (160, 704), whose street +// (12..15, 31) carries on Jubilife's (44..47, 0). +const StoryWorld& route_204_south_world(){ + static const StoryWorld world = joined({{"JubilifeCity", {128, 736}}, {"Route204South", {160, 704}}}); + return world; +} + +// 01.10: Route 204's north half (160, 672) up into Floaroma (160, 640). +const StoryWorld& route_204_north_world(){ + static const StoryWorld world = joined({{"Route204North", {160, 672}}, {"FloaromaTown", {160, 640}}}); + return world; +} + +// The Ravaged Path with the rock 01.09 smashes, (22, 43), open: no route +// before the smash can use it, since it only leads east, where the rocks +// shut everything else off. +Pokemon::MapData ravaged_path_smashed(){ + Pokemon::MapData map = story_map("RavagedPath"); + Pokemon::cell_at(map.tiles, map_point("RavagedPath", "rocks.13")) = Pokemon::TileKind::OPEN; + return map; +} + +// The interiors (houses, the lab, the Gym, the Gate, the Ravaged Path), +// each its own world, named by its JSON map. const std::map& room_worlds(){ static const std::map rooms = []{ std::map ret; - for (const char* room : {"PlayerHouse2F", "PlayerHouse1F", "RivalHouse1F", "RivalHouse2F", "RowansLab"}){ + for (const char* room : {"PlayerHouse2F", "PlayerHouse1F", "RivalHouse1F", "RivalHouse2F", "RowansLab", "OreburghGym", "OreburghGate"}){ ret[room].add(room, story_map(room), {0, 0}); } + ret["RavagedPath"].add("RavagedPath", ravaged_path_smashed(), {0, 0}); return ret; }(); return rooms; } -bool on_world(const StoryWorld& world, const std::string& map){ - if (&world == &south_world()){ - static const std::set south{"TwinleafTown", "Route201", "VerityLakefront", "SandgemTown", "Route202"}; - return south.contains(map); +// A world and the JSON maps it holds. +struct PlanWorld{ + const StoryWorld* world; + std::set maps; +}; + +// In order: the first that holds both ends of a walk plans it. Jubilife +// alone comes before the worlds that join it to a route, so 00.05's walks +// plan on the city alone. +const std::vector& plan_worlds(){ + static const std::vector worlds = []{ + std::vector ret{ + {&south_world(), {"TwinleafTown", "Route201", "VerityLakefront", "SandgemTown", "Route202"}}, + {&jubilife_maps(), {"JubilifeCity"}}, + {&oreburgh_city_maps(), {"OreburghCity"}}, + {&route_203_world(), {"Route203", "JubilifeCity"}}, + {&route_204_south_world(), {"JubilifeCity", "Route204South"}}, + {&route_204_north_world(), {"Route204North", "FloaromaTown"}}, + }; + for (const auto& [name, world] : room_worlds()){ + ret.push_back({&world, {name}}); + } + return ret; + }(); + return worlds; +} + +// Route 203's trainers, from the Diamond decomp's events (zone event +// narc_0329): where each stands on the JSON map, the way they look and +// how far. The one by the Gate wanders in BDSP and is off the map. +std::vector route_203_trainer_sights(){ + const auto at = [](const char* name){ + for (const Pokemon::MapObject& object : story_map("Route203").objects){ + if (object.name == name){ + return TilePos{object.x, object.y}; + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, std::string("Route 203 has no ") + name + "."); + }; + return { + {.name = "Youngster (west)", .pos = at("youngster_1"), .facing = Direction::WEST, .range = 2}, + {.name = "Lass (above row 18)", .pos = at("lass_2"), .facing = Direction::WEST, .range = 5}, + {.name = "Youngster (above row 18)", .pos = at("youngster_3"), .facing = Direction::SOUTH, .range = 4}, + {.name = "Lass (by the Gate)", .pos = at("lass_4"), .facing = Direction::SOUTH, .range = 5}, + }; +} + +// The trainers of the JSON map `map`, in its own tiles. +std::vector map_trainers(const std::string& map){ + if (map == "Route202"){ + return route202_trainer_sights(); } - return false; + if (map == "Route203"){ + return route_203_trainer_sights(); + } + if (map == "Route204South"){ + return route204_south_trainer_sights(); + } + if (map == "Route204North"){ + return route204_north_trainer_sights(); + } + return {}; } } +const std::string& story_banner_name(const std::string& map){ + return banner_name(map); +} + const StoryWorld* story_plan_world(const std::string& from, const std::string& to){ - if (on_world(south_world(), from) && on_world(south_world(), to)){ - return &south_world(); - } - if (from == "JubilifeCity" && to == from){ - return &jubilife_maps(); - } - const auto& rooms = room_worlds(); - if (auto iter = rooms.find(from); iter != rooms.end() && to == from){ - return &iter->second; + for (const PlanWorld& world : plan_worlds()){ + if (world.maps.contains(from) && world.maps.contains(to)){ + return world.world; + } } return nullptr; } @@ -109,12 +237,17 @@ std::optional story_to_local(const StoryWorld& world, const std::string } std::vector story_trainers(const StoryWorld& world){ - if (!on_world(world, "Route202")){ - return {}; - } - std::vector sights = route202_trainer_sights(); - for (TrainerSight& sight : sights){ - sight.pos = story_to_world(world, "Route202", sight.pos); + std::vector sights; + for (const PlanWorld& plan : plan_worlds()){ + if (plan.world != &world){ + continue; + } + for (const std::string& map : plan.maps){ + for (TrainerSight sight : map_trainers(map)){ + sight.pos = story_to_world(world, map, sight.pos); + sights.push_back(std::move(sight)); + } + } } return sights; } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.h index 171c3db300..c2ff2d9ee6 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryPlaces.h @@ -3,9 +3,11 @@ * From: https://github.com/PokemonAutomation/ * * Where the story's steps walk: poses are tiles of the JSON maps - * ("Route202"), named by map_point() and map_region(). A walk from one map to another is planned on a story world that holds both: the - * outdoors from Twinleaf to Route 202, Jubilife, and each interior of - * 00.01 to 00.04 on its own. + * ("Route202"), named by map_point() and map_region(). A walk from one map + * to another is planned on a story world that holds both: the outdoors + * from Twinleaf to Route 202, Jubilife, Oreburgh, Route 203 into Jubilife, + * Jubilife up Route 204's south half, Route 204's north half into + * Floaroma, and each interior on its own. * */ @@ -28,13 +30,17 @@ namespace PokemonBDSP{ // name them ("Route 202"), which the walker reads. const StoryWorld* story_plan_world(const std::string& from, const std::string& to); +// The location banner of the JSON map `map` ("Route 204" for both of its +// halves), or `map` itself for one no banner list names. +const std::string& story_banner_name(const std::string& map); + // The tile `local` of the map `map` in `world`. Pokemon::TilePos story_to_world(const StoryWorld& world, const std::string& map, const Pokemon::TilePos& local); // The world tile `pos` as a tile of `map`, or nothing if it is not on `map`. std::optional story_to_local(const StoryWorld& world, const std::string& map, const Pokemon::TilePos& pos); -// The trainers a walk on `world` may meet (Route 202's), in its tiles. +// The trainers a walk on `world` may meet (Routes 202 to 204's), in its tiles. std::vector story_trainers(const StoryWorld& world); diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable.cpp index b9660db99e..021fb6d3c5 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable.cpp @@ -11,10 +11,13 @@ * on the console (r72 to r261, 2026-10-30); 00.05 to 00.05c, measured there * first, are now planned on Jubilife's map with the people and triggers of * the Diamond decomp's zone events and scripts. Segment 01 ran on the - * console end to end (2026-10-31), its walks kept as events. + * console end to end (2026-10-31), its walks as events; 01.05 to 01.10 + * are now planned on the maps the same way (2026-11-02), and have not + * run on a console as plans yet. * */ +#include #include #include #include @@ -54,6 +57,14 @@ const char* const ROUTE_202 = "Route202"; const char* const JUBILIFE = "JubilifeCity"; // No map of it: a pose there names the room, not its tiles. const char* const TRAINERS_SCHOOL = "TrainersSchool"; +const char* const OREBURGH = "OreburghCity"; +const char* const GYM = "OreburghGym"; +const char* const GATE = "OreburghGate"; +const char* const ROUTE_203 = "Route203"; +const char* const ROUTE_204_SOUTH = "Route204South"; +const char* const RAVAGED_PATH = "RavagedPath"; +const char* const ROUTE_204_NORTH = "Route204North"; +const char* const FLOAROMA = "FloaromaTown"; Pose at(const char* map, int x, int y, std::optional facing = std::nullopt){ return Pokemon::tile_pose(map, {TilePos{x, y}}, facing); @@ -125,6 +136,12 @@ StoryStep legs(std::string name, std::vector legs, std::string why, st StoryStep to_walls(std::string name, std::vector legs, std::string why){ return PokemonBDSP::legs(std::move(name), std::move(legs), std::move(why), "to_wall"); } +// `step`, after which the player stands on `pose`: a walk to a wall from +// a few tiles ends on one. +StoryStep anchored(StoryStep step, Pose pose){ + step.pose_after = std::move(pose); + return step; +} // TODO(#114 coord events): the map schema has no scene triggers yet, so // each row steps onto the decomp's trigger tile, named in its reason. StoryStep step_into(std::string name, Direction direction, std::string why, std::optional pose_after = std::nullopt){ @@ -133,6 +150,16 @@ StoryStep step_into(std::string name, Direction direction, std::string why, std: step.pose_after = std::move(pose_after); return step; } +// Through a door or a cave mouth `direction` a D-pad tap at a time, each +// looked at, ending on the far map's location banner (the executor's +// "to_banner"): a hold can carry on past a fade it missed (r75, r219g). +// A tap after an unseen fade may step once more on the far side, so +// `pose_after` allows for it. +StoryStep tap_into(std::string name, Direction direction, std::string why, Pose pose_after){ + StoryStep step = step_into(std::move(name), direction, std::move(why), std::move(pose_after)); + step.variant = "to_banner"; + return step; +} StoryStep talk(std::string name, std::string why){ return make(StoryStepKind::TALK, std::move(name), std::move(why)); } @@ -486,6 +513,110 @@ std::vector jubilife(){ return {the_town_map(), the_coupons(), the_poketch()}; } +// `tile` of `map`, or the tile past it `direction`: where a tap_into() may +// leave us. +Pose arrival(const char* map, TilePos tile, Direction direction){ + return Pokemon::tile_pose(map, {tile, tile.next(direction)}, direction); +} +// Where the Oreburgh Gate lets us out onto Route 203: its warp tile (55, 9) +// (Diamond's warp (246, 749), zone event narc_0329), walking west. The one +// tile only: from it and (54, 9) together the joined Route 203 and Jubilife +// find no plan. The tap stops at the fade, which puts us on the warp tile; +// r78 passed 01.06 from a save there (by the sign, the cave mouth east). +Pose out_of_oreburgh_gate(){ + return at(ROUTE_203, 55, 9, WEST); +} +// Where Route 204's door (11, 1) lets us into the Ravaged Path: (19, 50), +// as in r237a. The tap stops at the fade, on that tile. +Pose into_the_ravaged_path(){ + return at(RAVAGED_PATH, "entrance", NORTH); +} + +// 01.05, planned on Oreburgh's and the Gate's maps with the Diamond decomp's +// zone events (narc_0044, narc_0250) and Platinum's script for the scene +// (scripts_oreburgh_city.s, the same trigger and variable). Barry's coord +// event, column 6 on rows 12 to 15 while VAR_OREBURGH_CITY_STATE is 2 (after +// Roark: the old walk met his "*thud*" there, 2026-10-29), runs him into us +// from the east: the game pushes us a tile west, facing east, and he +// leaves. The Gate's way west is row 22, across the Hiker's trigger (7, 22), +// spent in 01.02. +StoryCheckpoint oreburgh_to_route_203(){ + return row("01.05", "Oreburgh to Route 203", next_to(GYM, "roark", SOUTH, NORTH), { + // 01.04 talks to Roark from below him, up the clear middle column. + tap_into("out_of_the_gym", SOUTH, "down the middle column (5, 4..23) and out of the door (5, 24)", + arrival(OREBURGH, map_point(OREBURGH, "gym_doorstep"), SOUTH)), + route("east_of_barrys_trigger", at(OREBURGH, 7, 13, WEST), + "west along the street, north up the stairs west of the houses, west along the Gate's row", "town"), + step_into("barrys_trigger", WEST, "the trigger (6, 12..15)"), + scene("barry_bumps_into_us", at(OREBURGH, 5, 13, EAST), "Barry runs into us, talks of Eterna, and leaves (assumed: the push)"), + route("by_the_gate", next_to(OREBURGH, "gate_mouth", EAST, WEST), "west to the Gate's warp tile (2, 13)"), + // The warp's other end is the Gate's (27, 22), beside its east mouth. + tap_into("into_oreburgh_gate", WEST, "the Gate's mouth (1, 13)", arrival(GATE, {27, 22}, WEST)), + route("through_the_gate", at(GATE, 4, 22, WEST), "west along row 22 to the warp tile by the west mouth (3, 22)"), + // Route 203's banner or a failure here: the old walk west "until the + // banner" went on into the pond's pocket (r75). 01.06 heads south. + tap_into("out_of_oreburgh_gate", WEST, "the west mouth (3, 22)", out_of_oreburgh_gate()), + }); +} + +// Below Rowan's trigger in Jubilife: (47, 8), the east tile under the +// trigger (45..47, 7), which (48, 7) beside it is not. +Pose below_rowans_trigger(){ + return at(JUBILIFE, 47, 8, NORTH); +} +// Where Rowan's scene leaves us: walked up to him at (47, 5), then a tile +// west in front of the grunt (Platinum's movements; assumed for BDSP). +Pose after_the_grunts(){ + return Pokemon::tile_pose(JUBILIFE, {TilePos{46, 5}, TilePos{47, 5}}); +} + +// 01.07, from the Diamond decomp (zone event narc_0002) and Platinum's +// script (scripts_jubilife_city.s, the same trigger and variable): the +// coord event (45..47, 7) while VAR_JUBILIFE_CITY_STATE is 3 walks us up to +// Rowan, his and the grunts' lines, then the tag battle with Dawn; after +// it they leave. +StoryCheckpoint rowan_and_the_grunts(){ + return row("01.07", "Jubilife: Rowan, Dawn and the Galactic grunts", below_rowans_trigger(), { + step_into("rowans_trigger", NORTH, "the trigger (45..47, 7): the game walks us up to Rowan"), + battle("galactic_grunts", false, "the lines, then the tag battle with Dawn", "doubles"), + scene("rowan_and_dawn_leave", after_the_grunts(), + "Rowan's and Dawn's lines, then Bebe walks over with hers (r80 saved over her lines)"), + }); +} + +// 01.09 on the Ravaged Path's map (Platinum's, checked on the console in +// r235b): up the stairs to west of the rock (22, 43), smash it, east to +// above the way out (28, 45). Its other end is Route 204's (20, 26). +StoryCheckpoint through_the_ravaged_path(){ + const Pose west_of_the_rock = at(RAVAGED_PATH, "before_rocks.2", EAST); + return row("01.09", "Ravaged Path", into_the_ravaged_path(), { + route("west_of_the_rock", west_of_the_rock, "up the stairs and along the corridor"), + event("smash_the_rock", "Rock Smash on the rock east of us", west_of_the_rock), + route("above_the_way_out", at(RAVAGED_PATH, "above_exit", SOUTH), "east through the rock's tile"), + // One tap and Route 204's banner: a timed walk south went on past + // the cave's mouth to the cliff (r81). + tap_into("out_of_the_ravaged_path", SOUTH, "the way out (28, 45)", at(ROUTE_204_NORTH, "cave_exit", SOUTH)), + }); +} + +// 01.10: the line a human walked up Route 204's north half (2026-10-31, +// r246a to r251a), corner to corner (Test_Route204NorthLine checks each +// stretch is one straight leg), then north between the flower beds into +// Floaroma. Its trainers who see us are fought: Taylor on the line, the +// Twins as a pair. +StoryCheckpoint route_204_to_floaroma(){ + std::vector steps; + const std::vector line = map_region(ROUTE_204_NORTH, "floaroma_waypoints"); + for (size_t i = 0; i < line.size(); i++){ + steps.push_back(route(std::format("line_{}", i + 1), Pokemon::tile_pose(ROUTE_204_NORTH, {line[i]}), + "a corner of the human's line", "line")); + } + // 01.11 walks on from here: west 8 along row 29, north to the old woman. + steps.push_back(route("into_floaroma", at(FLOAROMA, "route_204_arrival", NORTH), "north up column 11 into Floaroma", "line")); + steps.push_back(save()); + return row("01.10", "Route 204 to Floaroma", at(ROUTE_204_NORTH, "cave_exit", SOUTH), std::move(steps)); +} + std::vector to_oreburgh(){ return { row("01.01", "Jubilife to Barry", std::nullopt, { @@ -511,38 +642,35 @@ std::vector to_oreburgh(){ event("out_of_the_center", "out from the counter"), event("gym_until_roark_beaten", "west into the gym, up it to Roark; again from the Center after a loss"), }), - row("01.05", "Oreburgh to Route 203", std::nullopt, { - event("out_of_the_gym", "out of the gym onto its doorstep"), - event("into_oreburgh_gate", "up to the platform and west into the Gate"), - event("out_of_oreburgh_gate", "west until Route 203's banner shows"), - }), + oreburgh_to_route_203(), }; } std::vector to_floaroma(){ return { - row("01.06", "Route 203 to Jubilife", std::nullopt, { - event("route_203_to_row_18", "on Route 203's map to row 18, below the girl"), - event("row_18_into_jubilife", "west along row 18, counting tiles"), - }), - row("01.07", "Jubilife through Rowan's scene", std::nullopt, { - event("west_to_the_crossroads", "west along the long road"), - event("north_to_rowan", "north up the street until Rowan's scene"), - battle("galactic_grunts", false, "the tag battle with Dawn", "doubles"), - scene("rowan_after_the_battle", std::nullopt, "Rowan's and Dawn's lines, then Bebe walks over with hers (r80 saved over her lines)"), - }), - row("01.08", "Jubilife to the Ravaged Path", std::nullopt, { - event("north_out_of_jubilife", "north until Route 204's banner"), - event("route_204_south_to_the_door", "on Route 204 South's map to below the Ravaged Path's door"), - event("into_the_ravaged_path", "north through the door"), + row("01.06", "Route 203 to Jubilife", out_of_oreburgh_gate(), { + // Trainers who saw us going east in 01.02 are beaten; any other + // sees us here and is fought (route_203_trainer_sights()). + route("below_rowans_trigger", below_rowans_trigger(), + "down Route 203 (its ledges only south), west along it into Jubilife's street, north up the road", "town"), }), - row("01.09", "Ravaged Path", std::nullopt, { - event("through_the_ravaged_path", "through the cave with Rock Smash, out onto Route 204 North"), - }), - row("01.10", "Route 204 to Floaroma", std::nullopt, { - event("route_204_to_floaroma", "the human's line north into Floaroma"), - save(), + rowan_and_the_grunts(), + row("01.08", "Jubilife to the Ravaged Path", after_the_grunts(), { + // One known tile to plan from: from both of the scene's ends + // the joined Jubilife and Route 204 find no plan. Row 5 is open + // from x 44 to 49, a wall at 43. + anchored(to_walls("west_to_the_wall", {{.direction = WEST, .tiles = 4}}, "west along row 5 to the wall"), + at(JUBILIFE, 44, 5, WEST)), + // Lass Sarah's east sight crosses the street (r233a, r234a): she + // may stop us for a battle. + route("below_the_ravaged_path", at(ROUTE_204_SOUTH, "below_door", NORTH), + "north out of Jubilife, up Route 204's street and grass to below the door (11, 1)", "town"), + // Its warp (Diamond's zone event narc_0330) lands us on the + // cave's (19, 50), as in r237a. + tap_into("into_the_ravaged_path", NORTH, "the door (11, 1)", into_the_ravaged_path()), }), + through_the_ravaged_path(), + route_204_to_floaroma(), row("01.11", "Floaroma's old couple", std::nullopt, { event("west_8_north_to_the_old_woman", "west 8, north until she stops us"), talk("old_woman", "she gives Mew when a Let's Go save is on the console"), diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable_Tests.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable_Tests.cpp index 9b5e4748df..1debf3d6bc 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable_Tests.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_StoryTable_Tests.cpp @@ -37,8 +37,9 @@ using Pokemon::TilePos; // The variants each kind does itself, besides the story's events. const std::map>& built_in_variants(){ static const std::map> variants{ - {StoryStepKind::ROUTE, {"", "town", "robust"}}, + {StoryStepKind::ROUTE, {"", "town", "robust", "line"}}, {StoryStepKind::LEGS, {"", "one_short_ok", "to_wall", "to_dialogue", "to_map"}}, + {StoryStepKind::STEP, {"", "to_banner"}}, {StoryStepKind::MASH, {"", "a"}}, {StoryStepKind::BATTLE, {"", "doubles"}}, }; diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.cpp b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.cpp index f3423bede7..4495803e9d 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.cpp +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.cpp @@ -12,7 +12,6 @@ #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/Async/InferenceRoutines.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" -#include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_Location.h" #include "PokemonBDSP/Programs/AutoStory/Utils/PokemonBDSP_AutoStory_RouteWalk.h" #include "PokemonBDSP_AutoStory_Talks.h" #include "PokemonBDSP_AutoStory_WildBattle.h" @@ -196,31 +195,6 @@ bool dpad_through_door_north(VideoStream& stream, ProControllerContext& context, return false; } -// Out of a building whose door is straight south of us, a D-pad tile at a -// time, until the location banner shows: it names the map we step into -// (e.g. "Oreburgh City") for a few seconds, and never shows inside. Ends in -// the doorway, facing south. Out of the Oreburgh Gym from in front of Roark -// (by hand, 2026-10-31): 18 taps inside, 2 taps of black fade, the banner on -// the 21st. The door's fade alone was missed between steps (r219g walked on -// south to the rocks below the gym). -bool dpad_out_of_building(VideoStream& stream, ProControllerContext& context, size_t max_taps, const std::string& label){ - pbf_press_dpad(context, DPAD_DOWN, 160ms, 700ms); // turn - for (size_t tap = 0; tap < max_taps; tap++){ - pbf_press_dpad(context, DPAD_DOWN, 160ms, 900ms); - context.wait_for_all_requests(); - const VideoSnapshot frame = stream.video().snapshot(); - if (frame && location_banner_showing(*frame.frame)){ - stream.log("[Walk] " + label + ": the location banner shows after " + std::to_string(tap + 1) + " taps; outside."); - context.wait_for(1000ms); - return true; - } - if (dialogue_opens(stream, context, 100ms)){ - finish_dialogue(stream, context, BUTTON_B, label + ": someone in the way"); - } - } - return false; -} - // For a player inside a Pokemon Center after a whiteout, at the counter: // out through the door. The door is straight south of the nurse in most // Centers; in Oreburgh's it was two tiles east of where we stood (by hand). @@ -269,54 +243,6 @@ void walk_out_of_center(VideoStream& stream, ProControllerContext& context, cons fail_step(stream, label + ": could not find the Pokemon Center's door."); } -// West until a transition, by the user's rule for a wall in the way: walk -// north to the top wall, which pins the row, then try the rows south of it -// one at a time. Trainers who stop us are fought. Returns at the first -// transition (a building, a cave, a gate), or when `arrived` matches. -void west_until_transition( - VideoStream& stream, ProControllerContext& context, const std::string& label, size_t max_legs, - const std::function& arrived -){ - for (size_t leg = 0; leg < max_legs; leg++){ - const WalkResult west = walk_dir(stream, context, Direction::WEST, 20000ms, label, arrived); - if (west.stop == WalkStop::ARRIVED){ - return; - } - if (west.stop == WalkStop::TRANSITION){ - context.wait_for(2000ms); - return; - } - if (west.stop == WalkStop::DIALOG){ - talk_or_fight(stream, context, label + ": someone in the way"); - continue; - } - if (west.stop != WalkStop::BLOCKED){ - continue; - } - walk_dir(stream, context, Direction::NORTH, 4000ms, label + ": north to the top wall"); - bool moved = false; - for (size_t row = 0; row < 10 && !moved; row++){ - hold_tiles(stream, context, Direction::SOUTH, 1); - const WalkResult probe = walk_dir(stream, context, Direction::WEST, 1500ms, label + ": west on the next row", arrived); - if (probe.stop == WalkStop::ARRIVED){ - return; - } - moved = probe.stop != WalkStop::BLOCKED || probe.walked > 1200ms; - if (probe.stop == WalkStop::TRANSITION){ - context.wait_for(2000ms); - return; - } - if (probe.stop == WalkStop::DIALOG){ - talk_or_fight(stream, context, label + ": someone in the way"); - } - } - if (!moved){ - fail_step(stream, label + ": no row below the top wall leads west."); - } - } - fail_step(stream, label + ": no transition after " + std::to_string(max_legs) + " legs."); -} - } diff --git a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.h b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.h index 07b9174beb..5672d71d00 100644 --- a/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.h +++ b/SerialPrograms/Source/PokemonBDSP/Programs/AutoStory/PokemonBDSP_AutoStory_Walks.h @@ -67,26 +67,10 @@ void dpad_tiles(VideoStream& stream, ProControllerContext& context, DpadPosition // Through a door straight north of us: tap up to face it, hold up into it, // and wait for its dark fade (by hand: brightness 7, about 1 s after). bool dpad_through_door_north(VideoStream& stream, ProControllerContext& context, const std::string& label); -// Out of a building whose door is straight south of us, a D-pad tile at a -// time, until the location banner shows: it names the map we step into -// (e.g. "Oreburgh City") for a few seconds, and never shows inside. Ends in -// the doorway, facing south. Out of the Oreburgh Gym from in front of Roark -// (by hand, 2026-10-31): 18 taps inside, 2 taps of black fade, the banner on -// the 21st. The door's fade alone was missed between steps (r219g walked on -// south to the rocks below the gym). -bool dpad_out_of_building(VideoStream& stream, ProControllerContext& context, size_t max_taps, const std::string& label); // For a player inside a Pokemon Center after a whiteout, at the counter: // out through the door. The door is straight south of the nurse in most // Centers; in Oreburgh's it was two tiles east of where we stood (by hand). void walk_out_of_center(VideoStream& stream, ProControllerContext& context, const std::string& label); -// West until a transition, by the user's rule for a wall in the way: walk -// north to the top wall, which pins the row, then try the rows south of it -// one at a time. Trainers who stop us are fought. Returns at the first -// transition (a building, a cave, a gate), or when `arrived` matches. -void west_until_transition( - VideoStream& stream, ProControllerContext& context, const std::string& label, size_t max_legs, - const std::function& arrived = nullptr -);