35 int map_id = -1, x = -1, y = -1;
37 for (
size_t i = 0; i < arg_vec.size(); ++i) {
38 const std::string& arg = arg_vec[i];
39 if ((arg ==
"--map") && i + 1 < arg_vec.size()) {
40 map_id = std::stoi(arg_vec[++i]);
41 }
else if ((arg ==
"--x") && i + 1 < arg_vec.size()) {
42 x = std::stoi(arg_vec[++i]);
43 }
else if ((arg ==
"--y") && i + 1 < arg_vec.size()) {
44 y = std::stoi(arg_vec[++i]);
48 if (map_id == -1 || x == -1 || y == -1) {
49 return absl::InvalidArgumentError(
"Usage: overworld get-tile --map <map_id> --x <x> --y <y>");
52 std::string rom_file = absl::GetFlag(FLAGS_rom);
53 if (rom_file.empty()) {
54 return absl::InvalidArgumentError(
"ROM file must be provided via --rom flag.");
59 if (!load_status.ok()) {
63 return absl::AbortedError(
"Failed to load ROM.");
67 auto ow_status = overworld.
Load(&rom);
68 if (!ow_status.ok()) {
72 uint16_t tile = overworld.
GetTile(x, y);
74 std::cout <<
"Tile at (" << x <<
", " << y <<
") on map " << map_id <<
" is: 0x" << std::hex << tile << std::endl;
76 return absl::OkStatus();
82 int map_id = -1, x = -1, y = -1, tile_id = -1;
84 for (
size_t i = 0; i < arg_vec.size(); ++i) {
85 const std::string& arg = arg_vec[i];
86 if ((arg ==
"--map") && i + 1 < arg_vec.size()) {
87 map_id = std::stoi(arg_vec[++i]);
88 }
else if ((arg ==
"--x") && i + 1 < arg_vec.size()) {
89 x = std::stoi(arg_vec[++i]);
90 }
else if ((arg ==
"--y") && i + 1 < arg_vec.size()) {
91 y = std::stoi(arg_vec[++i]);
92 }
else if ((arg ==
"--tile") && i + 1 < arg_vec.size()) {
93 tile_id = std::stoi(arg_vec[++i],
nullptr, 16);
97 if (map_id == -1 || x == -1 || y == -1 || tile_id == -1) {
98 return absl::InvalidArgumentError(
"Usage: overworld set-tile --map <map_id> --x <x> --y <y> --tile <tile_id>");
101 std::string rom_file = absl::GetFlag(FLAGS_rom);
102 if (rom_file.empty()) {
103 return absl::InvalidArgumentError(
"ROM file must be provided via --rom flag.");
108 if (!load_status.ok()) {
112 return absl::AbortedError(
"Failed to load ROM.");
116 auto status = overworld.
Load(&rom);
124 }
else if (map_id < 0x80) {
131 overworld.
SetTile(x, y,
static_cast<uint16_t
>(tile_id));
134 auto save_status = rom.
SaveToFile({.filename = rom_file});
135 if (!save_status.ok()) {
139 std::cout <<
"✅ Set tile at (" << x <<
", " << y <<
") on map " << map_id
140 <<
" to: 0x" << std::hex << tile_id << std::dec << std::endl;
142 return absl::OkStatus();
155 std::unordered_map<std::string, std::string> options;
156 std::vector<std::string> positional;
157 options.reserve(arg_vec.size());
159 for (
size_t i = 0; i < arg_vec.size(); ++i) {
160 const std::string& token = arg_vec[i];
161 if (absl::StartsWith(token,
"--")) {
164 auto eq_pos = token.find(
'=');
165 if (eq_pos != std::string::npos) {
166 key = token.substr(2, eq_pos - 2);
167 value = token.substr(eq_pos + 1);
169 key = token.substr(2);
170 if (i + 1 >= arg_vec.size()) {
171 return absl::InvalidArgumentError(
172 absl::StrCat(
"Missing value for --", key,
"\n", kFindTileUsage));
174 value = arg_vec[++i];
177 return absl::InvalidArgumentError(
178 absl::StrCat(
"Missing value for --", key,
"\n", kFindTileUsage));
180 options[key] = value;
182 positional.push_back(token);
186 if (!positional.empty()) {
187 return absl::InvalidArgumentError(
188 absl::StrCat(
"Unexpected positional arguments: ",
189 absl::StrJoin(positional,
", "),
"\n", kFindTileUsage));
192 auto tile_it = options.find(
"tile");
193 if (tile_it == options.end()) {
194 return absl::InvalidArgumentError(
195 absl::StrCat(
"Missing required --tile argument\n", kFindTileUsage));
200 if (tile_value < 0 || tile_value > 0xFFFF) {
201 return absl::InvalidArgumentError(
202 absl::StrCat(
"Tile ID must be between 0x0000 and 0xFFFF (got ",
203 tile_it->second,
")"));
205 const uint16_t tile_id =
static_cast<uint16_t
>(tile_value);
207 std::optional<int> map_filter;
208 if (
auto map_it = options.find(
"map"); map_it != options.end()) {
211 if (map_value < 0 || map_value >= 0xA0) {
212 return absl::InvalidArgumentError(
213 absl::StrCat(
"Map ID out of range: ", map_it->second));
215 map_filter = map_value;
218 std::optional<int> world_filter;
219 if (
auto world_it = options.find(
"world"); world_it != options.end()) {
222 world_filter = parsed_world;
225 if (map_filter.has_value()) {
228 if (world_filter.has_value() && inferred_world != *world_filter) {
229 return absl::InvalidArgumentError(
230 absl::StrCat(
"Map 0x",
231 absl::StrFormat(
"%02X", *map_filter),
234 " World but --world requested ",
237 if (!world_filter.has_value()) {
238 world_filter = inferred_world;
242 std::string format =
"text";
243 if (
auto format_it = options.find(
"format"); format_it != options.end()) {
244 format = absl::AsciiStrToLower(format_it->second);
245 if (format !=
"text" && format !=
"json") {
246 return absl::InvalidArgumentError(
247 absl::StrCat(
"Unsupported format: ", format_it->second));
251 std::string rom_file = absl::GetFlag(FLAGS_rom);
252 if (
auto rom_it = options.find(
"rom"); rom_it != options.end()) {
253 rom_file = rom_it->second;
256 if (rom_file.empty()) {
257 return absl::InvalidArgumentError(
258 "ROM file must be provided via --rom flag.");
263 if (!load_status.ok()) {
267 return absl::AbortedError(
"Failed to load ROM.");
271 auto ow_status = overworld.
Load(&rom);
272 if (!ow_status.ok()) {
277 search_options.
map_id = map_filter;
278 search_options.
world = world_filter;
284 if (format ==
"json") {
286 std::cout << absl::StrFormat(
287 " \"tile\": \"0x%04X\",\n", tile_id);
288 std::cout << absl::StrFormat(
289 " \"match_count\": %zu,\n", matches.size());
290 std::cout <<
" \"matches\": [\n";
291 for (
size_t i = 0; i < matches.size(); ++i) {
292 const auto& match = matches[i];
293 std::cout << absl::StrFormat(
294 " {\"map\": \"0x%02X\", \"world\": \"%s\", "
295 "\"local\": {\"x\": %d, \"y\": %d}, "
296 "\"global\": {\"x\": %d, \"y\": %d}}%s\n",
299 match.global_x, match.global_y,
300 (i + 1 == matches.size()) ?
"" :
",");
305 std::cout << absl::StrFormat(
306 "🔎 Tile 0x%04X → %zu match(es)\n", tile_id, matches.size());
307 if (matches.empty()) {
308 std::cout <<
" No matches found." << std::endl;
309 return absl::OkStatus();
312 for (
const auto& match : matches) {
313 std::cout << absl::StrFormat(
314 " • Map 0x%02X (%s World) local(%2d,%2d) global(%3d,%3d)\n",
317 match.global_x, match.global_y);
321 return absl::OkStatus();
327 const std::vector<std::string>& arg_vec) {
328 constexpr absl::string_view kUsage =
329 "Usage: overworld describe-map --map <map_id> [--format <json|text>]";
331 std::unordered_map<std::string, std::string> options;
332 std::vector<std::string> positional;
333 options.reserve(arg_vec.size());
335 for (
size_t i = 0; i < arg_vec.size(); ++i) {
336 const std::string& token = arg_vec[i];
337 if (absl::StartsWith(token,
"--")) {
340 auto eq_pos = token.find(
'=');
341 if (eq_pos != std::string::npos) {
342 key = token.substr(2, eq_pos - 2);
343 value = token.substr(eq_pos + 1);
345 key = token.substr(2);
346 if (i + 1 >= arg_vec.size()) {
347 return absl::InvalidArgumentError(
348 absl::StrCat(
"Missing value for --", key,
"\n", kUsage));
350 value = arg_vec[++i];
353 return absl::InvalidArgumentError(
354 absl::StrCat(
"Missing value for --", key,
"\n", kUsage));
356 options[key] = value;
358 positional.push_back(token);
362 if (!positional.empty()) {
363 return absl::InvalidArgumentError(
364 absl::StrCat(
"Unexpected positional arguments: ",
365 absl::StrJoin(positional,
", "),
"\n", kUsage));
368 auto map_it = options.find(
"map");
369 if (map_it == options.end()) {
370 return absl::InvalidArgumentError(std::string(kUsage));
376 return absl::InvalidArgumentError(
377 absl::StrCat(
"Map ID out of range: ", map_it->second));
380 std::string format =
"text";
381 if (
auto it = options.find(
"format"); it != options.end()) {
382 format = absl::AsciiStrToLower(it->second);
383 if (format !=
"text" && format !=
"json") {
384 return absl::InvalidArgumentError(
385 absl::StrCat(
"Unsupported format: ", it->second));
389 std::string rom_file = absl::GetFlag(FLAGS_rom);
390 if (
auto it = options.find(
"rom"); it != options.end()) {
391 rom_file = it->second;
394 if (rom_file.empty()) {
395 return absl::InvalidArgumentError(
396 "ROM file must be provided via --rom flag.");
401 if (!load_status.ok()) {
405 return absl::AbortedError(
"Failed to load ROM.");
409 auto ow_status = overworld_rom.
Load(&rom);
410 if (!ow_status.ok()) {
417 auto join_hex = [](
const std::vector<uint8_t>& values) {
418 std::vector<std::string> parts;
419 parts.reserve(values.size());
420 for (uint8_t v : values) {
421 parts.push_back(absl::StrFormat(
"0x%02X", v));
423 return absl::StrJoin(parts,
", ");
426 auto join_hex_json = [](
const std::vector<uint8_t>& values) {
427 std::vector<std::string> parts;
428 parts.reserve(values.size());
429 for (uint8_t v : values) {
430 parts.push_back(absl::StrFormat(
"\"0x%02X\"", v));
432 return absl::StrCat(
"[", absl::StrJoin(parts,
", "),
"]");
435 if (format ==
"json") {
437 std::cout << absl::StrFormat(
" \"map\": \"0x%02X\",\n", summary.map_id);
438 std::cout << absl::StrFormat(
" \"world\": \"%s\",\n",
440 std::cout << absl::StrFormat(
441 " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n",
442 summary.map_x, summary.map_y, summary.local_index);
443 std::cout << absl::StrFormat(
444 " \"size\": {\"label\": \"%s\", \"is_large\": %s, \"parent\": \"0x%02X\", \"quadrant\": %d},\n",
445 summary.area_size, summary.is_large_map ?
"true" :
"false",
446 summary.parent_map, summary.large_quadrant);
447 std::cout << absl::StrFormat(
448 " \"message\": \"0x%04X\",\n", summary.message_id);
449 std::cout << absl::StrFormat(
450 " \"area_graphics\": \"0x%02X\",\n", summary.area_graphics);
451 std::cout << absl::StrFormat(
452 " \"area_palette\": \"0x%02X\",\n", summary.area_palette);
453 std::cout << absl::StrFormat(
454 " \"main_palette\": \"0x%02X\",\n", summary.main_palette);
455 std::cout << absl::StrFormat(
456 " \"animated_gfx\": \"0x%02X\",\n", summary.animated_gfx);
457 std::cout << absl::StrFormat(
458 " \"subscreen_overlay\": \"0x%04X\",\n",
459 summary.subscreen_overlay);
460 std::cout << absl::StrFormat(
461 " \"area_specific_bg_color\": \"0x%04X\",\n",
462 summary.area_specific_bg_color);
463 std::cout << absl::StrFormat(
464 " \"sprite_graphics\": %s,\n", join_hex_json(summary.sprite_graphics));
465 std::cout << absl::StrFormat(
466 " \"sprite_palettes\": %s,\n", join_hex_json(summary.sprite_palettes));
467 std::cout << absl::StrFormat(
468 " \"area_music\": %s,\n", join_hex_json(summary.area_music));
469 std::cout << absl::StrFormat(
470 " \"static_graphics\": %s,\n",
471 join_hex_json(summary.static_graphics));
472 std::cout << absl::StrFormat(
473 " \"overlay\": {\"enabled\": %s, \"id\": \"0x%04X\"}\n",
474 summary.has_overlay ?
"true" :
"false", summary.overlay_id);
477 std::cout << absl::StrFormat(
"🗺️ Map 0x%02X (%s World)\n", summary.map_id,
479 std::cout << absl::StrFormat(
" Grid: (%d, %d) local-index %d\n",
480 summary.map_x, summary.map_y,
481 summary.local_index);
482 std::cout << absl::StrFormat(
483 " Size: %s%s | Parent: 0x%02X | Quadrant: %d\n",
484 summary.area_size, summary.is_large_map ?
" (large)" :
"",
485 summary.parent_map, summary.large_quadrant);
486 std::cout << absl::StrFormat(
487 " Message: 0x%04X | Area GFX: 0x%02X | Area Palette: 0x%02X\n",
488 summary.message_id, summary.area_graphics, summary.area_palette);
489 std::cout << absl::StrFormat(
490 " Main Palette: 0x%02X | Animated GFX: 0x%02X | Overlay: %s (0x%04X)\n",
491 summary.main_palette, summary.animated_gfx,
492 summary.has_overlay ?
"yes" :
"no", summary.overlay_id);
493 std::cout << absl::StrFormat(
494 " Subscreen Overlay: 0x%04X | BG Color: 0x%04X\n",
495 summary.subscreen_overlay, summary.area_specific_bg_color);
496 std::cout << absl::StrFormat(
" Sprite GFX: [%s]\n",
497 join_hex(summary.sprite_graphics));
498 std::cout << absl::StrFormat(
" Sprite Palettes: [%s]\n",
499 join_hex(summary.sprite_palettes));
500 std::cout << absl::StrFormat(
" Area Music: [%s]\n",
501 join_hex(summary.area_music));
502 std::cout << absl::StrFormat(
" Static GFX: [%s]\n",
503 join_hex(summary.static_graphics));
506 return absl::OkStatus();
512 const std::vector<std::string>& arg_vec) {
513 constexpr absl::string_view kUsage =
514 "Usage: overworld list-warps [--map <map_id>] [--world <light|dark|special>] "
515 "[--type <entrance|hole|exit|all>] [--format <json|text>]";
517 std::unordered_map<std::string, std::string> options;
518 std::vector<std::string> positional;
519 options.reserve(arg_vec.size());
521 for (
size_t i = 0; i < arg_vec.size(); ++i) {
522 const std::string& token = arg_vec[i];
523 if (absl::StartsWith(token,
"--")) {
526 auto eq_pos = token.find(
'=');
527 if (eq_pos != std::string::npos) {
528 key = token.substr(2, eq_pos - 2);
529 value = token.substr(eq_pos + 1);
531 key = token.substr(2);
532 if (i + 1 >= arg_vec.size()) {
533 return absl::InvalidArgumentError(
534 absl::StrCat(
"Missing value for --", key,
"\n", kUsage));
536 value = arg_vec[++i];
539 return absl::InvalidArgumentError(
540 absl::StrCat(
"Missing value for --", key,
"\n", kUsage));
542 options[key] = value;
544 positional.push_back(token);
548 if (!positional.empty()) {
549 return absl::InvalidArgumentError(
550 absl::StrCat(
"Unexpected positional arguments: ",
551 absl::StrJoin(positional,
", "),
"\n", kUsage));
554 std::optional<int> map_filter;
555 if (
auto it = options.find(
"map"); it != options.end()) {
559 return absl::InvalidArgumentError(
560 absl::StrCat(
"Map ID out of range: ", it->second));
562 map_filter = map_value;
565 std::optional<int> world_filter;
566 if (
auto it = options.find(
"world"); it != options.end()) {
569 world_filter = parsed_world;
572 std::optional<overworld::WarpType> type_filter;
573 if (
auto it = options.find(
"type"); it != options.end()) {
574 std::string lower = absl::AsciiStrToLower(it->second);
575 if (lower ==
"entrance" || lower ==
"entrances") {
577 }
else if (lower ==
"hole" || lower ==
"holes") {
579 }
else if (lower ==
"exit" || lower ==
"exits") {
581 }
else if (lower ==
"all" || lower.empty()) {
584 return absl::InvalidArgumentError(
585 absl::StrCat(
"Unknown warp type: ", it->second));
589 if (map_filter.has_value()) {
592 if (world_filter.has_value() && inferred_world != *world_filter) {
593 return absl::InvalidArgumentError(
594 absl::StrCat(
"Map 0x",
595 absl::StrFormat(
"%02X", *map_filter),
598 " World but --world requested ",
601 if (!world_filter.has_value()) {
602 world_filter = inferred_world;
606 std::string format =
"text";
607 if (
auto it = options.find(
"format"); it != options.end()) {
608 format = absl::AsciiStrToLower(it->second);
609 if (format !=
"text" && format !=
"json") {
610 return absl::InvalidArgumentError(
611 absl::StrCat(
"Unsupported format: ", it->second));
615 std::string rom_file = absl::GetFlag(FLAGS_rom);
616 if (
auto it = options.find(
"rom"); it != options.end()) {
617 rom_file = it->second;
620 if (rom_file.empty()) {
621 return absl::InvalidArgumentError(
622 "ROM file must be provided via --rom flag.");
627 if (!load_status.ok()) {
631 return absl::AbortedError(
"Failed to load ROM.");
635 auto ow_status = overworld_rom.
Load(&rom);
636 if (!ow_status.ok()) {
641 query.
map_id = map_filter;
642 query.
world = world_filter;
643 query.
type = type_filter;
648 if (format ==
"json") {
650 std::cout << absl::StrFormat(
" \"count\": %zu,\n", entries.size());
651 std::cout <<
" \"entries\": [\n";
652 for (
size_t i = 0; i < entries.size(); ++i) {
653 const auto& entry = entries[i];
655 std::cout << absl::StrFormat(
656 " \"type\": \"%s\",\n",
658 std::cout << absl::StrFormat(
659 " \"map\": \"0x%02X\",\n", entry.map_id);
660 std::cout << absl::StrFormat(
661 " \"world\": \"%s\",\n",
663 std::cout << absl::StrFormat(
664 " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n",
665 entry.map_x, entry.map_y, entry.local_index);
666 std::cout << absl::StrFormat(
667 " \"tile16\": {\"x\": %d, \"y\": %d},\n",
668 entry.tile16_x, entry.tile16_y);
669 std::cout << absl::StrFormat(
670 " \"pixel\": {\"x\": %d, \"y\": %d},\n",
671 entry.pixel_x, entry.pixel_y);
672 std::cout << absl::StrFormat(
673 " \"map_pos\": \"0x%04X\",\n", entry.map_pos);
674 std::cout << absl::StrFormat(
675 " \"deleted\": %s,\n", entry.deleted ?
"true" :
"false");
676 std::cout << absl::StrFormat(
678 entry.is_hole ?
"true" :
"false");
679 if (entry.entrance_id.has_value()) {
680 std::cout << absl::StrFormat(
681 ",\n \"entrance_id\": \"0x%02X\"",
684 if (entry.entrance_name.has_value()) {
685 std::cout << absl::StrFormat(
686 ",\n \"entrance_name\": \"%s\"",
687 *entry.entrance_name);
689 std::cout <<
"\n }" << (i + 1 == entries.size() ?
"" :
",") <<
"\n";
694 if (entries.empty()) {
695 std::cout <<
"No overworld warps match the specified filters." << std::endl;
696 return absl::OkStatus();
699 std::cout << absl::StrFormat(
"🌐 Overworld warps (%zu)\n", entries.size());
700 for (
const auto& entry : entries) {
701 std::string line = absl::StrFormat(
702 " • %-9s map 0x%02X (%s World) tile16(%02d,%02d) pixel(%4d,%4d)",
705 entry.pixel_x, entry.pixel_y);
706 if (entry.entrance_id.has_value()) {
707 line = absl::StrCat(line,
708 absl::StrFormat(
" id=0x%02X", *entry.entrance_id));
710 if (entry.entrance_name.has_value()) {
711 line = absl::StrCat(line,
" (", *entry.entrance_name,
")");
714 line = absl::StrCat(line,
" [deleted]");
717 line = absl::StrCat(line,
" [hole]");
719 std::cout << line << std::endl;
723 return absl::OkStatus();
733 int map_id = -1, x1 = -1, y1 = -1, x2 = -1, y2 = -1;
735 for (
size_t i = 0; i < arg_vec.size(); ++i) {
736 const std::string& arg = arg_vec[i];
737 if ((arg ==
"--map") && i + 1 < arg_vec.size()) {
738 map_id = std::stoi(arg_vec[++i]);
739 }
else if ((arg ==
"--x1") && i + 1 < arg_vec.size()) {
740 x1 = std::stoi(arg_vec[++i]);
741 }
else if ((arg ==
"--y1") && i + 1 < arg_vec.size()) {
742 y1 = std::stoi(arg_vec[++i]);
743 }
else if ((arg ==
"--x2") && i + 1 < arg_vec.size()) {
744 x2 = std::stoi(arg_vec[++i]);
745 }
else if ((arg ==
"--y2") && i + 1 < arg_vec.size()) {
746 y2 = std::stoi(arg_vec[++i]);
750 if (map_id == -1 || x1 == -1 || y1 == -1 || x2 == -1 || y2 == -1) {
751 return absl::InvalidArgumentError(
752 "Usage: overworld select-rect --map <map_id> --x1 <x1> --y1 <y1> --x2 <x2> --y2 <y2>");
755 std::cout <<
"✅ Selected rectangle on map " << map_id
756 <<
" from (" << x1 <<
"," << y1 <<
") to (" << x2 <<
"," << y2 <<
")" << std::endl;
758 int width = std::abs(x2 - x1) + 1;
759 int height = std::abs(y2 - y1) + 1;
760 std::cout <<
" Selection size: " << width <<
"x" << height <<
" tiles ("
761 << (width * height) <<
" total)" << std::endl;
763 return absl::OkStatus();