Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
673168a
FRLG BagDetector: leave out the scroll arrow from the top frame check
dayfine Oct 30, 2026
28795ac
FRLG: overworld maps from pokefirered
dayfine Oct 30, 2026
678e6bc
FRLG: plan overworld routes with the shared leg planner
dayfine Oct 30, 2026
3d0e1c3
FRLG: read which way the standing player faces
dayfine Oct 30, 2026
20606cd
FRLG: walk overworld routes with the shared leg walker
dayfine Oct 30, 2026
8c9f2e4
Merge nav/core into frlg/brock-mvp
dayfine Oct 30, 2026
e5d5ba3
FRLG: hold the D-pad for legs with a plain function
dayfine Oct 30, 2026
a9af8d0
FRLG: make OverworldMap plain data with free functions
dayfine Oct 30, 2026
57bd09b
FRLG: give each overworld map its own constant
dayfine Oct 30, 2026
db11f86
FRLG: build route steps from legs in a helper
dayfine Oct 30, 2026
76310a3
Merge nav/core into frlg/brock-mvp
dayfine Oct 30, 2026
5bc9ddb
Merge nav/core into frlg/brock-mvp
dayfine Oct 30, 2026
6e3df17
devtools: generate FRLG overworld maps as JSON
dayfine Oct 30, 2026
2a218c9
FRLG: load overworld maps from JSON resource files
dayfine Oct 30, 2026
c8eab23
FRLG: give map tiles a Tile type and a Grid
dayfine Oct 30, 2026
3731be9
FRLG: name the ground elevation PlayerState starts on
dayfine Oct 30, 2026
e90d70b
FRLG: keep named map points and flag sets in the map files
dayfine Oct 30, 2026
2835948
FRLG: route costs as a map keyed by RouteCost
dayfine Oct 30, 2026
362699b
FRLG: load the player's standing sprites from the sprite sheet
dayfine Oct 30, 2026
850c298
FRLG: plan routes on the maps from a new game to Brock
dayfine Oct 30, 2026
7ab199e
FRLG: name the flags key, for GCC's dangling-reference check
dayfine Oct 30, 2026
00e5dd7
FRLG: skip the player facing screenshots when their files are missing
dayfine Oct 30, 2026
2ed754e
Merge origin/nav/core into frlg/brock-mvp
dayfine Oct 30, 2026
91e78ba
Merge origin/nav/core into frlg/brock-mvp
dayfine Oct 31, 2026
d417b9a
Merge nav/core into frlg/brock-mvp
dayfine Oct 31, 2026
ed17b69
FRLG: make the leg mover a small abstract class
dayfine Nov 1, 2026
fa3c841
Merge origin/nav/core into frlg/brock-mvp
dayfine Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@



