Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -70,6 +70,11 @@ class ItemNameMatcher : public OCR::SmallDictionaryMatcher{
{"parlyz-heal", "Parlyz Heal"},
{"awakening", "Awakening"},
{"poke-ball", "Poke Ball"},
{"repel", "Repel"},
{"super-repel", "Super Repel"},
{"max-repel", "Max Repel"},
{"escape-rope", "Escape Rope"},
{"bicycle", "Bicycle"},
{"cancel", "Cancel"},
}){
JsonArray names;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ ImageFloatBox item_list_name_box(int row);
ImageFloatBox bag_item_count_box(int row);

// The item named in row `row`: a slug such as "potion", "antidote",
// "parlyz-heal", "awakening", "poke-ball", or "cancel" for the list's last row, in a
// "parlyz-heal", "awakening", "poke-ball", "repel",
// "super-repel", "max-repel", "escape-rope", "bicycle", or "cancel" for the list's last row, in a
// game in `language`; empty if it can't be read, as in a language the
// reader has no names for.
std::string read_item_list_name(Logger& logger, Language language, const ImageViewRGB32& screen, int row);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/* Coded Rooms and Tiles
*
* From: https://github.com/PokemonAutomation/
*
*/

#include <algorithm>
#include <map>
#include "PokemonFRLG_CodedRooms.h"
#include "PokemonFRLG_OverworldMaps.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


namespace{

// A floor's spinner tiles, by the way each slides the player.
struct SpinnerFloor{
std::string_view map;
std::vector<TilePos> north;
std::vector<TilePos> south;
std::vector<TilePos> west;
std::vector<TilePos> east;
std::vector<TilePos> stop;
};

// Read off pokefirered: each floor's layout (data/layouts/RocketHideout_
// B2F, _B3F map.bin, primary tileset gTileset_Building, secondary
// gTileset_SilphCo) with each metatile's behaviour from the tilesets'
// metatile_attributes.bin: MB_SPIN_UP (0x56), MB_SPIN_DOWN (0x57),
// MB_SPIN_LEFT (0x55), MB_SPIN_RIGHT (0x54), MB_STOP_SPINNING (0x58).
const std::vector<SpinnerFloor>& spinner_floors(){
static const std::vector<SpinnerFloor> FLOORS{
{
"RocketHideout_B2F",
{{9, 5}, {11, 6}, {7, 7}, {11, 8}, {15, 9}, {7, 10}, {9, 10}, {3, 11}, {15, 11}, {13, 12}, {15, 13}, {9, 14}, {5, 17}, {7, 18}, {5, 19}},
{{12, 5}, {8, 9}, {10, 9}, {14, 11}, {10, 13}, {8, 17}},
{{3, 4}, {7, 4}, {9, 4}, {11, 4}, {16, 5}, {16, 6}, {12, 13}, {3, 14}, {7, 14}, {12, 14}, {12, 17}, {12, 18}},
{{3, 6}, {12, 7}, {4, 9}, {3, 10}, {10, 11}, {12, 11}, {9, 12}, {11, 12}, {9, 20}},
{{1, 4}, {7, 6}, {13, 7}, {15, 8}, {13, 10}, {8, 11}, {14, 13}, {1, 14}, {5, 15}, {10, 15}, {8, 19}, {13, 20}},
},
{
"RocketHideout_B3F",
{{8, 11}, {10, 14}, {6, 15}, {6, 17}, {4, 18}},
{{9, 10}, {7, 16}},
{{4, 9}},
{{6, 9}, {2, 11}, {6, 13}, {5, 14}, {7, 14}, {4, 15}, {3, 16}, {2, 17}},
{{2, 9}, {8, 9}, {6, 11}, {10, 13}, {9, 14}, {7, 20}},
},
};
return FLOORS;
}

const SpinnerFloor* spinner_floor(std::string_view map){
for (const SpinnerFloor& floor : spinner_floors()){
if (floor.map == map){
return &floor;
}
}
return nullptr;
}

bool contains(const std::vector<TilePos>& tiles, const TilePos& point){
return std::any_of(tiles.begin(), tiles.end(), [&](const TilePos& tile){
return tile.x == point.x && tile.y == point.y;
});
}

bool has_object(const MapData& map, std::string_view name){
return std::any_of(map.objects.begin(), map.objects.end(), [&](const MapObject& object){
return object.name == name;
});
}

MapObject object(std::string name, Pokemon::ObjectKind kind, const TilePos& tile){
MapObject out;
out.name = std::move(name);
out.kind = kind;
out.x = tile.x;
out.y = tile.y;
return out;
}

// LAYOUT_POKEMON_CENTER_1F's collision and elevation, as the map lock's
// seven Centers (PokemonSimulation 6c2f5c8, data/games/frlg/maps/runtime/
// *_PokemonCenter_1F.json) all have them. The mat's warps: (6..8, 8),
// the middle one MB_SOUTH_ARROW_WARP-like ("walk_south"); the stairs to
// 2F at (1, 6), which isn't loaded.
const char CENTER_ROOM_JSON[] = R"({
"name": "PokemonCenter_1F",
"source": {"decomp_file": "data/layouts/PokemonCenter_1F/map.bin", "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", "date": "2026-11-09"},
"allow_running": false,
"tiles": ["#.#############", "###############", "....###..##....", "....#######....", "...............", "...............", "#..........##..", "...........##..", "...............", "###############"],
"elevation": ["030000000000000", "000000000000000", "333300033003333", "333300000003333", "333333333333333", "330333333333333", "044033333330033", "330333333330033", "333333333333333", "000000000000000"],
"points": {"warp_0": [6, 8], "warp_1": [7, 8], "warp_2": [8, 8], "nurse_front": [7, 4]},
"warps": [
{"x": 6, "y": 8, "enter": "walk_into"},
{"x": 7, "y": 8, "enter": "walk_south"},
{"x": 8, "y": 8, "enter": "walk_into"},
{"x": 1, "y": 6, "enter": "step_on"}
],
"objects": [{"name": "nurse", "kind": "npc", "x": 7, "y": 2}]
})";

