35 const std::vector<std::string>& arg_vec) {
36 int map_id = -1, x = -1, y = -1;
38 for (
size_t i = 0; i < arg_vec.size(); ++i) {
39 const std::string& arg = arg_vec[i];
40 if ((arg ==
"--map") && i + 1 < arg_vec.size()) {
41 map_id = std::stoi(arg_vec[++i]);
42 }
else if ((arg ==
"--x") && i + 1 < arg_vec.size()) {
43 x = std::stoi(arg_vec[++i]);
44 }
else if ((arg ==
"--y") && i + 1 < arg_vec.size()) {
45 y = std::stoi(arg_vec[++i]);
49 if (map_id == -1 || x == -1 || y == -1) {
50 return absl::InvalidArgumentError(
51 "Usage: overworld get-tile --map <map_id> --x <x> --y <y>");
54 std::string rom_file = absl::GetFlag(FLAGS_rom);
55 if (rom_file.empty()) {
56 return absl::InvalidArgumentError(
57 "ROM file must be provided via --rom flag.");
62 if (!load_status.ok()) {
66 return absl::AbortedError(
"Failed to load ROM.");
70 auto ow_status = overworld.
Load(&rom);
71 if (!ow_status.ok()) {
75 uint16_t tile = overworld.
GetTile(x, y);
77 std::cout <<
"Tile at (" << x <<
", " << y <<
") on map " << map_id
78 <<
" is: 0x" << std::hex << tile << std::endl;
80 return absl::OkStatus();
86 const std::vector<std::string>& arg_vec) {
87 int map_id = -1, x = -1, y = -1, tile_id = -1;
89 for (
size_t i = 0; i < arg_vec.size(); ++i) {
90 const std::string& arg = arg_vec[i];
91 if ((arg ==
"--map") && i + 1 < arg_vec.size()) {
92 map_id = std::stoi(arg_vec[++i]);
93 }
else if ((arg ==
"--x") && i + 1 < arg_vec.size()) {
94 x = std::stoi(arg_vec[++i]);
95 }
else if ((arg ==
"--y") && i + 1 < arg_vec.size()) {
96 y = std::stoi(arg_vec[++i]);
97 }
else if ((arg ==
"--tile") && i + 1 < arg_vec.size()) {
98 tile_id = std::stoi(arg_vec[++i],
nullptr, 16);
102 if (map_id == -1 || x == -1 || y == -1 || tile_id == -1) {
103 return absl::InvalidArgumentError(
104 "Usage: overworld set-tile --map <map_id> --x <x> --y <y> --tile "
108 std::string rom_file = absl::GetFlag(FLAGS_rom);
109 if (rom_file.empty()) {
110 return absl::InvalidArgumentError(
111 "ROM file must be provided via --rom flag.");
116 if (!load_status.ok()) {
120 return absl::AbortedError(
"Failed to load ROM.");
124 auto status = overworld.
Load(&rom);
132 }
else if (map_id < 0x80) {
139 overworld.
SetTile(x, y,
static_cast<uint16_t
>(tile_id));
142 auto save_status = rom.
SaveToFile({.filename = rom_file});
143 if (!save_status.ok()) {
147 std::cout <<
"✅ Set tile at (" << x <<
", " << y <<
") on map " << map_id
148 <<
" to: 0x" << std::hex << tile_id << std::dec << std::endl;
150 return absl::OkStatus();
164 const std::vector<std::string>& arg_vec) {
165 std::unordered_map<std::string, std::string> options;
166 std::vector<std::string> positional;
167 options.reserve(arg_vec.size());
169 for (
size_t i = 0; i < arg_vec.size(); ++i) {
170 const std::string& token = arg_vec[i];
171 if (absl::StartsWith(token,
"--")) {
174 auto eq_pos = token.find(
'=');
175 if (eq_pos != std::string::npos) {
176 key = token.substr(2, eq_pos - 2);
177 value = token.substr(eq_pos + 1);
179 key = token.substr(2);
180 if (i + 1 >= arg_vec.size()) {
181 return absl::InvalidArgumentError(
182 absl::StrCat(
"Missing value for --", key,
"\n", kFindTileUsage));
184 value = arg_vec[++i];
187 return absl::InvalidArgumentError(
188 absl::StrCat(
"Missing value for --", key,
"\n", kFindTileUsage));
190 options[key] = value;
192 positional.push_back(token);
196 if (!positional.empty()) {
197 return absl::InvalidArgumentError(absl::StrCat(
198 "Unexpected positional arguments: ", absl::StrJoin(positional,
", "),
199 "\n", kFindTileUsage));
202 auto tile_it = options.find(
"tile");
203 if (tile_it == options.end()) {
204 return absl::InvalidArgumentError(
205 absl::StrCat(
"Missing required --tile argument\n", kFindTileUsage));
209 if (tile_value < 0 || tile_value > 0xFFFF) {
210 return absl::InvalidArgumentError(
211 absl::StrCat(
"Tile ID must be between 0x0000 and 0xFFFF (got ",
212 tile_it->second,
")"));
214 const uint16_t tile_id =
static_cast<uint16_t
>(tile_value);
216 std::optional<int> map_filter;
217 if (
auto map_it = options.find(
"map"); map_it != options.end()) {
219 if (map_value < 0 || map_value >= 0xA0) {
220 return absl::InvalidArgumentError(
221 absl::StrCat(
"Map ID out of range: ", map_it->second));
223 map_filter = map_value;
226 std::optional<int> world_filter;
227 if (
auto world_it = options.find(
"world"); world_it != options.end()) {
230 world_filter = parsed_world;
233 if (map_filter.has_value()) {
236 if (world_filter.has_value() && inferred_world != *world_filter) {
237 return absl::InvalidArgumentError(absl::StrCat(
238 "Map 0x", absl::StrFormat(
"%02X", *map_filter),
" belongs to the ",
242 if (!world_filter.has_value()) {
243 world_filter = inferred_world;
247 std::string format =
"text";
248 if (
auto format_it = options.find(
"format"); format_it != options.end()) {
249 format = absl::AsciiStrToLower(format_it->second);
250 if (format !=
"text" && format !=
"json") {
251 return absl::InvalidArgumentError(
252 absl::StrCat(
"Unsupported format: ", format_it->second));
256 std::string rom_file = absl::GetFlag(FLAGS_rom);
257 if (
auto rom_it = options.find(
"rom"); rom_it != options.end()) {
258 rom_file = rom_it->second;
261 if (rom_file.empty()) {
262 return absl::InvalidArgumentError(
263 "ROM file must be provided via --rom flag.");
268 if (!load_status.ok()) {
272 return absl::AbortedError(
"Failed to load ROM.");
276 auto ow_status = overworld.
Load(&rom);
277 if (!ow_status.ok()) {
282 search_options.
map_id = map_filter;
283 search_options.
world = world_filter;
288 if (format ==
"json") {
290 std::cout << absl::StrFormat(
" \"tile\": \"0x%04X\",\n", tile_id);
291 std::cout << absl::StrFormat(
" \"match_count\": %zu,\n", matches.size());
292 std::cout <<
" \"matches\": [\n";
293 for (
size_t i = 0; i < matches.size(); ++i) {
294 const auto& match = matches[i];
295 std::cout << absl::StrFormat(
296 " {\"map\": \"0x%02X\", \"world\": \"%s\", "
297 "\"local\": {\"x\": %d, \"y\": %d}, "
298 "\"global\": {\"x\": %d, \"y\": %d}}%s\n",
300 match.local_y, match.global_x, match.global_y,
301 (i + 1 == matches.size()) ?
"" :
",");
306 std::cout << absl::StrFormat(
"🔎 Tile 0x%04X → %zu match(es)\n", tile_id,
308 if (matches.empty()) {
309 std::cout <<
" No matches found." << std::endl;
310 return absl::OkStatus();
313 for (
const auto& match : matches) {
314 std::cout << absl::StrFormat(
315 " • Map 0x%02X (%s World) local(%2d,%2d) global(%3d,%3d)\n",
317 match.local_y, 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(absl::StrCat(
364 "Unexpected positional arguments: ", absl::StrJoin(positional,
", "),
368 auto map_it = options.find(
"map");
369 if (map_it == options.end()) {
370 return absl::InvalidArgumentError(std::string(kUsage));
375 return absl::InvalidArgumentError(
376 absl::StrCat(
"Map ID out of range: ", map_it->second));
379 std::string format =
"text";
380 if (
auto it = options.find(
"format"); it != options.end()) {
381 format = absl::AsciiStrToLower(it->second);
382 if (format !=
"text" && format !=
"json") {
383 return absl::InvalidArgumentError(
384 absl::StrCat(
"Unsupported format: ", it->second));
388 std::string rom_file = absl::GetFlag(FLAGS_rom);
389 if (
auto it = options.find(
"rom"); it != options.end()) {
390 rom_file = it->second;
393 if (rom_file.empty()) {
394 return absl::InvalidArgumentError(
395 "ROM file must be provided via --rom flag.");
400 if (!load_status.ok()) {
404 return absl::AbortedError(
"Failed to load ROM.");
408 auto ow_status = overworld_rom.
Load(&rom);
409 if (!ow_status.ok()) {
416 auto join_hex = [](
const std::vector<uint8_t>& values) {
417 std::vector<std::string> parts;
418 parts.reserve(values.size());
419 for (uint8_t v : values) {
420 parts.push_back(absl::StrFormat(
"0x%02X", v));
422 return absl::StrJoin(parts,
", ");
425 auto join_hex_json = [](
const std::vector<uint8_t>& values) {
426 std::vector<std::string> parts;
427 parts.reserve(values.size());
428 for (uint8_t v : values) {
429 parts.push_back(absl::StrFormat(
"\"0x%02X\"", v));
431 return absl::StrCat(
"[", absl::StrJoin(parts,
", "),
"]");
434 if (format ==
"json") {
436 std::cout << absl::StrFormat(
" \"map\": \"0x%02X\",\n", summary.map_id);
437 std::cout << absl::StrFormat(
" \"world\": \"%s\",\n",
439 std::cout << absl::StrFormat(
440 " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n", summary.map_x,
441 summary.map_y, summary.local_index);
442 std::cout << absl::StrFormat(
443 " \"size\": {\"label\": \"%s\", \"is_large\": %s, \"parent\": "
444 "\"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(
" \"message\": \"0x%04X\",\n",
449 std::cout << absl::StrFormat(
" \"area_graphics\": \"0x%02X\",\n",
450 summary.area_graphics);
451 std::cout << absl::StrFormat(
" \"area_palette\": \"0x%02X\",\n",
452 summary.area_palette);
453 std::cout << absl::StrFormat(
" \"main_palette\": \"0x%02X\",\n",
454 summary.main_palette);
455 std::cout << absl::StrFormat(
" \"animated_gfx\": \"0x%02X\",\n",
456 summary.animated_gfx);
457 std::cout << absl::StrFormat(
" \"subscreen_overlay\": \"0x%04X\",\n",
458 summary.subscreen_overlay);
459 std::cout << absl::StrFormat(
" \"area_specific_bg_color\": \"0x%04X\",\n",
460 summary.area_specific_bg_color);
461 std::cout << absl::StrFormat(
" \"sprite_graphics\": %s,\n",
462 join_hex_json(summary.sprite_graphics));
463 std::cout << absl::StrFormat(
" \"sprite_palettes\": %s,\n",
464 join_hex_json(summary.sprite_palettes));
465 std::cout << absl::StrFormat(
" \"area_music\": %s,\n",
466 join_hex_json(summary.area_music));
467 std::cout << absl::StrFormat(
" \"static_graphics\": %s,\n",
468 join_hex_json(summary.static_graphics));
469 std::cout << absl::StrFormat(
470 " \"overlay\": {\"enabled\": %s, \"id\": \"0x%04X\"}\n",
471 summary.has_overlay ?
"true" :
"false", summary.overlay_id);
474 std::cout << absl::StrFormat(
"🗺️ Map 0x%02X (%s World)\n", summary.map_id,
476 std::cout << absl::StrFormat(
" Grid: (%d, %d) local-index %d\n",
477 summary.map_x, summary.map_y,
478 summary.local_index);
479 std::cout << absl::StrFormat(
480 " Size: %s%s | Parent: 0x%02X | Quadrant: %d\n", summary.area_size,
481 summary.is_large_map ?
" (large)" :
"", summary.parent_map,
482 summary.large_quadrant);
483 std::cout << absl::StrFormat(
484 " Message: 0x%04X | Area GFX: 0x%02X | Area Palette: 0x%02X\n",
485 summary.message_id, summary.area_graphics, summary.area_palette);
486 std::cout << absl::StrFormat(
487 " Main Palette: 0x%02X | Animated GFX: 0x%02X | Overlay: %s "
489 summary.main_palette, summary.animated_gfx,
490 summary.has_overlay ?
"yes" :
"no", summary.overlay_id);
491 std::cout << absl::StrFormat(
492 " Subscreen Overlay: 0x%04X | BG Color: 0x%04X\n",
493 summary.subscreen_overlay, summary.area_specific_bg_color);
494 std::cout << absl::StrFormat(
" Sprite GFX: [%s]\n",
495 join_hex(summary.sprite_graphics));
496 std::cout << absl::StrFormat(
" Sprite Palettes: [%s]\n",
497 join_hex(summary.sprite_palettes));
498 std::cout << absl::StrFormat(
" Area Music: [%s]\n",
499 join_hex(summary.area_music));
500 std::cout << absl::StrFormat(
" Static GFX: [%s]\n",
501 join_hex(summary.static_graphics));
504 return absl::OkStatus();
510 const std::vector<std::string>& arg_vec) {
511 constexpr absl::string_view kUsage =
512 "Usage: overworld list-warps [--map <map_id>] [--world "
513 "<light|dark|special>] "
514 "[--type <entrance|hole|exit|all>] [--format <json|text>]";
516 std::unordered_map<std::string, std::string> options;
517 std::vector<std::string> positional;
518 options.reserve(arg_vec.size());
520 for (
size_t i = 0; i < arg_vec.size(); ++i) {
521 const std::string& token = arg_vec[i];
522 if (absl::StartsWith(token,
"--")) {
525 auto eq_pos = token.find(
'=');
526 if (eq_pos != std::string::npos) {
527 key = token.substr(2, eq_pos - 2);
528 value = token.substr(eq_pos + 1);
530 key = token.substr(2);
531 if (i + 1 >= arg_vec.size()) {
532 return absl::InvalidArgumentError(
533 absl::StrCat(
"Missing value for --", key,
"\n", kUsage));
535 value = arg_vec[++i];
538 return absl::InvalidArgumentError(
539 absl::StrCat(
"Missing value for --", key,
"\n", kUsage));
541 options[key] = value;
543 positional.push_back(token);
547 if (!positional.empty()) {
548 return absl::InvalidArgumentError(absl::StrCat(
549 "Unexpected positional arguments: ", absl::StrJoin(positional,
", "),
553 std::optional<int> map_filter;
554 if (
auto it = options.find(
"map"); it != options.end()) {
557 return absl::InvalidArgumentError(
558 absl::StrCat(
"Map ID out of range: ", it->second));
560 map_filter = map_value;
563 std::optional<int> world_filter;
564 if (
auto it = options.find(
"world"); it != options.end()) {
567 world_filter = parsed_world;
570 std::optional<overworld::WarpType> type_filter;
571 if (
auto it = options.find(
"type"); it != options.end()) {
572 std::string lower = absl::AsciiStrToLower(it->second);
573 if (lower ==
"entrance" || lower ==
"entrances") {
575 }
else if (lower ==
"hole" || lower ==
"holes") {
577 }
else if (lower ==
"exit" || lower ==
"exits") {
579 }
else if (lower ==
"all" || lower.empty()) {
582 return absl::InvalidArgumentError(
583 absl::StrCat(
"Unknown warp type: ", it->second));
587 if (map_filter.has_value()) {
590 if (world_filter.has_value() && inferred_world != *world_filter) {
591 return absl::InvalidArgumentError(absl::StrCat(
592 "Map 0x", absl::StrFormat(
"%02X", *map_filter),
" belongs to the ",
596 if (!world_filter.has_value()) {
597 world_filter = inferred_world;
601 std::string format =
"text";
602 if (
auto it = options.find(
"format"); it != options.end()) {
603 format = absl::AsciiStrToLower(it->second);
604 if (format !=
"text" && format !=
"json") {
605 return absl::InvalidArgumentError(
606 absl::StrCat(
"Unsupported format: ", it->second));
610 std::string rom_file = absl::GetFlag(FLAGS_rom);
611 if (
auto it = options.find(
"rom"); it != options.end()) {
612 rom_file = it->second;
615 if (rom_file.empty()) {
616 return absl::InvalidArgumentError(
617 "ROM file must be provided via --rom flag.");
622 if (!load_status.ok()) {
626 return absl::AbortedError(
"Failed to load ROM.");
630 auto ow_status = overworld_rom.
Load(&rom);
631 if (!ow_status.ok()) {
636 query.
map_id = map_filter;
637 query.
world = world_filter;
638 query.
type = type_filter;
643 if (format ==
"json") {
645 std::cout << absl::StrFormat(
" \"count\": %zu,\n", entries.size());
646 std::cout <<
" \"entries\": [\n";
647 for (
size_t i = 0; i < entries.size(); ++i) {
648 const auto& entry = entries[i];
650 std::cout << absl::StrFormat(
" \"type\": \"%s\",\n",
652 std::cout << absl::StrFormat(
" \"map\": \"0x%02X\",\n",
654 std::cout << absl::StrFormat(
" \"world\": \"%s\",\n",
656 std::cout << absl::StrFormat(
657 " \"grid\": {\"x\": %d, \"y\": %d, \"index\": %d},\n",
658 entry.map_x, entry.map_y, entry.local_index);
659 std::cout << absl::StrFormat(
660 " \"tile16\": {\"x\": %d, \"y\": %d},\n", entry.tile16_x,
662 std::cout << absl::StrFormat(
" \"pixel\": {\"x\": %d, \"y\": %d},\n",
663 entry.pixel_x, entry.pixel_y);
664 std::cout << absl::StrFormat(
" \"map_pos\": \"0x%04X\",\n",
666 std::cout << absl::StrFormat(
" \"deleted\": %s,\n",
667 entry.deleted ?
"true" :
"false");
668 std::cout << absl::StrFormat(
" \"is_hole\": %s",
669 entry.is_hole ?
"true" :
"false");
670 if (entry.entrance_id.has_value()) {
671 std::cout << absl::StrFormat(
",\n \"entrance_id\": \"0x%02X\"",
674 if (entry.entrance_name.has_value()) {
675 std::cout << absl::StrFormat(
",\n \"entrance_name\": \"%s\"",
676 *entry.entrance_name);
678 std::cout <<
"\n }" << (i + 1 == entries.size() ?
"" :
",") <<
"\n";
683 if (entries.empty()) {
684 std::cout <<
"No overworld warps match the specified filters."
686 return absl::OkStatus();
689 std::cout << absl::StrFormat(
"🌐 Overworld warps (%zu)\n", entries.size());
690 for (
const auto& entry : entries) {
691 std::string line = absl::StrFormat(
692 " • %-9s map 0x%02X (%s World) tile16(%02d,%02d) pixel(%4d,%4d)",
695 entry.pixel_x, entry.pixel_y);
696 if (entry.entrance_id.has_value()) {
697 line = absl::StrCat(line,
698 absl::StrFormat(
" id=0x%02X", *entry.entrance_id));
700 if (entry.entrance_name.has_value()) {
701 line = absl::StrCat(line,
" (", *entry.entrance_name,
")");
704 line = absl::StrCat(line,
" [deleted]");
707 line = absl::StrCat(line,
" [hole]");
709 std::cout << line << std::endl;
713 return absl::OkStatus();
723 const std::vector<std::string>& arg_vec) {
724 int map_id = -1, x1 = -1, y1 = -1, x2 = -1, y2 = -1;
726 for (
size_t i = 0; i < arg_vec.size(); ++i) {
727 const std::string& arg = arg_vec[i];
728 if ((arg ==
"--map") && i + 1 < arg_vec.size()) {
729 map_id = std::stoi(arg_vec[++i]);
730 }
else if ((arg ==
"--x1") && i + 1 < arg_vec.size()) {
731 x1 = std::stoi(arg_vec[++i]);
732 }
else if ((arg ==
"--y1") && i + 1 < arg_vec.size()) {
733 y1 = std::stoi(arg_vec[++i]);
734 }
else if ((arg ==
"--x2") && i + 1 < arg_vec.size()) {
735 x2 = std::stoi(arg_vec[++i]);
736 }
else if ((arg ==
"--y2") && i + 1 < arg_vec.size()) {
737 y2 = std::stoi(arg_vec[++i]);
741 if (map_id == -1 || x1 == -1 || y1 == -1 || x2 == -1 || y2 == -1) {
742 return absl::InvalidArgumentError(
743 "Usage: overworld select-rect --map <map_id> --x1 <x1> --y1 <y1> --x2 "
747 std::cout <<
"✅ Selected rectangle on map " << map_id <<
" from (" << x1
748 <<
"," << y1 <<
") to (" << x2 <<
"," << y2 <<
")" << std::endl;
750 int width = std::abs(x2 - x1) + 1;
751 int height = std::abs(y2 - y1) + 1;
752 std::cout <<
" Selection size: " << width <<
"x" << height <<
" tiles ("
753 << (width * height) <<
" total)" << std::endl;
755 return absl::OkStatus();