yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld.cc
Go to the documentation of this file.
1#include "cli/cli.h"
4
5#include <algorithm>
6#include <cctype>
7#include <optional>
8#include <stdexcept>
9#include <string>
10#include <string_view>
11#include <unordered_map>
12#include <utility>
13#include <vector>
14
15#include "absl/flags/flag.h"
16#include "absl/flags/declare.h"
17#include "absl/status/statusor.h"
18#include "absl/strings/ascii.h"
19#include "absl/strings/match.h"
20#include "absl/strings/str_cat.h"
21#include "absl/strings/str_join.h"
22#include "absl/strings/str_format.h"
23
24ABSL_DECLARE_FLAG(std::string, rom);
25
26namespace yaze {
27namespace cli {
28
29// Note: These CLI commands currently operate directly on ROM data.
30// Future: Integrate with CanvasAutomationAPI for live GUI automation.
31
32// Legacy OverworldGetTile class removed - using new CommandHandler system
33// TODO: Implement OverworldGetTileCommandHandler
34absl::Status HandleOverworldGetTileLegacy(const std::vector<std::string>& arg_vec) {
35 int map_id = -1, x = -1, y = -1;
36
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]);
45 }
46 }
47
48 if (map_id == -1 || x == -1 || y == -1) {
49 return absl::InvalidArgumentError("Usage: overworld get-tile --map <map_id> --x <x> --y <y>");
50 }
51
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.");
55 }
56
57 Rom rom;
58 auto load_status = rom.LoadFromFile(rom_file);
59 if (!load_status.ok()) {
60 return load_status;
61 }
62 if (!rom.is_loaded()) {
63 return absl::AbortedError("Failed to load ROM.");
64 }
65
66 zelda3::Overworld overworld(&rom);
67 auto ow_status = overworld.Load(&rom);
68 if (!ow_status.ok()) {
69 return ow_status;
70 }
71
72 uint16_t tile = overworld.GetTile(x, y);
73
74 std::cout << "Tile at (" << x << ", " << y << ") on map " << map_id << " is: 0x" << std::hex << tile << std::endl;
75
76 return absl::OkStatus();
77}
78
79// Legacy OverworldSetTile class removed - using new CommandHandler system
80// TODO: Implement OverworldSetTileCommandHandler
81absl::Status HandleOverworldSetTileLegacy(const std::vector<std::string>& arg_vec) {
82 int map_id = -1, x = -1, y = -1, tile_id = -1;
83
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);
94 }
95 }
96
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>");
99 }
100
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.");
104 }
105
106 Rom rom;
107 auto load_status = rom.LoadFromFile(rom_file);
108 if (!load_status.ok()) {
109 return load_status;
110 }
111 if (!rom.is_loaded()) {
112 return absl::AbortedError("Failed to load ROM.");
113 }
114
115 zelda3::Overworld overworld(&rom);
116 auto status = overworld.Load(&rom);
117 if (!status.ok()) {
118 return status;
119 }
120
121 // Set the world based on map_id
122 if (map_id < 0x40) {
123 overworld.set_current_world(0); // Light World
124 } else if (map_id < 0x80) {
125 overworld.set_current_world(1); // Dark World
126 } else {
127 overworld.set_current_world(2); // Special World
128 }
129
130 // Set the tile
131 overworld.SetTile(x, y, static_cast<uint16_t>(tile_id));
132
133 // Save the ROM
134 auto save_status = rom.SaveToFile({.filename = rom_file});
135 if (!save_status.ok()) {
136 return save_status;
137 }
138
139 std::cout << "✅ Set tile at (" << x << ", " << y << ") on map " << map_id
140 << " to: 0x" << std::hex << tile_id << std::dec << std::endl;
141
142 return absl::OkStatus();
143}
144
145namespace {
146
147constexpr absl::string_view kFindTileUsage =
148 "Usage: overworld find-tile --tile <tile_id> [--map <map_id>] [--world <light|dark|special|0|1|2>] [--format <json|text>]";
149
150} // namespace
151
152// Legacy OverworldFindTile class removed - using new CommandHandler system
153// TODO: Implement OverworldFindTileCommandHandler
154absl::Status HandleOverworldFindTileLegacy(const std::vector<std::string>& arg_vec) {
155 std::unordered_map<std::string, std::string> options;
156 std::vector<std::string> positional;
157 options.reserve(arg_vec.size());
158
159 for (size_t i = 0; i < arg_vec.size(); ++i) {
160 const std::string& token = arg_vec[i];
161 if (absl::StartsWith(token, "--")) {
162 std::string key;
163 std::string value;
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);
168 } else {
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));
173 }
174 value = arg_vec[++i];
175 }
176 if (value.empty()) {
177 return absl::InvalidArgumentError(
178 absl::StrCat("Missing value for --", key, "\n", kFindTileUsage));
179 }
180 options[key] = value;
181 } else {
182 positional.push_back(token);
183 }
184 }
185
186 if (!positional.empty()) {
187 return absl::InvalidArgumentError(
188 absl::StrCat("Unexpected positional arguments: ",
189 absl::StrJoin(positional, ", "), "\n", kFindTileUsage));
190 }
191
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));
196 }
197
198 ASSIGN_OR_RETURN(int tile_value,
199 overworld::ParseNumeric(tile_it->second));
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, ")"));
204 }
205 const uint16_t tile_id = static_cast<uint16_t>(tile_value);
206
207 std::optional<int> map_filter;
208 if (auto map_it = options.find("map"); map_it != options.end()) {
209 ASSIGN_OR_RETURN(int map_value,
210 overworld::ParseNumeric(map_it->second));
211 if (map_value < 0 || map_value >= 0xA0) {
212 return absl::InvalidArgumentError(
213 absl::StrCat("Map ID out of range: ", map_it->second));
214 }
215 map_filter = map_value;
216 }
217
218 std::optional<int> world_filter;
219 if (auto world_it = options.find("world"); world_it != options.end()) {
220 ASSIGN_OR_RETURN(int parsed_world,
221 overworld::ParseWorldSpecifier(world_it->second));
222 world_filter = parsed_world;
223 }
224
225 if (map_filter.has_value()) {
226 ASSIGN_OR_RETURN(int inferred_world,
227 overworld::InferWorldFromMapId(*map_filter));
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),
232 " belongs to the ",
233 overworld::WorldName(inferred_world),
234 " World but --world requested ",
235 overworld::WorldName(*world_filter)));
236 }
237 if (!world_filter.has_value()) {
238 world_filter = inferred_world;
239 }
240 }
241
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));
248 }
249 }
250
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;
254 }
255
256 if (rom_file.empty()) {
257 return absl::InvalidArgumentError(
258 "ROM file must be provided via --rom flag.");
259 }
260
261 Rom rom;
262 auto load_status = rom.LoadFromFile(rom_file);
263 if (!load_status.ok()) {
264 return load_status;
265 }
266 if (!rom.is_loaded()) {
267 return absl::AbortedError("Failed to load ROM.");
268 }
269
270 zelda3::Overworld overworld(&rom);
271 auto ow_status = overworld.Load(&rom);
272 if (!ow_status.ok()) {
273 return ow_status;
274 }
275
276 overworld::TileSearchOptions search_options;
277 search_options.map_id = map_filter;
278 search_options.world = world_filter;
279
280 ASSIGN_OR_RETURN(auto matches,
281 overworld::FindTileMatches(overworld, tile_id,
282 search_options));
283
284 if (format == "json") {
285 std::cout << "{\n";
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",
297 match.map_id, overworld::WorldName(match.world), match.local_x,
298 match.local_y,
299 match.global_x, match.global_y,
300 (i + 1 == matches.size()) ? "" : ",");
301 }
302 std::cout << " ]\n";
303 std::cout << "}\n";
304 } else {
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();
310 }
311
312 for (const auto& match : matches) {
313 std::cout << absl::StrFormat(
314 " • Map 0x%02X (%s World) local(%2d,%2d) global(%3d,%3d)\n",
315 match.map_id, overworld::WorldName(match.world), match.local_x,
316 match.local_y,
317 match.global_x, match.global_y);
318 }
319 }
320
321 return absl::OkStatus();
322}
323
324// Legacy OverworldDescribeMap class removed - using new CommandHandler system
325// TODO: Implement OverworldDescribeMapCommandHandler
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>]";
330
331 std::unordered_map<std::string, std::string> options;
332 std::vector<std::string> positional;
333 options.reserve(arg_vec.size());
334
335 for (size_t i = 0; i < arg_vec.size(); ++i) {
336 const std::string& token = arg_vec[i];
337 if (absl::StartsWith(token, "--")) {
338 std::string key;
339 std::string value;
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);
344 } else {
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));
349 }
350 value = arg_vec[++i];
351 }
352 if (value.empty()) {
353 return absl::InvalidArgumentError(
354 absl::StrCat("Missing value for --", key, "\n", kUsage));
355 }
356 options[key] = value;
357 } else {
358 positional.push_back(token);
359 }
360 }
361
362 if (!positional.empty()) {
363 return absl::InvalidArgumentError(
364 absl::StrCat("Unexpected positional arguments: ",
365 absl::StrJoin(positional, ", "), "\n", kUsage));
366 }
367
368 auto map_it = options.find("map");
369 if (map_it == options.end()) {
370 return absl::InvalidArgumentError(std::string(kUsage));
371 }
372
373 ASSIGN_OR_RETURN(int map_value,
374 overworld::ParseNumeric(map_it->second));
375 if (map_value < 0 || map_value >= zelda3::kNumOverworldMaps) {
376 return absl::InvalidArgumentError(
377 absl::StrCat("Map ID out of range: ", map_it->second));
378 }
379
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));
386 }
387 }
388
389 std::string rom_file = absl::GetFlag(FLAGS_rom);
390 if (auto it = options.find("rom"); it != options.end()) {
391 rom_file = it->second;
392 }
393
394 if (rom_file.empty()) {
395 return absl::InvalidArgumentError(
396 "ROM file must be provided via --rom flag.");
397 }
398
399 Rom rom;
400 auto load_status = rom.LoadFromFile(rom_file);
401 if (!load_status.ok()) {
402 return load_status;
403 }
404 if (!rom.is_loaded()) {
405 return absl::AbortedError("Failed to load ROM.");
406 }
407
408 zelda3::Overworld overworld_rom(&rom);
409 auto ow_status = overworld_rom.Load(&rom);
410 if (!ow_status.ok()) {
411 return ow_status;
412 }
413
414 ASSIGN_OR_RETURN(auto summary,
415 overworld::BuildMapSummary(overworld_rom, map_value));
416
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));
422 }
423 return absl::StrJoin(parts, ", ");
424 };
425
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));
431 }
432 return absl::StrCat("[", absl::StrJoin(parts, ", "), "]");
433 };
434
435 if (format == "json") {
436 std::cout << "{\n";
437 std::cout << absl::StrFormat(" \"map\": \"0x%02X\",\n", summary.map_id);
438 std::cout << absl::StrFormat(" \"world\": \"%s\",\n",
439 overworld::WorldName(summary.world));
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);
475 std::cout << "}\n";
476 } else {
477 std::cout << absl::StrFormat("🗺️ Map 0x%02X (%s World)\n", summary.map_id,
478 overworld::WorldName(summary.world));
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));
504 }
505
506 return absl::OkStatus();
507}
508
509// Legacy OverworldListWarps class removed - using new CommandHandler system
510// TODO: Implement OverworldListWarpsCommandHandler
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>]";
516
517 std::unordered_map<std::string, std::string> options;
518 std::vector<std::string> positional;
519 options.reserve(arg_vec.size());
520
521 for (size_t i = 0; i < arg_vec.size(); ++i) {
522 const std::string& token = arg_vec[i];
523 if (absl::StartsWith(token, "--")) {
524 std::string key;
525 std::string value;
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);
530 } else {
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));
535 }
536 value = arg_vec[++i];
537 }
538 if (value.empty()) {
539 return absl::InvalidArgumentError(
540 absl::StrCat("Missing value for --", key, "\n", kUsage));
541 }
542 options[key] = value;
543 } else {
544 positional.push_back(token);
545 }
546 }
547
548 if (!positional.empty()) {
549 return absl::InvalidArgumentError(
550 absl::StrCat("Unexpected positional arguments: ",
551 absl::StrJoin(positional, ", "), "\n", kUsage));
552 }
553
554 std::optional<int> map_filter;
555 if (auto it = options.find("map"); it != options.end()) {
556 ASSIGN_OR_RETURN(int map_value,
557 overworld::ParseNumeric(it->second));
558 if (map_value < 0 || map_value >= zelda3::kNumOverworldMaps) {
559 return absl::InvalidArgumentError(
560 absl::StrCat("Map ID out of range: ", it->second));
561 }
562 map_filter = map_value;
563 }
564
565 std::optional<int> world_filter;
566 if (auto it = options.find("world"); it != options.end()) {
567 ASSIGN_OR_RETURN(int parsed_world,
569 world_filter = parsed_world;
570 }
571
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") {
576 type_filter = overworld::WarpType::kEntrance;
577 } else if (lower == "hole" || lower == "holes") {
578 type_filter = overworld::WarpType::kHole;
579 } else if (lower == "exit" || lower == "exits") {
580 type_filter = overworld::WarpType::kExit;
581 } else if (lower == "all" || lower.empty()) {
582 type_filter.reset();
583 } else {
584 return absl::InvalidArgumentError(
585 absl::StrCat("Unknown warp type: ", it->second));
586 }
587 }
588
589 if (map_filter.has_value()) {
590 ASSIGN_OR_RETURN(int inferred_world,
591 overworld::InferWorldFromMapId(*map_filter));
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),
596 " belongs to the ",
597 overworld::WorldName(inferred_world),
598 " World but --world requested ",
599 overworld::WorldName(*world_filter)));
600 }
601 if (!world_filter.has_value()) {
602 world_filter = inferred_world;
603 }
604 }
605
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));
612 }
613 }
614
615 std::string rom_file = absl::GetFlag(FLAGS_rom);
616 if (auto it = options.find("rom"); it != options.end()) {
617 rom_file = it->second;
618 }
619
620 if (rom_file.empty()) {
621 return absl::InvalidArgumentError(
622 "ROM file must be provided via --rom flag.");
623 }
624
625 Rom rom;
626 auto load_status = rom.LoadFromFile(rom_file);
627 if (!load_status.ok()) {
628 return load_status;
629 }
630 if (!rom.is_loaded()) {
631 return absl::AbortedError("Failed to load ROM.");
632 }
633
634 zelda3::Overworld overworld_rom(&rom);
635 auto ow_status = overworld_rom.Load(&rom);
636 if (!ow_status.ok()) {
637 return ow_status;
638 }
639
641 query.map_id = map_filter;
642 query.world = world_filter;
643 query.type = type_filter;
644
645 ASSIGN_OR_RETURN(auto entries,
646 overworld::CollectWarpEntries(overworld_rom, query));
647
648 if (format == "json") {
649 std::cout << "{\n";
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];
654 std::cout << " {\n";
655 std::cout << absl::StrFormat(
656 " \"type\": \"%s\",\n",
657 overworld::WarpTypeName(entry.type));
658 std::cout << absl::StrFormat(
659 " \"map\": \"0x%02X\",\n", entry.map_id);
660 std::cout << absl::StrFormat(
661 " \"world\": \"%s\",\n",
662 overworld::WorldName(entry.world));
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(
677 " \"is_hole\": %s",
678 entry.is_hole ? "true" : "false");
679 if (entry.entrance_id.has_value()) {
680 std::cout << absl::StrFormat(
681 ",\n \"entrance_id\": \"0x%02X\"",
682 *entry.entrance_id);
683 }
684 if (entry.entrance_name.has_value()) {
685 std::cout << absl::StrFormat(
686 ",\n \"entrance_name\": \"%s\"",
687 *entry.entrance_name);
688 }
689 std::cout << "\n }" << (i + 1 == entries.size() ? "" : ",") << "\n";
690 }
691 std::cout << " ]\n";
692 std::cout << "}\n";
693 } else {
694 if (entries.empty()) {
695 std::cout << "No overworld warps match the specified filters." << std::endl;
696 return absl::OkStatus();
697 }
698
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)",
703 overworld::WarpTypeName(entry.type), entry.map_id,
704 overworld::WorldName(entry.world), entry.tile16_x, entry.tile16_y,
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));
709 }
710 if (entry.entrance_name.has_value()) {
711 line = absl::StrCat(line, " (", *entry.entrance_name, ")");
712 }
713 if (entry.deleted) {
714 line = absl::StrCat(line, " [deleted]");
715 }
716 if (entry.is_hole && entry.type != overworld::WarpType::kHole) {
717 line = absl::StrCat(line, " [hole]");
718 }
719 std::cout << line << std::endl;
720 }
721 }
722
723 return absl::OkStatus();
724}
725
726// ============================================================================
727// Phase 4B: Canvas Automation API Commands
728// ============================================================================
729
730// Legacy OverworldSelectRect class removed - using new CommandHandler system
731// TODO: Implement OverworldSelectRectCommandHandler
732absl::Status HandleOverworldSelectRectLegacy(const std::vector<std::string>& arg_vec) {
733 int map_id = -1, x1 = -1, y1 = -1, x2 = -1, y2 = -1;
734
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]);
747 }
748 }
749
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>");
753 }
754
755 std::cout << "✅ Selected rectangle on map " << map_id
756 << " from (" << x1 << "," << y1 << ") to (" << x2 << "," << y2 << ")" << std::endl;
757
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;
762
763 return absl::OkStatus();
764}
765
766// Legacy OverworldScrollTo class removed - using new CommandHandler system
767// TODO: Implement OverworldScrollToCommandHandler
768absl::Status HandleOverworldScrollToLegacy(const std::vector<std::string>& arg_vec) {
769 int map_id = -1, x = -1, y = -1;
770 bool center = false;
771
772 for (size_t i = 0; i < arg_vec.size(); ++i) {
773 const std::string& arg = arg_vec[i];
774 if ((arg == "--map") && i + 1 < arg_vec.size()) {
775 map_id = std::stoi(arg_vec[++i]);
776 } else if ((arg == "--x") && i + 1 < arg_vec.size()) {
777 x = std::stoi(arg_vec[++i]);
778 } else if ((arg == "--y") && i + 1 < arg_vec.size()) {
779 y = std::stoi(arg_vec[++i]);
780 } else if (arg == "--center") {
781 center = true;
782 }
783 }
784
785 if (map_id == -1 || x == -1 || y == -1) {
786 return absl::InvalidArgumentError(
787 "Usage: overworld scroll-to --map <map_id> --x <x> --y <y> [--center]");
788 }
789
790 std::cout << "✅ Scrolled to tile (" << x << "," << y << ") on map " << map_id;
791 if (center) {
792 std::cout << " (centered)";
793 }
794 std::cout << std::endl;
795
796 return absl::OkStatus();
797}
798
799// Legacy OverworldSetZoom class removed - using new CommandHandler system
800// TODO: Implement OverworldSetZoomCommandHandler
801absl::Status HandleOverworldSetZoomLegacy(const std::vector<std::string>& arg_vec) {
802 float zoom = -1.0f;
803
804 for (size_t i = 0; i < arg_vec.size(); ++i) {
805 const std::string& arg = arg_vec[i];
806 if ((arg == "--zoom") && i + 1 < arg_vec.size()) {
807 zoom = std::stof(arg_vec[++i]);
808 }
809 }
810
811 if (zoom < 0.0f) {
812 return absl::InvalidArgumentError(
813 "Usage: overworld set-zoom --zoom <level>\n"
814 " Zoom level: 0.25 - 4.0");
815 }
816
817 // Clamp to valid range
818 zoom = std::max(0.25f, std::min(zoom, 4.0f));
819
820 std::cout << "✅ Set zoom level to " << zoom << "x" << std::endl;
821
822 return absl::OkStatus();
823}
824
825// Legacy OverworldGetVisibleRegion class removed - using new CommandHandler system
826// TODO: Implement OverworldGetVisibleRegionCommandHandler
827absl::Status HandleOverworldGetVisibleRegionLegacy(const std::vector<std::string>& arg_vec) {
828 int map_id = -1;
829 std::string format = "text";
830
831 for (size_t i = 0; i < arg_vec.size(); ++i) {
832 const std::string& arg = arg_vec[i];
833 if ((arg == "--map") && i + 1 < arg_vec.size()) {
834 map_id = std::stoi(arg_vec[++i]);
835 } else if ((arg == "--format") && i + 1 < arg_vec.size()) {
836 format = arg_vec[++i];
837 }
838 }
839
840 if (map_id == -1) {
841 return absl::InvalidArgumentError(
842 "Usage: overworld get-visible-region --map <map_id> [--format json|text]");
843 }
844
845 // Note: This would query the canvas automation API in a live GUI context
846 // For now, return placeholder data
847 if (format == "json") {
848 std::cout << R"({
849 "map_id": )" << map_id << R"(,
850 "visible_region": {
851 "min_x": 0,
852 "min_y": 0,
853 "max_x": 31,
854 "max_y": 31
855 },
856 "tile_count": 1024
857})" << std::endl;
858 } else {
859 std::cout << "Visible region on map " << map_id << ":" << std::endl;
860 std::cout << " Min: (0, 0)" << std::endl;
861 std::cout << " Max: (31, 31)" << std::endl;
862 std::cout << " Total visible tiles: 1024" << std::endl;
863 }
864
865 return absl::OkStatus();
866}
867
868} // namespace cli
869} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
absl::Status LoadFromFile(const std::string &filename, bool z3_load=true)
Definition rom.cc:289
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:536
bool is_loaded() const
Definition rom.h:197
Represents the full Overworld data, light and dark world.
Definition overworld.h:135
void set_current_world(int world)
Definition overworld.h:292
absl::Status Load(Rom *rom)
Definition overworld.cc:27
uint16_t GetTile(int x, int y) const
Definition overworld.h:293
void SetTile(int x, int y, uint16_t tile_id)
Definition overworld.h:302
ABSL_DECLARE_FLAG(std::string, rom)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
constexpr absl::string_view kFindTileUsage
Definition overworld.cc:147
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< int > ParseWorldSpecifier(std::string_view value)
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)
absl::Status HandleOverworldGetTileLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:34
absl::Status HandleOverworldFindTileLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:154
absl::Status HandleOverworldListWarpsLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:511
absl::Status HandleOverworldSetZoomLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:801
absl::Status HandleOverworldSelectRectLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:732
absl::Status HandleOverworldDescribeMapLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:326
absl::Status HandleOverworldSetTileLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:81
absl::Status HandleOverworldScrollToLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:768
absl::Status HandleOverworldGetVisibleRegionLegacy(const std::vector< std::string > &arg_vec)
Definition overworld.cc:827
constexpr int kNumOverworldMaps
Definition overworld.h:119
Main namespace for the application.
std::optional< WarpType > type