UnitTestDatabase make_UNIT_TESTS_ALL(){

Check warning on line 50 in SerialPrograms/Source/ComputerPrograms/ComputerPrograms_UnitTestRunner.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::ComputerPrograms::make_UNIT_TESTS_ALL(): function_lines=30 (advisory above 25, limit 50)
UnitTestDatabase ret;

add_tests_QtEventThreadPool(ret, GlobalThreadPools::computation_normal());
Expand All @@ -62,6 +62,7 @@
OCR::add_tests(ret);
Kernels::add_tests(ret);
Pokemon::add_tests_LegPlanner(ret);
Pokemon::add_tests_LegWalker(ret);
NintendoSwitch::add_tests_CheckOnlineDetector(ret);
NintendoSwitch::add_tests_FailedToConnectDetector(ret);
NintendoSwitch::add_tests_UpdatePopupDetector(ret);
Expand Down
70 changes: 70 additions & 0 deletions SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* Leg Walker
*
* From: https://github.com/PokemonAutomation/
*
*/

#include "Pokemon_LegWalker.h"

namespace PokemonAutomation{
namespace Pokemon{


std::string to_string(const LegSpec& leg){
return std::string(direction_name(leg.direction)) + " " + std::to_string(leg.tiles)
+ (leg.to_wall ? " (to wall)" : "");
}


Milliseconds leg_hold(const LegSpec& leg, int tiles){
if (leg.to_wall){
return leg.tile_time * (tiles + 2);
}
return leg.tile_time * (tiles - 1) + leg.tile_time / 2;
}

int tiles_before_interrupt(const LegSpec& leg, Milliseconds elapsed, Milliseconds detection_delay, int remaining){
const Milliseconds since_first_step = elapsed - detection_delay + leg.tile_time / 2;
if (since_first_step.count() <= 0){
return 0;
}
return std::clamp((int)(since_first_step / leg.tile_time), 0, remaining);
}


LegResult walk_leg_with(const LegSpec& leg, const LegCalls& calls, size_t max_interruptions){

Check warning on line 35 in SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.cpp

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::Pokemon::walk_leg_with( const LegSpec & leg , const LegCalls & calls , size_t max_interruptions): function_lines=31 (advisory above 25, limit 50)
LegResult result;
int remaining = leg.tiles;
while (true){
const std::optional<HoldOutcome> outcome = calls.hold_watching(leg_hold(leg, remaining));
if (!outcome){
result.tiles = leg.tiles;
return result;
}
result.interruptions++;
if (result.interruptions > max_interruptions){
calls.fail("Interrupted " + std::to_string(result.interruptions) + " times on: " + to_string(leg));
}
calls.clear(outcome->interrupt);
const std::string seen = calls.name(outcome->interrupt) + " after " + std::to_string(outcome->elapsed.count()) + "ms";
if (leg.to_wall){
// The wall stops the player on the leg's last tile however far
// they got, so the leg is simply held again.
calls.log(seen + ", walking to the wall again.");
continue;
}
const int walked = tiles_before_interrupt(leg, outcome->elapsed, calls.detection_delay(outcome->interrupt), remaining);
remaining -= walked;
result.exact = false;
calls.log(seen + ", about " + std::to_string(walked) + " tiles walked, " + std::to_string(remaining) + " to go.");
if (remaining <= 0){
result.tiles = leg.tiles;
return result;
}
}
}



}
}
212 changes: 212 additions & 0 deletions SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/* Leg Walker
*
* From: https://github.com/PokemonAutomation/
*
* Walks one leg of a plan: holds a direction for the leg's tiles while
* watching for whatever can stop the player part-way (a wild battle, a
* message, a trainer). Each of those is a LegInterrupt the game supplies.
* When one shows, the hold stops, the game clears it, and the rest of the
* leg is walked: a leg to a wall is simply held again, since the wall stops
* the player on its tile; for an open leg, how far the player got is worked
* out from when the interrupt was seen.
*
* How a direction is held (D-pad or stick) and how long a tile takes are
* the game's too, through LegMover. Templated on the console and the
* controller context, like run_until(), so this stays free of any one
* console's types.
*
*/

#ifndef PokemonAutomation_Pokemon_LegWalker_H
#define PokemonAutomation_Pokemon_LegWalker_H

#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "Common/Cpp/Time.h"
#include "CommonFramework/Exceptions/OperationFailedExceptionWithScreenshot.h"
#include "CommonTools/Async/InferenceRoutines.h"
#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h"
#include "Pokemon_Grid.h"

namespace PokemonAutomation{
namespace Pokemon{


// Something that stops the player part-way through a leg.
template <typename Console, typename Context>
class LegInterrupt{
public:
virtual ~LegInterrupt() = default;

virtual std::string name() const = 0;

// A new watcher for the screen the interruption brings up.
virtual std::unique_ptr<VisualInferenceCallback> make_watcher() const = 0;

// How long after the step that set it off the watcher fires. The games
// only interrupt between steps, so from this the walker works out how
// many tiles of an open leg were walked.
virtual Milliseconds detection_delay() const = 0;

// Clears it, leaving the player standing on the overworld, on the same
// tile and facing the same way.
virtual void clear(Console& console, Context& context) const = 0;
};

template <typename Console, typename Context>
using LegInterrupts = std::vector<std::unique_ptr<LegInterrupt<Console, Context>>>;


// How the game moves the player.
template <typename Context>
class LegMover{

Check failure on line 66 in SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.h

View workflow job for this annotation

GitHub Actions / code-health

Code health

PokemonAutomation::Pokemon::LegMover: single_method_interface=1, limit=0, allowed 0; prefer a callable (MoveOnlyFunction / std::function) over a single-method interface
public:
virtual ~LegMover() = default;
// Holds `direction` for `duration`, then lets go.
virtual void hold(Context& context, Direction direction, Milliseconds duration) const = 0;
};


// A straight leg, for a player standing still and facing its way.
struct LegSpec{
Direction direction = Direction::NORTH;
int tiles = 0;
// A wall stops the player after the last tile.
bool to_wall = false;
// How long one tile takes, for this leg's direction and way of moving.
Milliseconds tile_time{0};
};
std::string to_string(const LegSpec& leg);

struct LegResult{
// Tiles walked, which is `tiles` unless the leg gave up.
int tiles = 0;
// Whether `tiles` is certain: an interrupted open leg is an estimate.
bool exact = true;
size_t interruptions = 0;
};


// How long to hold for `tiles` tiles: an open leg lets go half a tile before
// the last step would end, so the step under way finishes and no other
// starts; a leg to a wall is held two tiles longer than it takes.
Milliseconds leg_hold(const LegSpec& leg, int tiles);

// How many tiles an open leg had walked when an interrupt was seen
// `elapsed` into its hold, given the interrupt's detection delay. Steps end
// a whole number of tiles into the hold, so this rounds to the nearest.
int tiles_before_interrupt(const LegSpec& leg, Milliseconds elapsed, Milliseconds detection_delay, int remaining);


// What one hold reported: nothing if it ran out, or which interrupt showed
// and how long into the hold.
struct HoldOutcome{
size_t interrupt = 0;
Milliseconds elapsed{0};
};

// The leg's bookkeeping, with the console side passed in, so it can be
// tested with scripted outcomes. `hold_watching` holds for the duration and
// reports the first interrupt seen; `clear` clears interrupt i;
// `detection_delay` is interrupt i's delay; `log` reports progress; `fail`
// throws, and is called after more than `max_interruptions`.
struct LegCalls{
std::function<std::optional<HoldOutcome>(Milliseconds hold)> hold_watching;
std::function<void(size_t interrupt)> clear;
std::function<Milliseconds(size_t interrupt)> detection_delay;
std::function<std::string(size_t interrupt)> name;
std::function<void(const std::string& message)> log;
std::function<void(const std::string& message)> fail;
};
LegResult walk_leg_with(const LegSpec& leg, const LegCalls& calls, size_t max_interruptions);


// Walks `leg`. Throws OperationFailedException if it is interrupted more
// than `max_interruptions` times.
template <typename Console, typename Context>
LegResult walk_leg(

Check warning on line 131 in SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.h

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::Pokemon::walk_leg( Console & console , Context & context , const LegMover<Context> & mover , const LegInterrupts<Console,Context> & interrupts , const LegSpec & leg , size_t max_interruptions = 5): function_lines=40 (advisory above 25, limit 50)
Console& console, Context& context,
const LegMover<Context>& mover,
const LegInterrupts<Console, Context>& interrupts,
const LegSpec& leg,
size_t max_interruptions = 5
){
auto hold_watching = [&](Milliseconds hold) -> std::optional<HoldOutcome>{
std::vector<std::unique_ptr<VisualInferenceCallback>> watchers;
std::vector<PeriodicInferenceCallback> callbacks;
for (const auto& interrupt : interrupts){
watchers.emplace_back(interrupt->make_watcher());
callbacks.emplace_back(*watchers.back());
}
context.wait_for_all_requests();
WallClock start = current_time();
const int ret = run_until<Context>(
console, context,
[&](Context& context){
start = current_time();
mover.hold(context, leg.direction, hold);
},
callbacks
);
if (ret < 0){
return std::nullopt;
}
return HoldOutcome{(size_t)ret, std::chrono::duration_cast<Milliseconds>(current_time() - start)};
};
LegCalls calls;
calls.hold_watching = hold_watching;
calls.clear = [&](size_t index){ interrupts[index]->clear(console, context); };
calls.detection_delay = [&](size_t index){ return interrupts[index]->detection_delay(); };
calls.name = [&](size_t index){ return interrupts[index]->name(); };
calls.log = [&](const std::string& message){ console.log("Leg: " + message, COLOR_ORANGE); };
calls.fail = [&](const std::string& message){
OperationFailedExceptionWithScreenshot::fire(ErrorReportMode::SEND_ERROR_REPORT, message, console);
};
return walk_leg_with(leg, calls, max_interruptions);
}


// After a leg: waits `watch` for an interrupt the last step set off, which
// shows after the hold has ended, and clears it. Throws
// OperationFailedException after `max_interruptions` in a row.
template <typename Console, typename Context>
void clear_late_interrupts(

Check warning on line 177 in SerialPrograms/Source/Pokemon/Navigation/Pokemon_LegWalker.h

View workflow job for this annotation

GitHub Actions / code-health

Code health (advisory)

PokemonAutomation::Pokemon::clear_late_interrupts( Console & console , Context & context , const LegInterrupts<Console,Context> & interrupts , Milliseconds watch , size_t max_interruptions = 5): function_lines=29 (advisory above 25, limit 50)
Console& console, Context& context,
const LegInterrupts<Console, Context>& interrupts,
Milliseconds watch,
size_t max_interruptions = 5
){
for (size_t count = 0; ; count++){
std::vector<std::unique_ptr<VisualInferenceCallback>> watchers;
std::vector<PeriodicInferenceCallback> callbacks;
for (const auto& interrupt : interrupts){
watchers.emplace_back(interrupt->make_watcher());
callbacks.emplace_back(*watchers.back());
}
context.wait_for_all_requests();
const int ret = wait_until(console, context, watch, callbacks);
if (ret < 0){
return;
}
if (count >= max_interruptions){
OperationFailedExceptionWithScreenshot::fire(
ErrorReportMode::SEND_ERROR_REPORT,
"Interrupted " + std::to_string(count) + " times in a row after a leg.",
console
);
}
console.log("Leg: " + interrupts[ret]->name() + " at the end of the leg.", COLOR_ORANGE);
interrupts[ret]->clear(console, context);
}
}




}
}
#endif
Loading
Loading