// LAYOUT_ROCKET_HIDEOUT_ELEVATOR's collision (map.bin: rows 0 to 2 and 6
// walls, 3 to 5 floor), the panel's bg event at (0, 2), and warp 1 at
// (2, 5), the one MB_SOUTH_ARROW_WARP tile (warp 0 at (1, 5) has no
// behaviour, so never fires).
const char LIFT_ROOM_JSON[] = R"({
"name": "RocketHideout_Elevator",
"source": {"decomp_file": "data/layouts/RocketHideout_Elevator/map.bin", "revision": "ce6406d8c2eaf46d543c8a2606de0c3cb1951982", "date": "2026-11-09"},
"tiles": ["#####", "#####", "S####", ".....", ".....", ".....", "#####"],
"points": {"warp_0": [1, 5], "warp_1": [2, 5]},
"warps": [{"x": 2, "y": 5, "enter": "walk_south"}]
})";

} // namespace


std::optional<Direction> coded_spin_direction(std::string_view map, const TilePos& point){
const SpinnerFloor* floor = spinner_floor(map);
if (floor == nullptr){
return std::nullopt;
}
if (contains(floor->north, point)){
return Direction::NORTH;
}
if (contains(floor->south, point)){
return Direction::SOUTH;
}
if (contains(floor->west, point)){
return Direction::WEST;
}
if (contains(floor->east, point)){
return Direction::EAST;
}
return std::nullopt;
}

bool coded_spin_stop(std::string_view map, const TilePos& point){
const SpinnerFloor* floor = spinner_floor(map);
return floor != nullptr && contains(floor->stop, point);
}


void add_coded_objects(MapData& map){
if (map.name != GAME_CORNER_MAP){
return;
}
if (!has_object(map, "poster")){
map.objects.push_back(object("poster", Pokemon::ObjectKind::SIGN, GAME_CORNER_POSTER));
}
if (!has_object(map, "poster_stairs")){
for (const TilePos& tile : {TilePos{15, 2}, TilePos{16, 2}, TilePos{17, 2}, TilePos{16, 3}, TilePos{17, 3}}){
map.objects.push_back(object("poster_stairs", Pokemon::ObjectKind::NPC, tile));
}
}
}


bool is_pokemon_center(std::string_view map){
return map.ends_with("_PokemonCenter_1F");
}

