8#include "absl/status/status.h"
9#include "absl/strings/ascii.h"
10#include "absl/strings/numbers.h"
11#include "absl/strings/str_cat.h"
12#include "absl/strings/str_format.h"
31 return static_cast<int>(raw_map_id & 0x00FF);
49 return absl::InvalidArgumentError(
50 absl::StrFormat(
"Map ID out of range: 0x%02X", map_id));
52 return absl::OkStatus();
71 constexpr size_t kEntranceCount =
73 if (
id < kEntranceCount) {
76 return absl::StrFormat(
"Entrance %d",
id);
80 uint16_t map_pos,
int pixel_x,
int pixel_y) {
96 int tile_index =
static_cast<int>(map_pos >> 1);
105 size_t processed = 0;
106 int result = std::stoi(std::string(value), &processed, base);
107 if (processed != value.size()) {
108 return absl::InvalidArgumentError(
109 absl::StrCat(
"Invalid numeric value: ", std::string(value)));
112}
catch (
const std::exception&) {
113 return absl::InvalidArgumentError(
114 absl::StrCat(
"Invalid numeric value: ", std::string(value)));
119 std::string lower = absl::AsciiStrToLower(std::string(value));
120 if (lower ==
"0" || lower ==
"light") {
123 if (lower ==
"1" || lower ==
"dark") {
126 if (lower ==
"2" || lower ==
"special") {
129 return absl::InvalidArgumentError(
130 absl::StrCat(
"Unknown world value: ", std::string(value)));
135 if (map_id < kDarkWorldOffset) {
138 if (map_id < kSpecialWorldOffset) {
153 return absl::StrCat(
"Unknown(", world,
")");
179 if (map ==
nullptr) {
180 return absl::InternalError(
181 absl::StrFormat(
"Failed to retrieve overworld map 0x%02X", map_id));
186 summary.
world = world;
193 summary.
area_size = AreaSizeToString(map->area_size());
207 for (
int i = 0; i < 3; ++i) {
212 for (
int i = 0; i < 4; ++i) {
213 summary.
area_music.push_back(map->area_music(i));
216 for (
int i = 0; i < 16; ++i) {
228 std::vector<WarpEntry> entries;
230 const auto& entrances = overworld.
entrances();
231 for (
const auto& entrance : entrances) {
234 entry.
deleted = entrance.deleted;
235 entry.
is_hole = entrance.is_hole_;
238 PopulateCommonWarpFields(entry, entrance.map_id_, entrance.map_pos_,
239 entrance.x_, entrance.y_);
241 if (query.
type.has_value() && *query.
type != entry.
type) {
251 entries.push_back(std::move(entry));
254 const auto& holes = overworld.
holes();
255 for (
const auto& hole : holes) {
262 PopulateCommonWarpFields(entry, hole.map_id_, hole.map_pos_, hole.x_,
265 if (query.
type.has_value() && *query.
type != entry.
type) {
275 entries.push_back(std::move(entry));
278 std::sort(entries.begin(), entries.end(), [](
const WarpEntry& a,
280 if (a.world != b.world) {
281 return a.world < b.world;
284 return a.map_id < b.map_id;
287 return a.tile16_y < b.tile16_y;
290 return a.tile16_x < b.tile16_x;
292 return static_cast<int>(a.
type) <
static_cast<int>(b.
type);
301 if (options.
map_id.has_value()) {
304 if (options.
world.has_value()) {
305 if (*options.
world < 0 || *options.
world > 2) {
306 return absl::InvalidArgumentError(
307 absl::StrFormat(
"Unknown world index: %d", *options.
world));
311 if (options.
map_id.has_value() && options.
world.has_value()) {
314 if (inferred_world != *options.
world) {
315 return absl::InvalidArgumentError(
317 "Map 0x%02X belongs to the %s World but --world requested %s",
323 std::vector<int> worlds;
324 if (options.
world.has_value()) {
325 worlds.push_back(*options.
world);
326 }
else if (options.
map_id.has_value()) {
329 worlds.push_back(inferred_world);
334 std::vector<TileMatch> matches;
336 for (
int world : worlds) {
353 return absl::InvalidArgumentError(
354 absl::StrFormat(
"Unknown world index: %d", world));
359 for (
int local_map = 0; local_map < world_maps; ++local_map) {
360 int map_id = world_start + local_map;
361 if (options.
map_id.has_value() && map_id != *options.
map_id) {
365 int map_x_index = local_map % 8;
366 int map_y_index = local_map / 8;
368 int global_x_start = map_x_index * 32;
369 int global_y_start = map_y_index * 32;
371 for (
int local_y = 0; local_y < 32; ++local_y) {
372 for (
int local_x = 0; local_x < 32; ++local_x) {
373 int global_x = global_x_start + local_x;
374 int global_y = global_y_start + local_y;
376 uint16_t current_tile = overworld.
GetTile(global_x, global_y);
377 if (current_tile == tile_id) {
378 matches.push_back({map_id, world, local_x, local_y, global_x,
391 std::vector<OverworldSprite> results;
394 for (
int game_state = 0; game_state < 3; ++game_state) {
395 const auto& sprites = overworld.
sprites(game_state);
397 for (
const auto& sprite : sprites) {
403 int map_id = sprite.map_id();
404 if (query.
map_id.has_value() && map_id != *query.
map_id) {
409 int world = (map_id >= kSpecialWorldOffset) ? 2
410 : (map_id >= kDarkWorldOffset ? 1 : 0);
412 if (query.
world.has_value() && world != *query.
world) {
420 entry.
x = sprite.x();
421 entry.
y = sprite.y();
425 results.push_back(entry);
434 const auto& entrances = overworld.
entrances();
436 if (entrance_id >= entrances.size()) {
437 return absl::NotFoundError(
438 absl::StrFormat(
"Entrance %d not found (max: %d)",
439 entrance_id, entrances.size() - 1));
442 const auto& entrance = entrances[entrance_id];
446 details.
map_id = entrance.map_id_;
449 details.
world = (details.
map_id >= kSpecialWorldOffset) ? 2
450 : (details.
map_id >= kDarkWorldOffset ? 1 : 0);
452 details.
x = entrance.x_;
453 details.
y = entrance.y_;
454 details.
area_x = entrance.area_x_;
455 details.
area_y = entrance.area_y_;
456 details.
map_pos = entrance.map_pos_;
457 details.
is_hole = entrance.is_hole_;
474 stats.
count =
static_cast<int>(matches.size());
477 if (options.
map_id.has_value()) {
479 if (options.
world.has_value()) {
491 for (
const auto& match : matches) {
492 stats.
positions.emplace_back(match.local_x, match.local_y);
Represents the full Overworld data, light and dark world.
void set_current_world(int world)
const std::vector< OverworldEntrance > & holes() const
auto sprites(int state) const
auto overworld_map(int i) const
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
uint16_t GetTile(int x, int y) const
const std::vector< OverworldEntrance > & entrances() const
#define RETURN_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
int WorldOffset(int world)
absl::Status ValidateMapId(int map_id)
int NormalizeMapId(uint16_t raw_map_id)
constexpr int kLightWorldOffset
void PopulateCommonWarpFields(WarpEntry &entry, uint16_t raw_map_id, uint16_t map_pos, int pixel_x, int pixel_y)
constexpr int kDarkWorldOffset
std::string EntranceLabel(uint8_t id)
constexpr int kSpecialWorldOffset
std::string AreaSizeToString(zelda3::AreaSizeEnum size)
absl::StatusOr< int > InferWorldFromMapId(int map_id)
absl::StatusOr< MapSummary > BuildMapSummary(zelda3::Overworld &overworld, int map_id)
absl::StatusOr< std::vector< WarpEntry > > CollectWarpEntries(const zelda3::Overworld &overworld, const WarpQuery &query)
absl::StatusOr< int > ParseNumeric(std::string_view value, int base)
absl::StatusOr< TileStatistics > AnalyzeTileUsage(zelda3::Overworld &overworld, uint16_t tile_id, const TileSearchOptions &options)
absl::StatusOr< int > ParseWorldSpecifier(std::string_view value)
absl::StatusOr< EntranceDetails > GetEntranceDetails(const zelda3::Overworld &overworld, uint8_t entrance_id)
absl::StatusOr< std::vector< OverworldSprite > > CollectOverworldSprites(const zelda3::Overworld &overworld, const SpriteQuery &query)
absl::StatusOr< std::vector< TileMatch > > FindTileMatches(zelda3::Overworld &overworld, uint16_t tile_id, const TileSearchOptions &options)
std::string WarpTypeName(WarpType type)
std::string WorldName(int world)
constexpr int kNumOverworldMaps
constexpr const char * kEntranceNames[]
Main namespace for the application.
std::optional< std::string > entrance_name
uint16_t area_specific_bg_color
std::vector< uint8_t > sprite_graphics
std::vector< uint8_t > static_graphics
std::vector< uint8_t > sprite_palettes
std::vector< uint8_t > area_music
uint16_t subscreen_overlay
std::optional< uint8_t > sprite_id
std::optional< int > world
std::optional< int > map_id
std::optional< int > world
std::optional< int > map_id
std::vector< std::pair< int, int > > positions
std::optional< std::string > entrance_name
std::optional< uint8_t > entrance_id
std::optional< int > map_id
std::optional< WarpType > type
std::optional< int > world