130 std::optional<int> world_filter;
131 if (
auto world_str = parser.
GetString(
"world")) {
132 std::string w = *world_str;
133 if (w ==
"light" || w ==
"0") {
135 }
else if (w ==
"dark" || w ==
"1") {
137 }
else if (w ==
"special" || w ==
"2") {
139 }
else if (w !=
"all") {
140 return absl::InvalidArgumentError(absl::StrCat(
141 "Invalid world: ", w,
". Use: light, dark, special, or all"));
150 std::vector<AreaInfo> areas;
151 std::map<int, int> map_to_parent;
154 int world = WorldFromMapId(map_id);
157 if (world_filter.has_value() && world != *world_filter) {
165 int local = LocalIndexFromMapId(map_id);
166 int grid_x = local % kMapsPerRow;
167 int grid_y = local / kMapsPerRow;
170 int min_x = grid_x * kScreenWidth;
171 int min_y = grid_y * kScreenHeight;
172 int max_x = min_x + kScreenWidth;
173 int max_y = min_y + kScreenHeight;
176 auto area_size = map->area_size();
178 max_x = min_x + kScreenWidth * 2;
179 max_y = min_y + kScreenHeight * 2;
181 max_x = min_x + kScreenWidth * 2;
183 max_y = min_y + kScreenHeight * 2;
186 int parent = map->parent();
187 map_to_parent[map_id] = parent;
191 area.
name = GetDefaultAreaName(map_id);
195 area.
size = GetAreaSizeName(area_size);
196 area.
parent_id = parent == map_id ? -1 : parent;
202 areas.push_back(area);
206 std::vector<AreaConnection> connections;
207 std::set<std::pair<int, int>> seen_connections;
211 int world = WorldFromMapId(map_id);
212 if (world_filter.has_value() && world != *world_filter)
219 int north_area = north_target & 0xFF;
223 int target_world = WorldFromMapId(north_area);
225 if (target_world == world || world == 2 || target_world == 2) {
226 auto key = std::minmax(map_id, north_area);
227 if (seen_connections.find(key) == seen_connections.end()) {
228 seen_connections.insert(key);
236 int local = LocalIndexFromMapId(map_id);
237 int grid_x = local % kMapsPerRow;
238 int grid_y = local / kMapsPerRow;
239 conn.
edge_x = grid_x * kScreenWidth + kScreenWidth / 2;
240 conn.
edge_y = grid_y * kScreenHeight;
243 connections.push_back(conn);
251 int west_area = west_target & 0xFF;
254 int target_world = WorldFromMapId(west_area);
255 if (target_world == world || world == 2 || target_world == 2) {
256 auto key = std::minmax(map_id, west_area);
257 if (seen_connections.find(key) == seen_connections.end()) {
258 seen_connections.insert(key);
265 int local = LocalIndexFromMapId(map_id);
266 int grid_x = local % kMapsPerRow;
267 int grid_y = local / kMapsPerRow;
268 conn.
edge_x = grid_x * kScreenWidth;
269 conn.
edge_y = grid_y * kScreenHeight + kScreenHeight / 2;
272 connections.push_back(conn);
280 int world = WorldFromMapId(map_id);
281 if (world_filter.has_value() && world != *world_filter)
284 int local = LocalIndexFromMapId(map_id);
285 int grid_x = local % kMapsPerRow;
286 int grid_y = local / kMapsPerRow;
289 int world_offset = (world == 0) ? 0
290 : (world == 1) ? kDarkWorldOffset
291 : kSpecialWorldOffset;
292 int maps_in_world = (world == 2) ? 32 : 64;
295 if (grid_x < kMapsPerRow - 1) {
296 int east_local = local + 1;
297 if (east_local < maps_in_world) {
298 int east_area = world_offset + east_local;
299 auto key = std::minmax(map_id, east_area);
300 if (seen_connections.find(key) == seen_connections.end()) {
301 seen_connections.insert(key);
307 conn.
edge_x = (grid_x + 1) * kScreenWidth;
308 conn.
edge_y = grid_y * kScreenHeight + kScreenHeight / 2;
311 connections.push_back(conn);
317 int rows_in_world = maps_in_world / kMapsPerRow;
318 if (grid_y < rows_in_world - 1) {
319 int south_local = local + kMapsPerRow;
320 if (south_local < maps_in_world) {
321 int south_area = world_offset + south_local;
322 auto key = std::minmax(map_id, south_area);
323 if (seen_connections.find(key) == seen_connections.end()) {
324 seen_connections.insert(key);
330 conn.
edge_x = grid_x * kScreenWidth + kScreenWidth / 2;
331 conn.
edge_y = (grid_y + 1) * kScreenHeight;
334 connections.push_back(conn);
341 std::vector<EntranceInfo> entrances;
345 const auto& ow_entrances = overworld.
entrances();
346 for (
size_t i = 0; i < ow_entrances.size(); ++i) {
347 const auto& entrance = ow_entrances[i];
348 if (entrance.deleted)
351 int area_id = entrance.map_id_ & 0xFF;
352 int world = WorldFromMapId(area_id);
354 if (world_filter.has_value() && world != *world_filter)
358 info.
id =
static_cast<int>(i);
362 info.
is_hole = entrance.is_hole_;
365 constexpr size_t kNumEntranceLabels =
367 if (entrance.entrance_id_ < kNumEntranceLabels) {
370 info.
name = absl::StrFormat(
"Entrance %d", entrance.entrance_id_);
375 uint8_t entrance_id = entrance.entrance_id_;
378 rom->
ReadWord(entrance_layout.address(
385 entrances.push_back(info);
389 const auto& holes = overworld.
holes();
390 for (
size_t i = 0; i < holes.size(); ++i) {
391 const auto& hole = holes[i];
393 int area_id = hole.map_id_ & 0xFF;
394 int world = WorldFromMapId(area_id);
396 if (world_filter.has_value() && world != *world_filter)
400 info.
id =
static_cast<int>(ow_entrances.size() + i);
406 constexpr size_t kNumEntranceLabels =
408 if (hole.entrance_id_ < kNumEntranceLabels) {
409 info.
name = absl::StrFormat(
"%s (Hole)",
412 info.
name = absl::StrFormat(
"Hole %d", hole.entrance_id_);
415 uint8_t entrance_id = hole.entrance_id_;
418 rom->
ReadWord(entrance_layout.address(
425 entrances.push_back(info);
429 std::vector<ExitInfo> exits;
430 const auto* ow_exits = overworld.
exits();
432 if (ow_exits !=
nullptr) {
433 for (
const auto& exit : *ow_exits) {
434 int area_id = exit.map_id_ & 0xFF;
435 int world = WorldFromMapId(area_id);
437 if (world_filter.has_value() && world != *world_filter)
442 info.
name = absl::StrFormat(
"Exit from room 0x%02X", exit.room_id_);
447 exits.push_back(info);
454 formatter.
AddField(
"version",
"1.0.0");
455 formatter.
AddField(
"generated", GetCurrentTimestamp());
460 for (
const auto& area : areas) {
463 formatter.
AddField(
"name", area.name);
464 formatter.
AddField(
"world", GetWorldName(area.world));
467 formatter.
AddField(
"x", area.grid_x);
468 formatter.
AddField(
"y", area.grid_y);
471 formatter.
AddField(
"size", area.size);
473 if (area.parent_id >= 0) {
474 formatter.
AddField(
"parent_id", area.parent_id);
478 formatter.
AddField(
"min_x", area.min_x);
479 formatter.
AddField(
"min_y", area.min_y);
480 formatter.
AddField(
"max_x", area.max_x);
481 formatter.
AddField(
"max_y", area.max_y);
490 for (
const auto& conn : connections) {
492 formatter.
AddField(
"from_area", conn.from_area);
493 formatter.
AddField(
"to_area", conn.to_area);
494 formatter.
AddField(
"direction", conn.direction);
497 formatter.
AddField(
"x", conn.edge_x);
498 formatter.
AddField(
"y", conn.edge_y);
501 formatter.
AddField(
"bidirectional", conn.bidirectional);
508 for (
const auto& entrance : entrances) {
510 formatter.
AddField(
"id", entrance.id);
511 formatter.
AddField(
"name", entrance.name);
512 formatter.
AddField(
"area_id", entrance.area_id);
515 formatter.
AddField(
"x", entrance.position_x);
516 formatter.
AddField(
"y", entrance.position_y);
519 formatter.
AddField(
"dest_room_id", entrance.dest_room_id);
520 formatter.
AddField(
"is_hole", entrance.is_hole);
527 for (
const auto& exit : exits) {
529 formatter.
AddField(
"room_id", exit.room_id);
530 formatter.
AddField(
"name", exit.name);
531 formatter.
AddField(
"return_area_id", exit.return_area_id);
534 formatter.
AddField(
"x", exit.return_x);
535 formatter.
AddField(
"y", exit.return_y);
544 formatter.
AddField(
"total_areas",
static_cast<int>(areas.size()));
545 formatter.
AddField(
"total_connections",
static_cast<int>(connections.size()));
546 formatter.
AddField(
"total_entrances",
static_cast<int>(entrances.size()));
547 formatter.
AddField(
"total_exits",
static_cast<int>(exits.size()));
552 return absl::OkStatus();