MapData pokemon_center_room(const std::string& name, const Pokemon::WarpDestination& outside){
MapData room = parse_map(CENTER_ROOM_JSON, name);
room.name = name;
for (MapWarp& warp : room.warps){
if (warp.y == CENTER_MAT.y){
warp.to = outside;
}
}
return room;
}

std::optional<std::string> add_pokemon_center_room(std::vector<MapData>& maps, size_t town){
for (const MapWarp& warp : maps[town].warps){
if (!warp.to || !is_pokemon_center(warp.to->map)){
continue;
}
const std::string center = warp.to->map;
const std::string door = center + "_door";
maps[town].points[door] = TilePos{warp.x, warp.y};
Pokemon::WarpDestination outside{maps[town].name, door};
maps.push_back(pokemon_center_room(center, outside));
return center;
}
return std::nullopt;
}


MapData rocket_hideout_lift_room(){
return parse_map(LIFT_ROOM_JSON, std::string(LIFT_MAP));
}

const std::vector<MapPosition>& rocket_hideout_lift_doors(){
static const std::vector<MapPosition> DOORS{
{"RocketHideout_B1F", {24, 25}},
{"RocketHideout_B2F", {28, 16}},
{"RocketHideout_B4F", {20, 23}},
};
return DOORS;
}



} // namespace PokemonFRLG
} // namespace NintendoSwitch
} // namespace PokemonAutomation
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* Coded Rooms and Tiles
*
* From: https://github.com/PokemonAutomation/
*
* Places the story scripts rather than maps (the user's call, 2026-11-09):
* every Pokemon Center's ground floor, which all share one layout, the
* Rocket Hideout's lift, a room of 5 by 7 tiles, and the tiles that maps
* can't say in glyphs: the Rocket Hideout's spinners and the Game
* Corner's poster. All of it is read off pret/pokefirered, cited where it
* is defined, so no map file has to carry it.
*
*/

#ifndef PokemonAutomation_PokemonFRLG_CodedRooms_H
#define PokemonAutomation_PokemonFRLG_CodedRooms_H

#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "PokemonFRLG_OverworldMap.h"
#include "PokemonFRLG_OverworldRoute.h"

namespace PokemonAutomation{
namespace NintendoSwitch{
namespace PokemonFRLG{


// The spinner arrow on `point` of the map named `map`, from the
// decompilation's metatile behaviours, if the map is one of the hideout's
// spinner floors (RocketHideout_B2F, RocketHideout_B3F).
std::optional<Direction> coded_spin_direction(std::string_view map, const TilePos& point);
// Whether `point` of the map named `map` is a coded MB_STOP_SPINNING tile.
bool coded_spin_stop(std::string_view map, const TilePos& point);


// The Game Corner, whose poster hides the hideout's switch.
inline constexpr std::string_view GAME_CORNER_MAP = "CeladonCity_GameCorner";
// The poster (pokefirered CeladonCity_GameCorner map.json, the bg event
// CeladonCity_GameCorner_EventScript_Poster at (11, 1)).
inline constexpr TilePos GAME_CORNER_POSTER{11, 1};
// Adds what the Game Corner's map can't say to `map`, if it is the Game
// Corner and hasn't them: the poster, an object "poster", and the tiles
// the switch turns into stairs (CeladonCity_GameCorner_EventScript_
// OpenRocketHideout's setmetatile: (15, 2), (16..17, 2..3)), objects
// "poster_stairs" that block them until it is pushed.
void add_coded_objects(MapData& map);


// A Pokemon Center's ground floor. pokefirered gives all of Kanto's and
// the Sevii Islands' but the Indigo Plateau's and One Island's the one
// layout, LAYOUT_POKEMON_CENTER_1F; the seven in the map lock have the
// same tiles and differ only in who stands where, none of them on the
// nurse's column.
// The mat inside the door: the player arrives on it and leaves from it,
// walking south.
inline constexpr TilePos CENTER_MAT{7, 8};
// Below the nurse's counter, facing her (the nurse at (7, 2)).
inline constexpr TilePos CENTER_NURSE_FRONT{7, 4};
// Whether `map` names a Pokemon Center's ground floor.
bool is_pokemon_center(std::string_view map);
// The ground floor of the Center named `name`, whose mat lets out on
// `outside` (the door tile of the town's map): its tiles, the nurse, and
// the mat's warps. No person but the nurse: who stands where differs, and
// the script keeps to the nurse's column.
MapData pokemon_center_room(const std::string& name, const Pokemon::WarpDestination& outside);
// For a sim: adds the coded room of the Center whose door is on
// `maps[town]` to `maps`, and returns its name; nothing if the town has
// no Center door. The town gets a point "<Center>_door" on its door tile
// for the room's mat to let out on.
std::optional<std::string> add_pokemon_center_room(std::vector<MapData>& maps, size_t town);


// The Rocket Hideout's lift (pokefirered LAYOUT_ROCKET_HIDEOUT_ELEVATOR,
// 5 by 7 tiles, rows 3 to 5 walkable).
inline constexpr std::string_view LIFT_MAP = "RocketHideout_Elevator";
// Where the floors' lift doors put the player (warp 1, an
// MB_SOUTH_ARROW_WARP tile): south from it leaves the lift.
inline constexpr TilePos LIFT_ARRIVAL{2, 5};
// Below the panel (a bg event at (0, 2), RocketHideout_Elevator_
// EventScript_FloorSelect), facing north.
inline constexpr TilePos LIFT_PANEL_FRONT{0, 3};
// The lift's room. Its door is a dynamic warp: the sim takes it to the
// floor picked (SimScript::lift_floors with rocket_hideout_lift_doors()).
MapData rocket_hideout_lift_room();
// Where the lift lets out for each floor on its panel, top first
// (setdynamicwarp in RocketHideout_Elevator's scripts): B1F (24, 25),
// B2F (28, 16), B4F (20, 23), each its floor's lift door.
const std::vector<MapPosition>& rocket_hideout_lift_doors();



} // namespace PokemonFRLG
} // namespace NintendoSwitch
} // namespace PokemonAutomation
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ inline constexpr Milliseconds TILE_TIME(267);
inline constexpr Milliseconds RUN_TILE_TIME(133);
// Surfing: a tile per 8 frames.
inline constexpr Milliseconds SURF_TILE_TIME(133);
// Riding the Bicycle, measured on console B (2026-11-09, Lavender Town
// row 13, wall to wall, from rest): 16 tiles (1, 13) to (17, 13) in
// 1800 ms of motion, east and back west alike, 112 ms a tile; 5 tiles
// from rest in 600 ms (120 ms a tile, the start up included). Not the
// decompilation's 4 frames a tile (67 ms): the bike speeds up from rest.
inline constexpr Milliseconds BIKE_TILE_TIME(112);
// The longest tap measured to only turn a player facing another way, and
// to take exactly one step for one facing its way.
inline constexpr Milliseconds LONGEST_TAP(133);
Expand All @@ -62,6 +68,10 @@ inline constexpr Milliseconds STEP_TAP_SPACING_WORKED(666);
// the D-pad this long. Not measured: pokefirered's fade from black is 16
// frames, then the field takes input; a tap sent at once was lost.
inline constexpr Milliseconds BATTLE_EXIT_LOCKOUT(500);
// A spinner tile (Rocket Hideout, Viridian Gym) slides the player a tile
// per 8 frames (pokefirered PlayerGoSpin(): MOVE_SPEED_FAST_1). Not
// measured on the console.
inline constexpr Milliseconds SPIN_TILE_TIME(133);


// The walker's presses.
Expand All @@ -87,6 +97,9 @@ inline constexpr Milliseconds STEP_TAP_RELEASE(700);
// How long the overworld must show nothing (no box, fade or battle) after
// a battle before the next tap: twice the lockout.
inline constexpr Milliseconds AFTER_BATTLE_QUIET(1000);
// The wait after a slide's last tile before the next press: the spin's
// last frames and the facing it leaves. Not measured.
inline constexpr Milliseconds SPIN_STOP_SETTLE(300);

static_assert(TURN_TAP <= LONGEST_TAP && FACE_HOLD <= LONGEST_TAP && STEP_TAP <= LONGEST_TAP);
static_assert(STEP_TAP + STEP_TAP_RELEASE > STEP_TAP_SPACING_WORKED);
Expand Down
Loading
Loading